feat(ui): boards styling

This commit is contained in:
psychedelicious 2023-07-20 21:32:34 +10:00
parent 8d77c5ca96
commit 190ba5af59
16 changed files with 482 additions and 225 deletions

View File

@ -17,13 +17,13 @@ import {
} from 'common/components/IAIImageFallback';
import ImageMetadataOverlay from 'common/components/ImageMetadataOverlay';
import { useImageUploadButton } from 'common/hooks/useImageUploadButton';
import ImageContextMenu from 'features/gallery/components/ImageContextMenu/ImageContextMenu';
import { MouseEvent, ReactElement, SyntheticEvent, memo } from 'react';
import { FaImage, FaUndo, FaUpload } from 'react-icons/fa';
import { ImageDTO, PostUploadAction } from 'services/api/types';
import { mode } from 'theme/util/mode';
import IAIDraggable from './IAIDraggable';
import IAIDroppable from './IAIDroppable';
import ImageContextMenu from 'features/gallery/components/ImageContextMenu/ImageContextMenu';
type IAIDndImageProps = {
imageDTO: ImageDTO | undefined;
@ -148,7 +148,9 @@ const IAIDndImage = (props: IAIDndImageProps) => {
maxH: 'full',
borderRadius: 'base',
shadow: isSelected ? 'selected.light' : undefined,
_dark: { shadow: isSelected ? 'selected.dark' : undefined },
_dark: {
shadow: isSelected ? 'selected.dark' : undefined,
},
...imageSx,
}}
/>
@ -183,13 +185,6 @@ const IAIDndImage = (props: IAIDndImageProps) => {
</>
)}
{!imageDTO && isUploadDisabled && noContentFallback}
{!isDropDisabled && (
<IAIDroppable
data={droppableData}
disabled={isDropDisabled}
dropLabel={dropLabel}
/>
)}
{imageDTO && !isDragDisabled && (
<IAIDraggable
data={draggableData}
@ -197,6 +192,13 @@ const IAIDndImage = (props: IAIDndImageProps) => {
onClick={onClick}
/>
)}
{!isDropDisabled && (
<IAIDroppable
data={droppableData}
disabled={isDropDisabled}
dropLabel={dropLabel}
/>
)}
{onClickReset && withResetIcon && imageDTO && (
<IAIIconButton
onClick={onClickReset}

View File

@ -13,10 +13,11 @@ type IAIDroppableProps = {
dropLabel?: ReactNode;
disabled?: boolean;
data?: TypesafeDroppableData;
hoverRef?: React.Ref<HTMLDivElement>;
};
const IAIDroppable = (props: IAIDroppableProps) => {
const { dropLabel, data, disabled } = props;
const { dropLabel, data, disabled, hoverRef } = props;
const dndId = useRef(uuidv4());
const { isOver, setNodeRef, active } = useDroppable({

View File

@ -23,34 +23,32 @@ const BoardContextMenu = memo(
dispatch(boardIdSelected(board?.board_id ?? board_id));
}, [board?.board_id, board_id, dispatch]);
return (
<Box sx={{ touchAction: 'none', height: 'full' }}>
<ContextMenu<HTMLDivElement>
menuProps={{ size: 'sm', isLazy: true }}
menuButtonProps={{
bg: 'transparent',
_hover: { bg: 'transparent' },
}}
renderMenu={() => (
<MenuList
sx={{ visibility: 'visible !important' }}
motionProps={menuListMotionProps}
>
<MenuItem icon={<FaFolder />} onClickCapture={handleSelectBoard}>
Select Board
</MenuItem>
{!board && <SystemBoardContextMenuItems board_id={board_id} />}
{board && (
<GalleryBoardContextMenuItems
board={board}
setBoardToDelete={setBoardToDelete}
/>
)}
</MenuList>
)}
>
{children}
</ContextMenu>
</Box>
<ContextMenu<HTMLDivElement>
menuProps={{ size: 'sm', isLazy: true }}
menuButtonProps={{
bg: 'transparent',
_hover: { bg: 'transparent' },
}}
renderMenu={() => (
<MenuList
sx={{ visibility: 'visible !important' }}
motionProps={menuListMotionProps}
>
<MenuItem icon={<FaFolder />} onClickCapture={handleSelectBoard}>
Select Board
</MenuItem>
{!board && <SystemBoardContextMenuItems board_id={board_id} />}
{board && (
<GalleryBoardContextMenuItems
board={board}
setBoardToDelete={setBoardToDelete}
/>
)}
</MenuList>
)}
>
{children}
</ContextMenu>
);
}
);

View File

@ -1,27 +1,21 @@
import {
Collapse,
Flex,
Grid,
GridItem,
useDisclosure,
} from '@chakra-ui/react';
import { ButtonGroup, Collapse, Flex, Grid, GridItem } from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit';
import { stateSelector } from 'app/store/store';
import { useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import IAIIconButton from 'common/components/IAIIconButton';
import { AnimatePresence, motion } from 'framer-motion';
import { OverlayScrollbarsComponent } from 'overlayscrollbars-react';
import { memo, useState } from 'react';
import { memo, useCallback, useState } from 'react';
import { FaSearch } from 'react-icons/fa';
import { useListAllBoardsQuery } from 'services/api/endpoints/boards';
import { BoardDTO } from 'services/api/types';
import { useFeatureStatus } from '../../../../system/hooks/useFeatureStatus';
import DeleteBoardModal from '../DeleteBoardModal';
import AddBoardButton from './AddBoardButton';
import AllAssetsBoard from './AllAssetsBoard';
import AllImagesBoard from './AllImagesBoard';
import BatchBoard from './BatchBoard';
import BoardsSearch from './BoardsSearch';
import GalleryBoard from './GalleryBoard';
import NoBoardBoard from './NoBoardBoard';
import DeleteBoardModal from '../DeleteBoardModal';
import { BoardDTO } from 'services/api/types';
import SystemBoardButton from './SystemBoardButton';
const selector = createSelector(
[stateSelector],
@ -48,7 +42,10 @@ const BoardsList = (props: Props) => {
)
: boards;
const [boardToDelete, setBoardToDelete] = useState<BoardDTO>();
const [searchMode, setSearchMode] = useState(false);
const [isSearching, setIsSearching] = useState(false);
const handleClickSearchIcon = useCallback(() => {
setIsSearching((v) => !v);
}, []);
return (
<>
@ -64,7 +61,54 @@ const BoardsList = (props: Props) => {
}}
>
<Flex sx={{ gap: 2, alignItems: 'center' }}>
<BoardsSearch setSearchMode={setSearchMode} />
<AnimatePresence mode="popLayout">
{isSearching ? (
<motion.div
key="boards-search"
initial={{
opacity: 0,
}}
exit={{
opacity: 0,
}}
animate={{
opacity: 1,
transition: { duration: 0.1 },
}}
style={{ width: '100%' }}
>
<BoardsSearch setIsSearching={setIsSearching} />
</motion.div>
) : (
<motion.div
key="system-boards-select"
initial={{
opacity: 0,
}}
exit={{
opacity: 0,
}}
animate={{
opacity: 1,
transition: { duration: 0.1 },
}}
style={{ width: '100%' }}
>
<ButtonGroup sx={{ w: 'full', ps: 1.5 }} isAttached>
<SystemBoardButton board_id="images" />
<SystemBoardButton board_id="assets" />
<SystemBoardButton board_id="no_board" />
</ButtonGroup>
</motion.div>
)}
</AnimatePresence>
<IAIIconButton
aria-label="Search Boards"
size="sm"
isChecked={isSearching}
onClick={handleClickSearchIcon}
icon={<FaSearch />}
/>
<AddBoardButton />
</Flex>
<OverlayScrollbarsComponent
@ -82,29 +126,10 @@ const BoardsList = (props: Props) => {
<Grid
className="list-container"
sx={{
gridTemplateRows: '6.5rem 6.5rem',
gridAutoFlow: 'column dense',
gridAutoColumns: '5rem',
gridTemplateColumns: `repeat(auto-fill, minmax(96px, 1fr));`,
maxH: 346,
}}
>
{!searchMode && (
<>
<GridItem sx={{ p: 1.5 }}>
<AllImagesBoard isSelected={selectedBoardId === 'images'} />
</GridItem>
<GridItem sx={{ p: 1.5 }}>
<AllAssetsBoard isSelected={selectedBoardId === 'assets'} />
</GridItem>
<GridItem sx={{ p: 1.5 }}>
<NoBoardBoard isSelected={selectedBoardId === 'no_board'} />
</GridItem>
{isBatchEnabled && (
<GridItem sx={{ p: 1.5 }}>
<BatchBoard isSelected={selectedBoardId === 'batch'} />
</GridItem>
)}
</>
)}
{filteredBoards &&
filteredBoards.map((board) => (
<GridItem key={board.board_id} sx={{ p: 1.5 }}>

View File

@ -10,7 +10,14 @@ import { stateSelector } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import { setBoardSearchText } from 'features/gallery/store/boardSlice';
import { memo } from 'react';
import {
ChangeEvent,
KeyboardEvent,
memo,
useCallback,
useEffect,
useRef,
} from 'react';
const selector = createSelector(
[stateSelector],
@ -22,31 +29,60 @@ const selector = createSelector(
);
type Props = {
setSearchMode: (searchMode: boolean) => void;
setIsSearching: (isSearching: boolean) => void;
};
const BoardsSearch = (props: Props) => {
const { setSearchMode } = props;
const { setIsSearching } = props;
const dispatch = useAppDispatch();
const { searchText } = useAppSelector(selector);
const inputRef = useRef<HTMLInputElement>(null);
const handleBoardSearch = (searchTerm: string) => {
setSearchMode(searchTerm.length > 0);
dispatch(setBoardSearchText(searchTerm));
};
const clearBoardSearch = () => {
setSearchMode(false);
const handleBoardSearch = useCallback(
(searchTerm: string) => {
dispatch(setBoardSearchText(searchTerm));
},
[dispatch]
);
const clearBoardSearch = useCallback(() => {
dispatch(setBoardSearchText(''));
};
setIsSearching(false);
}, [dispatch, setIsSearching]);
const handleKeydown = useCallback(
(e: KeyboardEvent<HTMLInputElement>) => {
// exit search mode on escape
if (e.key === 'Escape') {
clearBoardSearch();
}
},
[clearBoardSearch]
);
const handleChange = useCallback(
(e: ChangeEvent<HTMLInputElement>) => {
handleBoardSearch(e.target.value);
},
[handleBoardSearch]
);
useEffect(() => {
// focus the search box on mount
if (!inputRef.current) {
return;
}
inputRef.current.focus();
}, []);
return (
<InputGroup>
<Input
ref={inputRef}
placeholder="Search Boards..."
value={searchText}
onChange={(e) => {
handleBoardSearch(e.target.value);
}}
onKeyDown={handleKeydown}
onChange={handleChange}
/>
{searchText && searchText.length && (
<InputRightElement>
@ -55,7 +91,8 @@ const BoardsSearch = (props: Props) => {
size="xs"
variant="ghost"
aria-label="Clear Search"
icon={<CloseIcon boxSize={3} />}
opacity={0.5}
icon={<CloseIcon boxSize={2} />}
/>
</InputRightElement>
)}

View File

@ -6,9 +6,9 @@ import {
EditableInput,
EditablePreview,
Flex,
Icon,
Image,
Text,
useColorMode,
} from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit';
import { skipToken } from '@reduxjs/toolkit/dist/query';
@ -17,14 +17,12 @@ import { stateSelector } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import IAIDroppable from 'common/components/IAIDroppable';
import { IAINoContentFallback } from 'common/components/IAIImageFallback';
import { boardIdSelected } from 'features/gallery/store/gallerySlice';
import { memo, useCallback, useMemo } from 'react';
import { FaUser } from 'react-icons/fa';
import { memo, useCallback, useMemo, useState } from 'react';
import { FaFolder } from 'react-icons/fa';
import { useUpdateBoardMutation } from 'services/api/endpoints/boards';
import { useGetImageDTOQuery } from 'services/api/endpoints/images';
import { BoardDTO } from 'services/api/types';
import { mode } from 'theme/util/mode';
import BoardContextMenu from '../BoardContextMenu';
const AUTO_ADD_BADGE_STYLES: ChakraProps['sx'] = {
@ -66,8 +64,9 @@ const GalleryBoard = memo(
board.cover_image_name ?? skipToken
);
const { colorMode } = useColorMode();
const { board_name, board_id } = board;
const [localBoardName, setLocalBoardName] = useState(board_name);
const handleSelectBoard = useCallback(() => {
dispatch(boardIdSelected(board_id));
}, [board_id, dispatch]);
@ -75,10 +74,6 @@ const GalleryBoard = memo(
const [updateBoard, { isLoading: isUpdateBoardLoading }] =
useUpdateBoardMutation();
const handleUpdateBoardName = (newBoardName: string) => {
updateBoard({ board_id, changes: { board_name: newBoardName } });
};
const droppableData: MoveBoardDropData = useMemo(
() => ({
id: board_id,
@ -88,59 +83,116 @@ const GalleryBoard = memo(
[board_id]
);
const handleSubmit = useCallback(
(newBoardName: string) => {
if (!newBoardName) {
// empty strings are not allowed
setLocalBoardName(board_name);
return;
}
if (newBoardName === board_name) {
// don't updated the board name if it hasn't changed
return;
}
updateBoard({ board_id, changes: { board_name: newBoardName } })
.unwrap()
.then((response) => {
// update local state
setLocalBoardName(response.board_name);
})
.catch(() => {
// revert on error
setLocalBoardName(board_name);
});
},
[board_id, board_name, updateBoard]
);
const handleChange = useCallback((newBoardName: string) => {
setLocalBoardName(newBoardName);
}, []);
return (
<Box sx={{ touchAction: 'none', height: 'full' }}>
<BoardContextMenu
board={board}
board_id={board_id}
setBoardToDelete={setBoardToDelete}
<Box
sx={{ w: 'full', h: 'full', touchAction: 'none', userSelect: 'none' }}
>
<Flex
sx={{
position: 'relative',
justifyContent: 'center',
alignItems: 'center',
aspectRatio: '1/1',
w: 'full',
h: 'full',
}}
>
{(ref) => (
<Flex
key={board_id}
userSelect="none"
ref={ref}
sx={{
flexDir: 'column',
justifyContent: 'space-between',
alignItems: 'center',
cursor: 'pointer',
w: 'full',
h: 'full',
}}
>
<BoardContextMenu
board={board}
board_id={board_id}
setBoardToDelete={setBoardToDelete}
>
{(ref) => (
<Flex
ref={ref}
onClick={handleSelectBoard}
sx={{
w: 'full',
h: 'full',
position: 'relative',
justifyContent: 'center',
alignItems: 'center',
borderRadius: 'base',
w: 'full',
aspectRatio: '1/1',
overflow: 'hidden',
shadow: isSelected ? 'selected.light' : undefined,
_dark: { shadow: isSelected ? 'selected.dark' : undefined },
flexShrink: 0,
cursor: 'pointer',
}}
>
{board.cover_image_name && coverImage?.thumbnail_url && (
<Image src={coverImage?.thumbnail_url} draggable={false} />
)}
{!(board.cover_image_name && coverImage?.thumbnail_url) && (
<IAINoContentFallback
boxSize={8}
icon={FaUser}
sx={{
borderWidth: '2px',
borderStyle: 'solid',
borderColor: 'base.200',
_dark: {
borderColor: 'base.800',
},
}}
/>
)}
<Flex
sx={{
w: 'full',
h: 'full',
justifyContent: 'center',
alignItems: 'center',
borderRadius: 'base',
bg: 'base.200',
_dark: {
bg: 'base.800',
},
}}
>
{coverImage?.thumbnail_url ? (
<Image
src={coverImage?.thumbnail_url}
draggable={false}
sx={{
maxW: 'full',
maxH: 'full',
borderRadius: 'base',
borderBottomRadius: 'lg',
}}
/>
) : (
<Flex
sx={{
w: 'full',
h: 'full',
justifyContent: 'center',
alignItems: 'center',
}}
>
<Icon
boxSize={12}
as={FaFolder}
sx={{
mt: -3,
opacity: 0.7,
color: 'base.500',
_dark: {
color: 'base.500',
},
}}
/>
</Flex>
)}
</Flex>
<Flex
sx={{
position: 'absolute',
@ -160,56 +212,81 @@ const GalleryBoard = memo(
{board.image_count}
</Badge>
</Flex>
<Box
className="selection-box"
sx={{
position: 'absolute',
top: 0,
insetInlineEnd: 0,
bottom: 0,
insetInlineStart: 0,
borderRadius: 'base',
transitionProperty: 'common',
transitionDuration: 'common',
shadow: isSelected ? 'selected.light' : undefined,
_dark: {
shadow: isSelected ? 'selected.dark' : undefined,
},
}}
/>
<Flex
sx={{
position: 'absolute',
bottom: 0,
p: 1,
justifyContent: 'center',
alignItems: 'center',
w: 'full',
maxW: 'full',
borderBottomRadius: 'base',
bg: 'accent.400',
color: isSelected ? 'base.50' : 'base.100',
_dark: { color: 'base.800', bg: 'accent.200' },
lineHeight: 'short',
fontSize: 'xs',
}}
>
<Editable
value={localBoardName}
isDisabled={isUpdateBoardLoading}
submitOnBlur={true}
onChange={handleChange}
onSubmit={handleSubmit}
sx={{
w: 'full',
}}
>
<EditablePreview
sx={{
p: 0,
fontWeight: isSelected ? 700 : 500,
textAlign: 'center',
overflow: 'hidden',
textOverflow: 'ellipsis',
}}
noOfLines={1}
/>
<EditableInput
sx={{
p: 0,
_focusVisible: {
p: 0,
// get rid of the edit border
boxShadow: 'none',
},
}}
/>
</Editable>
</Flex>
<IAIDroppable
data={droppableData}
dropLabel={<Text fontSize="md">Move</Text>}
/>
</Flex>
<Flex
sx={{
width: 'full',
height: 'full',
justifyContent: 'center',
alignItems: 'center',
}}
>
<Editable
defaultValue={board_name}
submitOnBlur={true}
onSubmit={(nextValue) => {
handleUpdateBoardName(nextValue);
}}
sx={{ maxW: 'full' }}
>
<EditablePreview
sx={{
color: isSelected
? mode('base.900', 'base.50')(colorMode)
: mode('base.700', 'base.200')(colorMode),
fontWeight: isSelected ? 600 : undefined,
fontSize: 'xs',
textAlign: 'center',
p: 0,
overflow: 'hidden',
textOverflow: 'ellipsis',
}}
noOfLines={1}
/>
<EditableInput
sx={{
color: mode('base.900', 'base.50')(colorMode),
fontSize: 'xs',
borderColor: mode('base.500', 'base.500')(colorMode),
p: 0,
outline: 0,
}}
/>
</Editable>
</Flex>
</Flex>
)}
</BoardContextMenu>
)}
</BoardContextMenu>
</Flex>
</Box>
);
}

View File

@ -92,7 +92,7 @@ const GenericBoard = (props: GenericBoardProps) => {
h: 'full',
alignItems: 'center',
fontWeight: isSelected ? 600 : undefined,
fontSize: 'xs',
fontSize: 'sm',
color: isSelected ? 'base.900' : 'base.700',
_dark: { color: isSelected ? 'base.50' : 'base.200' },
}}

View File

@ -0,0 +1,53 @@
import { createSelector } from '@reduxjs/toolkit';
import { stateSelector } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import IAIButton from 'common/components/IAIButton';
import { boardIdSelected } from 'features/gallery/store/gallerySlice';
import { memo, useCallback, useMemo } from 'react';
import { useBoardName } from 'services/api/hooks/useBoardName';
type Props = {
board_id: 'images' | 'assets' | 'no_board';
};
const SystemBoardButton = ({ board_id }: Props) => {
const dispatch = useAppDispatch();
const selector = useMemo(
() =>
createSelector(
[stateSelector],
({ gallery }) => {
const { selectedBoardId } = gallery;
return { isSelected: selectedBoardId === board_id };
},
defaultSelectorOptions
),
[board_id]
);
const { isSelected } = useAppSelector(selector);
const boardName = useBoardName(board_id);
const handleClick = useCallback(() => {
dispatch(boardIdSelected(board_id));
}, [board_id, dispatch]);
return (
<IAIButton
onClick={handleClick}
size="sm"
isChecked={isSelected}
sx={{
flexGrow: 1,
borderRadius: 'base',
}}
>
{boardName}
</IAIButton>
);
};
export default memo(SystemBoardButton);

View File

@ -109,7 +109,7 @@ const GalleryDrawer = () => {
isResizable={true}
isOpen={shouldShowGallery}
onClose={handleCloseGallery}
minWidth={337}
minWidth={400}
>
<ImageGalleryContent />
</ResizableDrawer>

View File

@ -1,4 +1,4 @@
import { Box } from '@chakra-ui/react';
import { Box, Flex } from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit';
import { TypesafeDraggableData } from 'app/components/ImageDnd/typesafeDnd';
import { stateSelector } from 'app/store/store';
@ -86,38 +86,31 @@ const GalleryImage = (props: HoverableImageProps) => {
return (
<Box sx={{ w: 'full', h: 'full', touchAction: 'none' }}>
<ImageContextMenu imageDTO={imageDTO}>
{(ref) => (
<Box
position="relative"
key={imageName}
userSelect="none"
ref={ref}
sx={{
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
aspectRatio: '1/1',
}}
>
<IAIDndImage
onClick={handleClick}
imageDTO={imageDTO}
draggableData={draggableData}
isSelected={isSelected}
minSize={0}
onClickReset={handleDelete}
imageSx={{ w: 'full', h: 'full' }}
isDropDisabled={true}
isUploadDisabled={true}
thumbnail={true}
// resetIcon={<FaTrash />}
// resetTooltip="Delete image"
// withResetIcon // removed bc it's too easy to accidentally delete images
/>
</Box>
)}
</ImageContextMenu>
<Flex
userSelect="none"
sx={{
position: 'relative',
justifyContent: 'center',
alignItems: 'center',
aspectRatio: '1/1',
}}
>
<IAIDndImage
onClick={handleClick}
imageDTO={imageDTO}
draggableData={draggableData}
isSelected={isSelected}
minSize={0}
onClickReset={handleDelete}
imageSx={{ w: 'full', h: 'full' }}
isDropDisabled={true}
isUploadDisabled={true}
thumbnail={true}
// resetIcon={<FaTrash />}
// resetTooltip="Delete image"
// withResetIcon // removed bc it's too easy to accidentally delete images
/>
</Flex>
</Box>
);
};

View File

@ -105,7 +105,7 @@ const enabledTabsSelector = createSelector(
}
);
const MIN_GALLERY_WIDTH = 300;
const MIN_GALLERY_WIDTH = 350;
const DEFAULT_GALLERY_PCT = 20;
export const NO_GALLERY_TABS: InvokeTabName[] = ['modelManager'];

View File

@ -3,7 +3,7 @@ import { memo } from 'react';
import { PanelResizeHandle } from 'react-resizable-panels';
import { mode } from 'theme/util/mode';
type ResizeHandleProps = FlexProps & {
type ResizeHandleProps = Omit<FlexProps, 'direction'> & {
direction?: 'horizontal' | 'vertical';
};

View File

@ -6,9 +6,9 @@ export const useBoardName = (board_id: BoardId | null | undefined) => {
selectFromResult: ({ data }) => {
let boardName = '';
if (board_id === 'images') {
boardName = 'All Images';
boardName = 'Images';
} else if (board_id === 'assets') {
boardName = 'All Assets';
boardName = 'Assets';
} else if (board_id === 'no_board') {
boardName = 'No Board';
} else if (board_id === 'batch') {

View File

@ -0,0 +1,56 @@
import { editableAnatomy as parts } from '@chakra-ui/anatomy';
import {
createMultiStyleConfigHelpers,
defineStyle,
} from '@chakra-ui/styled-system';
import { mode } from '@chakra-ui/theme-tools';
const { definePartsStyle, defineMultiStyleConfig } =
createMultiStyleConfigHelpers(parts.keys);
const baseStylePreview = defineStyle({
borderRadius: 'md',
py: '1',
transitionProperty: 'common',
transitionDuration: 'normal',
});
const baseStyleInput = defineStyle((props) => ({
borderRadius: 'md',
py: '1',
transitionProperty: 'common',
transitionDuration: 'normal',
width: 'full',
_focusVisible: { boxShadow: 'outline' },
_placeholder: { opacity: 0.6 },
'::selection': {
color: mode('accent.900', 'accent.50')(props),
bg: mode('accent.200', 'accent.400')(props),
},
}));
const baseStyleTextarea = defineStyle({
borderRadius: 'md',
py: '1',
transitionProperty: 'common',
transitionDuration: 'normal',
width: 'full',
_focusVisible: { boxShadow: 'outline' },
_placeholder: { opacity: 0.6 },
});
const invokeAI = definePartsStyle((props) => ({
preview: baseStylePreview,
input: baseStyleInput(props),
textarea: baseStyleTextarea,
}));
export const editableTheme = defineMultiStyleConfig({
variants: {
invokeAI,
},
defaultProps: {
size: 'sm',
variant: 'invokeAI',
},
});

View File

@ -20,6 +20,7 @@ import { tabsTheme } from './components/tabs';
import { textTheme } from './components/text';
import { textareaTheme } from './components/textarea';
import { tooltipTheme } from './components/tooltip';
import { editableTheme } from './components/editable';
export const theme: ThemeOverride = {
config: {
@ -74,12 +75,23 @@ export const theme: ThemeOverride = {
'0px 0px 0px 1px var(--invokeai-colors-base-150), 0px 0px 0px 4px var(--invokeai-colors-accent-400)',
dark: '0px 0px 0px 1px var(--invokeai-colors-base-900), 0px 0px 0px 4px var(--invokeai-colors-accent-400)',
},
hoverSelected: {
light:
'0px 0px 0px 1px var(--invokeai-colors-base-150), 0px 0px 0px 4px var(--invokeai-colors-accent-500)',
dark: '0px 0px 0px 1px var(--invokeai-colors-base-900), 0px 0px 0px 4px var(--invokeai-colors-accent-300)',
},
hoverUnselected: {
light:
'0px 0px 0px 1px var(--invokeai-colors-base-150), 0px 0px 0px 4px var(--invokeai-colors-accent-200)',
dark: '0px 0px 0px 1px var(--invokeai-colors-base-900), 0px 0px 0px 4px var(--invokeai-colors-accent-600)',
},
nodeSelectedOutline: `0 0 0 2px var(--invokeai-colors-accent-450)`,
},
colors: InvokeAIColors,
components: {
Button: buttonTheme, // Button and IconButton
Input: inputTheme,
Editable: editableTheme,
Textarea: textareaTheme,
Tabs: tabsTheme,
Progress: progressTheme,

View File

@ -37,4 +37,7 @@ export const getInputOutlineStyles = (props: StyleFunctionProps) => ({
_placeholder: {
color: mode('base.700', 'base.400')(props),
},
'::selection': {
bg: mode('accent.200', 'accent.400')(props),
},
});