From 032c7e68d0f078e3bd22ef776017cdda572e370f Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Tue, 27 Jun 2023 00:12:33 +1000 Subject: [PATCH] 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. --- .../app/components/ThemeLocaleProvider.tsx | 31 +---- .../web/src/common/components/IAICollapse.tsx | 29 +++- .../web/src/common/components/IAIDndImage.tsx | 11 +- .../src/common/components/IAIDropOverlay.tsx | 14 +- .../common/components/IAIImageFallback.tsx | 12 +- .../components/IAIMantineMultiSelect.tsx | 86 ++++++++---- .../common/components/IAIMantineSelect.tsx | 88 ++++++++++--- .../common/components/IAISimpleCheckbox.tsx | 11 +- .../src/common/hooks/useChakraThemeTokens.ts | 124 ++++++++++++++++++ .../canvas/components/IAICanvasGrid.tsx | 17 ++- .../canvas/components/IAICanvasStatusText.tsx | 5 +- .../controlNet/components/ControlNet.tsx | 11 +- .../components/Boards/AllImagesBoard.tsx | 8 +- .../gallery/components/Boards/BoardsList.tsx | 4 +- .../components/Boards/HoverableBoard.tsx | 14 +- .../components/CurrentImageDisplay.tsx | 1 - .../components/ImageGalleryContent.tsx | 12 +- .../components/SelectedItemOverlay.tsx | 62 +++++---- .../components/IAINode/IAINodeHeader.tsx | 35 +++-- .../nodes/components/InvocationComponent.tsx | 12 +- .../features/nodes/components/NodeEditor.tsx | 4 +- .../nodes/components/NodeGraphOverlay.tsx | 25 ++-- .../nodes/components/panels/MinimapPanel.tsx | 40 +++--- .../ImageToImage/InitialImageDisplay.tsx | 4 +- .../ProcessButtons/CancelButton.tsx | 6 +- .../ProcessButtons/InvokeButton.tsx | 2 +- .../system/components/ColorModeButton.tsx | 25 ++++ .../SettingsModal/SettingsModal.tsx | 50 ++++--- .../features/system/components/SiteHeader.tsx | 6 +- .../system/components/StatusIndicator.tsx | 26 +++- .../system/components/ThemeChanger.tsx | 60 --------- .../ui/components/FloatingGalleryButton.tsx | 1 + .../FloatingParametersPanelButtons.tsx | 1 + .../ResizableDrawer/ResizableDrawer.tsx | 10 +- .../ui/components/tabs/ResizeHandle.tsx | 20 ++- .../tabs/TextToImage/TextToImageTabMain.tsx | 4 +- .../UnifiedCanvas/UnifiedCanvasContent.tsx | 8 +- .../web/src/features/ui/store/uiSlice.ts | 5 - .../web/src/features/ui/store/uiTypes.ts | 1 - .../frontend/web/src/mantine-theme/theme.ts | 2 +- .../frontend/web/src/theme/colors/greenTea.ts | 18 --- .../frontend/web/src/theme/colors/invokeAI.ts | 18 --- .../web/src/theme/colors/lightTheme.ts | 18 --- .../web/src/theme/colors/oceanBlue.ts | 18 --- .../web/src/theme/components/accordion.ts | 13 +- .../web/src/theme/components/button.ts | 41 +++--- .../web/src/theme/components/checkbox.ts | 23 ++-- .../web/src/theme/components/formLabel.ts | 5 +- .../frontend/web/src/theme/components/menu.ts | 24 ++-- .../web/src/theme/components/modal.ts | 19 +-- .../web/src/theme/components/numberInput.ts | 7 +- .../web/src/theme/components/popover.ts | 17 ++- .../web/src/theme/components/select.ts | 5 +- .../web/src/theme/components/slider.ts | 14 +- .../web/src/theme/components/switch.ts | 7 +- .../frontend/web/src/theme/components/tabs.ts | 64 ++++++--- .../frontend/web/src/theme/components/text.ts | 5 +- .../web/src/theme/components/tooltip.ts | 17 +++ invokeai/frontend/web/src/theme/theme.ts | 63 ++++++--- .../src/theme/util/generateColorPalette.ts | 39 ++---- .../src/theme/util/getInputOutlineStyles.ts | 30 ++--- invokeai/frontend/web/src/theme/util/mode.ts | 3 + 62 files changed, 839 insertions(+), 516 deletions(-) create mode 100644 invokeai/frontend/web/src/common/hooks/useChakraThemeTokens.ts create mode 100644 invokeai/frontend/web/src/features/system/components/ColorModeButton.tsx delete mode 100644 invokeai/frontend/web/src/features/system/components/ThemeChanger.tsx delete mode 100644 invokeai/frontend/web/src/theme/colors/greenTea.ts delete mode 100644 invokeai/frontend/web/src/theme/colors/invokeAI.ts delete mode 100644 invokeai/frontend/web/src/theme/colors/lightTheme.ts delete mode 100644 invokeai/frontend/web/src/theme/colors/oceanBlue.ts create mode 100644 invokeai/frontend/web/src/theme/components/tooltip.ts create mode 100644 invokeai/frontend/web/src/theme/util/mode.ts diff --git a/invokeai/frontend/web/src/app/components/ThemeLocaleProvider.tsx b/invokeai/frontend/web/src/app/components/ThemeLocaleProvider.tsx index 5eea4bb940..1e86e0ce1b 100644 --- a/invokeai/frontend/web/src/app/components/ThemeLocaleProvider.tsx +++ b/invokeai/frontend/web/src/app/components/ThemeLocaleProvider.tsx @@ -3,17 +3,10 @@ import { createLocalStorageManager, extendTheme, } from '@chakra-ui/react'; -import { RootState } from 'app/store/store'; -import { useAppSelector } from 'app/store/storeHooks'; -import { ReactNode, useEffect } from 'react'; +import { ReactNode, useEffect, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; 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 { MantineProvider } from '@mantine/core'; import { mantineTheme } from 'mantine-theme/theme'; @@ -24,29 +17,19 @@ type ThemeLocaleProviderProps = { children: ReactNode; }; -const THEMES = { - dark: invokeAIThemeColors, - light: lightThemeColors, - green: greenTeaThemeColors, - ocean: oceanBlueColors, -}; - const manager = createLocalStorageManager('@@invokeai-color-mode'); function ThemeLocaleProvider({ children }: ThemeLocaleProviderProps) { const { i18n } = useTranslation(); - const currentTheme = useAppSelector( - (state: RootState) => state.ui.currentTheme - ); - const direction = i18n.dir(); - const theme = extendTheme({ - ...invokeAITheme, - colors: THEMES[currentTheme as keyof typeof THEMES], - direction, - }); + const theme = useMemo(() => { + return extendTheme({ + ...invokeAITheme, + direction, + }); + }, [direction]); useEffect(() => { document.body.dir = direction; diff --git a/invokeai/frontend/web/src/common/components/IAICollapse.tsx b/invokeai/frontend/web/src/common/components/IAICollapse.tsx index ec23793741..5db26f3841 100644 --- a/invokeai/frontend/web/src/common/components/IAICollapse.tsx +++ b/invokeai/frontend/web/src/common/components/IAICollapse.tsx @@ -1,6 +1,14 @@ 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 { mode } from 'theme/util/mode'; export type IAIToggleCollapseProps = PropsWithChildren & { label: string; @@ -11,6 +19,7 @@ export type IAIToggleCollapseProps = PropsWithChildren & { const IAICollapse = (props: IAIToggleCollapseProps) => { const { label, isOpen, onToggle, children, withSwitch = false } = props; + const { colorMode } = useColorMode(); return ( { px: 4, borderTopRadius: 'base', borderBottomRadius: isOpen ? 0 : 'base', - bg: isOpen ? 'base.750' : 'base.800', - color: 'base.100', + bg: isOpen + ? mode('base.200', 'base.750')(colorMode) + : mode('base.150', 'base.800')(colorMode), + color: mode('base.900', 'base.100')(colorMode), _hover: { - bg: isOpen ? 'base.700' : 'base.750', + bg: isOpen + ? mode('base.250', 'base.700')(colorMode) + : mode('base.200', 'base.750')(colorMode), }, fontSize: 'sm', fontWeight: 600, @@ -50,7 +63,13 @@ const IAICollapse = (props: IAIToggleCollapseProps) => { )} - + {children} diff --git a/invokeai/frontend/web/src/common/components/IAIDndImage.tsx b/invokeai/frontend/web/src/common/components/IAIDndImage.tsx index bdf22c2df1..d0652dc8b9 100644 --- a/invokeai/frontend/web/src/common/components/IAIDndImage.tsx +++ b/invokeai/frontend/web/src/common/components/IAIDndImage.tsx @@ -5,6 +5,7 @@ import { Icon, IconButtonProps, Image, + useColorMode, } from '@chakra-ui/react'; import { useDraggable, useDroppable } from '@dnd-kit/core'; import { useCombinedRefs } from '@dnd-kit/utilities'; @@ -20,6 +21,7 @@ import { v4 as uuidv4 } from 'uuid'; import IAIDropOverlay from './IAIDropOverlay'; import { PostUploadAction } from 'services/api/thunks/image'; import { useImageUploadButton } from 'common/hooks/useImageUploadButton'; +import { mode } from 'theme/util/mode'; type IAIDndImageProps = { image: ImageDTO | null | undefined; @@ -62,6 +64,7 @@ const IAIDndImage = (props: IAIDndImageProps) => { } = props; const dndId = useRef(uuidv4()); + const { colorMode } = useColorMode(); const { isOver, @@ -99,10 +102,10 @@ const IAIDndImage = (props: IAIDndImageProps) => { ? {} : { cursor: 'pointer', - bg: 'base.800', + bg: mode('base.200', 'base.800')(colorMode), _hover: { - bg: 'base.750', - color: 'base.300', + bg: mode('base.300', 'base.650')(colorMode), + color: mode('base.500', 'base.300')(colorMode), }, }; @@ -181,7 +184,7 @@ const IAIDndImage = (props: IAIDndImageProps) => { borderRadius: 'base', transitionProperty: 'common', transitionDuration: '0.1s', - color: 'base.500', + color: mode('base.500', 'base.500')(colorMode), ...uploadButtonStyles, }} {...getUploadButtonProps()} diff --git a/invokeai/frontend/web/src/common/components/IAIDropOverlay.tsx b/invokeai/frontend/web/src/common/components/IAIDropOverlay.tsx index 79105c00d6..8ae54c30ab 100644 --- a/invokeai/frontend/web/src/common/components/IAIDropOverlay.tsx +++ b/invokeai/frontend/web/src/common/components/IAIDropOverlay.tsx @@ -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 { memo, useRef } from 'react'; +import { mode } from 'theme/util/mode'; import { v4 as uuidv4 } from 'uuid'; type Props = { @@ -11,6 +12,7 @@ type Props = { export const IAIDropOverlay = (props: Props) => { const { isOver, label = 'Drop' } = props; const motionId = useRef(uuidv4()); + const { colorMode } = useColorMode(); return ( { insetInlineStart: 0, w: 'full', h: 'full', - bg: 'base.900', + bg: mode('base.700', 'base.900')(colorMode), opacity: 0.7, borderRadius: 'base', alignItems: 'center', @@ -61,7 +63,9 @@ export const IAIDropOverlay = (props: Props) => { h: 'full', opacity: 1, borderWidth: 2, - borderColor: isOver ? 'base.200' : 'base.500', + borderColor: isOver + ? mode('base.50', 'base.200')(colorMode) + : mode('base.100', 'base.500')(colorMode), borderRadius: 'base', borderStyle: 'dashed', transitionProperty: 'common', @@ -75,7 +79,9 @@ export const IAIDropOverlay = (props: Props) => { fontSize: '2xl', fontWeight: 600, 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', transitionDuration: '0.1s', }} diff --git a/invokeai/frontend/web/src/common/components/IAIImageFallback.tsx b/invokeai/frontend/web/src/common/components/IAIImageFallback.tsx index 03a00d5b1c..4cff351aee 100644 --- a/invokeai/frontend/web/src/common/components/IAIImageFallback.tsx +++ b/invokeai/frontend/web/src/common/components/IAIImageFallback.tsx @@ -6,9 +6,10 @@ import { IconProps, Spinner, SpinnerProps, + useColorMode, } from '@chakra-ui/react'; -import { ReactElement } from 'react'; import { FaImage } from 'react-icons/fa'; +import { mode } from 'theme/util/mode'; type Props = FlexProps & { spinnerProps?: SpinnerProps; @@ -17,10 +18,11 @@ type Props = FlexProps & { export const IAIImageLoadingFallback = (props: Props) => { const { spinnerProps, ...rest } = props; const { sx, ...restFlexProps } = rest; + const { colorMode } = useColorMode(); return ( { const { sx: flexSx, ...restFlexProps } = props.flexProps ?? { sx: {} }; const { sx: iconSx, ...restIconProps } = props.iconProps ?? { sx: {} }; + const { colorMode } = useColorMode(); + return ( { > diff --git a/invokeai/frontend/web/src/common/components/IAIMantineMultiSelect.tsx b/invokeai/frontend/web/src/common/components/IAIMantineMultiSelect.tsx index 90be25d04d..0a324d3221 100644 --- a/invokeai/frontend/web/src/common/components/IAIMantineMultiSelect.tsx +++ b/invokeai/frontend/web/src/common/components/IAIMantineMultiSelect.tsx @@ -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 { useChakraThemeTokens } from 'common/hooks/useChakraThemeTokens'; import { memo } from 'react'; +import { mode } from 'theme/util/mode'; type IAIMultiSelectProps = MultiSelectProps & { tooltip?: string; @@ -8,71 +10,101 @@ type IAIMultiSelectProps = MultiSelectProps & { const IAIMantineMultiSelect = (props: IAIMultiSelectProps) => { 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 ( ({ label: { - color: 'var(--invokeai-colors-base-300)', + color: mode(base700, base300)(colorMode), fontWeight: 'normal', }, searchInput: { - '::placeholder': { - color: 'var(--invokeai-colors-base-700)', + ':placeholder': { + color: mode(base300, base700)(colorMode), }, }, input: { - backgroundColor: 'var(--invokeai-colors-base-900)', + backgroundColor: mode(base50, base900)(colorMode), borderWidth: '2px', - borderColor: 'var(--invokeai-colors-base-800)', - color: 'var(--invokeai-colors-base-100)', + borderColor: mode(base200, base800)(colorMode), + color: mode(base900, base100)(colorMode), paddingRight: 24, fontWeight: 600, - '&:hover': { borderColor: 'var(--invokeai-colors-base-700)' }, + '&:hover': { borderColor: mode(base300, base600)(colorMode) }, '&:focus': { - borderColor: 'var(--invokeai-colors-accent-600)', + borderColor: mode(accent300, accent600)(colorMode), + }, + '&:is(:focus, :hover)': { + borderColor: mode(base400, base500)(colorMode), }, '&: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: { - backgroundColor: 'var(--invokeai-colors-base-800)', - color: 'var(--invokeai-colors-base-100)', + backgroundColor: mode(base200, base800)(colorMode), + color: mode(base900, base100)(colorMode), button: { - color: 'var(--invokeai-colors-base-100)', + color: mode(base900, base100)(colorMode), }, '&:hover': { - backgroundColor: 'var(--invokeai-colors-base-700)', + backgroundColor: mode(base300, base700)(colorMode), cursor: 'pointer', }, }, dropdown: { - backgroundColor: 'var(--invokeai-colors-base-800)', - borderColor: 'var(--invokeai-colors-base-700)', + backgroundColor: mode(base200, base800)(colorMode), + borderColor: mode(base200, base800)(colorMode), + boxShadow, }, item: { - backgroundColor: 'var(--invokeai-colors-base-800)', - color: 'var(--invokeai-colors-base-200)', + backgroundColor: mode(base200, base800)(colorMode), + color: mode(base800, base200)(colorMode), padding: 6, '&[data-hovered]': { - color: 'var(--invokeai-colors-base-100)', - backgroundColor: 'var(--invokeai-colors-base-750)', + color: mode(base900, base100)(colorMode), + backgroundColor: mode(base300, base700)(colorMode), }, '&[data-active]': { - backgroundColor: 'var(--invokeai-colors-base-750)', + backgroundColor: mode(base300, base700)(colorMode), '&:hover': { - color: 'var(--invokeai-colors-base-100)', - backgroundColor: 'var(--invokeai-colors-base-750)', + color: mode(base900, base100)(colorMode), + backgroundColor: mode(base300, base700)(colorMode), }, }, '&[data-selected]': { - color: 'var(--invokeai-colors-base-50)', - backgroundColor: 'var(--invokeai-colors-accent-650)', + color: mode(base900, base50)(colorMode), + backgroundColor: mode(accent300, accent600)(colorMode), fontWeight: 600, '&:hover': { - backgroundColor: 'var(--invokeai-colors-accent-600)', + backgroundColor: mode(accent400, accent500)(colorMode), }, }, }, @@ -80,7 +112,7 @@ const IAIMantineMultiSelect = (props: IAIMultiSelectProps) => { width: 24, padding: 20, button: { - color: 'var(--invokeai-colors-base-100)', + color: mode(base900, base100)(colorMode), }, }, })} diff --git a/invokeai/frontend/web/src/common/components/IAIMantineSelect.tsx b/invokeai/frontend/web/src/common/components/IAIMantineSelect.tsx index 5f02140904..3638b59562 100644 --- a/invokeai/frontend/web/src/common/components/IAIMantineSelect.tsx +++ b/invokeai/frontend/web/src/common/components/IAIMantineSelect.tsx @@ -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 { useChakraThemeTokens } from 'common/hooks/useChakraThemeTokens'; import { memo } from 'react'; +import { mode } from 'theme/util/mode'; export type IAISelectDataType = { value: string; @@ -14,61 +16,105 @@ type IAISelectProps = SelectProps & { const IAIMantineSelect = (props: IAISelectProps) => { 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 (