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),
+ },
});