ui: support dark mode (#3592)

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

[032c7e6](032c7e68d0)

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:
blessedcoolant 2023-06-30 06:13:43 +12:00 committed by GitHub
commit 877483093a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
67 changed files with 869 additions and 556 deletions

View File

@ -24,16 +24,13 @@
},
"common": {
"hotkeysLabel": "Hotkeys",
"themeLabel": "Theme",
"darkMode": "Dark Mode",
"lightMode": "Light Mode",
"languagePickerLabel": "Language",
"reportBugLabel": "Report Bug",
"githubLabel": "Github",
"discordLabel": "Discord",
"settingsLabel": "Settings",
"darkTheme": "Dark",
"lightTheme": "Light",
"greenTheme": "Green",
"oceanTheme": "Ocean",
"langArabic": "العربية",
"langEnglish": "English",
"langDutch": "Nederlands",

View File

@ -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;

View File

@ -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 (
<Box>
<Flex
@ -21,10 +30,14 @@ const IAICollapse = (props: IAIToggleCollapseProps) => {
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) => {
)}
</Flex>
<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}
</Box>
</Collapse>

View File

@ -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()}

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 { 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 (
<motion.div
key={motionId.current}
@ -42,7 +44,7 @@ export const IAIDropOverlay = (props: Props) => {
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',
}}

View File

@ -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 (
<Flex
sx={{
bg: 'base.900',
bg: mode('base.200', 'base.900')(colorMode),
opacity: 0.7,
w: 'full',
h: 'full',
@ -45,10 +47,12 @@ type IAINoImageFallbackProps = {
export const IAINoImageFallback = (props: IAINoImageFallbackProps) => {
const { sx: flexSx, ...restFlexProps } = props.flexProps ?? { sx: {} };
const { sx: iconSx, ...restIconProps } = props.iconProps ?? { sx: {} };
const { colorMode } = useColorMode();
return (
<Flex
sx={{
bg: 'base.900',
bg: mode('base.200', 'base.900')(colorMode),
opacity: 0.7,
w: 'full',
h: 'full',
@ -61,7 +65,7 @@ export const IAINoImageFallback = (props: IAINoImageFallbackProps) => {
>
<Icon
as={props.as ?? FaImage}
sx={{ color: 'base.700', ...iconSx }}
sx={{ color: mode('base.700', 'base.500')(colorMode), ...iconSx }}
{...restIconProps}
/>
</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 { useChakraThemeTokens } from 'common/hooks/useChakraThemeTokens';
import { memo } from 'react';
import { mode } from 'theme/util/mode';
type IAIMultiSelectProps = MultiSelectProps & {
tooltip?: string;
@ -8,71 +10,99 @@ type IAIMultiSelectProps = MultiSelectProps & {
const IAIMantineMultiSelect = (props: IAIMultiSelectProps) => {
const { searchable = true, tooltip, ...rest } = props;
const {
base50,
base100,
base200,
base300,
base400,
base500,
base600,
base700,
base800,
base900,
accent200,
accent300,
accent400,
accent500,
accent600,
} = useChakraThemeTokens();
const [boxShadow] = useToken('shadows', ['dark-lg']);
const { colorMode } = useColorMode();
return (
<Tooltip label={tooltip} placement="top" hasArrow>
<MultiSelect
searchable={searchable}
styles={() => ({
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 +110,7 @@ const IAIMantineMultiSelect = (props: IAIMultiSelectProps) => {
width: 24,
padding: 20,
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 { useChakraThemeTokens } from 'common/hooks/useChakraThemeTokens';
import { memo } from 'react';
import { mode } from 'theme/util/mode';
export type IAISelectDataType = {
value: string;
@ -14,61 +16,104 @@ 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,
} = useChakraThemeTokens();
const { colorMode } = useColorMode();
const [boxShadow] = useToken('shadows', ['dark-lg']);
return (
<Tooltip label={tooltip} placement="top" hasArrow>
<Select
searchable={searchable}
styles={() => ({
label: {
color: 'var(--invokeai-colors-base-300)',
color: mode(base700, base300)(colorMode),
fontWeight: 'normal',
},
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: mode(accent200, accent600)(colorMode),
},
'&:disabled': {
backgroundColor: 'var(--invokeai-colors-base-700)',
color: 'var(--invokeai-colors-base-400)',
backgroundColor: mode(base300, base700)(colorMode),
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: {
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),
},
},
},
rightSection: {
width: 32,
button: {
color: mode(base900, base100)(colorMode),
},
},
})}
{...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 { mode } from 'theme/util/mode';
type IAISimpleCheckboxProps = CheckboxProps & {
label: string | ReactElement;
@ -7,9 +8,15 @@ type IAISimpleCheckboxProps = CheckboxProps & {
const IAISimpleCheckbox = (props: IAISimpleCheckboxProps) => {
const { label, ...rest } = props;
const { colorMode } = useColorMode();
return (
<Checkbox colorScheme="accent" {...rest}>
<Text color="base.200" fontSize="md">
<Text
sx={{
fontSize: 'sm',
color: mode('base.800', 'base.200')(colorMode),
}}
>
{label}
</Text>
</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,8 +1,7 @@
// Grid drawing adapted from https://longviewcoder.com/2021/12/08/konva-a-better-grid/
import { useToken } from '@chakra-ui/react';
import { useColorMode, useToken } from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit';
import { RootState } from 'app/store/store';
import { useAppSelector } from 'app/store/storeHooks';
import { canvasSelector } from 'features/canvas/store/canvasSelectors';
import { isEqual, range } from 'lodash-es';
@ -24,14 +23,14 @@ const selector = createSelector(
);
const IAICanvasGrid = () => {
const currentTheme = useAppSelector(
(state: RootState) => state.ui.currentTheme
);
const { stageScale, stageCoordinates, stageDimensions } =
useAppSelector(selector);
const { colorMode } = useColorMode();
const [gridLines, setGridLines] = useState<ReactNode[]>([]);
const [gridLineColor] = useToken('colors', ['gridLineColor']);
const [darkGridLineColor, lightGridLineColor] = useToken('colors', [
'base.800',
'base.200',
]);
const unscale = useCallback(
(value: number) => {
@ -89,7 +88,7 @@ const IAICanvasGrid = () => {
x={fullRect.x1 + i * 64}
y={fullRect.y1}
points={[0, 0, 0, ySize]}
stroke={gridLineColor}
stroke={colorMode === 'dark' ? darkGridLineColor : lightGridLineColor}
strokeWidth={1}
/>
));
@ -99,7 +98,7 @@ const IAICanvasGrid = () => {
x={fullRect.x1}
y={fullRect.y1 + i * 64}
points={[0, 0, xSize, 0]}
stroke={gridLineColor}
stroke={colorMode === 'dark' ? darkGridLineColor : lightGridLineColor}
strokeWidth={1}
/>
));
@ -109,9 +108,10 @@ const IAICanvasGrid = () => {
stageScale,
stageCoordinates,
stageDimensions,
currentTheme,
unscale,
gridLineColor,
colorMode,
darkGridLineColor,
lightGridLineColor,
]);
return <Group>{gridLines}</Group>;

View File

@ -104,7 +104,10 @@ const IAICanvasStatusText = () => {
margin: 1,
borderRadius: 'base',
pointerEvents: 'none',
bg: 'base.800',
bg: 'base.200',
_dark: {
bg: 'base.800',
},
}}
>
<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 { memo, useCallback } from 'react';
import { FaCopy, FaTrash } from 'react-icons/fa';
@ -22,6 +22,7 @@ import ParamControlNetShouldAutoConfig from './ParamControlNetShouldAutoConfig';
import ParamControlNetBeginEnd from './parameters/ParamControlNetBeginEnd';
import ParamControlNetControlMode from './parameters/ParamControlNetControlMode';
import ParamControlNetProcessorSelect from './parameters/ParamControlNetProcessorSelect';
import { mode } from 'theme/util/mode';
const expandedControlImageSx: ChakraProps['sx'] = { maxH: 96 };
@ -46,7 +47,7 @@ const ControlNet = (props: ControlNetProps) => {
} = props.controlNet;
const dispatch = useAppDispatch();
const [isExpanded, toggleIsExpanded] = useToggle(false);
const { colorMode } = useColorMode();
const handleDelete = useCallback(() => {
dispatch(controlNetRemoved({ controlNetId }));
}, [controlNetId, dispatch]);
@ -67,7 +68,7 @@ const ControlNet = (props: ControlNetProps) => {
flexDir: 'column',
gap: 2,
p: 3,
bg: 'base.850',
bg: mode('base.200', 'base.850')(colorMode),
borderRadius: 'base',
position: 'relative',
}}
@ -115,7 +116,7 @@ const ControlNet = (props: ControlNetProps) => {
<ChevronUpIcon
sx={{
boxSize: 4,
color: 'base.300',
color: mode('base.700', 'base.300')(colorMode),
transform: isExpanded ? 'rotate(0deg)' : 'rotate(180deg)',
transitionProperty: 'common',
transitionDuration: 'normal',
@ -130,7 +131,7 @@ const ControlNet = (props: ControlNetProps) => {
w: 1.5,
h: 1.5,
borderRadius: 'full',
bg: 'error.200',
bg: mode('error.700', 'error.200')(colorMode),
top: 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 { boardIdSelected } from '../../store/boardSlice';
import { useDispatch } from 'react-redux';
@ -10,9 +10,11 @@ import { ImageDTO } from 'services/api/types';
import { useRemoveImageFromBoardMutation } from 'services/api/endpoints/boardImages';
import { useDroppable } from '@dnd-kit/core';
import IAIDropOverlay from 'common/components/IAIDropOverlay';
import { mode } from 'theme/util/mode';
const AllImagesBoard = ({ isSelected }: { isSelected: boolean }) => {
const dispatch = useDispatch();
const { colorMode } = useColorMode();
const handleAllImagesBoardClick = () => {
dispatch(boardIdSelected());
@ -79,7 +81,9 @@ const AllImagesBoard = ({ isSelected }: { isSelected: boolean }) => {
</Flex>
<Text
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,
fontSize: 'xs',
}}

View File

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

View File

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

View File

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

View File

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

View File

@ -1,26 +1,40 @@
import { useColorMode, useToken } from '@chakra-ui/react';
import { motion } from 'framer-motion';
import { mode } from 'theme/util/mode';
export const SelectedItemOverlay = () => (
<motion.div
initial={{
opacity: 0,
}}
animate={{
opacity: 1,
transition: { duration: 0.1 },
}}
exit={{
opacity: 0,
transition: { duration: 0.1 },
}}
style={{
position: 'absolute',
top: 0,
insetInlineStart: 0,
width: '100%',
height: '100%',
boxShadow: 'inset 0px 0px 0px 2px var(--invokeai-colors-accent-300)',
borderRadius: 'var(--invokeai-radii-base)',
}}
/>
);
export const SelectedItemOverlay = () => {
const [accent400, accent500] = useToken('colors', [
'accent.400',
'accent.500',
]);
const { colorMode } = useColorMode();
return (
<motion.div
initial={{
opacity: 0,
}}
animate={{
opacity: 1,
transition: { duration: 0.1 },
}}
exit={{
opacity: 0,
transition: { duration: 0.1 },
}}
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;
return (
<Flex
borderTopRadius="md"
justifyContent="space-between"
background="base.700"
px={2}
py={1}
alignItems="center"
sx={{
borderTopRadius: 'md',
alignItems: 'center',
justifyContent: 'space-between',
px: 2,
py: 1,
bg: 'base.300',
_dark: { bg: 'base.700' },
}}
>
<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}
</Heading>
</Tooltip>
@ -30,7 +40,16 @@ const IAINodeHeader = (props: IAINodeHeaderProps) => {
hasArrow
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>
</Flex>
);

View File

@ -72,7 +72,14 @@ export const InvocationComponent = memo((props: NodeProps<InvocationValue>) => {
return (
<InvocationComponentWrapper selected={selected}>
<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 />
</Flex>
</InvocationComponentWrapper>
@ -86,8 +93,9 @@ export const InvocationComponent = memo((props: NodeProps<InvocationValue>) => {
sx={{
flexDirection: 'column',
borderBottomRadius: 'md',
bg: 'base.800',
py: 2,
bg: 'base.200',
_dark: { bg: 'base.800' },
}}
>
<IAINodeOutputs nodeId={nodeId} outputs={outputs} template={template} />

View File

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

View File

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

View File

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

View File

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

View File

@ -1,33 +1,32 @@
import {
ButtonGroup,
ButtonProps,
ButtonSpinner,
Menu,
MenuButton,
MenuItemOption,
MenuList,
MenuOptionGroup,
} from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import IAIIconButton, {
IAIIconButtonProps,
} from 'common/components/IAIIconButton';
import IAIIconButton from 'common/components/IAIIconButton';
import { systemSelector } from 'features/system/store/systemSelectors';
import {
CancelStrategy,
SystemState,
cancelScheduled,
cancelTypeChanged,
CancelStrategy,
} from 'features/system/store/systemSlice';
import { isEqual } from 'lodash-es';
import { useCallback, memo, useMemo } from 'react';
import {
ButtonSpinner,
ButtonGroup,
Menu,
MenuButton,
MenuList,
MenuOptionGroup,
MenuItemOption,
} from '@chakra-ui/react';
import { memo, useCallback, useMemo } from 'react';
import { useHotkeys } from 'react-hotkeys-hook';
import { useTranslation } from 'react-i18next';
import { MdCancel, MdCancelScheduleSend } from 'react-icons/md';
import { sessionCanceled } from 'services/api/thunks/session';
import { ChevronDownIcon } from '@chakra-ui/icons';
import { sessionCanceled } from 'services/api/thunks/session';
const cancelButtonSelector = createSelector(
systemSelector,
@ -55,7 +54,7 @@ interface CancelButtonProps {
}
const CancelButton = (
props: CancelButtonProps & Omit<IAIIconButtonProps, 'aria-label'>
props: CancelButtonProps & Omit<ButtonProps, 'aria-label'>
) => {
const dispatch = useAppDispatch();
const { btnGroupWidth = 'auto', ...rest } = props;
@ -145,6 +144,7 @@ const CancelButton = (
paddingY={0}
colorScheme="error"
minWidth={5}
{...rest}
/>
<MenuList minWidth="240px">
<MenuOptionGroup

View File

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

View File

@ -0,0 +1,32 @@
import { useColorMode } from '@chakra-ui/react';
import IAIIconButton from 'common/components/IAIIconButton';
import { useTranslation } from 'react-i18next';
import { FaMoon, FaSun } from 'react-icons/fa';
const ColorModeButton = () => {
const { colorMode, toggleColorMode } = useColorMode();
const { t } = useTranslation();
return (
<IAIIconButton
aria-label={
colorMode === 'dark' ? t('common.lightMode') : t('common.darkMode')
}
tooltip={
colorMode === 'dark' ? t('common.lightMode') : t('common.darkMode')
}
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 {
ChakraProps,
Flex,
Heading,
Modal,
@ -39,6 +38,7 @@ import { UIState } from 'features/ui/store/uiTypes';
import { isEqual } from 'lodash-es';
import {
ChangeEvent,
PropsWithChildren,
ReactElement,
cloneElement,
useCallback,
@ -83,14 +83,6 @@ const selector = createSelector(
}
);
const modalSectionStyles: ChakraProps['sx'] = {
flexDirection: 'column',
gap: 2,
p: 4,
bg: 'base.900',
borderRadius: 'base',
};
type ConfigOptions = {
shouldShowDeveloperSettings: boolean;
shouldShowResetWebUiText: boolean;
@ -183,12 +175,12 @@ const SettingsModal = ({ children, config }: SettingsModalProps) => {
isCentered
>
<ModalOverlay />
<ModalContent paddingInlineEnd={4}>
<ModalContent>
<ModalHeader>{t('common.settingsLabel')}</ModalHeader>
<ModalCloseButton />
<ModalBody>
<Flex sx={{ gap: 4, flexDirection: 'column' }}>
<Flex sx={modalSectionStyles}>
<StyledFlex>
<Heading size="sm">{t('settings.general')}</Heading>
<IAISwitch
label={t('settings.confirmOnDelete')}
@ -197,14 +189,14 @@ const SettingsModal = ({ children, config }: SettingsModalProps) => {
dispatch(setShouldConfirmOnDelete(e.target.checked))
}
/>
</Flex>
</StyledFlex>
<Flex sx={modalSectionStyles}>
<StyledFlex>
<Heading size="sm">{t('settings.generation')}</Heading>
<SettingsSchedulers />
</Flex>
</StyledFlex>
<Flex sx={modalSectionStyles}>
<StyledFlex>
<Heading size="sm">{t('settings.ui')}</Heading>
<IAISwitch
label={t('settings.displayHelpIcons')}
@ -245,10 +237,10 @@ const SettingsModal = ({ children, config }: SettingsModalProps) => {
)
}
/>
</Flex>
</StyledFlex>
{shouldShowDeveloperSettings && (
<Flex sx={modalSectionStyles}>
<StyledFlex>
<Heading size="sm">{t('settings.developer')}</Heading>
<IAISwitch
label={t('settings.shouldLogToConsole')}
@ -269,10 +261,10 @@ const SettingsModal = ({ children, config }: SettingsModalProps) => {
dispatch(setEnableImageDebugging(e.target.checked))
}
/>
</Flex>
</StyledFlex>
)}
<Flex sx={modalSectionStyles}>
<StyledFlex>
<Heading size="sm">{t('settings.resetWebUI')}</Heading>
<IAIButton colorScheme="error" onClick={handleClickResetWebUI}>
{t('settings.resetWebUI')}
@ -283,7 +275,7 @@ const SettingsModal = ({ children, config }: SettingsModalProps) => {
<Text>{t('settings.resetWebUIDesc2')}</Text>
</>
)}
</Flex>
</StyledFlex>
</Flex>
</ModalBody>
@ -319,3 +311,19 @@ const SettingsModal = ({ children, config }: SettingsModalProps) => {
};
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 ModelManagerModal from './ModelManager/ModelManagerModal';
import SettingsModal from './SettingsModal/SettingsModal';
import ThemeChanger from './ThemeChanger';
import { useFeatureStatus } from '../hooks/useFeatureStatus';
import ColorModeButton from './ColorModeButton';
const SiteHeader = () => {
const { t } = useTranslation();
@ -63,8 +63,6 @@ const SiteHeader = () => {
/>
</HotkeysModal>
<ThemeChanger />
{isLocalizationEnabled && <LanguagePicker />}
{isBugLinkEnabled && (
@ -121,6 +119,8 @@ const SiteHeader = () => {
</Link>
)}
<ColorModeButton />
<SettingsModal>
<IAIIconButton
aria-label={t('common.settingsLabel')}

View File

@ -1,12 +1,12 @@
import { Flex, Link } from '@chakra-ui/react';
import { useTranslation } from 'react-i18next';
import { FaCube, FaKeyboard, FaBug, FaGithub, FaDiscord } from 'react-icons/fa';
import { FaBug, FaCube, FaDiscord, FaGithub, FaKeyboard } from 'react-icons/fa';
import { MdSettings } from 'react-icons/md';
import HotkeysModal from './HotkeysModal/HotkeysModal';
import LanguagePicker from './LanguagePicker';
import ModelManagerModal from './ModelManager/ModelManagerModal';
import SettingsModal from './SettingsModal/SettingsModal';
import ThemeChanger from './ThemeChanger';
import IAIIconButton from 'common/components/IAIIconButton';
import { useFeatureStatus } from '../hooks/useFeatureStatus';
@ -53,8 +53,6 @@ const SiteHeaderMenu = () => {
/>
</HotkeysModal>
<ThemeChanger />
{isLocalizationEnabled && <LanguagePicker />}
{isBugLinkEnabled && (

View File

@ -35,6 +35,18 @@ const statusIndicatorSelector = createSelector(
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 {
isConnected,
@ -46,7 +58,7 @@ const StatusIndicator = () => {
const { t } = useTranslation();
const ref = useRef(null);
const statusColorScheme = useMemo(() => {
const statusString = useMemo(() => {
if (isProcessing) {
return 'working';
}
@ -90,9 +102,10 @@ const StatusIndicator = () => {
sx={{
fontSize: 'sm',
fontWeight: '600',
color: `${statusColorScheme}.400`,
pb: '1px',
userSelect: 'none',
color: LIGHT_COLOR_MAP[statusString],
_dark: { color: DARK_COLOR_MAP[statusString] },
}}
>
{t(statusTranslationKey as ResourceKey)}
@ -101,7 +114,14 @@ const StatusIndicator = () => {
</motion.div>
)}
</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>
);
};

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,
borderStartEndRadius: 0,
borderEndEndRadius: 0,
shadow: '2xl',
}}
>
<MdPhotoLibrary />

View File

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

View File

@ -6,6 +6,7 @@ import {
useOutsideClick,
useTheme,
SlideDirection,
useColorMode,
} from '@chakra-ui/react';
import {
Resizable,
@ -21,6 +22,7 @@ import {
getSlideDirection,
getStyles,
} from './util';
import { mode } from 'theme/util/mode';
type ResizableDrawerProps = ResizableProps & {
children: ReactNode;
@ -64,7 +66,7 @@ const ResizableDrawer = ({
sx = {},
}: ResizableDrawerProps) => {
const langDirection = useTheme().direction as LangDirection;
const { colorMode } = useColorMode();
const outsideClickRef = useRef<HTMLDivElement>(null);
const defaultWidth = useMemo(
@ -160,11 +162,11 @@ const ResizableDrawer = ({
handleStyles={handleStyles}
{...minMaxDimensions}
sx={{
borderColor: 'base.800',
borderColor: mode('base.200', 'base.800')(colorMode),
p: 4,
bg: 'base.900',
bg: mode('base.100', 'base.900')(colorMode),
height: 'full',
boxShadow: '0 0 4rem 0 rgba(0, 0, 0, 0.8)',
shadow: isOpen ? 'dark-lg' : undefined,
...containerStyles,
...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 { PanelResizeHandle } from 'react-resizable-panels';
import { mode } from 'theme/util/mode';
type ResizeHandleProps = FlexProps & {
direction?: 'horizontal' | 'vertical';
@ -8,6 +9,7 @@ type ResizeHandleProps = FlexProps & {
const ResizeHandle = (props: ResizeHandleProps) => {
const { direction = 'horizontal', ...rest } = props;
const { colorMode } = useColorMode();
if (direction === 'horizontal') {
return (
@ -21,7 +23,13 @@ const ResizeHandle = (props: ResizeHandleProps) => {
}}
{...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>
</PanelResizeHandle>
);
@ -38,7 +46,13 @@ const ResizeHandle = (props: ResizeHandleProps) => {
}}
{...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>
</PanelResizeHandle>
);

View File

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

View File

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

View File

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

View File

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

View File

@ -1,9 +1,8 @@
export { default as InvokeAiLogoComponent } from './features/system/components/InvokeAILogoComponent';
export { default as ThemeChanger } from './features/system/components/ThemeChanger';
export { default as IAIPopover } from './common/components/IAIPopover';
export { default as IAIIconButton } from './common/components/IAIIconButton';
export { default as SettingsModal } from './features/system/components/SettingsModal/SettingsModal';
export { default as StatusIndicator } from './features/system/components/StatusIndicator';
export { default as ModelSelect } from './features/system/components/ModelSelect';
export { default as InvokeAIUI } from './app/components/InvokeAIUI';
export type { PartialAppConfig } from './app/types/invokeai';
export { default as IAIIconButton } from './common/components/IAIIconButton';
export { default as IAIPopover } from './common/components/IAIPopover';
export { default as InvokeAiLogoComponent } from './features/system/components/InvokeAILogoComponent';
export { default as ModelSelect } from './features/system/components/ModelSelect';
export { default as SettingsModal } from './features/system/components/SettingsModal/SettingsModal';
export { default as StatusIndicator } from './features/system/components/StatusIndicator';

View File

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

View File

@ -0,0 +1,24 @@
import { InvokeAIThemeColors } from 'theme/themeTypes';
import { generateColorPalette } from 'theme/util/generateColorPalette';
const BASE = { H: 220, S: 16 };
const ACCENT = { H: 250, 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 InvokeAIColors: 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),
};

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

View File

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

View File

@ -3,6 +3,7 @@ import {
createMultiStyleConfigHelpers,
defineStyle,
} from '@chakra-ui/styled-system';
import { mode } from '@chakra-ui/theme-tools';
const { definePartsStyle, defineMultiStyleConfig } =
createMultiStyleConfigHelpers(parts.keys);
@ -11,14 +12,18 @@ const invokeAIControl = defineStyle((props) => {
const { colorScheme: c } = props;
return {
bg: mode('base.200', 'base.700')(props),
borderColor: mode('base.200', 'base.700')(props),
color: mode('base.900', 'base.100')(props),
_checked: {
bg: `${c}.200`,
borderColor: `${c}.200`,
color: 'base.900',
bg: mode(`${c}.300`, `${c}.600`)(props),
borderColor: mode(`${c}.300`, `${c}.600`)(props),
color: mode(`${c}.900`, `${c}.100`)(props),
_hover: {
bg: `${c}.300`,
borderColor: `${c}.300`,
bg: mode(`${c}.400`, `${c}.500`)(props),
borderColor: mode(`${c}.400`, `${c}.500`)(props),
},
_disabled: {
@ -29,9 +34,9 @@ const invokeAIControl = defineStyle((props) => {
},
_indeterminate: {
bg: `${c}.200`,
borderColor: `${c}.200`,
color: 'base.900',
bg: mode(`${c}.300`, `${c}.600`)(props),
borderColor: mode(`${c}.300`, `${c}.600`)(props),
color: mode(`${c}.900`, `${c}.100`)(props),
},
_disabled: {
@ -44,7 +49,7 @@ const invokeAIControl = defineStyle((props) => {
},
_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 { mode } from '@chakra-ui/theme-tools';
const invokeAI = defineStyle((_props) => {
const invokeAI = defineStyle((props) => {
return {
fontSize: 'sm',
marginEnd: 0,
@ -12,7 +13,7 @@ const invokeAI = defineStyle((_props) => {
_disabled: {
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 { createMultiStyleConfigHelpers } from '@chakra-ui/react';
import { mode } from '@chakra-ui/theme-tools';
const { definePartsStyle, defineMultiStyleConfig } =
createMultiStyleConfigHelpers(menuAnatomy.keys);
// define the base component styles
const invokeAI = definePartsStyle({
const invokeAI = definePartsStyle((props) => ({
// define the part you're going to style
button: {
// this will style the MenuButton component
fontWeight: '600',
bg: 'base.500',
color: 'base.200',
fontWeight: 500,
bg: mode('base.300', 'base.500')(props),
color: mode('base.900', 'base.100')(props),
_hover: {
bg: 'base.600',
color: 'white',
bg: mode('base.400', 'base.600')(props),
color: mode('base.900', 'base.50')(props),
fontWeight: 600,
},
},
list: {
zIndex: 9999,
bg: 'base.800',
bg: mode('base.200', 'base.800')(props),
},
item: {
// this will style the MenuItem and MenuItemOption components
fontSize: 'sm',
bg: 'base.800',
bg: mode('base.200', 'base.800')(props),
_hover: {
bg: 'base.750',
bg: mode('base.300', 'base.700')(props),
},
_focus: {
bg: 'base.700',
bg: mode('base.400', 'base.600')(props),
},
},
});
}));
export const menuTheme = defineMultiStyleConfig({
variants: {

View File

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

View File

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

View File

@ -3,7 +3,7 @@ import {
createMultiStyleConfigHelpers,
defineStyle,
} from '@chakra-ui/styled-system';
import { cssVar } from '@chakra-ui/theme-tools';
import { cssVar, mode } from '@chakra-ui/theme-tools';
const { defineMultiStyleConfig, definePartsStyle } =
createMultiStyleConfigHelpers(parts.keys);
@ -12,15 +12,20 @@ const $popperBg = cssVar('popper-bg');
const $arrowBg = cssVar('popper-arrow-bg');
const $arrowShadowColor = cssVar('popper-arrow-shadow-color');
const invokeAIContent = defineStyle((_props) => {
const invokeAIContent = defineStyle((props) => {
return {
[$arrowBg.variable]: `colors.base.800`,
[$popperBg.variable]: `colors.base.800`,
[$arrowShadowColor.variable]: `colors.base.600`,
[$arrowBg.variable]: mode('colors.base.100', 'colors.base.800')(props),
[$popperBg.variable]: mode('colors.base.100', 'colors.base.800')(props),
[$arrowShadowColor.variable]: mode(
'colors.base.400',
'colors.base.600'
)(props),
minW: 'unset',
width: 'unset',
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 { createMultiStyleConfigHelpers, defineStyle } from '@chakra-ui/react';
import { getInputOutlineStyles } from '../util/getInputOutlineStyles';
import { mode } from '@chakra-ui/theme-tools';
const { definePartsStyle, defineMultiStyleConfig } =
createMultiStyleConfigHelpers(parts.keys);
const invokeAIIcon = defineStyle((_props) => {
const invokeAIIcon = defineStyle((props) => {
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 { createMultiStyleConfigHelpers, defineStyle } from '@chakra-ui/react';
import { mode } from '@chakra-ui/theme-tools';
const { definePartsStyle, defineMultiStyleConfig } =
createMultiStyleConfigHelpers(parts.keys);
const invokeAITrack = defineStyle((_props) => {
const invokeAITrack = defineStyle((props) => {
return {
bg: 'base.400',
bg: mode('base.400', 'base.600')(props),
h: 1.5,
};
});
@ -14,23 +15,24 @@ const invokeAITrack = defineStyle((_props) => {
const invokeAIFilledTrack = defineStyle((props) => {
const { colorScheme: c } = props;
return {
bg: `${c}.600`,
bg: mode(`${c}.400`, `${c}.600`)(props),
h: 1.5,
};
});
const invokeAIThumb = defineStyle((_props) => {
const invokeAIThumb = defineStyle((props) => {
return {
w: 2,
h: 4,
bg: mode('base.50', 'base.100')(props),
};
});
const invokeAIMark = defineStyle((_props) => {
const invokeAIMark = defineStyle((props) => {
return {
fontSize: 'xs',
fontWeight: '500',
color: 'base.400',
color: mode('base.700', 'base.400')(props),
mt: 2,
insetInlineStart: 'unset',
};

View File

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

View File

@ -3,6 +3,7 @@ import {
createMultiStyleConfigHelpers,
defineStyle,
} from '@chakra-ui/styled-system';
import { mode } from '@chakra-ui/theme-tools';
const { defineMultiStyleConfig, definePartsStyle } =
createMultiStyleConfigHelpers(parts.keys);
@ -16,30 +17,51 @@ const invokeAIRoot = defineStyle((_props) => {
const invokeAITab = defineStyle((_props) => ({}));
const invokeAITablist = defineStyle((_props) => ({
display: 'flex',
flexDirection: 'column',
gap: 1,
color: 'base.700',
button: {
fontSize: 'sm',
padding: 2,
borderRadius: 'base',
_selected: {
borderBottomColor: 'base.800',
bg: 'accent.700',
color: 'accent.100',
const invokeAITablist = defineStyle((props) => {
const { colorScheme: c } = props;
return {
display: 'flex',
flexDirection: 'column',
gap: 1,
color: mode('base.700', 'base.400')(props),
button: {
fontSize: 'sm',
padding: 2,
borderRadius: 'base',
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: {
bg: 'accent.600',
color: 'accent.50',
bg: mode('base.100', 'base.800')(props),
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) => ({
padding: 0,

View File

@ -1,7 +1,8 @@
import { defineStyle, defineStyleConfig } from '@chakra-ui/react';
import { mode } from '@chakra-ui/theme-tools';
const subtext = defineStyle((_props) => ({
color: 'base.400',
const subtext = defineStyle((props) => ({
color: mode('colors.base.500', 'colors.base.400')(props),
}));
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,6 @@
import { ThemeOverride } from '@chakra-ui/react';
import type { StyleFunctionProps } from '@chakra-ui/styled-system';
import { invokeAIThemeColors } from 'theme/colors/invokeAI';
import { InvokeAIColors } from './colors/colors';
import { accordionTheme } from './components/accordion';
import { buttonTheme } from './components/button';
import { checkboxTheme } from './components/checkbox';
@ -12,13 +11,14 @@ import { modalTheme } from './components/modal';
import { numberInputTheme } from './components/numberInput';
import { popoverTheme } from './components/popover';
import { progressTheme } from './components/progress';
import { no_scrollbar, scrollbar as _scrollbar } from './components/scrollbar';
import { no_scrollbar } from './components/scrollbar';
import { selectTheme } from './components/select';
import { sliderTheme } from './components/slider';
import { switchTheme } from './components/switch';
import { tabsTheme } from './components/tabs';
import { textTheme } from './components/text';
import { textareaTheme } from './components/textarea';
import { tooltipTheme } from './components/tooltip';
export const theme: ThemeOverride = {
config: {
@ -26,30 +26,32 @@ export const theme: ThemeOverride = {
initialColorMode: 'dark',
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: {
global: (_props: StyleFunctionProps) => ({
body: {
bg: 'base.900',
color: 'base.50',
overflow: {
base: 'scroll',
xl: 'hidden',
},
},
global: () => ({
layerStyle: 'body',
'*': { ...no_scrollbar },
}),
},
direction: 'ltr',
fonts: {
body: `'InterVariable', 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
body: `'Inter Variable', sans-serif`,
},
shadows: {
light: {
@ -68,9 +70,7 @@ export const theme: ThemeOverride = {
},
nodeSelectedOutline: `0 0 0 2px var(--invokeai-colors-base-500)`,
},
colors: {
...invokeAIThemeColors,
},
colors: InvokeAIColors,
components: {
Button: buttonTheme, // Button and IconButton
Input: inputTheme,
@ -88,5 +88,6 @@ export const theme: ThemeOverride = {
Checkbox: checkboxTheme,
Menu: menuTheme,
Text: textTheme,
Tooltip: tooltipTheme,
},
};

View File

@ -11,7 +11,6 @@ export type InvokeAIThemeColors = {
okAlpha: Partial<InvokeAIPaletteSteps>;
error: Partial<InvokeAIPaletteSteps>;
errorAlpha: Partial<InvokeAIPaletteSteps>;
gridLineColor: string;
};
export type InvokeAIPaletteSteps = {

View File

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

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',
borderWidth: 2,
borderStyle: 'solid',
borderColor: 'base.800',
bg: 'base.900',
borderColor: mode('base.200', 'base.800')(props),
bg: mode('base.50', 'base.900')(props),
borderRadius: 'base',
color: 'base.100',
color: mode('base.900', 'base.100')(props),
boxShadow: 'none',
_hover: {
borderColor: 'base.600',
borderColor: mode('base.300', 'base.600')(props),
},
_focus: {
borderColor: 'accent.700',
borderColor: mode('accent.200', 'accent.600')(props),
boxShadow: 'none',
_hover: {
borderColor: 'accent.600',
borderColor: mode('accent.300', 'accent.500')(props),
},
},
_invalid: {
borderColor: 'error.700',
borderColor: mode('error.300', 'error.600')(props),
boxShadow: 'none',
_hover: {
borderColor: 'error.600',
borderColor: mode('error.400', 'error.500')(props),
},
},
_disabled: {
borderColor: 'base.700',
bg: 'base.700',
color: 'base.400',
borderColor: mode('base.300', 'base.700')(props),
bg: mode('base.300', 'base.700')(props),
color: mode('base.600', 'base.400')(props),
_hover: {
borderColor: 'base.700',
borderColor: mode('base.300', 'base.700')(props),
},
},
_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;