feat(ui): fix queue item count badge positioning

Previously this badge, floating over the queue menu button next to the invoke button, was rendered within the existing layout. When I initially positioned it, the app layout interfered - it would extend into an area reserved for a flex gap, which cut off the badge.

As a (bad) workaround, I had shifted the whole app down a few pixels to make room for it. What I should have done is what I've done in this commit - render the badge in a portal to take it out of the layout so we don't need that extra vertical padding.

Sleekified some styling a bit too.
This commit is contained in:
psychedelicious 2024-08-28 16:37:50 +10:00
parent 64e60a7fde
commit ce55a96125
7 changed files with 31 additions and 17 deletions

View File

@ -19,8 +19,6 @@ export const CanvasEditor = memo(() => {
<Flex <Flex
tabIndex={-1} tabIndex={-1}
ref={ref} ref={ref}
layerStyle="first"
p={2}
borderRadius="base" borderRadius="base"
position="relative" position="relative"
flexDirection="column" flexDirection="column"

View File

@ -82,10 +82,11 @@ export const StageComponent = memo(({ asPreview = false }: Props) => {
); );
return ( return (
<Flex position="relative" w="full" h="full" bg={dynamicGrid ? 'base.850' : 'base.900'}> <Flex position="relative" w="full" h="full" bg={dynamicGrid ? 'base.850' : 'base.900'} borderRadius="base">
{!dynamicGrid && ( {!dynamicGrid && (
<Flex <Flex
position="absolute" position="absolute"
borderRadius="base"
bgImage={TRANSPARENCY_CHECKER_PATTERN} bgImage={TRANSPARENCY_CHECKER_PATTERN}
top={0} top={0}
right={0} right={0}
@ -102,9 +103,6 @@ export const StageComponent = memo(({ asPreview = false }: Props) => {
left={0} left={0}
ref={containerRef} ref={containerRef}
borderRadius="base" borderRadius="base"
border={1}
borderStyle="solid"
borderColor="base.700"
overflow="hidden" overflow="hidden"
data-testid="control-layers-canvas" data-testid="control-layers-canvas"
/> />

View File

@ -59,7 +59,7 @@ const GalleryPanelContent = () => {
}, [boardSearchText.length, boardSearchDisclosure, boardsListPanel, dispatch]); }, [boardSearchText.length, boardSearchDisclosure, boardsListPanel, dispatch]);
return ( return (
<Flex ref={ref} position="relative" flexDirection="column" h="full" w="full" pt={2} tabIndex={-1}> <Flex ref={ref} position="relative" flexDirection="column" h="full" w="full" tabIndex={-1}>
<Flex alignItems="center" gap={0}> <Flex alignItems="center" gap={0}>
<GalleryHeader /> <GalleryHeader />
<Flex alignItems="center" justifyContent="space-between" w="full"> <Flex alignItems="center" justifyContent="space-between" w="full">

View File

@ -21,7 +21,7 @@ export const ImageViewer = memo(() => {
<Flex <Flex
ref={ref} ref={ref}
tabIndex={-1} tabIndex={-1}
layerStyle="first" layerStyle="body"
borderRadius="base" borderRadius="base"
position="absolute" position="absolute"
flexDirection="column" flexDirection="column"
@ -29,7 +29,6 @@ export const ImageViewer = memo(() => {
right={0} right={0}
bottom={0} bottom={0}
left={0} left={0}
p={2}
rowGap={4} rowGap={4}
alignItems="center" alignItems="center"
justifyContent="center" justifyContent="center"

View File

@ -7,16 +7,18 @@ import {
MenuDivider, MenuDivider,
MenuItem, MenuItem,
MenuList, MenuList,
Portal,
useDisclosure, useDisclosure,
} from '@invoke-ai/ui-library'; } from '@invoke-ai/ui-library';
import { useAppDispatch } from 'app/store/storeHooks'; import { useAppDispatch } from 'app/store/storeHooks';
import type { Coordinate } from 'features/controlLayers/store/types';
import { useClearQueueConfirmationAlertDialog } from 'features/queue/components/ClearQueueConfirmationAlertDialog'; import { useClearQueueConfirmationAlertDialog } from 'features/queue/components/ClearQueueConfirmationAlertDialog';
import { useClearQueue } from 'features/queue/hooks/useClearQueue'; import { useClearQueue } from 'features/queue/hooks/useClearQueue';
import { usePauseProcessor } from 'features/queue/hooks/usePauseProcessor'; import { usePauseProcessor } from 'features/queue/hooks/usePauseProcessor';
import { useResumeProcessor } from 'features/queue/hooks/useResumeProcessor'; import { useResumeProcessor } from 'features/queue/hooks/useResumeProcessor';
import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus'; import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
import { setActiveTab } from 'features/ui/store/uiSlice'; import { setActiveTab } from 'features/ui/store/uiSlice';
import { memo, useCallback } from 'react'; import { memo, useCallback, useEffect, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { PiPauseFill, PiPlayFill, PiTrashSimpleBold } from 'react-icons/pi'; import { PiPauseFill, PiPlayFill, PiTrashSimpleBold } from 'react-icons/pi';
import { RiListCheck, RiPlayList2Fill } from 'react-icons/ri'; import { RiListCheck, RiPlayList2Fill } from 'react-icons/ri';
@ -26,6 +28,8 @@ export const QueueActionsMenuButton = memo(() => {
const { isOpen, onOpen, onClose } = useDisclosure(); const { isOpen, onOpen, onClose } = useDisclosure();
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const { t } = useTranslation(); const { t } = useTranslation();
const [badgePos, setBadgePos] = useState<Coordinate | null>(null);
const menuButtonRef = useRef<HTMLButtonElement>(null);
const dialogState = useClearQueueConfirmationAlertDialog(); const dialogState = useClearQueueConfirmationAlertDialog();
const isPauseEnabled = useFeatureStatus('pauseQueue'); const isPauseEnabled = useFeatureStatus('pauseQueue');
const isResumeEnabled = useFeatureStatus('resumeQueue'); const isResumeEnabled = useFeatureStatus('resumeQueue');
@ -49,10 +53,17 @@ export const QueueActionsMenuButton = memo(() => {
dispatch(setActiveTab('queue')); dispatch(setActiveTab('queue'));
}, [dispatch]); }, [dispatch]);
useEffect(() => {
if (menuButtonRef.current) {
const { x, y } = menuButtonRef.current.getBoundingClientRect();
setBadgePos({ x: x - 10, y: y - 10 });
}
}, []);
return ( return (
<Box pos="relative"> <Box pos="relative">
<Menu isOpen={isOpen} onOpen={onOpen} onClose={onClose} placement="bottom-end"> <Menu isOpen={isOpen} onOpen={onOpen} onClose={onClose} placement="bottom-end">
<MenuButton as={IconButton} aria-label="Queue Actions Menu" icon={<RiListCheck />} /> <MenuButton ref={menuButtonRef} as={IconButton} aria-label="Queue Actions Menu" icon={<RiListCheck />} />
<MenuList> <MenuList>
<MenuItem <MenuItem
isDestructive isDestructive
@ -89,10 +100,18 @@ export const QueueActionsMenuButton = memo(() => {
</MenuItem> </MenuItem>
</MenuList> </MenuList>
</Menu> </Menu>
{queueSize > 0 && ( {queueSize > 0 && badgePos !== null && (
<Badge pos="absolute" insetInlineStart={-3} insetBlockStart={-1.5} colorScheme="invokeYellow" zIndex="docked"> <Portal>
{queueSize} <Badge
</Badge> pos="absolute"
insetInlineStart={badgePos.x}
insetBlockStart={badgePos.y}
colorScheme="invokeYellow"
zIndex="docked"
>
{queueSize}
</Badge>
</Portal>
)} )}
</Box> </Box>
); );

View File

@ -11,7 +11,7 @@ import { QueueActionsMenuButton } from './QueueActionsMenuButton';
const QueueControls = () => { const QueueControls = () => {
const isPrependEnabled = useFeatureStatus('prependQueue'); const isPrependEnabled = useFeatureStatus('prependQueue');
return ( return (
<Flex w="full" position="relative" borderRadius="base" gap={2} pt={2} flexDir="column"> <Flex w="full" position="relative" borderRadius="base" gap={2} flexDir="column">
<ButtonGroup size="lg" isAttached={false}> <ButtonGroup size="lg" isAttached={false}>
{isPrependEnabled && <QueueFrontButton />} {isPrependEnabled && <QueueFrontButton />}
<InvokeQueueBackButton /> <InvokeQueueBackButton />

View File

@ -18,7 +18,7 @@ export const VerticalNavBar = memo(() => {
const customNavComponent = useStore($customNavComponent); const customNavComponent = useStore($customNavComponent);
return ( return (
<Flex flexDir="column" alignItems="center" pt={4} pb={2} gap={4}> <Flex flexDir="column" alignItems="center" py={2} gap={4}>
<InvokeAILogoComponent /> <InvokeAILogoComponent />
<Flex gap={4} pt={6} h="full" flexDir="column"> <Flex gap={4} pt={6} h="full" flexDir="column">
<TabMountGate tab="generation"> <TabMountGate tab="generation">