From 49896258facdbde3151044889d056fa531c802c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arma=C4=9Fan?= Date: Mon, 22 Sep 2025 04:58:42 +0000 Subject: [PATCH 01/14] Add delay functionality to tooltips --- .../Button/IconButton.features.stories.tsx | 6 ++++++ .../TooltipV2/Tooltip.features.stories.tsx | 8 +++++++ packages/react/src/TooltipV2/Tooltip.tsx | 21 ++++++++++++++++--- 3 files changed, 32 insertions(+), 3 deletions(-) diff --git a/packages/react/src/Button/IconButton.features.stories.tsx b/packages/react/src/Button/IconButton.features.stories.tsx index 6c98391d520..edeb31328b9 100644 --- a/packages/react/src/Button/IconButton.features.stories.tsx +++ b/packages/react/src/Button/IconButton.features.stories.tsx @@ -95,3 +95,9 @@ export const KeybindingHintOnDescription = () => ( ) export const KeybindingHint = () => + +export const DelayedTooltip = () => ( + + + +) diff --git a/packages/react/src/TooltipV2/Tooltip.features.stories.tsx b/packages/react/src/TooltipV2/Tooltip.features.stories.tsx index d72956c19eb..e6430bc8396 100644 --- a/packages/react/src/TooltipV2/Tooltip.features.stories.tsx +++ b/packages/react/src/TooltipV2/Tooltip.features.stories.tsx @@ -194,3 +194,11 @@ export const KeybindingHint = () => ( ) + +export const WithDelay = () => ( +
+ + + +
+) diff --git a/packages/react/src/TooltipV2/Tooltip.tsx b/packages/react/src/TooltipV2/Tooltip.tsx index 8e7ed6afca8..37f9d1aac07 100644 --- a/packages/react/src/TooltipV2/Tooltip.tsx +++ b/packages/react/src/TooltipV2/Tooltip.tsx @@ -20,6 +20,11 @@ export type TooltipProps = React.PropsWithChildren< text: string type?: 'label' | 'description' keybindingHint?: KeybindingHintProps['keys'] + /** + * Delay in milliseconds before showing the tooltip + * @default 50 + */ + delay?: number } & SxProp > & React.HTMLAttributes @@ -83,7 +88,17 @@ export const TooltipContext = React.createContext<{tooltipId?: string}>({}) export const Tooltip = React.forwardRef( ( - {direction = 's', text, type = 'description', children, id, className, keybindingHint, ...rest}: TooltipProps, + { + direction = 's', + text, + type = 'description', + children, + id, + className, + keybindingHint, + delay = 50, + ...rest + }: TooltipProps, forwardedRef, ) => { const tooltipId = useId(id) @@ -286,12 +301,12 @@ export const Tooltip = React.forwardRef( onMouseOverCapture: (event: React.MouseEvent) => { // We use a `capture` event to ensure this is called first before // events that might cancel the opening timeout (like `onTouchEnd`) - // show tooltip after mouse has been hovering for at least 50ms + // show tooltip after mouse has been hovering for the specified delay time // (prevent showing tooltip when mouse is just passing through) openTimeoutRef.current = safeSetTimeout(() => { openTooltip() child.props.onMouseEnter?.(event) - }, 50) + }, delay) }, onMouseLeave: (event: React.MouseEvent) => { closeTooltip() From f021444d372170dd5c7f984ed9a7fc6e6956d6d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arma=C4=9Fan?= Date: Wed, 24 Sep 2025 04:45:23 +0000 Subject: [PATCH 02/14] Convert delay prop to boolean and when delay exists, delay 600ms else 50ms --- .../react/src/Button/IconButton.features.stories.tsx | 2 +- .../react/src/TooltipV2/Tooltip.features.stories.tsx | 2 +- packages/react/src/TooltipV2/Tooltip.tsx | 10 +++++----- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/react/src/Button/IconButton.features.stories.tsx b/packages/react/src/Button/IconButton.features.stories.tsx index edeb31328b9..49dfaa3b4c5 100644 --- a/packages/react/src/Button/IconButton.features.stories.tsx +++ b/packages/react/src/Button/IconButton.features.stories.tsx @@ -97,7 +97,7 @@ export const KeybindingHintOnDescription = () => ( export const KeybindingHint = () => export const DelayedTooltip = () => ( - + ) diff --git a/packages/react/src/TooltipV2/Tooltip.features.stories.tsx b/packages/react/src/TooltipV2/Tooltip.features.stories.tsx index e6430bc8396..d863a3e17bb 100644 --- a/packages/react/src/TooltipV2/Tooltip.features.stories.tsx +++ b/packages/react/src/TooltipV2/Tooltip.features.stories.tsx @@ -197,7 +197,7 @@ export const KeybindingHint = () => ( export const WithDelay = () => (
- +
diff --git a/packages/react/src/TooltipV2/Tooltip.tsx b/packages/react/src/TooltipV2/Tooltip.tsx index 37f9d1aac07..1531540f34c 100644 --- a/packages/react/src/TooltipV2/Tooltip.tsx +++ b/packages/react/src/TooltipV2/Tooltip.tsx @@ -21,10 +21,9 @@ export type TooltipProps = React.PropsWithChildren< type?: 'label' | 'description' keybindingHint?: KeybindingHintProps['keys'] /** - * Delay in milliseconds before showing the tooltip - * @default 50 + * Additional 600ms delay before showing the tooltip. Default is 50ms. */ - delay?: number + delay?: boolean } & SxProp > & React.HTMLAttributes @@ -96,7 +95,7 @@ export const Tooltip = React.forwardRef( id, className, keybindingHint, - delay = 50, + delay = false, ...rest }: TooltipProps, forwardedRef, @@ -299,6 +298,7 @@ export const Tooltip = React.forwardRef( child.props.onFocus?.(event) }, onMouseOverCapture: (event: React.MouseEvent) => { + const delayTime = delay ? 600 : 50 // We use a `capture` event to ensure this is called first before // events that might cancel the opening timeout (like `onTouchEnd`) // show tooltip after mouse has been hovering for the specified delay time @@ -306,7 +306,7 @@ export const Tooltip = React.forwardRef( openTimeoutRef.current = safeSetTimeout(() => { openTooltip() child.props.onMouseEnter?.(event) - }, delay) + }, delayTime) }, onMouseLeave: (event: React.MouseEvent) => { closeTooltip() From cc268fede88896d4bb0c87a475d5cfe8c5b3d59a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arma=C4=9Fan?= Date: Wed, 24 Sep 2025 08:55:34 +0000 Subject: [PATCH 03/14] Revert "Convert delay prop to boolean and when delay exists, delay 600ms else 50ms" This reverts commit f021444d372170dd5c7f984ed9a7fc6e6956d6d9. --- .../react/src/Button/IconButton.features.stories.tsx | 2 +- .../react/src/TooltipV2/Tooltip.features.stories.tsx | 2 +- packages/react/src/TooltipV2/Tooltip.tsx | 10 +++++----- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/react/src/Button/IconButton.features.stories.tsx b/packages/react/src/Button/IconButton.features.stories.tsx index 49dfaa3b4c5..edeb31328b9 100644 --- a/packages/react/src/Button/IconButton.features.stories.tsx +++ b/packages/react/src/Button/IconButton.features.stories.tsx @@ -97,7 +97,7 @@ export const KeybindingHintOnDescription = () => ( export const KeybindingHint = () => export const DelayedTooltip = () => ( - + ) diff --git a/packages/react/src/TooltipV2/Tooltip.features.stories.tsx b/packages/react/src/TooltipV2/Tooltip.features.stories.tsx index d863a3e17bb..e6430bc8396 100644 --- a/packages/react/src/TooltipV2/Tooltip.features.stories.tsx +++ b/packages/react/src/TooltipV2/Tooltip.features.stories.tsx @@ -197,7 +197,7 @@ export const KeybindingHint = () => ( export const WithDelay = () => (
- +
diff --git a/packages/react/src/TooltipV2/Tooltip.tsx b/packages/react/src/TooltipV2/Tooltip.tsx index 1531540f34c..37f9d1aac07 100644 --- a/packages/react/src/TooltipV2/Tooltip.tsx +++ b/packages/react/src/TooltipV2/Tooltip.tsx @@ -21,9 +21,10 @@ export type TooltipProps = React.PropsWithChildren< type?: 'label' | 'description' keybindingHint?: KeybindingHintProps['keys'] /** - * Additional 600ms delay before showing the tooltip. Default is 50ms. + * Delay in milliseconds before showing the tooltip + * @default 50 */ - delay?: boolean + delay?: number } & SxProp > & React.HTMLAttributes @@ -95,7 +96,7 @@ export const Tooltip = React.forwardRef( id, className, keybindingHint, - delay = false, + delay = 50, ...rest }: TooltipProps, forwardedRef, @@ -298,7 +299,6 @@ export const Tooltip = React.forwardRef( child.props.onFocus?.(event) }, onMouseOverCapture: (event: React.MouseEvent) => { - const delayTime = delay ? 600 : 50 // We use a `capture` event to ensure this is called first before // events that might cancel the opening timeout (like `onTouchEnd`) // show tooltip after mouse has been hovering for the specified delay time @@ -306,7 +306,7 @@ export const Tooltip = React.forwardRef( openTimeoutRef.current = safeSetTimeout(() => { openTooltip() child.props.onMouseEnter?.(event) - }, delayTime) + }, delay) }, onMouseLeave: (event: React.MouseEvent) => { closeTooltip() From 37cdedb43f9aa55741af4d9acc5a2d6be02ee5fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arma=C4=9Fan?= Date: Wed, 24 Sep 2025 09:06:06 +0000 Subject: [PATCH 04/14] Just for test --- .../TooltipV2/Tooltip.features.stories.tsx | 69 ++++++++++++++++++- 1 file changed, 68 insertions(+), 1 deletion(-) diff --git a/packages/react/src/TooltipV2/Tooltip.features.stories.tsx b/packages/react/src/TooltipV2/Tooltip.features.stories.tsx index e6430bc8396..df6c6e4c268 100644 --- a/packages/react/src/TooltipV2/Tooltip.features.stories.tsx +++ b/packages/react/src/TooltipV2/Tooltip.features.stories.tsx @@ -1,7 +1,16 @@ import {IconButton, Button, Link, ActionMenu, ActionList, VisuallyHidden} from '..' import Octicon from '../Octicon' import {Tooltip} from './Tooltip' -import {SearchIcon, BookIcon, CheckIcon, TriangleDownIcon, GitBranchIcon, InfoIcon} from '@primer/octicons-react' +import { + SearchIcon, + BookIcon, + CheckIcon, + TriangleDownIcon, + GitBranchIcon, + InfoIcon, + StarIcon, + HeartIcon, +} from '@primer/octicons-react' import classes from './Tooltip.features.stories.module.css' export default { @@ -202,3 +211,61 @@ export const WithDelay = () => (
) + +export const OcticonPicker = { + args: { + delay: 300, + }, + argTypes: { + delay: { + control: { + type: 'number', + min: 0, + max: 2000, + step: 50, + }, + description: 'Delay in milliseconds before showing the tooltip', + }, + }, + render: ({delay}: {delay: number}) => { + const octicons = [ + {icon: SearchIcon, name: 'Search'}, + {icon: BookIcon, name: 'Book'}, + {icon: CheckIcon, name: 'Check'}, + {icon: StarIcon, name: 'Star'}, + {icon: HeartIcon, name: 'Heart'}, + {icon: SearchIcon, name: 'Search'}, + {icon: BookIcon, name: 'Book'}, + {icon: CheckIcon, name: 'Check'}, + {icon: StarIcon, name: 'Star'}, + {icon: HeartIcon, name: 'Heart'}, + {icon: SearchIcon, name: 'Search'}, + {icon: BookIcon, name: 'Book'}, + {icon: CheckIcon, name: 'Check'}, + {icon: StarIcon, name: 'Star'}, + {icon: HeartIcon, name: 'Heart'}, + ] + + return ( +
+ {octicons.map((octicon, index) => ( + + console.log(`Selected ${octicon.name}`)} + icon={octicon.icon} + /> + + ))} +
+ ) + }, +} From d83f903f9946ca6f16282dd36acf538a22da40b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arma=C4=9Fan?= Date: Wed, 24 Sep 2025 09:10:00 +0000 Subject: [PATCH 05/14] add direction as control for test --- .../src/TooltipV2/Tooltip.features.stories.tsx | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/packages/react/src/TooltipV2/Tooltip.features.stories.tsx b/packages/react/src/TooltipV2/Tooltip.features.stories.tsx index df6c6e4c268..abbb29d20d3 100644 --- a/packages/react/src/TooltipV2/Tooltip.features.stories.tsx +++ b/packages/react/src/TooltipV2/Tooltip.features.stories.tsx @@ -1,6 +1,6 @@ import {IconButton, Button, Link, ActionMenu, ActionList, VisuallyHidden} from '..' import Octicon from '../Octicon' -import {Tooltip} from './Tooltip' +import {Tooltip, type TooltipDirection} from './Tooltip' import { SearchIcon, BookIcon, @@ -226,8 +226,15 @@ export const OcticonPicker = { }, description: 'Delay in milliseconds before showing the tooltip', }, + direction: { + control: { + type: 'select', + }, + options: ['nw', 'n', 'ne', 'e', 'se', 's', 'sw', 'w'], + description: 'Direction of the tooltip', + }, }, - render: ({delay}: {delay: number}) => { + render: ({delay, direction}: {delay: number; direction: TooltipDirection}) => { const octicons = [ {icon: SearchIcon, name: 'Search'}, {icon: BookIcon, name: 'Book'}, @@ -257,9 +264,10 @@ export const OcticonPicker = { }} > {octicons.map((octicon, index) => ( - + console.log(`Selected ${octicon.name}`)} icon={octicon.icon} /> From 05fa73edfe355c685b80328f78dd24b55b26c44e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arma=C4=9Fan?= Date: Thu, 25 Sep 2025 03:30:15 +0000 Subject: [PATCH 06/14] Add instant, medium, long options for tooltip delay --- .../Button/IconButton.features.stories.tsx | 5 +- .../TooltipV2/Tooltip.features.stories.tsx | 79 +++---------------- packages/react/src/TooltipV2/Tooltip.tsx | 16 +++- 3 files changed, 25 insertions(+), 75 deletions(-) diff --git a/packages/react/src/Button/IconButton.features.stories.tsx b/packages/react/src/Button/IconButton.features.stories.tsx index edeb31328b9..674a3d7df2b 100644 --- a/packages/react/src/Button/IconButton.features.stories.tsx +++ b/packages/react/src/Button/IconButton.features.stories.tsx @@ -96,8 +96,9 @@ export const KeybindingHintOnDescription = () => ( export const KeybindingHint = () => -export const DelayedTooltip = () => ( - +export const LongDelayedTooltip = () => ( + // Ideal for cases where we don't want to show the tooltip immediately — for example, when the user is just passing over the element. + ) diff --git a/packages/react/src/TooltipV2/Tooltip.features.stories.tsx b/packages/react/src/TooltipV2/Tooltip.features.stories.tsx index abbb29d20d3..d625c91e81b 100644 --- a/packages/react/src/TooltipV2/Tooltip.features.stories.tsx +++ b/packages/react/src/TooltipV2/Tooltip.features.stories.tsx @@ -1,6 +1,6 @@ import {IconButton, Button, Link, ActionMenu, ActionList, VisuallyHidden} from '..' import Octicon from '../Octicon' -import {Tooltip, type TooltipDirection} from './Tooltip' +import {Tooltip} from './Tooltip' import { SearchIcon, BookIcon, @@ -8,7 +8,6 @@ import { TriangleDownIcon, GitBranchIcon, InfoIcon, - StarIcon, HeartIcon, } from '@primer/octicons-react' import classes from './Tooltip.features.stories.module.css' @@ -204,76 +203,18 @@ export const KeybindingHint = () => ( ) -export const WithDelay = () => ( +export const WithMediumDelay = () => (
- +
) -export const OcticonPicker = { - args: { - delay: 300, - }, - argTypes: { - delay: { - control: { - type: 'number', - min: 0, - max: 2000, - step: 50, - }, - description: 'Delay in milliseconds before showing the tooltip', - }, - direction: { - control: { - type: 'select', - }, - options: ['nw', 'n', 'ne', 'e', 'se', 's', 'sw', 'w'], - description: 'Direction of the tooltip', - }, - }, - render: ({delay, direction}: {delay: number; direction: TooltipDirection}) => { - const octicons = [ - {icon: SearchIcon, name: 'Search'}, - {icon: BookIcon, name: 'Book'}, - {icon: CheckIcon, name: 'Check'}, - {icon: StarIcon, name: 'Star'}, - {icon: HeartIcon, name: 'Heart'}, - {icon: SearchIcon, name: 'Search'}, - {icon: BookIcon, name: 'Book'}, - {icon: CheckIcon, name: 'Check'}, - {icon: StarIcon, name: 'Star'}, - {icon: HeartIcon, name: 'Heart'}, - {icon: SearchIcon, name: 'Search'}, - {icon: BookIcon, name: 'Book'}, - {icon: CheckIcon, name: 'Check'}, - {icon: StarIcon, name: 'Star'}, - {icon: HeartIcon, name: 'Heart'}, - ] - - return ( -
- {octicons.map((octicon, index) => ( - - console.log(`Selected ${octicon.name}`)} - icon={octicon.icon} - /> - - ))} -
- ) - }, -} +export const WithLongDelay = () => ( +
+ + + +
+) diff --git a/packages/react/src/TooltipV2/Tooltip.tsx b/packages/react/src/TooltipV2/Tooltip.tsx index 37f9d1aac07..d7114c4cb28 100644 --- a/packages/react/src/TooltipV2/Tooltip.tsx +++ b/packages/react/src/TooltipV2/Tooltip.tsx @@ -22,9 +22,11 @@ export type TooltipProps = React.PropsWithChildren< keybindingHint?: KeybindingHintProps['keys'] /** * Delay in milliseconds before showing the tooltip - * @default 50 + * @default instant 50ms + * medium 400ms + * long 1200ms */ - delay?: number + delay?: 'instant' | 'medium' | 'long' } & SxProp > & React.HTMLAttributes @@ -96,7 +98,7 @@ export const Tooltip = React.forwardRef( id, className, keybindingHint, - delay = 50, + delay = 'instant', ...rest }: TooltipProps, forwardedRef, @@ -299,6 +301,12 @@ export const Tooltip = React.forwardRef( child.props.onFocus?.(event) }, onMouseOverCapture: (event: React.MouseEvent) => { + const delayTimeMap = { + instant: 50, + medium: 400, + long: 1200, + } + const delayTime = delayTimeMap[delay] || 50 // We use a `capture` event to ensure this is called first before // events that might cancel the opening timeout (like `onTouchEnd`) // show tooltip after mouse has been hovering for the specified delay time @@ -306,7 +314,7 @@ export const Tooltip = React.forwardRef( openTimeoutRef.current = safeSetTimeout(() => { openTooltip() child.props.onMouseEnter?.(event) - }, delay) + }, delayTime) }, onMouseLeave: (event: React.MouseEvent) => { closeTooltip() From 172af1c204dc2ac3f9ea482f5ad7fb378864a0a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arma=C4=9Fan?= Date: Thu, 25 Sep 2025 03:39:24 +0000 Subject: [PATCH 07/14] take the variable outside of the function --- packages/react/src/TooltipV2/Tooltip.tsx | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/packages/react/src/TooltipV2/Tooltip.tsx b/packages/react/src/TooltipV2/Tooltip.tsx index d7114c4cb28..68dcce1e3b0 100644 --- a/packages/react/src/TooltipV2/Tooltip.tsx +++ b/packages/react/src/TooltipV2/Tooltip.tsx @@ -80,6 +80,13 @@ const interactiveElements = [ 'textarea', ] +// Map delay prop to actual time in ms +const delayTimeMap = { + instant: 50, + medium: 400, + long: 1200, +} + const isInteractive = (element: HTMLElement) => { return ( interactiveElements.some(selector => element.matches(selector)) || @@ -301,11 +308,6 @@ export const Tooltip = React.forwardRef( child.props.onFocus?.(event) }, onMouseOverCapture: (event: React.MouseEvent) => { - const delayTimeMap = { - instant: 50, - medium: 400, - long: 1200, - } const delayTime = delayTimeMap[delay] || 50 // We use a `capture` event to ensure this is called first before // events that might cancel the opening timeout (like `onTouchEnd`) From ae77ce5bd1231a81bd5de71aab4555c1d6465b87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arma=C4=9Fan?= Date: Thu, 25 Sep 2025 11:41:53 +0800 Subject: [PATCH 08/14] Change tooltip delay from patch to minor Tooltip now supports delay functionality with options for instant, medium, and long delays. --- .changeset/famous-jobs-applaud.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/famous-jobs-applaud.md diff --git a/.changeset/famous-jobs-applaud.md b/.changeset/famous-jobs-applaud.md new file mode 100644 index 00000000000..5b3ea4feaf7 --- /dev/null +++ b/.changeset/famous-jobs-applaud.md @@ -0,0 +1,5 @@ +--- +"@primer/react": minor +--- + +Tooltip: Add delay functionality to tooltips with the options of `instant` (default), `medium`, `long` From 51d5ba524b591b0289001062120504357e649464 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arma=C4=9Fan?= Date: Thu, 25 Sep 2025 11:58:19 +0800 Subject: [PATCH 09/14] Update packages/react/src/TooltipV2/Tooltip.features.stories.tsx Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- packages/react/src/TooltipV2/Tooltip.features.stories.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react/src/TooltipV2/Tooltip.features.stories.tsx b/packages/react/src/TooltipV2/Tooltip.features.stories.tsx index d625c91e81b..a8f1123a6e6 100644 --- a/packages/react/src/TooltipV2/Tooltip.features.stories.tsx +++ b/packages/react/src/TooltipV2/Tooltip.features.stories.tsx @@ -205,7 +205,7 @@ export const KeybindingHint = () => ( export const WithMediumDelay = () => (
- +
From 52f36a8c28c061ec63382892f55474d9353d8bee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arma=C4=9Fan?= Date: Fri, 26 Sep 2025 04:24:24 +0000 Subject: [PATCH 10/14] replace instant with medium. Add some comments for context --- .../TooltipV2/Tooltip.examples.stories.tsx | 50 ++++++++++++++++++- packages/react/src/TooltipV2/Tooltip.tsx | 13 ++--- 2 files changed, 56 insertions(+), 7 deletions(-) diff --git a/packages/react/src/TooltipV2/Tooltip.examples.stories.tsx b/packages/react/src/TooltipV2/Tooltip.examples.stories.tsx index 37f0e359a1f..287c8a53c98 100644 --- a/packages/react/src/TooltipV2/Tooltip.examples.stories.tsx +++ b/packages/react/src/TooltipV2/Tooltip.examples.stories.tsx @@ -3,7 +3,17 @@ import {Button, IconButton, Breadcrumbs, ActionMenu, ActionList} from '..' import {PageHeader} from '../PageHeader' import {Tooltip} from './Tooltip' import {Dialog} from '../experimental' -import {GitBranchIcon, KebabHorizontalIcon, TriangleDownIcon, CheckIcon, XIcon} from '@primer/octicons-react' +import { + GitBranchIcon, + KebabHorizontalIcon, + TriangleDownIcon, + CheckIcon, + XIcon, + HeartIcon, + BookIcon, + StarIcon, + SearchIcon, +} from '@primer/octicons-react' import {default as VisuallyHidden} from '../_VisuallyHidden' export default { @@ -185,3 +195,41 @@ export const DialogTrigger = () => { ) } + +export const OcticonPicker = () => { + const octicons = [ + {icon: SearchIcon, name: 'Search'}, + {icon: BookIcon, name: 'Book'}, + {icon: CheckIcon, name: 'Check'}, + {icon: StarIcon, name: 'Star'}, + {icon: HeartIcon, name: 'Heart'}, + {icon: SearchIcon, name: 'Search'}, + {icon: BookIcon, name: 'Book'}, + {icon: CheckIcon, name: 'Check'}, + {icon: StarIcon, name: 'Star'}, + {icon: HeartIcon, name: 'Heart'}, + {icon: SearchIcon, name: 'Search'}, + {icon: BookIcon, name: 'Book'}, + {icon: CheckIcon, name: 'Check'}, + {icon: StarIcon, name: 'Star'}, + {icon: HeartIcon, name: 'Heart'}, + ] + + return ( +
+ {octicons.map((octicon, index) => ( + + + + ))} +
+ ) +} diff --git a/packages/react/src/TooltipV2/Tooltip.tsx b/packages/react/src/TooltipV2/Tooltip.tsx index 68dcce1e3b0..45ffd71dd61 100644 --- a/packages/react/src/TooltipV2/Tooltip.tsx +++ b/packages/react/src/TooltipV2/Tooltip.tsx @@ -22,11 +22,11 @@ export type TooltipProps = React.PropsWithChildren< keybindingHint?: KeybindingHintProps['keys'] /** * Delay in milliseconds before showing the tooltip - * @default instant 50ms - * medium 400ms - * long 1200ms + * @default short (50ms) + * medium (400ms) + * long (1200ms) */ - delay?: 'instant' | 'medium' | 'long' + delay?: 'short' | 'medium' | 'long' } & SxProp > & React.HTMLAttributes @@ -81,8 +81,9 @@ const interactiveElements = [ ] // Map delay prop to actual time in ms +// For context on delay times, see https://github.com/github/primer/issues/3313#issuecomment-3336696699 const delayTimeMap = { - instant: 50, + short: 50, medium: 400, long: 1200, } @@ -105,7 +106,7 @@ export const Tooltip = React.forwardRef( id, className, keybindingHint, - delay = 'instant', + delay = 'short', ...rest }: TooltipProps, forwardedRef, From f11d231aa29c8795b41f5100e7bb983b8bca25bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arma=C4=9Fan?= Date: Tue, 30 Sep 2025 05:07:07 +0000 Subject: [PATCH 11/14] Do not show tooltip if the mouse is laready moved out --- packages/react/src/TooltipV2/Tooltip.examples.stories.tsx | 2 +- packages/react/src/TooltipV2/Tooltip.tsx | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/react/src/TooltipV2/Tooltip.examples.stories.tsx b/packages/react/src/TooltipV2/Tooltip.examples.stories.tsx index 287c8a53c98..05e58ffc3a2 100644 --- a/packages/react/src/TooltipV2/Tooltip.examples.stories.tsx +++ b/packages/react/src/TooltipV2/Tooltip.examples.stories.tsx @@ -226,7 +226,7 @@ export const OcticonPicker = () => { }} > {octicons.map((octicon, index) => ( - + ))} diff --git a/packages/react/src/TooltipV2/Tooltip.tsx b/packages/react/src/TooltipV2/Tooltip.tsx index 45ffd71dd61..d090374dfcc 100644 --- a/packages/react/src/TooltipV2/Tooltip.tsx +++ b/packages/react/src/TooltipV2/Tooltip.tsx @@ -315,6 +315,8 @@ export const Tooltip = React.forwardRef( // show tooltip after mouse has been hovering for the specified delay time // (prevent showing tooltip when mouse is just passing through) openTimeoutRef.current = safeSetTimeout(() => { + // if the mouse is already moved out, do not show the tooltip + if (!openTimeoutRef.current) return openTooltip() child.props.onMouseEnter?.(event) }, delayTime) From ec9d0534ee2e642ea4c8020f3f37a291aba24bfe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arma=C4=9Fan?= Date: Wed, 1 Oct 2025 03:22:28 +0000 Subject: [PATCH 12/14] use default description type --- packages/react/src/TooltipV2/Tooltip.examples.stories.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react/src/TooltipV2/Tooltip.examples.stories.tsx b/packages/react/src/TooltipV2/Tooltip.examples.stories.tsx index 05e58ffc3a2..0dfc4f896a3 100644 --- a/packages/react/src/TooltipV2/Tooltip.examples.stories.tsx +++ b/packages/react/src/TooltipV2/Tooltip.examples.stories.tsx @@ -226,7 +226,7 @@ export const OcticonPicker = () => { }} > {octicons.map((octicon, index) => ( - + ))} From 9d5bce049d00f6f4670861f50bb8a312af1452bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arma=C4=9Fan?= Date: Wed, 1 Oct 2025 03:24:49 +0000 Subject: [PATCH 13/14] prettier --- packages/react/src/TooltipV2/Tooltip.tsx | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/react/src/TooltipV2/Tooltip.tsx b/packages/react/src/TooltipV2/Tooltip.tsx index 6ed0464be59..a291ecc9e10 100644 --- a/packages/react/src/TooltipV2/Tooltip.tsx +++ b/packages/react/src/TooltipV2/Tooltip.tsx @@ -17,13 +17,13 @@ export type TooltipProps = React.PropsWithChildren<{ text: string type?: 'label' | 'description' keybindingHint?: KeybindingHintProps['keys'] - /** - * Delay in milliseconds before showing the tooltip - * @default short (50ms) - * medium (400ms) - * long (1200ms) - */ - delay?: 'short' | 'medium' | 'long' + /** + * Delay in milliseconds before showing the tooltip + * @default short (50ms) + * medium (400ms) + * long (1200ms) + */ + delay?: 'short' | 'medium' | 'long' }> & React.HTMLAttributes From c2354689b86bcda50903fa57a430dafb1dfc0348 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arma=C4=9Fan?= Date: Wed, 1 Oct 2025 03:43:23 +0000 Subject: [PATCH 14/14] convert octicon picker to emoji picker --- .../TooltipV2/Tooltip.examples.stories.tsx | 72 +++++++++++-------- 1 file changed, 41 insertions(+), 31 deletions(-) diff --git a/packages/react/src/TooltipV2/Tooltip.examples.stories.tsx b/packages/react/src/TooltipV2/Tooltip.examples.stories.tsx index 0dfc4f896a3..e6e55e43804 100644 --- a/packages/react/src/TooltipV2/Tooltip.examples.stories.tsx +++ b/packages/react/src/TooltipV2/Tooltip.examples.stories.tsx @@ -3,17 +3,7 @@ import {Button, IconButton, Breadcrumbs, ActionMenu, ActionList} from '..' import {PageHeader} from '../PageHeader' import {Tooltip} from './Tooltip' import {Dialog} from '../experimental' -import { - GitBranchIcon, - KebabHorizontalIcon, - TriangleDownIcon, - CheckIcon, - XIcon, - HeartIcon, - BookIcon, - StarIcon, - SearchIcon, -} from '@primer/octicons-react' +import {GitBranchIcon, KebabHorizontalIcon, TriangleDownIcon, CheckIcon, XIcon} from '@primer/octicons-react' import {default as VisuallyHidden} from '../_VisuallyHidden' export default { @@ -196,23 +186,28 @@ export const DialogTrigger = () => { ) } -export const OcticonPicker = () => { - const octicons = [ - {icon: SearchIcon, name: 'Search'}, - {icon: BookIcon, name: 'Book'}, - {icon: CheckIcon, name: 'Check'}, - {icon: StarIcon, name: 'Star'}, - {icon: HeartIcon, name: 'Heart'}, - {icon: SearchIcon, name: 'Search'}, - {icon: BookIcon, name: 'Book'}, - {icon: CheckIcon, name: 'Check'}, - {icon: StarIcon, name: 'Star'}, - {icon: HeartIcon, name: 'Heart'}, - {icon: SearchIcon, name: 'Search'}, - {icon: BookIcon, name: 'Book'}, - {icon: CheckIcon, name: 'Check'}, - {icon: StarIcon, name: 'Star'}, - {icon: HeartIcon, name: 'Heart'}, +export const EmojiPicker = () => { + // This example demonstrates a grid of emojis/icons with tooltips that appear after a long delay. + // This pattern is used in places like emoji reactions on comments and the icon picker in the issues dashboard's saved views on GitHub. + // The delay improves UX by preventing distraction when users move their cursor across multiple emojis/icons, + // especially since these icons are generally familiar and don't require immediate explanation. + + const emojis = [ + {emoji: '😀', name: 'Grinning Face'}, + {emoji: '😍', name: 'Heart Eyes'}, + {emoji: '🎉', name: 'Party Popper'}, + {emoji: '👍', name: 'Thumbs Up'}, + {emoji: '❤️', name: 'Red Heart'}, + {emoji: '🔥', name: 'Fire'}, + {emoji: '💯', name: 'Hundred Points'}, + {emoji: '🚀', name: 'Rocket'}, + {emoji: '⭐', name: 'Star'}, + {emoji: '🎯', name: 'Direct Hit'}, + {emoji: '💡', name: 'Light Bulb'}, + {emoji: '🌟', name: 'Glowing Star'}, + {emoji: '🎊', name: 'Confetti Ball'}, + {emoji: '✨', name: 'Sparkles'}, + {emoji: '🌈', name: 'Rainbow'}, ] return ( @@ -225,9 +220,24 @@ export const OcticonPicker = () => { padding: '16px', }} > - {octicons.map((octicon, index) => ( - - + {emojis.map((emojiItem, index) => ( + + ))}