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

@@ -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,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 (
<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 +112,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,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 (
<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>