Skip to content
22 changes: 22 additions & 0 deletions invokeai/frontend/web/src/common/components/IAITooltip.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import type { TooltipProps } from '@invoke-ai/ui-library';
import { Tooltip } from '@invoke-ai/ui-library';
import { useAppSelector } from 'app/store/storeHooks';
import { selectSystemShouldEnableInformationalPopovers } from 'features/system/store/systemSlice';
import type { PropsWithChildren } from 'react';
import { memo } from 'react';

/**
* A Tooltip component that respects the user's "Enable Informational Popovers" setting.
* When the setting is disabled, this component returns its children without a tooltip wrapper.
*/
export const IAITooltip = memo(({ children, ...rest }: PropsWithChildren<TooltipProps>) => {
const shouldEnableInformationalPopovers = useAppSelector(selectSystemShouldEnableInformationalPopovers);

if (!shouldEnableInformationalPopovers) {
return children;
}

return <Tooltip {...rest}>{children}</Tooltip>;
});

IAITooltip.displayName = 'IAITooltip';
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { MenuItemProps } from '@invoke-ai/ui-library';
import { Flex, MenuItem, Tooltip } from '@invoke-ai/ui-library';
import { Flex, MenuItem } from '@invoke-ai/ui-library';
import { IAITooltip } from 'common/components/IAITooltip';
import type { ReactNode } from 'react';

type Props = MenuItemProps & {
Expand All @@ -9,7 +10,7 @@ type Props = MenuItemProps & {

export const IconMenuItem = ({ tooltip, icon, ...props }: Props) => {
return (
<Tooltip label={tooltip} placement="top" gutter={12}>
<IAITooltip label={tooltip} placement="top" gutter={12}>
<MenuItem
display="flex"
alignItems="center"
Expand All @@ -21,7 +22,7 @@ export const IconMenuItem = ({ tooltip, icon, ...props }: Props) => {
>
{icon}
</MenuItem>
</Tooltip>
</IAITooltip>
);
};

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { IconButton, Menu, MenuButton, MenuGroup, MenuItem, MenuList } from '@invoke-ai/ui-library';
import { IAITooltip } from 'common/components/IAITooltip';
import {
useAddControlLayer,
useAddInpaintMask,
Expand Down Expand Up @@ -26,17 +27,18 @@ export const EntityListGlobalActionBarAddLayerMenu = memo(() => {

return (
<Menu>
<MenuButton
as={IconButton}
minW={8}
variant="link"
alignSelf="stretch"
tooltip={t('controlLayers.addLayer')}
aria-label={t('controlLayers.addLayer')}
icon={<PiPlusBold />}
data-testid="control-layers-add-layer-menu-button"
isDisabled={isBusy}
/>
<IAITooltip label={t('controlLayers.addLayer')}>
<MenuButton
as={IconButton}
minW={8}
variant="link"
alignSelf="stretch"
aria-label={t('controlLayers.addLayer')}
icon={<PiPlusBold />}
data-testid="control-layers-add-layer-menu-button"
isDisabled={isBusy}
/>
</IAITooltip>
<MenuList>
<MenuGroup title={t('controlLayers.regional')}>
<MenuItem icon={<PiPlusBold />} onClick={addInpaintMask} isDisabled={!isInpaintLayerEnabled}>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { IconButton } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { IAITooltip } from 'common/components/IAITooltip';
import { useCanvasIsBusy } from 'features/controlLayers/hooks/useCanvasIsBusy';
import { entityDuplicated } from 'features/controlLayers/store/canvasSlice';
import { selectSelectedEntityIdentifier } from 'features/controlLayers/store/selectors';
Expand All @@ -20,16 +21,17 @@ export const EntityListSelectedEntityActionBarDuplicateButton = memo(() => {
}, [dispatch, selectedEntityIdentifier]);

return (
<IconButton
onClick={onClick}
isDisabled={!selectedEntityIdentifier || isBusy}
minW={8}
variant="link"
alignSelf="stretch"
aria-label={t('controlLayers.duplicate')}
tooltip={t('controlLayers.duplicate')}
icon={<PiCopyFill />}
/>
<IAITooltip label={t('controlLayers.duplicate')}>
<IconButton
onClick={onClick}
isDisabled={!selectedEntityIdentifier || isBusy}
minW={8}
variant="link"
alignSelf="stretch"
aria-label={t('controlLayers.duplicate')}
icon={<PiCopyFill />}
/>
</IAITooltip>
);
});

Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,7 @@
import {
Box,
Flex,
Popover,
PopoverBody,
PopoverContent,
PopoverTrigger,
Portal,
Tooltip,
} from '@invoke-ai/ui-library';
import { Box, Flex, Popover, PopoverBody, PopoverContent, PopoverTrigger, Portal } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import RgbColorPicker from 'common/components/ColorPicker/RgbColorPicker';
import { IAITooltip } from 'common/components/IAITooltip';
import { rgbColorToString } from 'common/util/colorCodeTransformers';
import { MaskFillStyle } from 'features/controlLayers/components/common/MaskFillStyle';
import { entityFillColorChanged, entityFillStyleChanged } from 'features/controlLayers/store/canvasSlice';
Expand Down Expand Up @@ -56,8 +48,8 @@ export const EntityListSelectedEntityActionBarFill = memo(() => {
return (
<Popover isLazy>
<PopoverTrigger>
<Flex role="button" aria-label={t('controlLayers.maskFill')} tabIndex={-1} w={8} h={8}>
<Tooltip label={t('controlLayers.maskFill')}>
<IAITooltip label={t('controlLayers.maskFill')}>
<Flex role="button" aria-label={t('controlLayers.maskFill')} tabIndex={-1} w={8} h={8}>
<Flex w="full" h="full" alignItems="center" justifyContent="center">
<Box
borderRadius="full"
Expand All @@ -68,8 +60,8 @@ export const EntityListSelectedEntityActionBarFill = memo(() => {
bg={rgbColorToString(fill.color)}
/>
</Flex>
</Tooltip>
</Flex>
</Flex>
</IAITooltip>
</PopoverTrigger>
<Portal>
<PopoverContent>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { IconButton } from '@invoke-ai/ui-library';
import { useAppSelector } from 'app/store/storeHooks';
import { IAITooltip } from 'common/components/IAITooltip';
import { useEntityFilter } from 'features/controlLayers/hooks/useEntityFilter';
import { selectSelectedEntityIdentifier } from 'features/controlLayers/store/selectors';
import { isFilterableEntityIdentifier } from 'features/controlLayers/store/types';
Expand All @@ -21,16 +22,17 @@ export const EntityListSelectedEntityActionBarFilterButton = memo(() => {
}

return (
<IconButton
onClick={filter.start}
isDisabled={filter.isDisabled}
minW={8}
variant="link"
alignSelf="stretch"
aria-label={t('controlLayers.filter.filter')}
tooltip={t('controlLayers.filter.filter')}
icon={<PiShootingStarFill />}
/>
<IAITooltip label={t('controlLayers.filter.filter')}>
<IconButton
onClick={filter.start}
isDisabled={filter.isDisabled}
minW={8}
variant="link"
alignSelf="stretch"
aria-label={t('controlLayers.filter.filter')}
icon={<PiShootingStarFill />}
/>
</IAITooltip>
);
});

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { IconButton } from '@invoke-ai/ui-library';
import { useAppSelector } from 'app/store/storeHooks';
import { IAITooltip } from 'common/components/IAITooltip';
import { useCanvasIsBusy } from 'features/controlLayers/hooks/useCanvasIsBusy';
import { useInvertMask } from 'features/controlLayers/hooks/useInvertMask';
import { selectSelectedEntityIdentifier } from 'features/controlLayers/store/selectors';
Expand Down Expand Up @@ -28,16 +29,17 @@ export const EntityListSelectedEntityActionBarInvertMaskButton = memo(() => {
: t('controlLayers.invertMask');

return (
<IconButton
onClick={invertMask}
isDisabled={isBusy}
minW={8}
variant="link"
alignSelf="stretch"
aria-label={label}
tooltip={label}
icon={<PiSelectionInverseBold />}
/>
<IAITooltip label={label}>
<IconButton
onClick={invertMask}
isDisabled={isBusy}
minW={8}
variant="link"
alignSelf="stretch"
aria-label={label}
icon={<PiSelectionInverseBold />}
/>
</IAITooltip>
);
});

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { IconButton } from '@invoke-ai/ui-library';
import { useAppSelector } from 'app/store/storeHooks';
import { IAITooltip } from 'common/components/IAITooltip';
import { useEntityAdapterSafe } from 'features/controlLayers/contexts/EntityAdapterContext';
import { useCanvasIsBusy } from 'features/controlLayers/hooks/useCanvasIsBusy';
import { useSaveLayerToAssets } from 'features/controlLayers/hooks/useSaveLayerToAssets';
Expand Down Expand Up @@ -28,16 +29,17 @@ export const EntityListSelectedEntityActionBarSaveToAssetsButton = memo(() => {
}

return (
<IconButton
onClick={onClick}
isDisabled={!selectedEntityIdentifier || isBusy}
minW={8}
variant="link"
alignSelf="stretch"
aria-label={t('controlLayers.saveLayerToAssets')}
tooltip={t('controlLayers.saveLayerToAssets')}
icon={<PiFloppyDiskBold />}
/>
<IAITooltip label={t('controlLayers.saveLayerToAssets')}>
<IconButton
onClick={onClick}
isDisabled={!selectedEntityIdentifier || isBusy}
minW={8}
variant="link"
alignSelf="stretch"
aria-label={t('controlLayers.saveLayerToAssets')}
icon={<PiFloppyDiskBold />}
/>
</IAITooltip>
);
});

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { IconButton } from '@invoke-ai/ui-library';
import { useAppSelector } from 'app/store/storeHooks';
import { IAITooltip } from 'common/components/IAITooltip';
import { useEntitySegmentAnything } from 'features/controlLayers/hooks/useEntitySegmentAnything';
import { selectSelectedEntityIdentifier } from 'features/controlLayers/store/selectors';
import { isSegmentableEntityIdentifier } from 'features/controlLayers/store/types';
Expand All @@ -21,16 +22,17 @@ export const EntityListSelectedEntityActionBarSelectObjectButton = memo(() => {
}

return (
<IconButton
onClick={segment.start}
isDisabled={segment.isDisabled}
minW={8}
variant="link"
alignSelf="stretch"
aria-label={t('controlLayers.selectObject.selectObject')}
tooltip={t('controlLayers.selectObject.selectObject')}
icon={<PiShapesFill />}
/>
<IAITooltip label={t('controlLayers.selectObject.selectObject')}>
<IconButton
onClick={segment.start}
isDisabled={segment.isDisabled}
minW={8}
variant="link"
alignSelf="stretch"
aria-label={t('controlLayers.selectObject.selectObject')}
icon={<PiShapesFill />}
/>
</IAITooltip>
);
});

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { IconButton } from '@invoke-ai/ui-library';
import { useAppSelector } from 'app/store/storeHooks';
import { IAITooltip } from 'common/components/IAITooltip';
import { useEntityTransform } from 'features/controlLayers/hooks/useEntityTransform';
import { selectSelectedEntityIdentifier } from 'features/controlLayers/store/selectors';
import { isTransformableEntityIdentifier } from 'features/controlLayers/store/types';
Expand All @@ -21,16 +22,17 @@ export const EntityListSelectedEntityActionBarTransformButton = memo(() => {
}

return (
<IconButton
onClick={transform.start}
isDisabled={transform.isDisabled}
minW={8}
variant="link"
alignSelf="stretch"
aria-label={t('controlLayers.transform.transform')}
tooltip={t('controlLayers.transform.transform')}
icon={<PiFrameCornersBold />}
/>
<IAITooltip label={t('controlLayers.transform.transform')}>
<IconButton
onClick={transform.start}
isDisabled={transform.isDisabled}
minW={8}
variant="link"
alignSelf="stretch"
aria-label={t('controlLayers.transform.transform')}
icon={<PiFrameCornersBold />}
/>
</IAITooltip>
);
});

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { FormControl, FormLabel, Switch, Tooltip } from '@invoke-ai/ui-library';
import { FormControl, FormLabel, Switch } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { IAITooltip } from 'common/components/IAITooltip';
import {
selectIsolatedLayerPreview,
settingsIsolatedLayerPreviewToggled,
Expand All @@ -16,12 +17,12 @@ export const CanvasOperationIsolatedLayerPreviewSwitch = memo(() => {
}, [dispatch]);

return (
<Tooltip label={t('controlLayers.settings.isolatedLayerPreviewDesc')}>
<IAITooltip label={t('controlLayers.settings.isolatedLayerPreviewDesc')}>
<FormControl w="min-content">
<FormLabel m={0}>{t('controlLayers.settings.isolatedPreview')}</FormLabel>
<Switch size="sm" isChecked={isolatedLayerPreview} onChange={onChangeIsolatedPreview} />
</FormControl>
</Tooltip>
</IAITooltip>
);
});

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Combobox, FormControl, Tooltip } from '@invoke-ai/ui-library';
import { Combobox, FormControl } from '@invoke-ai/ui-library';
import { useAppSelector } from 'app/store/storeHooks';
import { IAITooltip } from 'common/components/IAITooltip';
import { useGroupedModelCombobox } from 'common/hooks/useGroupedModelCombobox';
import { selectBase } from 'features/controlLayers/store/paramsSlice';
import { memo, useCallback, useMemo } from 'react';
Expand Down Expand Up @@ -52,7 +53,7 @@ export const ControlLayerControlAdapterModel = memo(({ modelKey, onChange: onCha
});

return (
<Tooltip label={selectedModel?.description}>
<IAITooltip label={selectedModel?.description}>
<FormControl isInvalid={!value || currentBaseModel !== selectedModel?.base} w="full">
<Combobox
options={options}
Expand All @@ -62,7 +63,7 @@ export const ControlLayerControlAdapterModel = memo(({ modelKey, onChange: onCha
noOptionsMessage={noOptionsMessage}
/>
</FormControl>
</Tooltip>
</IAITooltip>
);
});

Expand Down
Loading