From a7cd369da78a212a0cbc6bcd5b5fc5c46c70e169 Mon Sep 17 00:00:00 2001 From: Pavel Klibani Date: Fri, 12 Apr 2024 20:58:06 +0200 Subject: [PATCH] Feat(web-react): TooltipModern tooltip hover - Tooltip stay open after mouse hover Solves #DS-1219 --- .../web-react/src/components/Tooltip/README.md | 1 + .../src/components/Tooltip/TooltipModern.tsx | 2 ++ .../src/components/Tooltip/useFloating.ts | 15 ++++++++++++++- .../demo/TooltipWithFloatingUI.tsx | 2 ++ .../stories/TooltipModern.stories.tsx | 18 ++++++++++++++++-- packages/web-react/src/types/tooltip.ts | 1 + 6 files changed, 36 insertions(+), 3 deletions(-) diff --git a/packages/web-react/src/components/Tooltip/README.md b/packages/web-react/src/components/Tooltip/README.md index 90f90d1f6b..ab084a3594 100644 --- a/packages/web-react/src/components/Tooltip/README.md +++ b/packages/web-react/src/components/Tooltip/README.md @@ -321,6 +321,7 @@ const [open, setOpen] = React.useState(false); | `isOpen` | `bool` | - | ✔ | Open state | | `onToggle` | `() => void` | - | ✔ | Function for toggle open state of dropdown | | `placement` | [Placement Dictionary][dictionary-placement] | "bottom" | ✕ | Placement of tooltip | +| `isFocusableOnHover` | `bool` | false | ✕ | Allows you to mouse over a tooltip without closing it. We suggest turning off the `click` trigger if you use this feature. | | `trigger` | ["click" \| "hover" \| "manual"] | ["click", "hover" ] | ✕ | How tooltip is triggered: `click`, `hover`, `manual`. You may pass multiple triggers. If you pass `manual`, there will be no toggle functionality and you should provide your own toggle solution. | On top of the API options, the components accept [additional attributes][readme-additional-attributes]. diff --git a/packages/web-react/src/components/Tooltip/TooltipModern.tsx b/packages/web-react/src/components/Tooltip/TooltipModern.tsx index 6c5e9af554..924d16d3c8 100644 --- a/packages/web-react/src/components/Tooltip/TooltipModern.tsx +++ b/packages/web-react/src/components/Tooltip/TooltipModern.tsx @@ -18,6 +18,7 @@ const TooltipModern = (props: TooltipModernProps) => { id, isDismissible = false, isOpen = false, + isFocusableOnHover = false, onToggle, placement: tooltipPlacement, trigger = ['click', 'hover'], @@ -60,6 +61,7 @@ const TooltipModern = (props: TooltipModernProps) => { isOpen, offset: tooltipOffset, onToggle, + isFocusableOnHover, shiftProp, sizeProp, tooltipArrowWidth, diff --git a/packages/web-react/src/components/Tooltip/useFloating.ts b/packages/web-react/src/components/Tooltip/useFloating.ts index 22daa34121..1bb6ff5981 100644 --- a/packages/web-react/src/components/Tooltip/useFloating.ts +++ b/packages/web-react/src/components/Tooltip/useFloating.ts @@ -6,6 +6,7 @@ import { inline, limitShift, offset, + safePolygon, shift, size, useClick, @@ -28,6 +29,7 @@ type UseTooltipUIProps = { isOpen?: boolean; offset?: number; onToggle: (isOpen: boolean) => void; + isFocusableOnHover?: boolean; shiftProp: boolean; sizeProp: boolean; tooltipArrowWidth?: number; @@ -51,6 +53,7 @@ export const useFloating = (props: UseTooltipUIProps) => { isOpen = false, offset: tooltipOffset = 0, onToggle, + isFocusableOnHover, shiftProp, sizeProp, tooltipArrowWidth = 0, @@ -65,6 +68,13 @@ export const useFloating = (props: UseTooltipUIProps) => { const isHoverEnabled = trigger?.includes(TOOLTIP_TRIGGER.HOVER); const isClickEnabled = trigger?.includes(TOOLTIP_TRIGGER.CLICK); + const useSafePolygons = (isClickable: boolean) => + isClickable + ? safePolygon({ + requireIntent: false, + }) + : undefined; + // Floating UI library settings const { x, y, refs, context, placement, middlewareData } = useFloatingUI({ open: isOpen, @@ -125,7 +135,10 @@ export const useFloating = (props: UseTooltipUIProps) => { // Floating UI library interaction hooks const click = useClick(context, { enabled: isClickEnabled }); - const hover = useHover(context, { enabled: isHoverEnabled }); + const hover = useHover(context, { + enabled: isHoverEnabled, + handleClose: useSafePolygons(!!isFocusableOnHover), + }); const dismiss = useDismiss(context); const role = useRole(context, { role: 'tooltip' }); const { getReferenceProps, getFloatingProps } = useInteractions([click, hover, dismiss, role]); diff --git a/packages/web-react/src/components/TooltipModern/demo/TooltipWithFloatingUI.tsx b/packages/web-react/src/components/TooltipModern/demo/TooltipWithFloatingUI.tsx index 495ad2ac89..205475c90e 100644 --- a/packages/web-react/src/components/TooltipModern/demo/TooltipWithFloatingUI.tsx +++ b/packages/web-react/src/components/TooltipModern/demo/TooltipWithFloatingUI.tsx @@ -129,6 +129,8 @@ const TooltipWithFloatingUI = () => { flipFallbackPlacements={suggestedFallbackPlacement} enableShifting={shift} enableSizing={size} + trigger={['hover']} + isFocusableOnHover > I have a tooltip 😎 diff --git a/packages/web-react/src/components/TooltipModern/stories/TooltipModern.stories.tsx b/packages/web-react/src/components/TooltipModern/stories/TooltipModern.stories.tsx index a2d0dad6a1..9eb4ed137e 100644 --- a/packages/web-react/src/components/TooltipModern/stories/TooltipModern.stories.tsx +++ b/packages/web-react/src/components/TooltipModern/stories/TooltipModern.stories.tsx @@ -45,6 +45,19 @@ const meta: Meta = { defaultValue: { summary: 'bottom' }, }, }, + isFocusableOnHover: { + control: 'boolean', + }, + trigger: { + control: 'select', + options: ['click, hover', 'hover', 'click'], + table: { + defaultValue: { summary: 'click, hover' }, + }, + }, + isOpen: { + control: 'boolean', + }, }, args: { children: ( @@ -53,7 +66,7 @@ const meta: Meta = { point to the center of the trigger. ), - isOpen: true, + isOpen: false, id: 'TooltipModernExample', enableFlipping: true, enableShifting: true, @@ -61,6 +74,7 @@ const meta: Meta = { enableFlippingCrossAxis: true, trigger: ['click', 'hover'], placement: 'bottom', + isFocusableOnHover: false, flipFallbackPlacements: ['bottom', 'left', 'right', 'top'], }, }; @@ -70,7 +84,7 @@ type Story = StoryObj; const TooltipModernWithHooks = (args: SpiritTooltipModernProps) => { const { children, isOpen } = args; - const [isTooltipOpen, setIsTooltipOpen] = useState(true); + const [isTooltipOpen, setIsTooltipOpen] = useState(isOpen); const viewportRef = useRef(null); const contentRef = useRef(null); diff --git a/packages/web-react/src/types/tooltip.ts b/packages/web-react/src/types/tooltip.ts index e766a3ab89..45b2c856f2 100644 --- a/packages/web-react/src/types/tooltip.ts +++ b/packages/web-react/src/types/tooltip.ts @@ -61,5 +61,6 @@ export interface SpiritTooltipModernProps extends TooltipModernProps, ChildrenPr enableSizing?: boolean; flipFallbackAxisSideDirection?: 'none' | 'start' | 'end'; flipFallbackPlacements?: Placement | Placement[]; + isFocusableOnHover?: boolean; trigger?: TooltipTriggerType[]; }