UNPKG

@neo4j-ndl/react

Version:

React implementation of Neo4j Design System

479 lines (374 loc) 16.7 kB
# Tooltip Import: `import { Tooltip } from '@neo4j-ndl/react'` ## Props ### Tooltip | Prop | Type | Required | Default | Description | |------|------|----------|---------|-------------| | `autoUpdateOptions` | `AutoUpdateOptions` | | | | | `children` | `ReactNode` | | | Content displayed inside the tooltip. Wraps `Tooltip.Trigger`, `Tooltip.Content`, and optional parts | | `floatingStrategy` | `'absolute' \| 'fixed'` | | | Positioning strategy of the tooltip. Defaults to `fixed` when inside a dialog, otherwise `absolute` | | `followCursor` | `boolean` | | | Whether the tooltip should follow the mouse cursor. Only applies to 'simple' tooltips with mouse hover. | | `hoverDelay` | `{ open: number; close: number; }` | | | Hover open/close delays in milliseconds | | `isDisabled` | `boolean` | | `false` | Whether the tooltip is disabled | | `isInitialOpen` | `boolean` | | | Whether the tooltip should be open on first render | | `isOpen` | `boolean` | | | Controls the open state . If omitted, the tooltip manages its own state | | `isPortaled` | `boolean` | | | Whether the content is rendered in a portal. | | `onOpenChange` | `((open: boolean, event?: Event, reason?: OpenChangeReason) => void) \| undefined` | | | Callback fired when the open state changes, if omitted, the tooltip manages its own state using internal hooks | | `placement` | `'bottom-end' \| 'bottom-start' \| 'bottom' \| 'left-end' \| 'left-start' \| 'left' \| 'right-end' \| 'right-start' \| 'right' \| 'top-end' \| 'top-start' \| 'top'` | | | Placement of the tooltip content relative to the trigger | | `shouldCloseOnReferenceClick` | `boolean` | | | Whether the tooltip should close when the reference element is clicked. Useful when the trigger moves after click. | | `type` | `'rich' \| 'simple'` | | | Visual style of the tooltip content. `simple` renders plain text, `rich` renders a composable panel @deprecated rich type will be removed in v5, use simple type instead. | ### Tooltip.Actions | Prop | Type | Required | Default | Description | |------|------|----------|---------|-------------| | `children` | `ReactNode` | | | Content displayed inside the actions container | | `ref` | `Ref<HTMLDivElement>` | | | A ref to apply to the root element. | ### Tooltip.Body | Prop | Type | Required | Default | Description | |------|------|----------|---------|-------------| | `children` | `ReactNode` | | | Content displayed inside the body | | `passThroughProps` | `Partial<Omit<TypographyProps & { as?: ElementType<any, keyof IntrinsicElements>; } & BaseProps<ElementType<any, keyof IntrinsicElements>>, "ref">>` | | | Additional props to pass to the Typography component | | `ref` | `Ref<HTMLSpanElement>` | | | A ref to apply to the root element. | ### Tooltip.Content | Prop | Type | Required | Default | Description | |------|------|----------|---------|-------------| | `children` | `ReactNode` | | | Content displayed inside the tooltip | | `ref` | `Ref<HTMLDivElement>` | | | A ref to apply to the root element. | ### Tooltip.Header | Prop | Type | Required | Default | Description | |------|------|----------|---------|-------------| | `children` | `ReactNode` | | | Content displayed inside the header | | `passThroughProps` | `Partial<Omit<TypographyProps & { as?: ElementType<any, keyof IntrinsicElements>; } & BaseProps<ElementType<any, keyof IntrinsicElements>>, "ref">>` | | | @deprecated Use `typographyVariant` instead | | `ref` | `Ref<HTMLSpanElement>` | | | A ref to apply to the root element. | | `typographyVariant` | `'body-large' \| 'body-medium' \| 'body-small' \| 'code' \| 'display' \| 'label' \| 'subheading-large' \| 'subheading-medium' \| 'subheading-small' \| 'title-1' \| 'title-2' \| 'title-3' \| 'title-4'` | | `subheading-medium` | | ### Tooltip.Trigger | Prop | Type | Required | Default | Description | |------|------|----------|---------|-------------| | `children` | `ReactNode` | | | Content displayed inside the trigger element | | `hasButtonWrapper` | `boolean` | | `false` | Whether the trigger element can be any element without an extra button wrapper. The child element will be cloned with the necessary trigger props and merged refs | | `ref` | `Ref<HTMLButtonElement>` | | | A ref to apply to the root element. | ## Accessibility ## Keyboard interactions Behavior depends on the `type` prop. Simple and rich tooltips follow different interaction models. ### Simple tooltips (`type="simple"`) Implements the keyboard interactions defined in the [WAI-ARIA tooltip pattern](https://www.w3.org/WAI/ARIA/apg/patterns/tooltip/). | Key | Description | |-----|-------------| | `Tab` | Moves focus to the trigger; the tooltip opens when the trigger receives focus | | `Shift + Tab` | Moves focus away from the trigger; the tooltip closes when focus leaves | | `Escape` | Closes the tooltip while focus remains on the trigger | Simple tooltips also open on mouse hover and close when the pointer leaves the trigger and tooltip area. ### Rich tooltips (`type="rich"`) Rich tooltips use a light-dismiss dialog interaction model for panels with actions or structured content. It doesn't trap focus, and clicking outside it will immediately close the component. | Key | Description | |-----|-------------| | `Tab` | When closed, moves focus to the trigger. When open, moves focus between focusable elements inside the tooltip | | `Shift + Tab` | When open, moves focus between focusable elements. Tabbing past the last element closes the tooltip and returns focus to the trigger | | `Space` | On the trigger, toggles the tooltip open and closed. When open, activates the focused button | | `Enter` | On the trigger, toggles the tooltip open (same as click) | | `Escape` | Closes the tooltip and returns focus to the trigger | ## WAI-ARIA roles and attributes ### Simple tooltips Implements the [WAI-ARIA tooltip pattern](https://www.w3.org/WAI/ARIA/apg/patterns/tooltip/). - The popup (`Tooltip.Content`) has `role="tooltip"` - When open, the trigger is described via `aria-describedby`, wired by Floating UI to the portaled tooltip element ### Rich tooltips - The popup (`Tooltip.Content`) has `role="dialog"` - The popup is labeled via `aria-labelledby` (auto-wired from `Tooltip.Header`) or `aria-label` ## Implementation guidelines - Rich tooltips must have an accessible name — provide `Tooltip.Header` or set `aria-label` on `Tooltip.Content` via `htmlAttributes`. A console warning is emitted if neither is provided - Use `hasButtonWrapper` on `Tooltip.Trigger` with Needle trigger buttons (`IconButton`, `FilledButton`, etc.) — accessible names are already provided via `description` or visible label text - Do not rely on a simple tooltip as the only source of essential information — provide a persistent accessible name or description on the trigger when the content is required to operate the control - Rich tooltips with `Tooltip.Actions` should use Needle `Button` variants (e.g. `OutlinedButton`, `TextButton`) or `TextLink` for actions so they are keyboard focusable, styled consistently, and operable - Set `isDisabled` when the tooltip would open alongside another overlay (e.g. an open menu), which can create confusing or conflicting information for assistive technology users ### Related WCAG criteria - [1.4.13 Content on Hover or Focus](https://www.w3.org/WAI/WCAG22/Understanding/content-on-hover-or-focus.html) (AA): Simple and rich tooltips can be dismissed with `Escape`; rich tooltips can also be dismissed by activating the trigger again - [2.1.1 Keyboard](https://www.w3.org/WAI/WCAG22/Understanding/keyboard.html) (A): Simple tooltips are available to keyboard users via focus; rich tooltips and their actions are fully operable via keyboard - [4.1.2 Name, Role, Value](https://www.w3.org/WAI/WCAG22/Understanding/name-role-value.html) (A): `role="tooltip"` / `role="dialog"`, accessible name (`aria-labelledby` / `aria-label` for rich tooltips), and accessible description (`aria-describedby` for simple tooltips when open) are set automatically by the component ## Examples ### Default ```tsx import '@neo4j-ndl/base/lib/neo4j-ds-styles.css'; import { Tooltip } from '@neo4j-ndl/react'; const Component = () => { return ( <Tooltip type="simple"> <Tooltip.Trigger>Hover me!</Tooltip.Trigger> <Tooltip.Content>This is a tooltip!</Tooltip.Content> </Tooltip> ); }; export default Component; ``` ### Controlled ```tsx import '@neo4j-ndl/base/lib/neo4j-ds-styles.css'; import { FilledButton, Tooltip } from '@neo4j-ndl/react'; import { useState } from 'react'; const Component = () => { const [isOpen, setIsOpen] = useState(false); return ( <Tooltip type="simple" isOpen={isOpen}> <Tooltip.Trigger hasButtonWrapper> <FilledButton size="medium" onClick={() => setIsOpen((prev) => !prev)}> Click me </FilledButton> </Tooltip.Trigger> <Tooltip.Content>This is a controlled tooltip</Tooltip.Content> </Tooltip> ); }; export default Component; ``` ### Disabled ```tsx import '@neo4j-ndl/base/lib/neo4j-ds-styles.css'; import { Tooltip } from '@neo4j-ndl/react'; const Component = () => { return ( <Tooltip type="simple" isDisabled={true}> <Tooltip.Trigger>Hover me!</Tooltip.Trigger> <Tooltip.Content>This is a tooltip!</Tooltip.Content> </Tooltip> ); }; export default Component; ``` ### Follow Cursor ```tsx import '@neo4j-ndl/base/lib/neo4j-ds-styles.css'; import { Tooltip } from '@neo4j-ndl/react'; const triggerStyle: React.CSSProperties = { alignItems: 'center', backgroundColor: 'var(--theme-color-primary-bg-weak)', borderRadius: '4px', color: 'var(--theme-color-primary-text-inverse)', display: 'flex', height: '300px', justifyContent: 'center', padding: '8px', width: '300px', }; const Component = () => { return ( <div style={{ display: 'flex', gap: '24px' }}> <Tooltip type="simple" followCursor placement="bottom"> <Tooltip.Trigger> <div style={triggerStyle}>Hover me (x-axis)</div> </Tooltip.Trigger> <Tooltip.Content>Follows cursor along x-axis</Tooltip.Content> </Tooltip> <Tooltip type="simple" followCursor placement="right"> <Tooltip.Trigger> <div style={triggerStyle}>Hover me (y-axis)</div> </Tooltip.Trigger> <Tooltip.Content>Follows cursor along y-axis</Tooltip.Content> </Tooltip> </div> ); }; export default Component; ``` ### Hover Delay ```tsx import '@neo4j-ndl/base/lib/neo4j-ds-styles.css'; import { Tooltip } from '@neo4j-ndl/react'; const Component = () => { return ( <Tooltip type="simple" hoverDelay={{ close: 200, open: 200 }}> <Tooltip.Trigger>Hover me!</Tooltip.Trigger> <Tooltip.Content>This is a tooltip!</Tooltip.Content> </Tooltip> ); }; export default Component; ``` ### In Dialog ```tsx import '@neo4j-ndl/base/lib/neo4j-ds-styles.css'; import { Dialog, FilledButton, Tooltip, Typography } from '@neo4j-ndl/react'; import { useState } from 'react'; const Component = () => { const [isDialogOpen, setDialogOpen] = useState(false); const closeDialog = () => setDialogOpen(false); return ( <div className="n-flex n-justify-center"> <FilledButton onClick={() => setDialogOpen(true)}> Open Dialog </FilledButton> <Dialog isOpen={isDialogOpen} onClose={closeDialog}> <Dialog.Header>Header</Dialog.Header> <Dialog.Content> <Tooltip type="simple"> <Tooltip.Trigger> <Typography variant="body-medium">Hover me!</Typography> </Tooltip.Trigger> <Tooltip.Content>This is a long tooltip!</Tooltip.Content> </Tooltip> </Dialog.Content> </Dialog> </div> ); }; export default Component; ``` ### Initial Open ```tsx import '@neo4j-ndl/base/lib/neo4j-ds-styles.css'; import { Tooltip } from '@neo4j-ndl/react'; const Component = () => { return ( <Tooltip type="simple" isInitialOpen={true}> <Tooltip.Trigger>Hover me!</Tooltip.Trigger> <Tooltip.Content>This is a tooltip!</Tooltip.Content> </Tooltip> ); }; export default Component; ``` ### Placements ```tsx import '@neo4j-ndl/base/lib/neo4j-ds-styles.css'; import { FilledButton, Tooltip } from '@neo4j-ndl/react'; const Component = () => { const placements = [ { label: 'Top', placement: 'top' as const }, { label: 'Bottom', placement: 'bottom' as const }, { label: 'Left', placement: 'left' as const }, { label: 'Right', placement: 'right' as const }, { label: 'Top Start', placement: 'top-start' as const }, { label: 'Top End', placement: 'top-end' as const }, { label: 'Bottom Start', placement: 'bottom-start' as const }, { label: 'Bottom End', placement: 'bottom-end' as const }, ]; return ( <div className="n-grid n-grid-cols-4 n-gap-token-16 n-p-token-16 n-place-items-center"> {placements.map(({ placement, label }) => ( <Tooltip key={placement} type="simple" placement={placement}> <Tooltip.Trigger hasButtonWrapper> <FilledButton size="small">{label}</FilledButton> </Tooltip.Trigger> <Tooltip.Content>Tooltip on {label.toLowerCase()}</Tooltip.Content> </Tooltip> ))} </div> ); }; export default Component; ``` ### Rich ```tsx import '@neo4j-ndl/base/lib/neo4j-ds-styles.css'; import { FilledButton, Tooltip } from '@neo4j-ndl/react'; const Component = () => { return ( <Tooltip type="rich"> <Tooltip.Trigger hasButtonWrapper> <FilledButton size="medium">Click me!</FilledButton> </Tooltip.Trigger> <Tooltip.Content> <Tooltip.Header>Header</Tooltip.Header> <Tooltip.Body> This rich tooltip demonstrates all the available subcomponents: Header and Body, without Actions. </Tooltip.Body> </Tooltip.Content> </Tooltip> ); }; export default Component; ``` ### Rich Actions ```tsx import '@neo4j-ndl/base/lib/neo4j-ds-styles.css'; import { FilledButton, OutlinedButton, TextButton, Tooltip, } from '@neo4j-ndl/react'; const Component = () => { return ( <Tooltip type="rich"> <Tooltip.Trigger hasButtonWrapper> <FilledButton size="medium">Click me!</FilledButton> </Tooltip.Trigger> <Tooltip.Content> <Tooltip.Header>Header</Tooltip.Header> <Tooltip.Body> This rich tooltip demonstrates all the available subcomponents: Header, Body, and Actions. </Tooltip.Body> <Tooltip.Actions> <OutlinedButton onClick={() => alert('Primary action')}> Action </OutlinedButton> <TextButton onClick={() => alert('Secondary action')}> Action </TextButton> </Tooltip.Actions> </Tooltip.Content> </Tooltip> ); }; export default Component; ``` ### Rich Link ```tsx import '@neo4j-ndl/base/lib/neo4j-ds-styles.css'; import { FilledButton, TextLink, Tooltip } from '@neo4j-ndl/react'; const Component = () => { return ( <Tooltip type="rich"> <Tooltip.Trigger hasButtonWrapper> <FilledButton size="medium">Click me!</FilledButton> </Tooltip.Trigger> <Tooltip.Content> <Tooltip.Header>Header</Tooltip.Header> <Tooltip.Body> This rich tooltip demonstrates all the available subcomponents: Header, Body, and Actions. </Tooltip.Body> <Tooltip.Actions> <TextLink type="external" href="#"> External link </TextLink> </Tooltip.Actions> </Tooltip.Content> </Tooltip> ); }; export default Component; ``` ### Trigger Button Wrapper ```tsx import '@neo4j-ndl/base/lib/neo4j-ds-styles.css'; import { FilledButton, Tooltip } from '@neo4j-ndl/react'; const Component = () => { return ( <Tooltip type="simple"> <Tooltip.Trigger hasButtonWrapper> <FilledButton size="medium">Click me!</FilledButton> </Tooltip.Trigger> <Tooltip.Content>This is a tooltip!</Tooltip.Content> </Tooltip> ); }; export default Component; ``` ### With Keyboard Display ```tsx import '@neo4j-ndl/base/lib/neo4j-ds-styles.css'; import { Kbd, Tooltip } from '@neo4j-ndl/react'; const Component = () => { return ( <Tooltip type="simple"> <Tooltip.Trigger>Hover me!</Tooltip.Trigger> <Tooltip.Content> This is a keyboard shortcut:{' '} <Kbd modifierKeys={['meta']} keys={['B']} /> </Tooltip.Content> </Tooltip> ); }; export default Component; ```