feat(ui): remove themes, add hand-crafted dark and light modes

Themes are very fun but due to the differences in perceived saturation and lightness across the
the color spectrum, it's impossible to have have multiple themes that look great without hand-
crafting *every* shade for *every* theme. We've ended up with 4 OK themes (well, 3, because the
light theme was pretty bad).

I've removed the themes and added color mode support. There is now a single dark and light mode,
each with their own color palette and the classic grey / purple / yellow invoke colors that
@blessedcoolant first designed.

I've re-styled almost everything except the model manager and lightbox, which I keep forgetting
to work on.

One new concept is the Chakra `layerStyle`. This lets us define "layers" - think body, first layer,
second layer, etc - that can be applied on various components. By defining layers, we can be more
consistent about the z-axis and its relationship to color and lightness.
This commit is contained in:
psychedelicious 2023-06-27 00:12:33 +10:00
parent 28d78a8fb4
commit 032c7e68d0
62 changed files with 839 additions and 516 deletions

View File

@ -3,17 +3,10 @@ import {
createLocalStorageManager, createLocalStorageManager,
extendTheme, extendTheme,
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import { RootState } from 'app/store/store'; import { ReactNode, useEffect, useMemo } from 'react';
import { useAppSelector } from 'app/store/storeHooks';
import { ReactNode, useEffect } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { theme as invokeAITheme } from 'theme/theme'; import { theme as invokeAITheme } from 'theme/theme';
import { greenTeaThemeColors } from 'theme/colors/greenTea';
import { invokeAIThemeColors } from 'theme/colors/invokeAI';
import { lightThemeColors } from 'theme/colors/lightTheme';
import { oceanBlueColors } from 'theme/colors/oceanBlue';
import '@fontsource-variable/inter'; import '@fontsource-variable/inter';
import { MantineProvider } from '@mantine/core'; import { MantineProvider } from '@mantine/core';
import { mantineTheme } from 'mantine-theme/theme'; import { mantineTheme } from 'mantine-theme/theme';
@ -24,29 +17,19 @@ type ThemeLocaleProviderProps = {
children: ReactNode; children: ReactNode;
}; };
const THEMES = {
dark: invokeAIThemeColors,
light: lightThemeColors,
green: greenTeaThemeColors,
ocean: oceanBlueColors,
};
const manager = createLocalStorageManager('@@invokeai-color-mode'); const manager = createLocalStorageManager('@@invokeai-color-mode');
function ThemeLocaleProvider({ children }: ThemeLocaleProviderProps) { function ThemeLocaleProvider({ children }: ThemeLocaleProviderProps) {
const { i18n } = useTranslation(); const { i18n } = useTranslation();
const currentTheme = useAppSelector(
(state: RootState) => state.ui.currentTheme
);
const direction = i18n.dir(); const direction = i18n.dir();
const theme = extendTheme({ const theme = useMemo(() => {
...invokeAITheme, return extendTheme({
colors: THEMES[currentTheme as keyof typeof THEMES], ...invokeAITheme,
direction, direction,
}); });
}, [direction]);
useEffect(() => { useEffect(() => {
document.body.dir = direction; document.body.dir = direction;

View File

@ -1,6 +1,14 @@
import { ChevronUpIcon } from '@chakra-ui/icons'; import { ChevronUpIcon } from '@chakra-ui/icons';
import { Box, Collapse, Flex, Spacer, Switch } from '@chakra-ui/react'; import {
Box,
Collapse,
Flex,
Spacer,
Switch,
useColorMode,
} from '@chakra-ui/react';
import { PropsWithChildren, memo } from 'react'; import { PropsWithChildren, memo } from 'react';
import { mode } from 'theme/util/mode';
export type IAIToggleCollapseProps = PropsWithChildren & { export type IAIToggleCollapseProps = PropsWithChildren & {
label: string; label: string;
@ -11,6 +19,7 @@ export type IAIToggleCollapseProps = PropsWithChildren & {
const IAICollapse = (props: IAIToggleCollapseProps) => { const IAICollapse = (props: IAIToggleCollapseProps) => {
const { label, isOpen, onToggle, children, withSwitch = false } = props; const { label, isOpen, onToggle, children, withSwitch = false } = props;
const { colorMode } = useColorMode();
return ( return (
<Box> <Box>
<Flex <Flex
@ -21,10 +30,14 @@ const IAICollapse = (props: IAIToggleCollapseProps) => {
px: 4, px: 4,
borderTopRadius: 'base', borderTopRadius: 'base',
borderBottomRadius: isOpen ? 0 : 'base', borderBottomRadius: isOpen ? 0 : 'base',
bg: isOpen ? 'base.750' : 'base.800', bg: isOpen
color: 'base.100', ? mode('base.200', 'base.750')(colorMode)
: mode('base.150', 'base.800')(colorMode),
color: mode('base.900', 'base.100')(colorMode),
_hover: { _hover: {
bg: isOpen ? 'base.700' : 'base.750', bg: isOpen
? mode('base.250', 'base.700')(colorMode)
: mode('base.200', 'base.750')(colorMode),
}, },
fontSize: 'sm', fontSize: 'sm',
fontWeight: 600, fontWeight: 600,
@ -50,7 +63,13 @@ const IAICollapse = (props: IAIToggleCollapseProps) => {
)} )}
</Flex> </Flex>
<Collapse in={isOpen} animateOpacity style={{ overflow: 'unset' }}> <Collapse in={isOpen} animateOpacity style={{ overflow: 'unset' }}>
<Box sx={{ p: 4, borderBottomRadius: 'base', bg: 'base.800' }}> <Box
sx={{
p: 4,
borderBottomRadius: 'base',
bg: mode('base.100', 'base.800')(colorMode),
}}
>
{children} {children}
</Box> </Box>
</Collapse> </Collapse>

View File

@ -5,6 +5,7 @@ import {
Icon, Icon,
IconButtonProps, IconButtonProps,
Image, Image,
useColorMode,
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import { useDraggable, useDroppable } from '@dnd-kit/core'; import { useDraggable, useDroppable } from '@dnd-kit/core';
import { useCombinedRefs } from '@dnd-kit/utilities'; import { useCombinedRefs } from '@dnd-kit/utilities';
@ -20,6 +21,7 @@ import { v4 as uuidv4 } from 'uuid';
import IAIDropOverlay from './IAIDropOverlay'; import IAIDropOverlay from './IAIDropOverlay';
import { PostUploadAction } from 'services/api/thunks/image'; import { PostUploadAction } from 'services/api/thunks/image';
import { useImageUploadButton } from 'common/hooks/useImageUploadButton'; import { useImageUploadButton } from 'common/hooks/useImageUploadButton';
import { mode } from 'theme/util/mode';
type IAIDndImageProps = { type IAIDndImageProps = {
image: ImageDTO | null | undefined; image: ImageDTO | null | undefined;
@ -62,6 +64,7 @@ const IAIDndImage = (props: IAIDndImageProps) => {
} = props; } = props;
const dndId = useRef(uuidv4()); const dndId = useRef(uuidv4());
const { colorMode } = useColorMode();
const { const {
isOver, isOver,
@ -99,10 +102,10 @@ const IAIDndImage = (props: IAIDndImageProps) => {
? {} ? {}
: { : {
cursor: 'pointer', cursor: 'pointer',
bg: 'base.800', bg: mode('base.200', 'base.800')(colorMode),
_hover: { _hover: {
bg: 'base.750', bg: mode('base.300', 'base.650')(colorMode),
color: 'base.300', color: mode('base.500', 'base.300')(colorMode),
}, },
}; };
@ -181,7 +184,7 @@ const IAIDndImage = (props: IAIDndImageProps) => {
borderRadius: 'base', borderRadius: 'base',
transitionProperty: 'common', transitionProperty: 'common',
transitionDuration: '0.1s', transitionDuration: '0.1s',
color: 'base.500', color: mode('base.500', 'base.500')(colorMode),
...uploadButtonStyles, ...uploadButtonStyles,
}} }}
{...getUploadButtonProps()} {...getUploadButtonProps()}

View File

@ -1,6 +1,7 @@
import { Flex, Text } from '@chakra-ui/react'; import { Flex, Text, useColorMode } from '@chakra-ui/react';
import { motion } from 'framer-motion'; import { motion } from 'framer-motion';
import { memo, useRef } from 'react'; import { memo, useRef } from 'react';
import { mode } from 'theme/util/mode';
import { v4 as uuidv4 } from 'uuid'; import { v4 as uuidv4 } from 'uuid';
type Props = { type Props = {
@ -11,6 +12,7 @@ type Props = {
export const IAIDropOverlay = (props: Props) => { export const IAIDropOverlay = (props: Props) => {
const { isOver, label = 'Drop' } = props; const { isOver, label = 'Drop' } = props;
const motionId = useRef(uuidv4()); const motionId = useRef(uuidv4());
const { colorMode } = useColorMode();
return ( return (
<motion.div <motion.div
key={motionId.current} key={motionId.current}
@ -42,7 +44,7 @@ export const IAIDropOverlay = (props: Props) => {
insetInlineStart: 0, insetInlineStart: 0,
w: 'full', w: 'full',
h: 'full', h: 'full',
bg: 'base.900', bg: mode('base.700', 'base.900')(colorMode),
opacity: 0.7, opacity: 0.7,
borderRadius: 'base', borderRadius: 'base',
alignItems: 'center', alignItems: 'center',
@ -61,7 +63,9 @@ export const IAIDropOverlay = (props: Props) => {
h: 'full', h: 'full',
opacity: 1, opacity: 1,
borderWidth: 2, borderWidth: 2,
borderColor: isOver ? 'base.200' : 'base.500', borderColor: isOver
? mode('base.50', 'base.200')(colorMode)
: mode('base.100', 'base.500')(colorMode),
borderRadius: 'base', borderRadius: 'base',
borderStyle: 'dashed', borderStyle: 'dashed',
transitionProperty: 'common', transitionProperty: 'common',
@ -75,7 +79,9 @@ export const IAIDropOverlay = (props: Props) => {
fontSize: '2xl', fontSize: '2xl',
fontWeight: 600, fontWeight: 600,
transform: isOver ? 'scale(1.1)' : 'scale(1)', transform: isOver ? 'scale(1.1)' : 'scale(1)',
color: isOver ? 'base.100' : 'base.500', color: isOver
? mode('base.100', 'base.100')(colorMode)
: mode('base.200', 'base.500')(colorMode),
transitionProperty: 'common', transitionProperty: 'common',
transitionDuration: '0.1s', transitionDuration: '0.1s',
}} }}

View File

@ -6,9 +6,10 @@ import {
IconProps, IconProps,
Spinner, Spinner,
SpinnerProps, SpinnerProps,
useColorMode,
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import { ReactElement } from 'react';
import { FaImage } from 'react-icons/fa'; import { FaImage } from 'react-icons/fa';
import { mode } from 'theme/util/mode';
type Props = FlexProps & { type Props = FlexProps & {
spinnerProps?: SpinnerProps; spinnerProps?: SpinnerProps;
@ -17,10 +18,11 @@ type Props = FlexProps & {
export const IAIImageLoadingFallback = (props: Props) => { export const IAIImageLoadingFallback = (props: Props) => {
const { spinnerProps, ...rest } = props; const { spinnerProps, ...rest } = props;
const { sx, ...restFlexProps } = rest; const { sx, ...restFlexProps } = rest;
const { colorMode } = useColorMode();
return ( return (
<Flex <Flex
sx={{ sx={{
bg: 'base.900', bg: mode('base.200', 'base.900')(colorMode),
opacity: 0.7, opacity: 0.7,
w: 'full', w: 'full',
h: 'full', h: 'full',
@ -45,10 +47,12 @@ type IAINoImageFallbackProps = {
export const IAINoImageFallback = (props: IAINoImageFallbackProps) => { export const IAINoImageFallback = (props: IAINoImageFallbackProps) => {
const { sx: flexSx, ...restFlexProps } = props.flexProps ?? { sx: {} }; const { sx: flexSx, ...restFlexProps } = props.flexProps ?? { sx: {} };
const { sx: iconSx, ...restIconProps } = props.iconProps ?? { sx: {} }; const { sx: iconSx, ...restIconProps } = props.iconProps ?? { sx: {} };
const { colorMode } = useColorMode();
return ( return (
<Flex <Flex
sx={{ sx={{
bg: 'base.900', bg: mode('base.200', 'base.900')(colorMode),
opacity: 0.7, opacity: 0.7,
w: 'full', w: 'full',
h: 'full', h: 'full',
@ -61,7 +65,7 @@ export const IAINoImageFallback = (props: IAINoImageFallbackProps) => {
> >
<Icon <Icon
as={props.as ?? FaImage} as={props.as ?? FaImage}
sx={{ color: 'base.700', ...iconSx }} sx={{ color: mode('base.700', 'base.500')(colorMode), ...iconSx }}
{...restIconProps} {...restIconProps}
/> />
</Flex> </Flex>

View File

@ -1,6 +1,8 @@
import { Tooltip } from '@chakra-ui/react'; import { Tooltip, useColorMode, useToken } from '@chakra-ui/react';
import { MultiSelect, MultiSelectProps } from '@mantine/core'; import { MultiSelect, MultiSelectProps } from '@mantine/core';
import { useChakraThemeTokens } from 'common/hooks/useChakraThemeTokens';
import { memo } from 'react'; import { memo } from 'react';
import { mode } from 'theme/util/mode';
type IAIMultiSelectProps = MultiSelectProps & { type IAIMultiSelectProps = MultiSelectProps & {
tooltip?: string; tooltip?: string;
@ -8,71 +10,101 @@ type IAIMultiSelectProps = MultiSelectProps & {
const IAIMantineMultiSelect = (props: IAIMultiSelectProps) => { const IAIMantineMultiSelect = (props: IAIMultiSelectProps) => {
const { searchable = true, tooltip, ...rest } = props; const { searchable = true, tooltip, ...rest } = props;
const {
base50,
base100,
base200,
base250,
base300,
base400,
base500,
base600,
base700,
base800,
base900,
accent200,
accent300,
accent400,
accent500,
accent600,
accent700,
} = useChakraThemeTokens();
const [boxShadow] = useToken('shadows', ['dark-lg']);
const { colorMode } = useColorMode();
return ( return (
<Tooltip label={tooltip} placement="top" hasArrow> <Tooltip label={tooltip} placement="top" hasArrow>
<MultiSelect <MultiSelect
searchable={searchable} searchable={searchable}
styles={() => ({ styles={() => ({
label: { label: {
color: 'var(--invokeai-colors-base-300)', color: mode(base700, base300)(colorMode),
fontWeight: 'normal', fontWeight: 'normal',
}, },
searchInput: { searchInput: {
'::placeholder': { ':placeholder': {
color: 'var(--invokeai-colors-base-700)', color: mode(base300, base700)(colorMode),
}, },
}, },
input: { input: {
backgroundColor: 'var(--invokeai-colors-base-900)', backgroundColor: mode(base50, base900)(colorMode),
borderWidth: '2px', borderWidth: '2px',
borderColor: 'var(--invokeai-colors-base-800)', borderColor: mode(base200, base800)(colorMode),
color: 'var(--invokeai-colors-base-100)', color: mode(base900, base100)(colorMode),
paddingRight: 24, paddingRight: 24,
fontWeight: 600, fontWeight: 600,
'&:hover': { borderColor: 'var(--invokeai-colors-base-700)' }, '&:hover': { borderColor: mode(base300, base600)(colorMode) },
'&:focus': { '&:focus': {
borderColor: 'var(--invokeai-colors-accent-600)', borderColor: mode(accent300, accent600)(colorMode),
},
'&:is(:focus, :hover)': {
borderColor: mode(base400, base500)(colorMode),
}, },
'&:focus-within': { '&:focus-within': {
borderColor: 'var(--invokeai-colors-accent-600)', borderColor: mode(accent200, accent600)(colorMode),
},
'&:disabled': {
backgroundColor: mode(base300, base700)(colorMode),
color: mode(base600, base400)(colorMode),
}, },
}, },
value: { value: {
backgroundColor: 'var(--invokeai-colors-base-800)', backgroundColor: mode(base200, base800)(colorMode),
color: 'var(--invokeai-colors-base-100)', color: mode(base900, base100)(colorMode),
button: { button: {
color: 'var(--invokeai-colors-base-100)', color: mode(base900, base100)(colorMode),
}, },
'&:hover': { '&:hover': {
backgroundColor: 'var(--invokeai-colors-base-700)', backgroundColor: mode(base300, base700)(colorMode),
cursor: 'pointer', cursor: 'pointer',
}, },
}, },
dropdown: { dropdown: {
backgroundColor: 'var(--invokeai-colors-base-800)', backgroundColor: mode(base200, base800)(colorMode),
borderColor: 'var(--invokeai-colors-base-700)', borderColor: mode(base200, base800)(colorMode),
boxShadow,
}, },
item: { item: {
backgroundColor: 'var(--invokeai-colors-base-800)', backgroundColor: mode(base200, base800)(colorMode),
color: 'var(--invokeai-colors-base-200)', color: mode(base800, base200)(colorMode),
padding: 6, padding: 6,
'&[data-hovered]': { '&[data-hovered]': {
color: 'var(--invokeai-colors-base-100)', color: mode(base900, base100)(colorMode),
backgroundColor: 'var(--invokeai-colors-base-750)', backgroundColor: mode(base300, base700)(colorMode),
}, },
'&[data-active]': { '&[data-active]': {
backgroundColor: 'var(--invokeai-colors-base-750)', backgroundColor: mode(base300, base700)(colorMode),
'&:hover': { '&:hover': {
color: 'var(--invokeai-colors-base-100)', color: mode(base900, base100)(colorMode),
backgroundColor: 'var(--invokeai-colors-base-750)', backgroundColor: mode(base300, base700)(colorMode),
}, },
}, },
'&[data-selected]': { '&[data-selected]': {
color: 'var(--invokeai-colors-base-50)', color: mode(base900, base50)(colorMode),
backgroundColor: 'var(--invokeai-colors-accent-650)', backgroundColor: mode(accent300, accent600)(colorMode),
fontWeight: 600, fontWeight: 600,
'&:hover': { '&:hover': {
backgroundColor: 'var(--invokeai-colors-accent-600)', backgroundColor: mode(accent400, accent500)(colorMode),
}, },
}, },
}, },
@ -80,7 +112,7 @@ const IAIMantineMultiSelect = (props: IAIMultiSelectProps) => {
width: 24, width: 24,
padding: 20, padding: 20,
button: { button: {
color: 'var(--invokeai-colors-base-100)', color: mode(base900, base100)(colorMode),
}, },
}, },
})} })}

View File

@ -1,6 +1,8 @@
import { Tooltip } from '@chakra-ui/react'; import { Tooltip, useColorMode, useToken } from '@chakra-ui/react';
import { Select, SelectProps } from '@mantine/core'; import { Select, SelectProps } from '@mantine/core';
import { useChakraThemeTokens } from 'common/hooks/useChakraThemeTokens';
import { memo } from 'react'; import { memo } from 'react';
import { mode } from 'theme/util/mode';
export type IAISelectDataType = { export type IAISelectDataType = {
value: string; value: string;
@ -14,61 +16,105 @@ type IAISelectProps = SelectProps & {
const IAIMantineSelect = (props: IAISelectProps) => { const IAIMantineSelect = (props: IAISelectProps) => {
const { searchable = true, tooltip, ...rest } = props; const { searchable = true, tooltip, ...rest } = props;
const {
base50,
base100,
base200,
base300,
base400,
base500,
base600,
base700,
base800,
base900,
accent200,
accent300,
accent400,
accent500,
accent600,
accent700,
} = useChakraThemeTokens();
const { colorMode } = useColorMode();
const [boxShadow] = useToken('shadows', ['dark-lg']);
return ( return (
<Tooltip label={tooltip} placement="top" hasArrow> <Tooltip label={tooltip} placement="top" hasArrow>
<Select <Select
searchable={searchable} searchable={searchable}
styles={() => ({ styles={() => ({
label: { label: {
color: 'var(--invokeai-colors-base-300)', color: mode(base700, base300)(colorMode),
fontWeight: 'normal', fontWeight: 'normal',
}, },
input: { input: {
backgroundColor: 'var(--invokeai-colors-base-900)', backgroundColor: mode(base50, base900)(colorMode),
borderWidth: '2px', borderWidth: '2px',
borderColor: 'var(--invokeai-colors-base-800)', borderColor: mode(base200, base800)(colorMode),
color: 'var(--invokeai-colors-base-100)', color: mode(base900, base100)(colorMode),
paddingRight: 24, paddingRight: 24,
fontWeight: 600, fontWeight: 600,
'&:hover': { borderColor: 'var(--invokeai-colors-base-700)' }, '&:hover': { borderColor: mode(base300, base600)(colorMode) },
'&:focus': { '&:focus': {
borderColor: 'var(--invokeai-colors-accent-600)', borderColor: mode(accent300, accent600)(colorMode),
},
'&:is(:focus, :hover)': {
borderColor: mode(base400, base500)(colorMode),
},
'&:focus-within': {
borderColor: mode(accent200, accent600)(colorMode),
}, },
'&:disabled': { '&:disabled': {
backgroundColor: 'var(--invokeai-colors-base-700)', backgroundColor: mode(base300, base700)(colorMode),
color: 'var(--invokeai-colors-base-400)', color: mode(base600, base400)(colorMode),
},
},
value: {
backgroundColor: mode(base100, base900)(colorMode),
color: mode(base900, base100)(colorMode),
button: {
color: mode(base900, base100)(colorMode),
},
'&:hover': {
backgroundColor: mode(base300, base700)(colorMode),
cursor: 'pointer',
}, },
}, },
dropdown: { dropdown: {
backgroundColor: 'var(--invokeai-colors-base-800)', backgroundColor: mode(base200, base800)(colorMode),
borderColor: 'var(--invokeai-colors-base-700)', borderColor: mode(base200, base800)(colorMode),
boxShadow,
}, },
item: { item: {
backgroundColor: 'var(--invokeai-colors-base-800)', backgroundColor: mode(base200, base800)(colorMode),
color: 'var(--invokeai-colors-base-200)', color: mode(base800, base200)(colorMode),
padding: 6, padding: 6,
'&[data-hovered]': { '&[data-hovered]': {
color: 'var(--invokeai-colors-base-100)', color: mode(base900, base100)(colorMode),
backgroundColor: 'var(--invokeai-colors-base-750)', backgroundColor: mode(base300, base700)(colorMode),
}, },
'&[data-active]': { '&[data-active]': {
backgroundColor: 'var(--invokeai-colors-base-750)', backgroundColor: mode(base300, base700)(colorMode),
'&:hover': { '&:hover': {
color: 'var(--invokeai-colors-base-100)', color: mode(base900, base100)(colorMode),
backgroundColor: 'var(--invokeai-colors-base-750)', backgroundColor: mode(base300, base700)(colorMode),
}, },
}, },
'&[data-selected]': { '&[data-selected]': {
color: 'var(--invokeai-colors-base-50)', color: mode(base900, base50)(colorMode),
backgroundColor: 'var(--invokeai-colors-accent-650)', backgroundColor: mode(accent300, accent600)(colorMode),
fontWeight: 600, fontWeight: 600,
'&:hover': { '&:hover': {
backgroundColor: 'var(--invokeai-colors-accent-600)', backgroundColor: mode(accent400, accent500)(colorMode),
}, },
}, },
}, },
rightSection: { rightSection: {
width: 32, width: 32,
button: {
color: mode(base900, base100)(colorMode),
},
}, },
})} })}
{...rest} {...rest}

View File

@ -1,5 +1,6 @@
import { Checkbox, CheckboxProps, Text } from '@chakra-ui/react'; import { Checkbox, CheckboxProps, Text, useColorMode } from '@chakra-ui/react';
import { memo, ReactElement } from 'react'; import { memo, ReactElement } from 'react';
import { mode } from 'theme/util/mode';
type IAISimpleCheckboxProps = CheckboxProps & { type IAISimpleCheckboxProps = CheckboxProps & {
label: string | ReactElement; label: string | ReactElement;
@ -7,9 +8,15 @@ type IAISimpleCheckboxProps = CheckboxProps & {
const IAISimpleCheckbox = (props: IAISimpleCheckboxProps) => { const IAISimpleCheckbox = (props: IAISimpleCheckboxProps) => {
const { label, ...rest } = props; const { label, ...rest } = props;
const { colorMode } = useColorMode();
return ( return (
<Checkbox colorScheme="accent" {...rest}> <Checkbox colorScheme="accent" {...rest}>
<Text color="base.200" fontSize="md"> <Text
sx={{
fontSize: 'sm',
color: mode('base.800', 'base.200')(colorMode),
}}
>
{label} {label}
</Text> </Text>
</Checkbox> </Checkbox>

View File

@ -0,0 +1,124 @@
import { useToken } from '@chakra-ui/react';
export const useChakraThemeTokens = () => {
const [
base50,
base100,
base150,
base200,
base250,
base300,
base350,
base400,
base450,
base500,
base550,
base600,
base650,
base700,
base750,
base800,
base850,
base900,
base950,
accent50,
accent100,
accent150,
accent200,
accent250,
accent300,
accent350,
accent400,
accent450,
accent500,
accent550,
accent600,
accent650,
accent700,
accent750,
accent800,
accent850,
accent900,
accent950,
] = useToken('colors', [
'base.50',
'base.100',
'base.150',
'base.200',
'base.250',
'base.300',
'base.350',
'base.400',
'base.450',
'base.500',
'base.550',
'base.600',
'base.650',
'base.700',
'base.750',
'base.800',
'base.850',
'base.900',
'base.950',
'accent.50',
'accent.100',
'accent.150',
'accent.200',
'accent.250',
'accent.300',
'accent.350',
'accent.400',
'accent.450',
'accent.500',
'accent.550',
'accent.600',
'accent.650',
'accent.700',
'accent.750',
'accent.800',
'accent.850',
'accent.900',
'accent.950',
]);
return {
base50,
base100,
base150,
base200,
base250,
base300,
base350,
base400,
base450,
base500,
base550,
base600,
base650,
base700,
base750,
base800,
base850,
base900,
base950,
accent50,
accent100,
accent150,
accent200,
accent250,
accent300,
accent350,
accent400,
accent450,
accent500,
accent550,
accent600,
accent650,
accent700,
accent750,
accent800,
accent850,
accent900,
accent950,
};
};

View File

@ -1,6 +1,6 @@
// Grid drawing adapted from https://longviewcoder.com/2021/12/08/konva-a-better-grid/ // Grid drawing adapted from https://longviewcoder.com/2021/12/08/konva-a-better-grid/
import { useToken } from '@chakra-ui/react'; import { useColorMode, useColorModeValue, useToken } from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit'; import { createSelector } from '@reduxjs/toolkit';
import { RootState } from 'app/store/store'; import { RootState } from 'app/store/store';
import { useAppSelector } from 'app/store/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
@ -29,9 +29,12 @@ const IAICanvasGrid = () => {
); );
const { stageScale, stageCoordinates, stageDimensions } = const { stageScale, stageCoordinates, stageDimensions } =
useAppSelector(selector); useAppSelector(selector);
const { colorMode } = useColorMode();
const [gridLines, setGridLines] = useState<ReactNode[]>([]); const [gridLines, setGridLines] = useState<ReactNode[]>([]);
const [darkGridLineColor, lightGridLineColor] = useToken('colors', [
const [gridLineColor] = useToken('colors', ['gridLineColor']); 'base.800',
'base.200',
]);
const unscale = useCallback( const unscale = useCallback(
(value: number) => { (value: number) => {
@ -89,7 +92,7 @@ const IAICanvasGrid = () => {
x={fullRect.x1 + i * 64} x={fullRect.x1 + i * 64}
y={fullRect.y1} y={fullRect.y1}
points={[0, 0, 0, ySize]} points={[0, 0, 0, ySize]}
stroke={gridLineColor} stroke={colorMode === 'dark' ? darkGridLineColor : lightGridLineColor}
strokeWidth={1} strokeWidth={1}
/> />
)); ));
@ -99,7 +102,7 @@ const IAICanvasGrid = () => {
x={fullRect.x1} x={fullRect.x1}
y={fullRect.y1 + i * 64} y={fullRect.y1 + i * 64}
points={[0, 0, xSize, 0]} points={[0, 0, xSize, 0]}
stroke={gridLineColor} stroke={colorMode === 'dark' ? darkGridLineColor : lightGridLineColor}
strokeWidth={1} strokeWidth={1}
/> />
)); ));
@ -111,7 +114,9 @@ const IAICanvasGrid = () => {
stageDimensions, stageDimensions,
currentTheme, currentTheme,
unscale, unscale,
gridLineColor, colorMode,
darkGridLineColor,
lightGridLineColor,
]); ]);
return <Group>{gridLines}</Group>; return <Group>{gridLines}</Group>;

View File

@ -104,7 +104,10 @@ const IAICanvasStatusText = () => {
margin: 1, margin: 1,
borderRadius: 'base', borderRadius: 'base',
pointerEvents: 'none', pointerEvents: 'none',
bg: 'base.800', bg: 'base.200',
_dark: {
bg: 'base.800',
},
}} }}
> >
<Box <Box

View File

@ -1,4 +1,4 @@
import { Box, ChakraProps, Flex } from '@chakra-ui/react'; import { Box, ChakraProps, Flex, useColorMode } from '@chakra-ui/react';
import { useAppDispatch } from 'app/store/storeHooks'; import { useAppDispatch } from 'app/store/storeHooks';
import { memo, useCallback } from 'react'; import { memo, useCallback } from 'react';
import { FaCopy, FaTrash } from 'react-icons/fa'; import { FaCopy, FaTrash } from 'react-icons/fa';
@ -22,6 +22,7 @@ import ParamControlNetShouldAutoConfig from './ParamControlNetShouldAutoConfig';
import ParamControlNetBeginEnd from './parameters/ParamControlNetBeginEnd'; import ParamControlNetBeginEnd from './parameters/ParamControlNetBeginEnd';
import ParamControlNetControlMode from './parameters/ParamControlNetControlMode'; import ParamControlNetControlMode from './parameters/ParamControlNetControlMode';
import ParamControlNetProcessorSelect from './parameters/ParamControlNetProcessorSelect'; import ParamControlNetProcessorSelect from './parameters/ParamControlNetProcessorSelect';
import { mode } from 'theme/util/mode';
const expandedControlImageSx: ChakraProps['sx'] = { maxH: 96 }; const expandedControlImageSx: ChakraProps['sx'] = { maxH: 96 };
@ -46,7 +47,7 @@ const ControlNet = (props: ControlNetProps) => {
} = props.controlNet; } = props.controlNet;
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const [isExpanded, toggleIsExpanded] = useToggle(false); const [isExpanded, toggleIsExpanded] = useToggle(false);
const { colorMode } = useColorMode();
const handleDelete = useCallback(() => { const handleDelete = useCallback(() => {
dispatch(controlNetRemoved({ controlNetId })); dispatch(controlNetRemoved({ controlNetId }));
}, [controlNetId, dispatch]); }, [controlNetId, dispatch]);
@ -67,7 +68,7 @@ const ControlNet = (props: ControlNetProps) => {
flexDir: 'column', flexDir: 'column',
gap: 2, gap: 2,
p: 3, p: 3,
bg: 'base.850', bg: mode('base.200', 'base.850')(colorMode),
borderRadius: 'base', borderRadius: 'base',
position: 'relative', position: 'relative',
}} }}
@ -115,7 +116,7 @@ const ControlNet = (props: ControlNetProps) => {
<ChevronUpIcon <ChevronUpIcon
sx={{ sx={{
boxSize: 4, boxSize: 4,
color: 'base.300', color: mode('base.700', 'base.300')(colorMode),
transform: isExpanded ? 'rotate(0deg)' : 'rotate(180deg)', transform: isExpanded ? 'rotate(0deg)' : 'rotate(180deg)',
transitionProperty: 'common', transitionProperty: 'common',
transitionDuration: 'normal', transitionDuration: 'normal',
@ -130,7 +131,7 @@ const ControlNet = (props: ControlNetProps) => {
w: 1.5, w: 1.5,
h: 1.5, h: 1.5,
borderRadius: 'full', borderRadius: 'full',
bg: 'error.200', bg: mode('error.700', 'error.200')(colorMode),
top: 4, top: 4,
insetInlineEnd: 4, insetInlineEnd: 4,
}} }}

View File

@ -1,4 +1,4 @@
import { Flex, Text } from '@chakra-ui/react'; import { Flex, Text, useColorMode } from '@chakra-ui/react';
import { FaImages } from 'react-icons/fa'; import { FaImages } from 'react-icons/fa';
import { boardIdSelected } from '../../store/boardSlice'; import { boardIdSelected } from '../../store/boardSlice';
import { useDispatch } from 'react-redux'; import { useDispatch } from 'react-redux';
@ -10,9 +10,11 @@ import { ImageDTO } from 'services/api/types';
import { useRemoveImageFromBoardMutation } from 'services/api/endpoints/boardImages'; import { useRemoveImageFromBoardMutation } from 'services/api/endpoints/boardImages';
import { useDroppable } from '@dnd-kit/core'; import { useDroppable } from '@dnd-kit/core';
import IAIDropOverlay from 'common/components/IAIDropOverlay'; import IAIDropOverlay from 'common/components/IAIDropOverlay';
import { mode } from 'theme/util/mode';
const AllImagesBoard = ({ isSelected }: { isSelected: boolean }) => { const AllImagesBoard = ({ isSelected }: { isSelected: boolean }) => {
const dispatch = useDispatch(); const dispatch = useDispatch();
const { colorMode } = useColorMode();
const handleAllImagesBoardClick = () => { const handleAllImagesBoardClick = () => {
dispatch(boardIdSelected()); dispatch(boardIdSelected());
@ -79,7 +81,9 @@ const AllImagesBoard = ({ isSelected }: { isSelected: boolean }) => {
</Flex> </Flex>
<Text <Text
sx={{ sx={{
color: isSelected ? 'base.50' : 'base.200', color: isSelected
? mode('base.900', 'base.50')(colorMode)
: mode('base.700', 'base.200')(colorMode),
fontWeight: isSelected ? 600 : undefined, fontWeight: isSelected ? 600 : undefined,
fontSize: 'xs', fontSize: 'xs',
}} }}

View File

@ -62,13 +62,13 @@ const BoardsList = (props: Props) => {
return ( return (
<Collapse in={isOpen} animateOpacity> <Collapse in={isOpen} animateOpacity>
<Flex <Flex
layerStyle={'first'}
sx={{ sx={{
flexDir: 'column', flexDir: 'column',
gap: 2, gap: 2,
bg: 'base.800',
borderRadius: 'base',
p: 2, p: 2,
mt: 2, mt: 2,
borderRadius: 'base',
}} }}
> >
<Flex sx={{ gap: 2, alignItems: 'center' }}> <Flex sx={{ gap: 2, alignItems: 'center' }}>

View File

@ -8,6 +8,7 @@ import {
Image, Image,
MenuItem, MenuItem,
MenuList, MenuList,
useColorMode,
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import { useAppDispatch } from 'app/store/storeHooks'; import { useAppDispatch } from 'app/store/storeHooks';
@ -30,6 +31,7 @@ import { AnimatePresence } from 'framer-motion';
import IAIDropOverlay from 'common/components/IAIDropOverlay'; import IAIDropOverlay from 'common/components/IAIDropOverlay';
import { SelectedItemOverlay } from '../SelectedItemOverlay'; import { SelectedItemOverlay } from '../SelectedItemOverlay';
import { DeleteBoardImagesContext } from '../../../../app/contexts/DeleteBoardImagesContext'; import { DeleteBoardImagesContext } from '../../../../app/contexts/DeleteBoardImagesContext';
import { mode } from 'theme/util/mode';
interface HoverableBoardProps { interface HoverableBoardProps {
board: BoardDTO; board: BoardDTO;
@ -43,6 +45,8 @@ const HoverableBoard = memo(({ board, isSelected }: HoverableBoardProps) => {
board.cover_image_name ?? skipToken board.cover_image_name ?? skipToken
); );
const { colorMode } = useColorMode();
const { board_name, board_id } = board; const { board_name, board_id } = board;
const { onClickDeleteBoardImages } = useContext(DeleteBoardImagesContext); const { onClickDeleteBoardImages } = useContext(DeleteBoardImagesContext);
@ -110,7 +114,7 @@ const HoverableBoard = memo(({ board, isSelected }: HoverableBoardProps) => {
</MenuItem> </MenuItem>
)} )}
<MenuItem <MenuItem
sx={{ color: 'error.300' }} sx={{ color: mode('error.700', 'error.300')(colorMode) }}
icon={<FaTrash />} icon={<FaTrash />}
onClickCapture={handleDeleteBoard} onClickCapture={handleDeleteBoard}
> >
@ -180,7 +184,9 @@ const HoverableBoard = memo(({ board, isSelected }: HoverableBoardProps) => {
> >
<EditablePreview <EditablePreview
sx={{ sx={{
color: isSelected ? 'base.50' : 'base.200', color: isSelected
? mode('base.900', 'base.50')(colorMode)
: mode('base.700', 'base.200')(colorMode),
fontWeight: isSelected ? 600 : undefined, fontWeight: isSelected ? 600 : undefined,
fontSize: 'xs', fontSize: 'xs',
textAlign: 'center', textAlign: 'center',
@ -190,9 +196,9 @@ const HoverableBoard = memo(({ board, isSelected }: HoverableBoardProps) => {
/> />
<EditableInput <EditableInput
sx={{ sx={{
color: 'base.50', color: mode('base.900', 'base.50')(colorMode),
fontSize: 'xs', fontSize: 'xs',
borderColor: 'base.500', borderColor: mode('base.500', 'base.500')(colorMode),
p: 0, p: 0,
outline: 0, outline: 0,
}} }}

View File

@ -32,7 +32,6 @@ const CurrentImageDisplay = () => {
height: '100%', height: '100%',
width: '100%', width: '100%',
rowGap: 4, rowGap: 4,
borderRadius: 'base',
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
}} }}

View File

@ -9,6 +9,7 @@ import {
Text, Text,
VStack, VStack,
forwardRef, forwardRef,
useColorMode,
useDisclosure, useDisclosure,
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
@ -61,6 +62,7 @@ import BoardsList from './Boards/BoardsList';
import { boardsSelector } from '../store/boardSlice'; import { boardsSelector } from '../store/boardSlice';
import { ChevronUpIcon } from '@chakra-ui/icons'; import { ChevronUpIcon } from '@chakra-ui/icons';
import { useListAllBoardsQuery } from 'services/api/endpoints/boards'; import { useListAllBoardsQuery } from 'services/api/endpoints/boards';
import { mode } from 'theme/util/mode';
const itemSelector = createSelector( const itemSelector = createSelector(
[(state: RootState) => state], [(state: RootState) => state],
@ -135,6 +137,8 @@ const ImageGalleryContent = () => {
}, },
}); });
const { colorMode } = useColorMode();
const { const {
shouldPinGallery, shouldPinGallery,
galleryImageMinimumWidth, galleryImageMinimumWidth,
@ -267,13 +271,17 @@ const ImageGalleryContent = () => {
alignItems: 'center', alignItems: 'center',
px: 2, px: 2,
_hover: { _hover: {
bg: 'base.800', bg: mode('base.100', 'base.800')(colorMode),
}, },
}} }}
> >
<Text <Text
noOfLines={1} noOfLines={1}
sx={{ w: 'full', color: 'base.200', fontWeight: 600 }} sx={{
w: 'full',
color: mode('base.800', 'base.200')(colorMode),
fontWeight: 600,
}}
> >
{selectedBoard ? selectedBoard.board_name : 'All Images'} {selectedBoard ? selectedBoard.board_name : 'All Images'}
</Text> </Text>

View File

@ -1,26 +1,40 @@
import { useColorMode, useToken } from '@chakra-ui/react';
import { motion } from 'framer-motion'; import { motion } from 'framer-motion';
import { mode } from 'theme/util/mode';
export const SelectedItemOverlay = () => ( export const SelectedItemOverlay = () => {
<motion.div const [accent400, accent500] = useToken('colors', [
initial={{ 'accent.400',
opacity: 0, 'accent.500',
}} ]);
animate={{
opacity: 1, const { colorMode } = useColorMode();
transition: { duration: 0.1 },
}} return (
exit={{ <motion.div
opacity: 0, initial={{
transition: { duration: 0.1 }, opacity: 0,
}} }}
style={{ animate={{
position: 'absolute', opacity: 1,
top: 0, transition: { duration: 0.1 },
insetInlineStart: 0, }}
width: '100%', exit={{
height: '100%', opacity: 0,
boxShadow: 'inset 0px 0px 0px 2px var(--invokeai-colors-accent-300)', transition: { duration: 0.1 },
borderRadius: 'var(--invokeai-radii-base)', }}
}} style={{
/> position: 'absolute',
); top: 0,
insetInlineStart: 0,
width: '100%',
height: '100%',
boxShadow: `inset 0px 0px 0px 2px ${mode(
accent400,
accent500
)(colorMode)}`,
borderRadius: 'var(--invokeai-radii-base)',
}}
/>
);
};

View File

@ -12,15 +12,25 @@ const IAINodeHeader = (props: IAINodeHeaderProps) => {
const { nodeId, template } = props; const { nodeId, template } = props;
return ( return (
<Flex <Flex
borderTopRadius="md" sx={{
justifyContent="space-between" borderTopRadius: 'md',
background="base.700" alignItems: 'center',
px={2} justifyContent: 'space-between',
py={1} px: 2,
alignItems="center" py: 1,
bg: 'base.300',
_dark: { bg: 'base.700' },
}}
> >
<Tooltip label={nodeId}> <Tooltip label={nodeId}>
<Heading size="xs" fontWeight={600} color="base.100"> <Heading
size="xs"
sx={{
fontWeight: 600,
color: 'base.900',
_dark: { color: 'base.100' },
}}
>
{template.title} {template.title}
</Heading> </Heading>
</Tooltip> </Tooltip>
@ -30,7 +40,16 @@ const IAINodeHeader = (props: IAINodeHeaderProps) => {
hasArrow hasArrow
shouldWrapChildren shouldWrapChildren
> >
<Icon color="base.300" as={FaInfoCircle} h="min-content" /> <Icon
sx={{
h: 'min-content',
color: 'base.700',
_dark: {
color: 'base.300',
},
}}
as={FaInfoCircle}
/>
</Tooltip> </Tooltip>
</Flex> </Flex>
); );

View File

@ -72,7 +72,14 @@ export const InvocationComponent = memo((props: NodeProps<InvocationValue>) => {
return ( return (
<InvocationComponentWrapper selected={selected}> <InvocationComponentWrapper selected={selected}>
<Flex sx={{ alignItems: 'center', justifyContent: 'center' }}> <Flex sx={{ alignItems: 'center', justifyContent: 'center' }}>
<Icon color="base.400" boxSize={32} as={FaExclamationCircle}></Icon> <Icon
as={FaExclamationCircle}
sx={{
boxSize: 32,
color: 'base.600',
_dark: { color: 'base.400' },
}}
></Icon>
<IAINodeResizer /> <IAINodeResizer />
</Flex> </Flex>
</InvocationComponentWrapper> </InvocationComponentWrapper>
@ -86,8 +93,9 @@ export const InvocationComponent = memo((props: NodeProps<InvocationValue>) => {
sx={{ sx={{
flexDirection: 'column', flexDirection: 'column',
borderBottomRadius: 'md', borderBottomRadius: 'md',
bg: 'base.800',
py: 2, py: 2,
bg: 'base.200',
_dark: { bg: 'base.800' },
}} }}
> >
<IAINodeOutputs nodeId={nodeId} outputs={outputs} template={template} /> <IAINodeOutputs nodeId={nodeId} outputs={outputs} template={template} />

View File

@ -8,12 +8,12 @@ import { memo } from 'react';
const NodeEditor = () => { const NodeEditor = () => {
return ( return (
<Box <Box
layerStyle={'first'}
sx={{ sx={{
position: 'relative', position: 'relative',
width: 'full', width: 'full',
height: 'full', height: 'full',
borderRadius: 'md', borderRadius: 'base',
bg: 'base.850',
}} }}
> >
<ReactFlowProvider> <ReactFlowProvider>

View File

@ -11,17 +11,20 @@ const NodeGraphOverlay = () => {
return ( return (
<Box <Box
as="pre" as="pre"
fontFamily="monospace" sx={{
position="absolute" fontFamily: 'monospace',
top={2} position: 'absolute',
right={2} top: 2,
opacity={0.7} right: 2,
background="base.800" opacity: 0.7,
p={2} p: 2,
maxHeight={500} maxHeight: 500,
maxWidth={500} maxWidth: 500,
overflowY="scroll" overflowY: 'scroll',
borderRadius="md" borderRadius: 'base',
bg: 'base.200',
_dark: { bg: 'base.800' },
}}
> >
{JSON.stringify(graph, null, 2)} {JSON.stringify(graph, null, 2)}
</Box> </Box>

View File

@ -1,15 +1,25 @@
import { RootState } from 'app/store/store'; import { useColorModeValue } from '@chakra-ui/react';
import { useAppSelector } from 'app/store/storeHooks'; import { memo } from 'react';
import { CSSProperties, memo } from 'react';
import { MiniMap } from 'reactflow'; import { MiniMap } from 'reactflow';
const MinimapStyle: CSSProperties = {
background: 'var(--invokeai-colors-base-500)',
};
const MinimapPanel = () => { const MinimapPanel = () => {
const currentTheme = useAppSelector( const miniMapStyle = useColorModeValue(
(state: RootState) => state.ui.currentTheme {
background: 'var(--invokeai-colors-base-200)',
},
{
background: 'var(--invokeai-colors-base-500)',
}
);
const nodeColor = useColorModeValue(
'var(--invokeai-colors-accent-300)',
'var(--invokeai-colors-accent-700)'
);
const maskColor = useColorModeValue(
'var(--invokeai-colors-blackAlpha-300)',
'var(--invokeai-colors-blackAlpha-600)'
); );
return ( return (
@ -18,15 +28,9 @@ const MinimapPanel = () => {
pannable pannable
zoomable zoomable
nodeBorderRadius={30} nodeBorderRadius={30}
style={MinimapStyle} style={miniMapStyle}
nodeColor={ nodeColor={nodeColor}
currentTheme === 'light' maskColor={maskColor}
? 'var(--invokeai-colors-accent-700)'
: currentTheme === 'green'
? 'var(--invokeai-colors-accent-600)'
: 'var(--invokeai-colors-accent-700)'
}
maskColor="var(--invokeai-colors-base-700)"
/> />
); );
}; };

View File

@ -4,17 +4,17 @@ import InitialImagePreview from './InitialImagePreview';
const InitialImageDisplay = () => { const InitialImageDisplay = () => {
return ( return (
<Flex <Flex
layerStyle={'first'}
sx={{ sx={{
position: 'relative', position: 'relative',
flexDirection: 'column', flexDirection: 'column',
height: '100%', height: '100%',
width: '100%', width: '100%',
rowGap: 4, rowGap: 4,
borderRadius: 'base',
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
bg: 'base.850',
p: 4, p: 4,
borderRadius: 'base',
}} }}
> >
<Flex <Flex

View File

@ -20,6 +20,8 @@ import {
MenuList, MenuList,
MenuOptionGroup, MenuOptionGroup,
MenuItemOption, MenuItemOption,
ButtonGroupProps,
ButtonProps,
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import { useHotkeys } from 'react-hotkeys-hook'; import { useHotkeys } from 'react-hotkeys-hook';
@ -55,7 +57,7 @@ interface CancelButtonProps {
} }
const CancelButton = ( const CancelButton = (
props: CancelButtonProps & Omit<IAIIconButtonProps, 'aria-label'> props: CancelButtonProps & Omit<ButtonProps, 'aria-label'>
) => { ) => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const { btnGroupWidth = 'auto', ...rest } = props; const { btnGroupWidth = 'auto', ...rest } = props;
@ -139,12 +141,14 @@ const CancelButton = (
<MenuButton <MenuButton
as={IAIIconButton} as={IAIIconButton}
tooltip={t('parameters.cancel.setType')} tooltip={t('parameters.cancel.setType')}
tooltipPlacement="top"
aria-label={t('parameters.cancel.setType')} aria-label={t('parameters.cancel.setType')}
icon={<ChevronDownIcon w="1em" h="1em" />} icon={<ChevronDownIcon w="1em" h="1em" />}
paddingX={0} paddingX={0}
paddingY={0} paddingY={0}
colorScheme="error" colorScheme="error"
minWidth={5} minWidth={5}
{...rest}
/> />
<MenuList minWidth="240px"> <MenuList minWidth="240px">
<MenuOptionGroup <MenuOptionGroup

View File

@ -71,7 +71,7 @@ export default function InvokeButton(props: InvokeButton) {
flexGrow={1} flexGrow={1}
w="100%" w="100%"
tooltip={t('parameters.invoke')} tooltip={t('parameters.invoke')}
tooltipProps={{ placement: 'bottom' }} tooltipProps={{ placement: 'top' }}
colorScheme="accent" colorScheme="accent"
id="invoke-button" id="invoke-button"
{...rest} {...rest}

View File

@ -0,0 +1,25 @@
import { useColorMode } from '@chakra-ui/react';
import IAIIconButton from 'common/components/IAIIconButton';
import { FaMoon, FaSun } from 'react-icons/fa';
const ColorModeButton = () => {
const { colorMode, toggleColorMode } = useColorMode();
return (
<IAIIconButton
aria-label="Toggle Color Mode"
size="sm"
icon={
colorMode === 'dark' ? (
<FaSun fontSize={19} />
) : (
<FaMoon fontSize={18} />
)
}
onClick={toggleColorMode}
variant="link"
/>
);
};
export default ColorModeButton;

View File

@ -1,5 +1,4 @@
import { import {
ChakraProps,
Flex, Flex,
Heading, Heading,
Modal, Modal,
@ -10,6 +9,7 @@ import {
ModalHeader, ModalHeader,
ModalOverlay, ModalOverlay,
Text, Text,
useColorMode,
useDisclosure, useDisclosure,
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit'; import { createSelector } from '@reduxjs/toolkit';
@ -39,6 +39,7 @@ import { UIState } from 'features/ui/store/uiTypes';
import { isEqual } from 'lodash-es'; import { isEqual } from 'lodash-es';
import { import {
ChangeEvent, ChangeEvent,
PropsWithChildren,
ReactElement, ReactElement,
cloneElement, cloneElement,
useCallback, useCallback,
@ -47,6 +48,7 @@ import {
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { LogLevelName } from 'roarr'; import { LogLevelName } from 'roarr';
import SettingsSchedulers from './SettingsSchedulers'; import SettingsSchedulers from './SettingsSchedulers';
import { mode } from 'theme/util/mode';
const selector = createSelector( const selector = createSelector(
[systemSelector, uiSelector], [systemSelector, uiSelector],
@ -83,14 +85,6 @@ const selector = createSelector(
} }
); );
const modalSectionStyles: ChakraProps['sx'] = {
flexDirection: 'column',
gap: 2,
p: 4,
bg: 'base.900',
borderRadius: 'base',
};
type ConfigOptions = { type ConfigOptions = {
shouldShowDeveloperSettings: boolean; shouldShowDeveloperSettings: boolean;
shouldShowResetWebUiText: boolean; shouldShowResetWebUiText: boolean;
@ -183,12 +177,12 @@ const SettingsModal = ({ children, config }: SettingsModalProps) => {
isCentered isCentered
> >
<ModalOverlay /> <ModalOverlay />
<ModalContent paddingInlineEnd={4}> <ModalContent>
<ModalHeader>{t('common.settingsLabel')}</ModalHeader> <ModalHeader>{t('common.settingsLabel')}</ModalHeader>
<ModalCloseButton /> <ModalCloseButton />
<ModalBody> <ModalBody>
<Flex sx={{ gap: 4, flexDirection: 'column' }}> <Flex sx={{ gap: 4, flexDirection: 'column' }}>
<Flex sx={modalSectionStyles}> <StyledFlex>
<Heading size="sm">{t('settings.general')}</Heading> <Heading size="sm">{t('settings.general')}</Heading>
<IAISwitch <IAISwitch
label={t('settings.confirmOnDelete')} label={t('settings.confirmOnDelete')}
@ -197,14 +191,14 @@ const SettingsModal = ({ children, config }: SettingsModalProps) => {
dispatch(setShouldConfirmOnDelete(e.target.checked)) dispatch(setShouldConfirmOnDelete(e.target.checked))
} }
/> />
</Flex> </StyledFlex>
<Flex sx={modalSectionStyles}> <StyledFlex>
<Heading size="sm">{t('settings.generation')}</Heading> <Heading size="sm">{t('settings.generation')}</Heading>
<SettingsSchedulers /> <SettingsSchedulers />
</Flex> </StyledFlex>
<Flex sx={modalSectionStyles}> <StyledFlex>
<Heading size="sm">{t('settings.ui')}</Heading> <Heading size="sm">{t('settings.ui')}</Heading>
<IAISwitch <IAISwitch
label={t('settings.displayHelpIcons')} label={t('settings.displayHelpIcons')}
@ -245,10 +239,10 @@ const SettingsModal = ({ children, config }: SettingsModalProps) => {
) )
} }
/> />
</Flex> </StyledFlex>
{shouldShowDeveloperSettings && ( {shouldShowDeveloperSettings && (
<Flex sx={modalSectionStyles}> <StyledFlex>
<Heading size="sm">{t('settings.developer')}</Heading> <Heading size="sm">{t('settings.developer')}</Heading>
<IAISwitch <IAISwitch
label={t('settings.shouldLogToConsole')} label={t('settings.shouldLogToConsole')}
@ -269,10 +263,10 @@ const SettingsModal = ({ children, config }: SettingsModalProps) => {
dispatch(setEnableImageDebugging(e.target.checked)) dispatch(setEnableImageDebugging(e.target.checked))
} }
/> />
</Flex> </StyledFlex>
)} )}
<Flex sx={modalSectionStyles}> <StyledFlex>
<Heading size="sm">{t('settings.resetWebUI')}</Heading> <Heading size="sm">{t('settings.resetWebUI')}</Heading>
<IAIButton colorScheme="error" onClick={handleClickResetWebUI}> <IAIButton colorScheme="error" onClick={handleClickResetWebUI}>
{t('settings.resetWebUI')} {t('settings.resetWebUI')}
@ -283,7 +277,7 @@ const SettingsModal = ({ children, config }: SettingsModalProps) => {
<Text>{t('settings.resetWebUIDesc2')}</Text> <Text>{t('settings.resetWebUIDesc2')}</Text>
</> </>
)} )}
</Flex> </StyledFlex>
</Flex> </Flex>
</ModalBody> </ModalBody>
@ -319,3 +313,19 @@ const SettingsModal = ({ children, config }: SettingsModalProps) => {
}; };
export default SettingsModal; export default SettingsModal;
const StyledFlex = (props: PropsWithChildren) => {
return (
<Flex
layerStyle="second"
sx={{
flexDirection: 'column',
gap: 2,
p: 4,
borderRadius: 'base',
}}
>
{props.children}
</Flex>
);
};

View File

@ -12,8 +12,8 @@ import InvokeAILogoComponent from './InvokeAILogoComponent';
import LanguagePicker from './LanguagePicker'; import LanguagePicker from './LanguagePicker';
import ModelManagerModal from './ModelManager/ModelManagerModal'; import ModelManagerModal from './ModelManager/ModelManagerModal';
import SettingsModal from './SettingsModal/SettingsModal'; import SettingsModal from './SettingsModal/SettingsModal';
import ThemeChanger from './ThemeChanger';
import { useFeatureStatus } from '../hooks/useFeatureStatus'; import { useFeatureStatus } from '../hooks/useFeatureStatus';
import ColorModeButton from './ColorModeButton';
const SiteHeader = () => { const SiteHeader = () => {
const { t } = useTranslation(); const { t } = useTranslation();
@ -63,8 +63,6 @@ const SiteHeader = () => {
/> />
</HotkeysModal> </HotkeysModal>
<ThemeChanger />
{isLocalizationEnabled && <LanguagePicker />} {isLocalizationEnabled && <LanguagePicker />}
{isBugLinkEnabled && ( {isBugLinkEnabled && (
@ -121,6 +119,8 @@ const SiteHeader = () => {
</Link> </Link>
)} )}
<ColorModeButton />
<SettingsModal> <SettingsModal>
<IAIIconButton <IAIIconButton
aria-label={t('common.settingsLabel')} aria-label={t('common.settingsLabel')}

View File

@ -35,6 +35,18 @@ const statusIndicatorSelector = createSelector(
defaultSelectorOptions defaultSelectorOptions
); );
const DARK_COLOR_MAP = {
ok: 'green.400',
working: 'yellow.400',
error: 'red.400',
};
const LIGHT_COLOR_MAP = {
ok: 'green.600',
working: 'yellow.500',
error: 'red.500',
};
const StatusIndicator = () => { const StatusIndicator = () => {
const { const {
isConnected, isConnected,
@ -46,7 +58,7 @@ const StatusIndicator = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const ref = useRef(null); const ref = useRef(null);
const statusColorScheme = useMemo(() => { const statusString = useMemo(() => {
if (isProcessing) { if (isProcessing) {
return 'working'; return 'working';
} }
@ -90,9 +102,10 @@ const StatusIndicator = () => {
sx={{ sx={{
fontSize: 'sm', fontSize: 'sm',
fontWeight: '600', fontWeight: '600',
color: `${statusColorScheme}.400`,
pb: '1px', pb: '1px',
userSelect: 'none', userSelect: 'none',
color: LIGHT_COLOR_MAP[statusString],
_dark: { color: DARK_COLOR_MAP[statusString] },
}} }}
> >
{t(statusTranslationKey as ResourceKey)} {t(statusTranslationKey as ResourceKey)}
@ -101,7 +114,14 @@ const StatusIndicator = () => {
</motion.div> </motion.div>
)} )}
</AnimatePresence> </AnimatePresence>
<Icon as={FaCircle} boxSize="0.5rem" color={`${statusColorScheme}.400`} /> <Icon
as={FaCircle}
sx={{
boxSize: '0.5rem',
color: LIGHT_COLOR_MAP[statusString],
_dark: { color: DARK_COLOR_MAP[statusString] },
}}
/>
</Flex> </Flex>
); );
}; };

View File

@ -1,60 +0,0 @@
import {
IconButton,
Menu,
MenuButton,
MenuItemOption,
MenuList,
MenuOptionGroup,
Tooltip,
} from '@chakra-ui/react';
import { RootState } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { setCurrentTheme } from 'features/ui/store/uiSlice';
import i18n from 'i18n';
import { map } from 'lodash-es';
import { useTranslation } from 'react-i18next';
import { FaPalette } from 'react-icons/fa';
export const THEMES = {
dark: i18n.t('common.darkTheme'),
light: i18n.t('common.lightTheme'),
green: i18n.t('common.greenTheme'),
ocean: i18n.t('common.oceanTheme'),
};
export default function ThemeChanger() {
const { t } = useTranslation();
const dispatch = useAppDispatch();
const currentTheme = useAppSelector(
(state: RootState) => state.ui.currentTheme
);
return (
<Menu closeOnSelect={false}>
<Tooltip label={t('common.themeLabel')} hasArrow>
<MenuButton
as={IconButton}
icon={<FaPalette />}
variant="link"
aria-label={t('common.themeLabel')}
fontSize={20}
minWidth={8}
/>
</Tooltip>
<MenuList>
<MenuOptionGroup value={currentTheme}>
{map(THEMES, (themeName, themeKey: keyof typeof THEMES) => (
<MenuItemOption
key={themeKey}
value={themeKey}
onClick={() => dispatch(setCurrentTheme(themeKey))}
>
{themeName}
</MenuItemOption>
))}
</MenuOptionGroup>
</MenuList>
</Menu>
);
}

View File

@ -51,6 +51,7 @@ const FloatingGalleryButton = () => {
w: 8, w: 8,
borderStartEndRadius: 0, borderStartEndRadius: 0,
borderEndEndRadius: 0, borderEndEndRadius: 0,
shadow: '2xl',
}} }}
> >
<MdPhotoLibrary /> <MdPhotoLibrary />

View File

@ -19,6 +19,7 @@ import { FaSlidersH } from 'react-icons/fa';
const floatingButtonStyles: ChakraProps['sx'] = { const floatingButtonStyles: ChakraProps['sx'] = {
borderStartStartRadius: 0, borderStartStartRadius: 0,
borderEndStartRadius: 0, borderEndStartRadius: 0,
shadow: '2xl',
}; };
export const floatingParametersPanelButtonSelector = createSelector( export const floatingParametersPanelButtonSelector = createSelector(

View File

@ -6,6 +6,7 @@ import {
useOutsideClick, useOutsideClick,
useTheme, useTheme,
SlideDirection, SlideDirection,
useColorMode,
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import { import {
Resizable, Resizable,
@ -21,6 +22,7 @@ import {
getSlideDirection, getSlideDirection,
getStyles, getStyles,
} from './util'; } from './util';
import { mode } from 'theme/util/mode';
type ResizableDrawerProps = ResizableProps & { type ResizableDrawerProps = ResizableProps & {
children: ReactNode; children: ReactNode;
@ -64,7 +66,7 @@ const ResizableDrawer = ({
sx = {}, sx = {},
}: ResizableDrawerProps) => { }: ResizableDrawerProps) => {
const langDirection = useTheme().direction as LangDirection; const langDirection = useTheme().direction as LangDirection;
const { colorMode } = useColorMode();
const outsideClickRef = useRef<HTMLDivElement>(null); const outsideClickRef = useRef<HTMLDivElement>(null);
const defaultWidth = useMemo( const defaultWidth = useMemo(
@ -160,11 +162,11 @@ const ResizableDrawer = ({
handleStyles={handleStyles} handleStyles={handleStyles}
{...minMaxDimensions} {...minMaxDimensions}
sx={{ sx={{
borderColor: 'base.800', borderColor: mode('base.200', 'base.800')(colorMode),
p: 4, p: 4,
bg: 'base.900', bg: mode('base.100', 'base.900')(colorMode),
height: 'full', height: 'full',
boxShadow: '0 0 4rem 0 rgba(0, 0, 0, 0.8)', shadow: isOpen ? 'dark-lg' : undefined,
...containerStyles, ...containerStyles,
...sx, ...sx,
}} }}

View File

@ -1,6 +1,7 @@
import { Box, Flex, FlexProps } from '@chakra-ui/react'; import { Box, Flex, FlexProps, useColorMode } from '@chakra-ui/react';
import { memo } from 'react'; import { memo } from 'react';
import { PanelResizeHandle } from 'react-resizable-panels'; import { PanelResizeHandle } from 'react-resizable-panels';
import { mode } from 'theme/util/mode';
type ResizeHandleProps = FlexProps & { type ResizeHandleProps = FlexProps & {
direction?: 'horizontal' | 'vertical'; direction?: 'horizontal' | 'vertical';
@ -8,6 +9,7 @@ type ResizeHandleProps = FlexProps & {
const ResizeHandle = (props: ResizeHandleProps) => { const ResizeHandle = (props: ResizeHandleProps) => {
const { direction = 'horizontal', ...rest } = props; const { direction = 'horizontal', ...rest } = props;
const { colorMode } = useColorMode();
if (direction === 'horizontal') { if (direction === 'horizontal') {
return ( return (
@ -21,7 +23,13 @@ const ResizeHandle = (props: ResizeHandleProps) => {
}} }}
{...rest} {...rest}
> >
<Box sx={{ w: 0.5, h: 'calc(100% - 4px)', bg: 'base.850' }} /> <Box
sx={{
w: 0.5,
h: 'calc(100% - 4px)',
bg: mode('base.100', 'base.850')(colorMode),
}}
/>
</Flex> </Flex>
</PanelResizeHandle> </PanelResizeHandle>
); );
@ -38,7 +46,13 @@ const ResizeHandle = (props: ResizeHandleProps) => {
}} }}
{...rest} {...rest}
> >
<Box sx={{ w: 'calc(100% - 4px)', h: 0.5, bg: 'base.850' }} /> <Box
sx={{
w: 'calc(100% - 4px)',
h: 0.5,
bg: mode('base.100', 'base.850')(colorMode),
}}
/>
</Flex> </Flex>
</PanelResizeHandle> </PanelResizeHandle>
); );

View File

@ -4,13 +4,13 @@ import CurrentImageDisplay from 'features/gallery/components/CurrentImageDisplay
const TextToImageTabMain = () => { const TextToImageTabMain = () => {
return ( return (
<Box <Box
layerStyle={'first'}
sx={{ sx={{
position: 'relative', position: 'relative',
width: '100%', width: '100%',
height: '100%', height: '100%',
borderRadius: 'base',
bg: 'base.850',
p: 4, p: 4,
borderRadius: 'base',
}} }}
> >
<Flex <Flex

View File

@ -67,14 +67,14 @@ const UnifiedCanvasContent = () => {
if (shouldUseCanvasBetaLayout) { if (shouldUseCanvasBetaLayout) {
return ( return (
<Box <Box
layerStyle="first"
ref={setDroppableRef} ref={setDroppableRef}
tabIndex={0} tabIndex={0}
sx={{ sx={{
w: 'full', w: 'full',
h: 'full', h: 'full',
borderRadius: 'base',
bg: 'base.850',
p: 4, p: 4,
borderRadius: 'base',
}} }}
> >
<Flex <Flex
@ -110,11 +110,11 @@ const UnifiedCanvasContent = () => {
ref={setDroppableRef} ref={setDroppableRef}
tabIndex={-1} tabIndex={-1}
sx={{ sx={{
layerStyle: 'first',
w: 'full', w: 'full',
h: 'full', h: 'full',
borderRadius: 'base',
bg: 'base.850',
p: 4, p: 4,
borderRadius: 'base',
}} }}
> >
<Flex <Flex

View File

@ -8,7 +8,6 @@ import { SchedulerParam } from 'features/parameters/store/parameterZodSchemas';
export const initialUIState: UIState = { export const initialUIState: UIState = {
activeTab: 0, activeTab: 0,
currentTheme: 'dark',
shouldPinParametersPanel: true, shouldPinParametersPanel: true,
shouldShowParametersPanel: true, shouldShowParametersPanel: true,
shouldShowImageDetails: false, shouldShowImageDetails: false,
@ -30,9 +29,6 @@ export const uiSlice = createSlice({
setActiveTab: (state, action: PayloadAction<number | InvokeTabName>) => { setActiveTab: (state, action: PayloadAction<number | InvokeTabName>) => {
setActiveTabReducer(state, action.payload); setActiveTabReducer(state, action.payload);
}, },
setCurrentTheme: (state, action: PayloadAction<string>) => {
state.currentTheme = action.payload;
},
setShouldPinParametersPanel: (state, action: PayloadAction<boolean>) => { setShouldPinParametersPanel: (state, action: PayloadAction<boolean>) => {
state.shouldPinParametersPanel = action.payload; state.shouldPinParametersPanel = action.payload;
state.shouldShowParametersPanel = true; state.shouldShowParametersPanel = true;
@ -110,7 +106,6 @@ export const uiSlice = createSlice({
export const { export const {
setActiveTab, setActiveTab,
setCurrentTheme,
setShouldPinParametersPanel, setShouldPinParametersPanel,
setShouldShowParametersPanel, setShouldShowParametersPanel,
setShouldShowImageDetails, setShouldShowImageDetails,

View File

@ -16,7 +16,6 @@ export type Rect = Coordinates & Dimensions;
export interface UIState { export interface UIState {
activeTab: number; activeTab: number;
currentTheme: string;
shouldPinParametersPanel: boolean; shouldPinParametersPanel: boolean;
shouldShowParametersPanel: boolean; shouldShowParametersPanel: boolean;
shouldShowImageDetails: boolean; shouldShowImageDetails: boolean;

View File

@ -2,7 +2,7 @@ import { MantineThemeOverride } from '@mantine/core';
export const mantineTheme: MantineThemeOverride = { export const mantineTheme: MantineThemeOverride = {
colorScheme: 'dark', colorScheme: 'dark',
fontFamily: `'InterVariable', sans-serif`, fontFamily: `'Inter Variable', sans-serif`,
components: { components: {
ScrollArea: { ScrollArea: {
defaultProps: { defaultProps: {

View File

@ -1,18 +0,0 @@
import { InvokeAIThemeColors } from 'theme/themeTypes';
import { generateColorPalette } from '../util/generateColorPalette';
export const greenTeaThemeColors: InvokeAIThemeColors = {
base: generateColorPalette(223, 10),
baseAlpha: generateColorPalette(223, 10, false, true),
accent: generateColorPalette(160, 60),
accentAlpha: generateColorPalette(160, 60, false, true),
working: generateColorPalette(47, 68),
workingAlpha: generateColorPalette(47, 68, false, true),
warning: generateColorPalette(28, 75),
warningAlpha: generateColorPalette(28, 75, false, true),
ok: generateColorPalette(122, 49),
okAlpha: generateColorPalette(122, 49, false, true),
error: generateColorPalette(0, 50),
errorAlpha: generateColorPalette(0, 50, false, true),
gridLineColor: 'rgba(255, 255, 255, 0.15)',
};

View File

@ -1,18 +0,0 @@
import { InvokeAIThemeColors } from 'theme/themeTypes';
import { generateColorPalette } from 'theme/util/generateColorPalette';
export const invokeAIThemeColors: InvokeAIThemeColors = {
base: generateColorPalette(220, 15),
baseAlpha: generateColorPalette(220, 15, false, true),
accent: generateColorPalette(250, 50),
accentAlpha: generateColorPalette(250, 50, false, true),
working: generateColorPalette(47, 67),
workingAlpha: generateColorPalette(47, 67, false, true),
warning: generateColorPalette(28, 75),
warningAlpha: generateColorPalette(28, 75, false, true),
ok: generateColorPalette(113, 70),
okAlpha: generateColorPalette(113, 70, false, true),
error: generateColorPalette(0, 76),
errorAlpha: generateColorPalette(0, 76, false, true),
gridLineColor: 'rgba(150, 150, 180, 0.15)',
};

View File

@ -1,18 +0,0 @@
import { InvokeAIThemeColors } from 'theme/themeTypes';
import { generateColorPalette } from '../util/generateColorPalette';
export const lightThemeColors: InvokeAIThemeColors = {
base: generateColorPalette(223, 10, true),
baseAlpha: generateColorPalette(223, 10, true, true),
accent: generateColorPalette(40, 80, true),
accentAlpha: generateColorPalette(40, 80, true, true),
working: generateColorPalette(47, 68, true),
workingAlpha: generateColorPalette(47, 68, true, true),
warning: generateColorPalette(28, 75, true),
warningAlpha: generateColorPalette(28, 75, true, true),
ok: generateColorPalette(122, 49, true),
okAlpha: generateColorPalette(122, 49, true, true),
error: generateColorPalette(0, 50, true),
errorAlpha: generateColorPalette(0, 50, true, true),
gridLineColor: 'rgba(0, 0, 0, 0.15)',
};

View File

@ -1,18 +0,0 @@
import { InvokeAIThemeColors } from 'theme/themeTypes';
import { generateColorPalette } from '../util/generateColorPalette';
export const oceanBlueColors: InvokeAIThemeColors = {
base: generateColorPalette(220, 30),
baseAlpha: generateColorPalette(220, 30, false, true),
accent: generateColorPalette(210, 80),
accentAlpha: generateColorPalette(210, 80, false, true),
working: generateColorPalette(47, 68),
workingAlpha: generateColorPalette(47, 68, false, true),
warning: generateColorPalette(28, 75),
warningAlpha: generateColorPalette(28, 75, false, true),
ok: generateColorPalette(122, 49),
okAlpha: generateColorPalette(122, 49, false, true),
error: generateColorPalette(0, 100),
errorAlpha: generateColorPalette(0, 100, false, true),
gridLineColor: 'rgba(136, 148, 184, 0.15)',
};

View File

@ -3,6 +3,7 @@ import {
createMultiStyleConfigHelpers, createMultiStyleConfigHelpers,
defineStyle, defineStyle,
} from '@chakra-ui/styled-system'; } from '@chakra-ui/styled-system';
import { mode } from '@chakra-ui/theme-tools';
const { definePartsStyle, defineMultiStyleConfig } = const { definePartsStyle, defineMultiStyleConfig } =
createMultiStyleConfigHelpers(parts.keys); createMultiStyleConfigHelpers(parts.keys);
@ -18,16 +19,16 @@ const invokeAIButton = defineStyle((props) => {
fontSize: 'sm', fontSize: 'sm',
border: 'none', border: 'none',
borderRadius: 'base', borderRadius: 'base',
bg: `${c}.800`, bg: mode(`${c}.200`, `${c}.700`)(props),
color: 'base.100', color: mode(`${c}.900`, `${c}.100`)(props),
_hover: { _hover: {
bg: `${c}.700`, bg: mode(`${c}.250`, `${c}.650`)(props),
}, },
_expanded: { _expanded: {
bg: `${c}.750`, bg: mode(`${c}.250`, `${c}.650`)(props),
borderBottomRadius: 'none', borderBottomRadius: 'none',
_hover: { _hover: {
bg: `${c}.700`, bg: mode(`${c}.300`, `${c}.600`)(props),
}, },
}, },
}; };
@ -36,7 +37,7 @@ const invokeAIButton = defineStyle((props) => {
const invokeAIPanel = defineStyle((props) => { const invokeAIPanel = defineStyle((props) => {
const { colorScheme: c } = props; const { colorScheme: c } = props;
return { return {
bg: `${c}.800`, bg: mode(`${c}.100`, `${c}.800`)(props),
borderRadius: 'base', borderRadius: 'base',
borderTopRadius: 'none', borderTopRadius: 'none',
}; };

View File

@ -1,44 +1,55 @@
import { defineStyle, defineStyleConfig } from '@chakra-ui/react'; import { defineStyle, defineStyleConfig } from '@chakra-ui/react';
import { mode } from '@chakra-ui/theme-tools';
const invokeAI = defineStyle((props) => { const invokeAI = defineStyle((props) => {
const { colorScheme: c } = props; const { colorScheme: c } = props;
// must specify `_disabled` colors if we override `_hover`, else hover on disabled has no styles // must specify `_disabled` colors if we override `_hover`, else hover on disabled has no styles
const _disabled = { const _disabled = {
bg: `${c}.600`, bg: mode(`${c}.350`, `${c}.700`)(props),
color: `${c}.100`, color: mode(`${c}.750`, `${c}.150`)(props),
svg: { svg: {
fill: `${c}.100`, fill: mode(`${c}.750`, `${c}.150`)(props),
}, },
opacity: 1,
filter: 'saturate(65%)',
}; };
return { return {
bg: `${c}.700`, bg: mode(`${c}.200`, `${c}.600`)(props),
color: `${c}.100`, color: mode(`${c}.850`, `${c}.100`)(props),
borderRadius: 'base', borderRadius: 'base',
textShadow: mode(
`0 0 0.3rem var(--invokeai-colors-${c}-50)`,
`0 0 0.3rem var(--invokeai-colors-${c}-900)`
)(props),
svg: { svg: {
fill: `${c}.100`, fill: mode(`${c}.850`, `${c}.100`)(props),
filter: mode(
`drop-shadow(0px 0px 0.3rem var(--invokeai-colors-${c}-100))`,
`drop-shadow(0px 0px 0.3rem var(--invokeai-colors-${c}-800))`
)(props),
}, },
_disabled, _disabled,
_hover: { _hover: {
bg: `${c}.650`, bg: mode(`${c}.300`, `${c}.500`)(props),
color: `${c}.50`, color: mode(`${c}.900`, `${c}.50`)(props),
svg: { svg: {
fill: `${c}.50`, fill: mode(`${c}.900`, `${c}.50`)(props),
}, },
_disabled, _disabled,
}, },
_checked: { _checked: {
bg: 'accent.700', bg: mode('accent.200', 'accent.600')(props),
color: 'accent.100', color: mode('accent.800', 'accent.100')(props),
svg: { svg: {
fill: 'accent.100', fill: mode('accent.800', 'accent.100')(props),
}, },
_disabled, _disabled,
_hover: { _hover: {
bg: 'accent.600', bg: mode('accent.300', 'accent.500')(props),
color: 'accent.50', color: mode('accent.900', 'accent.50')(props),
svg: { svg: {
fill: 'accent.50', fill: mode('accent.900', 'accent.50')(props),
}, },
_disabled, _disabled,
}, },

View File

@ -3,6 +3,7 @@ import {
createMultiStyleConfigHelpers, createMultiStyleConfigHelpers,
defineStyle, defineStyle,
} from '@chakra-ui/styled-system'; } from '@chakra-ui/styled-system';
import { mode } from '@chakra-ui/theme-tools';
const { definePartsStyle, defineMultiStyleConfig } = const { definePartsStyle, defineMultiStyleConfig } =
createMultiStyleConfigHelpers(parts.keys); createMultiStyleConfigHelpers(parts.keys);
@ -11,14 +12,18 @@ const invokeAIControl = defineStyle((props) => {
const { colorScheme: c } = props; const { colorScheme: c } = props;
return { return {
bg: mode('base.200', 'base.700')(props),
borderColor: mode('base.200', 'base.700')(props),
color: mode('base.900', 'base.100')(props),
_checked: { _checked: {
bg: `${c}.200`, bg: mode(`${c}.300`, `${c}.600`)(props),
borderColor: `${c}.200`, borderColor: mode(`${c}.300`, `${c}.600`)(props),
color: 'base.900', color: mode(`${c}.900`, `${c}.100`)(props),
_hover: { _hover: {
bg: `${c}.300`, bg: mode(`${c}.400`, `${c}.500`)(props),
borderColor: `${c}.300`, borderColor: mode(`${c}.400`, `${c}.500`)(props),
}, },
_disabled: { _disabled: {
@ -29,9 +34,9 @@ const invokeAIControl = defineStyle((props) => {
}, },
_indeterminate: { _indeterminate: {
bg: `${c}.200`, bg: mode(`${c}.300`, `${c}.600`)(props),
borderColor: `${c}.200`, borderColor: mode(`${c}.300`, `${c}.600`)(props),
color: 'base.900', color: mode(`${c}.900`, `${c}.100`)(props),
}, },
_disabled: { _disabled: {
@ -44,7 +49,7 @@ const invokeAIControl = defineStyle((props) => {
}, },
_invalid: { _invalid: {
borderColor: 'red.300', borderColor: mode('error.600', 'error.300')(props),
}, },
}; };
}); });

View File

@ -1,6 +1,7 @@
import { defineStyle, defineStyleConfig } from '@chakra-ui/styled-system'; import { defineStyle, defineStyleConfig } from '@chakra-ui/styled-system';
import { mode } from '@chakra-ui/theme-tools';
const invokeAI = defineStyle((_props) => { const invokeAI = defineStyle((props) => {
return { return {
fontSize: 'sm', fontSize: 'sm',
marginEnd: 0, marginEnd: 0,
@ -12,7 +13,7 @@ const invokeAI = defineStyle((_props) => {
_disabled: { _disabled: {
opacity: 0.4, opacity: 0.4,
}, },
color: 'base.300', color: mode('base.700', 'base.300')(props),
}; };
}); });

View File

@ -1,38 +1,40 @@
import { menuAnatomy } from '@chakra-ui/anatomy'; import { menuAnatomy } from '@chakra-ui/anatomy';
import { createMultiStyleConfigHelpers } from '@chakra-ui/react'; import { createMultiStyleConfigHelpers } from '@chakra-ui/react';
import { mode } from '@chakra-ui/theme-tools';
const { definePartsStyle, defineMultiStyleConfig } = const { definePartsStyle, defineMultiStyleConfig } =
createMultiStyleConfigHelpers(menuAnatomy.keys); createMultiStyleConfigHelpers(menuAnatomy.keys);
// define the base component styles // define the base component styles
const invokeAI = definePartsStyle({ const invokeAI = definePartsStyle((props) => ({
// define the part you're going to style // define the part you're going to style
button: { button: {
// this will style the MenuButton component // this will style the MenuButton component
fontWeight: '600', fontWeight: 500,
bg: 'base.500', bg: mode('base.300', 'base.500')(props),
color: 'base.200', color: mode('base.900', 'base.100')(props),
_hover: { _hover: {
bg: 'base.600', bg: mode('base.400', 'base.600')(props),
color: 'white', color: mode('base.900', 'base.50')(props),
fontWeight: 600,
}, },
}, },
list: { list: {
zIndex: 9999, zIndex: 9999,
bg: 'base.800', bg: mode('base.200', 'base.800')(props),
}, },
item: { item: {
// this will style the MenuItem and MenuItemOption components // this will style the MenuItem and MenuItemOption components
fontSize: 'sm', fontSize: 'sm',
bg: 'base.800', bg: mode('base.200', 'base.800')(props),
_hover: { _hover: {
bg: 'base.750', bg: mode('base.300', 'base.700')(props),
}, },
_focus: { _focus: {
bg: 'base.700', bg: mode('base.400', 'base.600')(props),
}, },
}, },
}); }));
export const menuTheme = defineMultiStyleConfig({ export const menuTheme = defineMultiStyleConfig({
variants: { variants: {

View File

@ -3,28 +3,31 @@ import {
createMultiStyleConfigHelpers, createMultiStyleConfigHelpers,
defineStyle, defineStyle,
} from '@chakra-ui/styled-system'; } from '@chakra-ui/styled-system';
import { mode } from '@chakra-ui/theme-tools';
const { defineMultiStyleConfig, definePartsStyle } = const { defineMultiStyleConfig, definePartsStyle } =
createMultiStyleConfigHelpers(parts.keys); createMultiStyleConfigHelpers(parts.keys);
const invokeAIOverlay = defineStyle({ const invokeAIOverlay = defineStyle((props) => ({
bg: 'blackAlpha.600', bg: mode('blackAlpha.700', 'blackAlpha.700')(props),
}); }));
const invokeAIDialogContainer = defineStyle({}); const invokeAIDialogContainer = defineStyle({});
const invokeAIDialog = defineStyle((_props) => { const invokeAIDialog = defineStyle((props) => {
return { return {
bg: 'base.850', layerStyle: 'first',
maxH: '80vh', maxH: '80vh',
}; };
}); });
const invokeAIHeader = defineStyle((_props) => { const invokeAIHeader = defineStyle((props) => {
return { return {
fontWeight: '600', fontWeight: '600',
fontSize: 'lg', fontSize: 'lg',
color: 'base.200', layerStyle: 'first',
borderTopRadius: 'base',
borderInlineEndRadius: 'base',
}; };
}); });
@ -37,7 +40,7 @@ const invokeAIBody = defineStyle({
const invokeAIFooter = defineStyle({}); const invokeAIFooter = defineStyle({});
export const invokeAI = definePartsStyle((props) => ({ export const invokeAI = definePartsStyle((props) => ({
overlay: invokeAIOverlay, overlay: invokeAIOverlay(props),
dialogContainer: invokeAIDialogContainer, dialogContainer: invokeAIDialogContainer,
dialog: invokeAIDialog(props), dialog: invokeAIDialog(props),
header: invokeAIHeader(props), header: invokeAIHeader(props),

View File

@ -5,6 +5,7 @@ import {
} from '@chakra-ui/styled-system'; } from '@chakra-ui/styled-system';
import { getInputOutlineStyles } from '../util/getInputOutlineStyles'; import { getInputOutlineStyles } from '../util/getInputOutlineStyles';
import { mode } from '@chakra-ui/theme-tools';
const { defineMultiStyleConfig, definePartsStyle } = const { defineMultiStyleConfig, definePartsStyle } =
createMultiStyleConfigHelpers(parts.keys); createMultiStyleConfigHelpers(parts.keys);
@ -33,7 +34,7 @@ const invokeAIStepperGroup = defineStyle((_props) => {
}; };
}); });
const invokeAIStepper = defineStyle((_props) => { const invokeAIStepper = defineStyle((props) => {
return { return {
border: 'none', border: 'none',
// expand arrow hitbox // expand arrow hitbox
@ -43,11 +44,11 @@ const invokeAIStepper = defineStyle((_props) => {
my: 0, my: 0,
svg: { svg: {
color: 'base.300', color: mode('base.700', 'base.300')(props),
width: 2.5, width: 2.5,
height: 2.5, height: 2.5,
_hover: { _hover: {
color: 'base.50', color: mode('base.900', 'base.100')(props),
}, },
}, },
}; };

View File

@ -3,7 +3,7 @@ import {
createMultiStyleConfigHelpers, createMultiStyleConfigHelpers,
defineStyle, defineStyle,
} from '@chakra-ui/styled-system'; } from '@chakra-ui/styled-system';
import { cssVar } from '@chakra-ui/theme-tools'; import { cssVar, mode } from '@chakra-ui/theme-tools';
const { defineMultiStyleConfig, definePartsStyle } = const { defineMultiStyleConfig, definePartsStyle } =
createMultiStyleConfigHelpers(parts.keys); createMultiStyleConfigHelpers(parts.keys);
@ -12,15 +12,20 @@ const $popperBg = cssVar('popper-bg');
const $arrowBg = cssVar('popper-arrow-bg'); const $arrowBg = cssVar('popper-arrow-bg');
const $arrowShadowColor = cssVar('popper-arrow-shadow-color'); const $arrowShadowColor = cssVar('popper-arrow-shadow-color');
const invokeAIContent = defineStyle((_props) => { const invokeAIContent = defineStyle((props) => {
return { return {
[$arrowBg.variable]: `colors.base.800`, [$arrowBg.variable]: mode('colors.base.100', 'colors.base.800')(props),
[$popperBg.variable]: `colors.base.800`, [$popperBg.variable]: mode('colors.base.100', 'colors.base.800')(props),
[$arrowShadowColor.variable]: `colors.base.600`, [$arrowShadowColor.variable]: mode(
'colors.base.400',
'colors.base.600'
)(props),
minW: 'unset', minW: 'unset',
width: 'unset', width: 'unset',
p: 4, p: 4,
bg: 'base.800', bg: mode('base.100', 'base.800')(props),
border: 'none',
shadow: 'dark-lg',
}; };
}); });

View File

@ -1,13 +1,14 @@
import { selectAnatomy as parts } from '@chakra-ui/anatomy'; import { selectAnatomy as parts } from '@chakra-ui/anatomy';
import { createMultiStyleConfigHelpers, defineStyle } from '@chakra-ui/react'; import { createMultiStyleConfigHelpers, defineStyle } from '@chakra-ui/react';
import { getInputOutlineStyles } from '../util/getInputOutlineStyles'; import { getInputOutlineStyles } from '../util/getInputOutlineStyles';
import { mode } from '@chakra-ui/theme-tools';
const { definePartsStyle, defineMultiStyleConfig } = const { definePartsStyle, defineMultiStyleConfig } =
createMultiStyleConfigHelpers(parts.keys); createMultiStyleConfigHelpers(parts.keys);
const invokeAIIcon = defineStyle((_props) => { const invokeAIIcon = defineStyle((props) => {
return { return {
color: 'base.300', color: mode('base.200', 'base.300')(props),
}; };
}); });

View File

@ -1,12 +1,13 @@
import { sliderAnatomy as parts } from '@chakra-ui/anatomy'; import { sliderAnatomy as parts } from '@chakra-ui/anatomy';
import { createMultiStyleConfigHelpers, defineStyle } from '@chakra-ui/react'; import { createMultiStyleConfigHelpers, defineStyle } from '@chakra-ui/react';
import { mode } from '@chakra-ui/theme-tools';
const { definePartsStyle, defineMultiStyleConfig } = const { definePartsStyle, defineMultiStyleConfig } =
createMultiStyleConfigHelpers(parts.keys); createMultiStyleConfigHelpers(parts.keys);
const invokeAITrack = defineStyle((_props) => { const invokeAITrack = defineStyle((props) => {
return { return {
bg: 'base.400', bg: mode('base.400', 'base.600')(props),
h: 1.5, h: 1.5,
}; };
}); });
@ -14,23 +15,24 @@ const invokeAITrack = defineStyle((_props) => {
const invokeAIFilledTrack = defineStyle((props) => { const invokeAIFilledTrack = defineStyle((props) => {
const { colorScheme: c } = props; const { colorScheme: c } = props;
return { return {
bg: `${c}.600`, bg: mode(`${c}.400`, `${c}.600`)(props),
h: 1.5, h: 1.5,
}; };
}); });
const invokeAIThumb = defineStyle((_props) => { const invokeAIThumb = defineStyle((props) => {
return { return {
w: 2, w: 2,
h: 4, h: 4,
bg: mode('base.50', 'base.100')(props),
}; };
}); });
const invokeAIMark = defineStyle((_props) => { const invokeAIMark = defineStyle((props) => {
return { return {
fontSize: 'xs', fontSize: 'xs',
fontWeight: '500', fontWeight: '500',
color: 'base.400', color: mode('base.700', 'base.400')(props),
mt: 2, mt: 2,
insetInlineStart: 'unset', insetInlineStart: 'unset',
}; };

View File

@ -3,6 +3,7 @@ import {
createMultiStyleConfigHelpers, createMultiStyleConfigHelpers,
defineStyle, defineStyle,
} from '@chakra-ui/styled-system'; } from '@chakra-ui/styled-system';
import { mode } from '@chakra-ui/theme-tools';
const { defineMultiStyleConfig, definePartsStyle } = const { defineMultiStyleConfig, definePartsStyle } =
createMultiStyleConfigHelpers(parts.keys); createMultiStyleConfigHelpers(parts.keys);
@ -11,13 +12,13 @@ const invokeAITrack = defineStyle((props) => {
const { colorScheme: c } = props; const { colorScheme: c } = props;
return { return {
bg: 'base.600', bg: mode('base.300', 'base.600')(props),
_focusVisible: { _focusVisible: {
boxShadow: 'none', boxShadow: 'none',
}, },
_checked: { _checked: {
bg: `${c}.600`, bg: mode(`${c}.400`, `${c}.500`)(props),
}, },
}; };
}); });
@ -26,7 +27,7 @@ const invokeAIThumb = defineStyle((props) => {
const { colorScheme: c } = props; const { colorScheme: c } = props;
return { return {
bg: `${c}.50`, bg: mode(`${c}.50`, `${c}.50`)(props),
}; };
}); });

View File

@ -3,6 +3,7 @@ import {
createMultiStyleConfigHelpers, createMultiStyleConfigHelpers,
defineStyle, defineStyle,
} from '@chakra-ui/styled-system'; } from '@chakra-ui/styled-system';
import { mode } from '@chakra-ui/theme-tools';
const { defineMultiStyleConfig, definePartsStyle } = const { defineMultiStyleConfig, definePartsStyle } =
createMultiStyleConfigHelpers(parts.keys); createMultiStyleConfigHelpers(parts.keys);
@ -16,30 +17,51 @@ const invokeAIRoot = defineStyle((_props) => {
const invokeAITab = defineStyle((_props) => ({})); const invokeAITab = defineStyle((_props) => ({}));
const invokeAITablist = defineStyle((_props) => ({ const invokeAITablist = defineStyle((props) => {
display: 'flex', const { colorScheme: c } = props;
flexDirection: 'column',
gap: 1, return {
color: 'base.700', display: 'flex',
button: { flexDirection: 'column',
fontSize: 'sm', gap: 1,
padding: 2, color: mode('base.700', 'base.400')(props),
borderRadius: 'base', button: {
_selected: { fontSize: 'sm',
borderBottomColor: 'base.800', padding: 2,
bg: 'accent.700', borderRadius: 'base',
color: 'accent.100', textShadow: mode(
`0 0 0.3rem var(--invokeai-colors-accent-100)`,
`0 0 0.3rem var(--invokeai-colors-accent-900)`
)(props),
svg: {
fill: mode('base.700', 'base.300')(props),
},
_selected: {
borderBottomColor: 'base.800',
bg: mode('accent.200', 'accent.600')(props),
color: mode('accent.800', 'accent.100')(props),
_hover: {
bg: mode('accent.300', 'accent.500')(props),
color: mode('accent.900', 'accent.50')(props),
},
svg: {
fill: mode('base.900', 'base.50')(props),
filter: mode(
`drop-shadow(0px 0px 0.3rem var(--invokeai-colors-accent-100))`,
`drop-shadow(0px 0px 0.3rem var(--invokeai-colors-accent-900))`
)(props),
},
},
_hover: { _hover: {
bg: 'accent.600', bg: mode('base.100', 'base.800')(props),
color: 'accent.50', color: mode('base.900', 'base.50')(props),
svg: {
fill: mode(`base.800`, `base.100`)(props),
},
}, },
}, },
_hover: { };
bg: 'base.600', });
color: 'base.50',
},
},
}));
const invokeAITabpanel = defineStyle((_props) => ({ const invokeAITabpanel = defineStyle((_props) => ({
padding: 0, padding: 0,

View File

@ -1,7 +1,8 @@
import { defineStyle, defineStyleConfig } from '@chakra-ui/react'; import { defineStyle, defineStyleConfig } from '@chakra-ui/react';
import { mode } from '@chakra-ui/theme-tools';
const subtext = defineStyle((_props) => ({ const subtext = defineStyle((props) => ({
color: 'base.400', color: mode('colors.base.500', 'colors.base.400')(props),
})); }));
export const textTheme = defineStyleConfig({ export const textTheme = defineStyleConfig({

View File

@ -0,0 +1,17 @@
import { defineStyle, defineStyleConfig } from '@chakra-ui/react';
import { mode } from '@chakra-ui/theme-tools';
import { cssVar } from '@chakra-ui/theme-tools';
const $arrowBg = cssVar('popper-arrow-bg');
// define the base component styles
const baseStyle = defineStyle((props) => ({
borderRadius: 'base',
shadow: 'dark-lg',
bg: mode('base.700', 'base.200')(props),
[$arrowBg.variable]: mode('colors.base.700', 'colors.base.200')(props),
pb: 1.5,
}));
// export the component theme
export const tooltipTheme = defineStyleConfig({ baseStyle });

View File

@ -1,7 +1,5 @@
import { ThemeOverride } from '@chakra-ui/react'; import { ThemeOverride } from '@chakra-ui/react';
import type { StyleFunctionProps } from '@chakra-ui/styled-system';
import { invokeAIThemeColors } from 'theme/colors/invokeAI';
import { accordionTheme } from './components/accordion'; import { accordionTheme } from './components/accordion';
import { buttonTheme } from './components/button'; import { buttonTheme } from './components/button';
import { checkboxTheme } from './components/checkbox'; import { checkboxTheme } from './components/checkbox';
@ -19,6 +17,15 @@ import { switchTheme } from './components/switch';
import { tabsTheme } from './components/tabs'; import { tabsTheme } from './components/tabs';
import { textTheme } from './components/text'; import { textTheme } from './components/text';
import { textareaTheme } from './components/textarea'; import { textareaTheme } from './components/textarea';
import { tooltipTheme } from './components/tooltip';
import { generateColorPalette } from './util/generateColorPalette';
const BASE = { H: 240, S: 8 };
const ACCENT = { H: 260, S: 52 };
const WORKING = { H: 47, S: 50 };
const WARNING = { H: 28, S: 50 };
const OK = { H: 113, S: 50 };
const ERROR = { H: 0, S: 50 };
export const theme: ThemeOverride = { export const theme: ThemeOverride = {
config: { config: {
@ -26,30 +33,32 @@ export const theme: ThemeOverride = {
initialColorMode: 'dark', initialColorMode: 'dark',
useSystemColorMode: false, useSystemColorMode: false,
}, },
layerStyles: {
body: {
bg: 'base.50',
color: 'base.900',
'.chakra-ui-dark &': { bg: 'base.900', color: 'base.50' },
},
first: {
bg: 'base.100',
color: 'base.900',
'.chakra-ui-dark &': { bg: 'base.850', color: 'base.100' },
},
second: {
bg: 'base.200',
color: 'base.900',
'.chakra-ui-dark &': { bg: 'base.800', color: 'base.100' },
},
},
styles: { styles: {
global: (_props: StyleFunctionProps) => ({ global: (props) => ({
body: { layerStyle: 'body',
bg: 'base.900',
color: 'base.50',
overflow: {
base: 'scroll',
xl: 'hidden',
},
},
'*': { ...no_scrollbar }, '*': { ...no_scrollbar },
}), }),
}, },
direction: 'ltr', direction: 'ltr',
fonts: { fonts: {
body: `'InterVariable', sans-serif`, body: `'Inter Variable', sans-serif`,
},
breakpoints: {
base: '0em', // 0px and onwards
sm: '30em', // 480px and onwards
md: '48em', // 768px and onwards
lg: '62em', // 992px and onwards
xl: '80em', // 1280px and onwards
'2xl': '96em', // 1536px and onwards
}, },
shadows: { shadows: {
light: { light: {
@ -69,7 +78,18 @@ export const theme: ThemeOverride = {
nodeSelectedOutline: `0 0 0 2px var(--invokeai-colors-base-500)`, nodeSelectedOutline: `0 0 0 2px var(--invokeai-colors-base-500)`,
}, },
colors: { colors: {
...invokeAIThemeColors, base: generateColorPalette(BASE.H, BASE.S),
baseAlpha: generateColorPalette(BASE.H, BASE.S, true),
accent: generateColorPalette(ACCENT.H, ACCENT.S),
accentAlpha: generateColorPalette(ACCENT.H, ACCENT.S, true),
working: generateColorPalette(WORKING.H, WORKING.S),
workingAlpha: generateColorPalette(WORKING.H, WORKING.S, true),
warning: generateColorPalette(WARNING.H, WARNING.S),
warningAlpha: generateColorPalette(WARNING.H, WARNING.S, true),
ok: generateColorPalette(OK.H, OK.S),
okAlpha: generateColorPalette(OK.H, OK.S, true),
error: generateColorPalette(ERROR.H, ERROR.S),
errorAlpha: generateColorPalette(ERROR.H, ERROR.S, true),
}, },
components: { components: {
Button: buttonTheme, // Button and IconButton Button: buttonTheme, // Button and IconButton
@ -88,5 +108,6 @@ export const theme: ThemeOverride = {
Checkbox: checkboxTheme, Checkbox: checkboxTheme,
Menu: menuTheme, Menu: menuTheme,
Text: textTheme, Text: textTheme,
Tooltip: tooltipTheme,
}, },
}; };

View File

@ -2,46 +2,35 @@ import { InvokeAIPaletteSteps } from 'theme/themeTypes';
/** /**
* Add two numbers together * Add two numbers together
* @param {String | Number} hue Hue of the color (0-360) - Reds 0, Greens 120, Blues 240 * @param {String | Number} H Hue of the color (0-360) - Reds 0, Greens 120, Blues 240
* @param {String | Number} saturation Saturation of the color (0-100) * @param {String | Number} L Saturation of the color (0-100)
* @param {boolean} light True to generate light color palette * @param {Boolean} alpha Whether or not to generate this palette as a transparency palette
*/ */
export function generateColorPalette( export function generateColorPalette(
hue: string | number, H: string | number,
saturation: string | number, S: string | number,
light = false,
alpha = false alpha = false
) { ) {
hue = String(hue); H = String(H);
saturation = String(saturation); S = String(S);
const colorSteps = Array.from({ length: 21 }, (_, i) => i * 50); const colorSteps = Array.from({ length: 21 }, (_, i) => i * 50);
const lightnessSteps = [ const lightnessSteps = [
0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 59, 64, 68, 73, 77, 82, 86, 0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 59, 64, 68, 73, 77, 82, 86,
95, 100, 95, 100,
]; ];
const darkPalette: Partial<InvokeAIPaletteSteps> = {}; const p = colorSteps.reduce((palette, step, index) => {
const lightPalette: Partial<InvokeAIPaletteSteps> = {};
colorSteps.forEach((colorStep, index) => {
const A = alpha ? lightnessSteps[index] / 100 : 1; const A = alpha ? lightnessSteps[index] / 100 : 1;
// Lightness should be 50% for alpha colors // Lightness should be 50% for alpha colors
const darkPaletteLightness = alpha const L = alpha ? 50 : lightnessSteps[colorSteps.length - 1 - index];
? 50
: lightnessSteps[colorSteps.length - 1 - index];
darkPalette[ palette[step as keyof typeof palette] = `hsl(${H} ${S}% ${L}% / ${A})`;
colorStep as keyof typeof darkPalette
] = `hsl(${hue} ${saturation}% ${darkPaletteLightness}% / ${A})`;
const lightPaletteLightness = alpha ? 50 : lightnessSteps[index]; return palette;
}, {} as InvokeAIPaletteSteps);
lightPalette[ return p;
colorStep as keyof typeof lightPalette
] = `hsl(${hue} ${saturation}% ${lightPaletteLightness}% / ${A})`;
});
return light ? lightPalette : darkPalette;
} }

View File

@ -1,40 +1,40 @@
import { StyleFunctionProps } from '@chakra-ui/theme-tools'; import { StyleFunctionProps, mode } from '@chakra-ui/theme-tools';
export const getInputOutlineStyles = (_props?: StyleFunctionProps) => ({ export const getInputOutlineStyles = (props: StyleFunctionProps) => ({
outline: 'none', outline: 'none',
borderWidth: 2, borderWidth: 2,
borderStyle: 'solid', borderStyle: 'solid',
borderColor: 'base.800', borderColor: mode('base.200', 'base.800')(props),
bg: 'base.900', bg: mode('base.50', 'base.900')(props),
borderRadius: 'base', borderRadius: 'base',
color: 'base.100', color: mode('base.900', 'base.100')(props),
boxShadow: 'none', boxShadow: 'none',
_hover: { _hover: {
borderColor: 'base.600', borderColor: mode('base.300', 'base.600')(props),
}, },
_focus: { _focus: {
borderColor: 'accent.700', borderColor: mode('accent.200', 'accent.600')(props),
boxShadow: 'none', boxShadow: 'none',
_hover: { _hover: {
borderColor: 'accent.600', borderColor: mode('accent.300', 'accent.500')(props),
}, },
}, },
_invalid: { _invalid: {
borderColor: 'error.700', borderColor: mode('error.300', 'error.600')(props),
boxShadow: 'none', boxShadow: 'none',
_hover: { _hover: {
borderColor: 'error.600', borderColor: mode('error.400', 'error.500')(props),
}, },
}, },
_disabled: { _disabled: {
borderColor: 'base.700', borderColor: mode('base.300', 'base.700')(props),
bg: 'base.700', bg: mode('base.300', 'base.700')(props),
color: 'base.400', color: mode('base.600', 'base.400')(props),
_hover: { _hover: {
borderColor: 'base.700', borderColor: mode('base.300', 'base.700')(props),
}, },
}, },
_placeholder: { _placeholder: {
color: 'base.500', color: mode('base.700', 'base.400')(props),
}, },
}); });

View File

@ -0,0 +1,3 @@
export const mode =
(light: string, dark: string) => (colorMode: 'light' | 'dark') =>
colorMode === 'light' ? light : dark;