diff --git a/invokeai/frontend/web/src/common/components/IAIDndImage.tsx b/invokeai/frontend/web/src/common/components/IAIDndImage.tsx index 6082843c55..57d54e155e 100644 --- a/invokeai/frontend/web/src/common/components/IAIDndImage.tsx +++ b/invokeai/frontend/web/src/common/components/IAIDndImage.tsx @@ -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 && ( - - )} {imageDTO && !isDragDisabled && ( { onClick={onClick} /> )} + {!isDropDisabled && ( + + )} {onClickReset && withResetIcon && imageDTO && ( ; }; const IAIDroppable = (props: IAIDroppableProps) => { - const { dropLabel, data, disabled } = props; + const { dropLabel, data, disabled, hoverRef } = props; const dndId = useRef(uuidv4()); const { isOver, setNodeRef, active } = useDroppable({ diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardContextMenu.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardContextMenu.tsx index fa3a6b03be..3b3303f0c8 100644 --- a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardContextMenu.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardContextMenu.tsx @@ -23,34 +23,32 @@ const BoardContextMenu = memo( dispatch(boardIdSelected(board?.board_id ?? board_id)); }, [board?.board_id, board_id, dispatch]); return ( - - - menuProps={{ size: 'sm', isLazy: true }} - menuButtonProps={{ - bg: 'transparent', - _hover: { bg: 'transparent' }, - }} - renderMenu={() => ( - - } onClickCapture={handleSelectBoard}> - Select Board - - {!board && } - {board && ( - - )} - - )} - > - {children} - - + + menuProps={{ size: 'sm', isLazy: true }} + menuButtonProps={{ + bg: 'transparent', + _hover: { bg: 'transparent' }, + }} + renderMenu={() => ( + + } onClickCapture={handleSelectBoard}> + Select Board + + {!board && } + {board && ( + + )} + + )} + > + {children} + ); } ); diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/BoardsList.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/BoardsList.tsx index 61b8856ff9..60be0c4ab3 100644 --- a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/BoardsList.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/BoardsList.tsx @@ -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(); - 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) => { }} > - + + {isSearching ? ( + + + + ) : ( + + + + + + + + )} + + } + /> { - {!searchMode && ( - <> - - - - - - - - - - {isBatchEnabled && ( - - - - )} - - )} {filteredBoards && filteredBoards.map((board) => ( diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/BoardsSearch.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/BoardsSearch.tsx index fffe50f6a7..f556b83d24 100644 --- a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/BoardsSearch.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/BoardsSearch.tsx @@ -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(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) => { + // exit search mode on escape + if (e.key === 'Escape') { + clearBoardSearch(); + } + }, + [clearBoardSearch] + ); + + const handleChange = useCallback( + (e: ChangeEvent) => { + handleBoardSearch(e.target.value); + }, + [handleBoardSearch] + ); + + useEffect(() => { + // focus the search box on mount + if (!inputRef.current) { + return; + } + inputRef.current.focus(); + }, []); return ( { - handleBoardSearch(e.target.value); - }} + onKeyDown={handleKeydown} + onChange={handleChange} /> {searchText && searchText.length && ( @@ -55,7 +91,8 @@ const BoardsSearch = (props: Props) => { size="xs" variant="ghost" aria-label="Clear Search" - icon={} + opacity={0.5} + icon={} /> )} diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/GalleryBoard.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/GalleryBoard.tsx index 46e7cbcca8..5d76ad743c 100644 --- a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/GalleryBoard.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/GalleryBoard.tsx @@ -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 ( - - + - {(ref) => ( - + + {(ref) => ( - {board.cover_image_name && coverImage?.thumbnail_url && ( - - )} - {!(board.cover_image_name && coverImage?.thumbnail_url) && ( - - )} + + {coverImage?.thumbnail_url ? ( + + ) : ( + + + + )} + + + + + + + + + Move} /> - - - { - handleUpdateBoardName(nextValue); - }} - sx={{ maxW: 'full' }} - > - - - - - - )} - + )} + + ); } diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/GenericBoard.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/GenericBoard.tsx index 226100c490..fa7f944a24 100644 --- a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/GenericBoard.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/GenericBoard.tsx @@ -17,7 +17,7 @@ type GenericBoardProps = { badgeCount?: number; }; -const formatBadgeCount = (count: number) => +export const formatBadgeCount = (count: number) => Intl.NumberFormat('en-US', { notation: 'compact', maximumFractionDigits: 1, @@ -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' }, }} diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/SystemBoardButton.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/SystemBoardButton.tsx new file mode 100644 index 0000000000..b538eee9d1 --- /dev/null +++ b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/SystemBoardButton.tsx @@ -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 ( + + {boardName} + + ); +}; + +export default memo(SystemBoardButton); diff --git a/invokeai/frontend/web/src/features/gallery/components/GalleryBoardName.tsx b/invokeai/frontend/web/src/features/gallery/components/GalleryBoardName.tsx index 12454dd15b..27565a52aa 100644 --- a/invokeai/frontend/web/src/features/gallery/components/GalleryBoardName.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/GalleryBoardName.tsx @@ -4,8 +4,9 @@ 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 { memo } from 'react'; +import { memo, useMemo } from 'react'; import { useBoardName } from 'services/api/hooks/useBoardName'; +import { useBoardTotal } from 'services/api/hooks/useBoardTotal'; const selector = createSelector( [stateSelector], @@ -26,6 +27,17 @@ const GalleryBoardName = (props: Props) => { const { isOpen, onToggle } = props; const { selectedBoardId } = useAppSelector(selector); const boardName = useBoardName(selectedBoardId); + const numOfBoardImages = useBoardTotal(selectedBoardId); + + const formattedBoardName = useMemo(() => { + if (!boardName || !numOfBoardImages) { + return ''; + } + if (boardName.length > 20) { + return `${boardName.substring(0, 20)}... (${numOfBoardImages})`; + } + return `${boardName} (${numOfBoardImages})`; + }, [boardName, numOfBoardImages]); return ( { }, }} > - {boardName} + {formattedBoardName} diff --git a/invokeai/frontend/web/src/features/gallery/components/GalleryPanel.tsx b/invokeai/frontend/web/src/features/gallery/components/GalleryPanel.tsx index 2aa44e50a1..1bbec03f3e 100644 --- a/invokeai/frontend/web/src/features/gallery/components/GalleryPanel.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/GalleryPanel.tsx @@ -109,7 +109,7 @@ const GalleryDrawer = () => { isResizable={true} isOpen={shouldShowGallery} onClose={handleCloseGallery} - minWidth={337} + minWidth={400} > diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GalleryImage.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GalleryImage.tsx index dcce3a1b18..bf627b9591 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GalleryImage.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GalleryImage.tsx @@ -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 ( - - {(ref) => ( - - } - // resetTooltip="Delete image" - // withResetIcon // removed bc it's too easy to accidentally delete images - /> - - )} - + + } + // resetTooltip="Delete image" + // withResetIcon // removed bc it's too easy to accidentally delete images + /> + ); }; diff --git a/invokeai/frontend/web/src/features/ui/components/InvokeTabs.tsx b/invokeai/frontend/web/src/features/ui/components/InvokeTabs.tsx index 94195a27c1..6c683470e7 100644 --- a/invokeai/frontend/web/src/features/ui/components/InvokeTabs.tsx +++ b/invokeai/frontend/web/src/features/ui/components/InvokeTabs.tsx @@ -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']; diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/ResizeHandle.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/ResizeHandle.tsx index 7ef0b48784..57f2e89ef0 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/ResizeHandle.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/ResizeHandle.tsx @@ -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 & { direction?: 'horizontal' | 'vertical'; }; diff --git a/invokeai/frontend/web/src/services/api/hooks/useBoardName.ts b/invokeai/frontend/web/src/services/api/hooks/useBoardName.ts index d63b6e0425..cbe0ec1808 100644 --- a/invokeai/frontend/web/src/services/api/hooks/useBoardName.ts +++ b/invokeai/frontend/web/src/services/api/hooks/useBoardName.ts @@ -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') { diff --git a/invokeai/frontend/web/src/services/api/hooks/useBoardTotal.ts b/invokeai/frontend/web/src/services/api/hooks/useBoardTotal.ts new file mode 100644 index 0000000000..8deccd8947 --- /dev/null +++ b/invokeai/frontend/web/src/services/api/hooks/useBoardTotal.ts @@ -0,0 +1,53 @@ +import { skipToken } from '@reduxjs/toolkit/dist/query'; +import { + ASSETS_CATEGORIES, + BoardId, + IMAGE_CATEGORIES, + INITIAL_IMAGE_LIMIT, +} from 'features/gallery/store/gallerySlice'; +import { useMemo } from 'react'; +import { ListImagesArgs, useListImagesQuery } from '../endpoints/images'; + +const baseQueryArg: ListImagesArgs = { + offset: 0, + limit: INITIAL_IMAGE_LIMIT, + is_intermediate: false, +}; + +const imagesQueryArg: ListImagesArgs = { + categories: IMAGE_CATEGORIES, + ...baseQueryArg, +}; + +const assetsQueryArg: ListImagesArgs = { + categories: ASSETS_CATEGORIES, + ...baseQueryArg, +}; + +const noBoardQueryArg: ListImagesArgs = { + board_id: 'none', + ...baseQueryArg, +}; + +export const useBoardTotal = (board_id: BoardId | null | undefined) => { + const queryArg = useMemo(() => { + if (!board_id) { + return; + } + if (board_id === 'images') { + return imagesQueryArg; + } else if (board_id === 'assets') { + return assetsQueryArg; + } else if (board_id === 'no_board') { + return noBoardQueryArg; + } else { + return { board_id, ...baseQueryArg }; + } + }, [board_id]); + + const { total } = useListImagesQuery(queryArg ?? skipToken, { + selectFromResult: ({ currentData }) => ({ total: currentData?.total }), + }); + + return total; +}; diff --git a/invokeai/frontend/web/src/theme/colors/colors.ts b/invokeai/frontend/web/src/theme/colors/colors.ts index bcb2e43c0b..99260ee071 100644 --- a/invokeai/frontend/web/src/theme/colors/colors.ts +++ b/invokeai/frontend/web/src/theme/colors/colors.ts @@ -2,11 +2,16 @@ 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 }; +const ACCENT = { H: 250, S: 42 }; +// const ACCENT = { H: 250, S: 52 }; +const WORKING = { H: 47, S: 42 }; +// const WORKING = { H: 47, S: 50 }; +const WARNING = { H: 28, S: 42 }; +// const WARNING = { H: 28, S: 50 }; +const OK = { H: 113, S: 42 }; +// const OK = { H: 113, S: 50 }; +const ERROR = { H: 0, S: 42 }; +// const ERROR = { H: 0, S: 50 }; export const InvokeAIColors: InvokeAIThemeColors = { base: generateColorPalette(BASE.H, BASE.S), diff --git a/invokeai/frontend/web/src/theme/components/editable.ts b/invokeai/frontend/web/src/theme/components/editable.ts new file mode 100644 index 0000000000..19321e5968 --- /dev/null +++ b/invokeai/frontend/web/src/theme/components/editable.ts @@ -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', + }, +}); diff --git a/invokeai/frontend/web/src/theme/theme.ts b/invokeai/frontend/web/src/theme/theme.ts index 42a5a12c3f..6f7a719e85 100644 --- a/invokeai/frontend/web/src/theme/theme.ts +++ b/invokeai/frontend/web/src/theme/theme.ts @@ -4,6 +4,7 @@ import { InvokeAIColors } from './colors/colors'; import { accordionTheme } from './components/accordion'; import { buttonTheme } from './components/button'; import { checkboxTheme } from './components/checkbox'; +import { editableTheme } from './components/editable'; import { formLabelTheme } from './components/formLabel'; import { inputTheme } from './components/input'; import { menuTheme } from './components/menu'; @@ -72,7 +73,17 @@ export const theme: ThemeOverride = { selected: { light: '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)', + dark: '0px 0px 0px 1px var(--invokeai-colors-base-900), 0px 0px 0px 4px var(--invokeai-colors-accent-500)', + }, + 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)`, }, @@ -80,6 +91,7 @@ export const theme: ThemeOverride = { components: { Button: buttonTheme, // Button and IconButton Input: inputTheme, + Editable: editableTheme, Textarea: textareaTheme, Tabs: tabsTheme, Progress: progressTheme, diff --git a/invokeai/frontend/web/src/theme/util/getInputOutlineStyles.ts b/invokeai/frontend/web/src/theme/util/getInputOutlineStyles.ts index 8cf64cbd94..ba5fc9e4c1 100644 --- a/invokeai/frontend/web/src/theme/util/getInputOutlineStyles.ts +++ b/invokeai/frontend/web/src/theme/util/getInputOutlineStyles.ts @@ -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), + }, });