feat(ui): boards styling

- Refine layout
- Update colors - more minimal, fewer shaded boxes
- Add indicator for search icons showing a search term is entered
- Handle new `projectName` and `projectUrl` ui props
This commit is contained in:
psychedelicious 2024-07-10 16:20:01 +10:00 committed by Kent Keirsey
parent dfd94bbd0b
commit 48a57f0da8
9 changed files with 112 additions and 69 deletions

View File

@ -40,11 +40,11 @@ const BoardsList = () => {
return (
<>
<Flex layerStyle="first" flexDir="column" gap={2} p={2} my={2} borderRadius="base" maxHeight="100%">
<Flex flexDir="column" gap={2} borderRadius="base" maxHeight="100%">
<OverlayScrollbarsComponent defer style={overlayScrollbarsStyles} options={overlayScrollbarsParams.options}>
{allowPrivateBoards && (
<>
<Flex w="full" justifyContent="space-between" alignItems="center">
<Flex w="full" justifyContent="space-between" alignItems="center" ps={2}>
<Text fontSize="md" fontWeight="medium" userSelect="none">
{t('boards.private')}
</Text>
@ -63,7 +63,7 @@ const BoardsList = () => {
</Flex>
</>
)}
<Flex w="full" justifyContent="space-between" alignItems="center">
<Flex w="full" justifyContent="space-between" alignItems="center" ps={2}>
<Text fontSize="md" fontWeight="medium" userSelect="none">
{allowPrivateBoards ? t('boards.shared') : t('boards.boards')}
</Text>

View File

@ -40,7 +40,7 @@ const BoardsSearch = () => {
);
return (
<InputGroup>
<InputGroup pt={2}>
<Input
placeholder={t('boards.searchBoard')}
value={boardSearchText}

View File

@ -35,7 +35,7 @@ const editableInputStyles: SystemStyleObject = {
};
const _hover: SystemStyleObject = {
bg: 'base.800',
bg: 'base.850',
};
interface GalleryBoardProps {
@ -118,7 +118,7 @@ const GalleryBoard = ({ board, isSelected, setBoardToDelete }: GalleryBoardProps
py={1}
px={2}
gap={2}
bg={isSelected ? 'base.800' : undefined}
bg={isSelected ? 'base.850' : undefined}
_hover={_hover}
>
<CoverImage board={board} />
@ -147,13 +147,7 @@ const GalleryBoard = ({ board, isSelected, setBoardToDelete }: GalleryBoardProps
<EditableInput sx={editableInputStyles} />
</Editable>
{autoAddBoardId === board.board_id && !editingDisclosure.isOpen && <AutoAddBadge />}
{board.archived && !editingDisclosure.isOpen && (
<Icon
as={PiArchiveBold}
fill="base.300"
filter="drop-shadow(0px 0px 0.1rem var(--invoke-colors-base-800))"
/>
)}
{board.archived && !editingDisclosure.isOpen && <Icon as={PiArchiveBold} fill="base.300" />}
{!editingDisclosure.isOpen && <Text variant="subtext">{board.image_count}</Text>}
<IAIDroppable data={droppableData} dropLabel={<Text fontSize="md">{t('unifiedCanvas.move')}</Text>} />

View File

@ -17,7 +17,7 @@ interface Props {
}
const _hover: SystemStyleObject = {
bg: 'base.800',
bg: 'base.850',
};
const NoBoardBoard = memo(({ isSelected }: Props) => {
@ -71,7 +71,7 @@ const NoBoardBoard = memo(({ isSelected }: Props) => {
px={2}
py={1}
gap={2}
bg={isSelected ? 'base.800' : undefined}
bg={isSelected ? 'base.850' : undefined}
_hover={_hover}
>
<Flex w={8} h={8} justifyContent="center" alignItems="center">

View File

@ -8,7 +8,16 @@ const GalleryBoardName = () => {
const boardName = useBoardName(selectedBoardId);
return (
<Flex as="button" w="full" borderWidth={1} borderRadius="base" alignItems="center" justifyContent="center" px={2}>
<Flex
as="button"
h="full"
w="full"
borderWidth={1}
borderRadius="base"
alignItems="center"
justifyContent="center"
px={2}
>
<Text fontWeight="semibold" fontSize="md" noOfLines={1} wordBreak="break-all" color="base.200">
{boardName}
</Text>

View File

@ -0,0 +1,30 @@
import { Flex, Link, Text } from '@invoke-ai/ui-library';
import { useStore } from '@nanostores/react';
import { $projectName, $projectUrl } from 'app/store/nanostores/projectId';
import { memo } from 'react';
import GalleryBoardName from './GalleryBoardName';
const GalleryHeader = () => {
const projectName = useStore($projectName);
const projectUrl = useStore($projectUrl);
if (projectName && projectUrl) {
return (
<Flex gap={2} w="full" alignItems="center" justifyContent="space-evenly" pe={2}>
<Text fontSize="md" fontWeight="semibold" noOfLines={1} w="full" textAlign="center">
<Link href={projectUrl}>{projectName}</Link>
</Text>
<GalleryBoardName />
</Flex>
);
}
return (
<Flex w="full" pe={2}>
<GalleryBoardName />
</Flex>
);
};
export default memo(GalleryHeader);

View File

@ -17,7 +17,7 @@ const GallerySettingsPopover = () => {
return (
<Popover isLazy>
<PopoverTrigger>
<IconButton aria-label={t('gallery.gallerySettings')} size="sm" icon={<RiSettings4Fill />} />
<IconButton aria-label={t('gallery.gallerySettings')} icon={<RiSettings4Fill />} variant="link" h="full" />
</PopoverTrigger>
<PopoverContent>
<PopoverBody>

View File

@ -1,18 +1,16 @@
import type { ChakraProps } from '@invoke-ai/ui-library';
import { Box, Collapse, Flex, IconButton, Tab, TabList, Tabs, useDisclosure } from '@invoke-ai/ui-library';
import { useStore } from '@nanostores/react';
import { $galleryHeader } from 'app/store/nanostores/galleryHeader';
import { Box, Collapse, Flex, IconButton, Spacer, Tab, TabList, Tabs, useDisclosure } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import GalleryHeader from 'features/gallery/components/GalleryHeader';
import { galleryViewChanged } from 'features/gallery/store/gallerySlice';
import ResizeHandle from 'features/ui/components/tabs/ResizeHandle';
import { memo, useCallback, useRef } from 'react';
import { useTranslation } from 'react-i18next';
import { PiImagesBold, PiMagnifyingGlassBold } from 'react-icons/pi';
import { PiMagnifyingGlassBold } from 'react-icons/pi';
import { Panel, PanelGroup } from 'react-resizable-panels';
import BoardsList from './Boards/BoardsList/BoardsList';
import BoardsSearch from './Boards/BoardsList/BoardsSearch';
import GalleryBoardName from './GalleryBoardName';
import GallerySettingsPopover from './GallerySettingsPopover/GallerySettingsPopover';
import GalleryImageGrid from './ImageGrid/GalleryImageGrid';
import { GalleryPagination } from './ImageGrid/GalleryPagination';
@ -20,29 +18,22 @@ import { GallerySearch } from './ImageGrid/GallerySearch';
const baseStyles: ChakraProps['sx'] = {
fontWeight: 'semibold',
fontSize: 'md',
fontSize: 'sm',
color: 'base.300',
borderBottom: '1px solid',
borderBottomColor: 'invokeBlue.800',
};
const selectedStyles: ChakraProps['sx'] = {
borderColor: 'invokeBlue.800',
borderBottomColor: 'base.850',
borderColor: 'base.800',
borderBottomColor: 'base.900',
color: 'invokeBlue.300',
};
const searchIconStyles: ChakraProps['sx'] = {
borderBottom: '1px solid',
borderBottomColor: 'invokeBlue.800',
maxW: '16',
};
const ImageGalleryContent = () => {
const { t } = useTranslation();
const galleryView = useAppSelector((s) => s.gallery.galleryView);
const searchTerm = useAppSelector((s) => s.gallery.searchTerm);
const boardSearchText = useAppSelector((s) => s.gallery.boardSearchText);
const dispatch = useAppDispatch();
const galleryHeader = useStore($galleryHeader);
const searchDisclosure = useDisclosure({ defaultIsOpen: false });
const boardSearchDisclosure = useDisclosure({ defaultIsOpen: false });
@ -57,62 +48,81 @@ const ImageGalleryContent = () => {
const panelGroupRef = useRef(null);
return (
<Flex layerStyle="first" position="relative" flexDirection="column" h="full" w="full" p={2} gap={2}>
<Flex alignItems="center" justifyContent="space-between" gap={2}>
{galleryHeader}
<GalleryBoardName />
<Flex position="relative" flexDirection="column" h="full" w="full" pt={2}>
<Flex alignItems="center" gap={2}>
<GalleryHeader />
<GallerySettingsPopover />
<IconButton
onClick={boardSearchDisclosure.onToggle}
tooltip={`${t('gallery.displayBoardSearch')}`}
aria-label={t('gallery.displayBoardSearch')}
icon={<PiMagnifyingGlassBold />}
size="sm"
/>
<Box position="relative" h="full">
<IconButton
w="full"
h="full"
onClick={boardSearchDisclosure.onToggle}
tooltip={`${t('gallery.displayBoardSearch')}`}
aria-label={t('gallery.displayBoardSearch')}
icon={<PiMagnifyingGlassBold />}
variant="link"
/>
{boardSearchText && (
<Box
position="absolute"
w={2}
h={2}
bg="invokeBlue.300"
borderRadius="full"
insetBlockStart={2}
insetInlineEnd={2}
/>
)}
</Box>
</Flex>
<PanelGroup ref={panelGroupRef} direction="vertical">
<Panel>
<Collapse in={boardSearchDisclosure.isOpen}>
<Box mt="2">
<BoardsSearch />
</Box>
<BoardsSearch />
</Collapse>
<BoardsList />
</Panel>
<ResizeHandle orientation="horizontal" />
<Panel>
<Flex flexDirection="column" alignItems="center" justifyContent="space-between" gap={2} h="full">
<Tabs isFitted index={galleryView === 'images' ? 0 : 1} variant="enclosed" size="sm" w="full" my="2">
<TabList fontSize="sm" borderBottom="none">
<Flex flexDirection="column" alignItems="center" justifyContent="space-between" h="full" w="full">
<Tabs index={galleryView === 'images' ? 0 : 1} variant="enclosed" display="flex" flexDir="column" w="full">
<TabList gap={2} fontSize="sm" borderColor="base.800">
<Tab sx={baseStyles} _selected={selectedStyles} onClick={handleClickImages} data-testid="images-tab">
<Flex alignItems="center" justifyContent="center" gap="2" w="full">
<PiImagesBold size="16px" />
{t('parameters.images')}
</Flex>
{t('parameters.images')}
</Tab>
<Tab sx={baseStyles} _selected={selectedStyles} onClick={handleClickAssets} data-testid="assets-tab">
<Flex alignItems="center" justifyContent="center" gap="2" w="full">
<PiImagesBold size="16px" />
{t('gallery.assets')}
</Flex>
{t('gallery.assets')}
</Tab>
<Tab sx={searchIconStyles} onClick={searchDisclosure.onToggle}>
<Spacer />
<Box position="relative">
<IconButton
w="full"
h="full"
onClick={searchDisclosure.onToggle}
tooltip={`${t('gallery.displaySearch')}`}
aria-label={t('gallery.displaySearch')}
icon={<PiMagnifyingGlassBold />}
fontSize={16}
textAlign="center"
color="base.400"
variant="unstyled"
minW="unset"
variant="link"
/>
</Tab>
{searchTerm && (
<Box
position="absolute"
w={2}
h={2}
bg="invokeBlue.300"
borderRadius="full"
insetBlockStart={2}
insetInlineEnd={2}
/>
)}
</Box>
</TabList>
</Tabs>
<Box mb="2" w="full">
<Box w="full">
<Collapse in={searchDisclosure.isOpen}>
<GallerySearch />
<Box w="full" pt={2}>
<GallerySearch />
</Box>
</Collapse>
</Box>
<GalleryImageGrid />

View File

@ -124,7 +124,7 @@ const Content = () => {
}, [calculateNewLimit, container, dispatch]);
return (
<Box position="relative" w="full" h="full">
<Box position="relative" w="full" h="full" mt={2}>
<Box
ref={containerRef}
position="absolute"