-
Notifications
You must be signed in to change notification settings - Fork 668
IconButton: introduce tooltips on icon buttons behind the unsafeDisableTooltip prop for an incremental rollout
#4224
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 21 commits
8a507be
3169fe4
71c9b6f
d6f26f1
7ca980a
bb3c92a
2c75e8c
b30e480
841c517
20a0c4f
ed6759d
8c5a010
9604383
6841b54
ce78708
12132a2
480b6aa
290e33e
f8ad63a
7be1721
c20bb37
11e60b4
8c48b2f
bb428b7
579cab5
b3f4f26
c990fc9
2a8d931
414c662
8ba386f
5b5fa94
a44ab67
a4cef3b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| --- | ||
| '@primer/react': minor | ||
| --- | ||
|
|
||
| IconButton: introduce tooltips on icon buttons by default with an option to disable with `disableTooltip` prop when necessary. | ||
|
|
||
| This change is considered potentially breaking due to introducing a new behaviour by default. Please upgrade with caution. |
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (not blocking) Ooh, we might want to bring Only show tooltip on :focus-visible, not :focus #4092 before we start enabling them in dotcom 😢
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I pushed a commit for this in this PR (copied your code) - let me know if you have any additional feedback 🙌🏻 Also do you have any recommendation about how to resolve that
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. sigh, looks like jsdom doesn't support the selector yet 🤷 Added a commit to fallback to opening the tooltip in that scenario: c20bb37 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,10 @@ | ||
| import {HeartIcon} from '@primer/octicons-react' | ||
| import {HeartIcon, InboxIcon, ChevronDownIcon} from '@primer/octicons-react' | ||
| import React from 'react' | ||
| import {IconButton} from '.' | ||
| import {ActionMenu} from '../ActionMenu' | ||
| import {ActionList} from '../ActionList' | ||
| import {Tooltip} from '../TooltipV2' | ||
| import {default as TooltipV1} from '../Tooltip' | ||
|
|
||
| export default { | ||
| title: 'Components/IconButton/Features', | ||
|
|
@@ -19,3 +23,53 @@ export const Small = () => <IconButton size="small" icon={HeartIcon} aria-label= | |
| export const Medium = () => <IconButton size="medium" icon={HeartIcon} aria-label="Favorite" /> | ||
|
|
||
| export const Large = () => <IconButton size="large" icon={HeartIcon} aria-label="Favorite" /> | ||
|
|
||
| export const DisabledTooltip = () => ( | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I am thinking to remove this story after the PR is reviewed - not to encourage this use case as it is only here as a feature flag.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (non blocking) Good idea to remove it! |
||
| <IconButton disableTooltip={true} icon={HeartIcon} variant="primary" aria-label="Favorite" /> | ||
| ) | ||
|
|
||
| export const WithDescription = () => ( | ||
| <IconButton icon={InboxIcon} aria-label="Notifications" description="You have no unread notifications." /> | ||
| ) | ||
|
|
||
| export const ExternalTooltip = () => ( | ||
| <Tooltip text="this is a supportive description for icon button" direction="se"> | ||
| <IconButton icon={HeartIcon} aria-label="HeartIcon" /> | ||
| </Tooltip> | ||
| ) | ||
|
|
||
| export const ExternalTooltipVersion1 = () => ( | ||
| <TooltipV1 text="this is a supportive description for icon button" direction="se"> | ||
| <IconButton icon={HeartIcon} aria-label="HeartIcon" /> | ||
| </TooltipV1> | ||
| ) | ||
|
|
||
| export const AsAMenuAnchor = () => ( | ||
| <ActionMenu> | ||
| <ActionMenu.Anchor> | ||
| <IconButton icon={ChevronDownIcon} aria-label="Something" /> | ||
| </ActionMenu.Anchor> | ||
|
|
||
| <ActionMenu.Overlay width="medium"> | ||
| <ActionList> | ||
| <ActionList.Item onSelect={() => alert('Copy link clicked')}> | ||
| Copy link | ||
| <ActionList.TrailingVisual>⌘C</ActionList.TrailingVisual> | ||
| </ActionList.Item> | ||
| <ActionList.Item onSelect={() => alert('Quote reply clicked')}> | ||
| Quote reply | ||
| <ActionList.TrailingVisual>⌘Q</ActionList.TrailingVisual> | ||
| </ActionList.Item> | ||
| <ActionList.Item onSelect={() => alert('Edit comment clicked')}> | ||
| Edit comment | ||
| <ActionList.TrailingVisual>⌘E</ActionList.TrailingVisual> | ||
| </ActionList.Item> | ||
| <ActionList.Divider /> | ||
| <ActionList.Item variant="danger" onSelect={() => alert('Delete file clicked')}> | ||
| Delete file | ||
| <ActionList.TrailingVisual>⌘D</ActionList.TrailingVisual> | ||
| </ActionList.Item> | ||
| </ActionList> | ||
| </ActionMenu.Overlay> | ||
| </ActionMenu> | ||
| ) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,20 +4,75 @@ import type {ForwardRefComponent as PolymorphicForwardRefComponent} from '../uti | |
| import {ButtonBase} from './ButtonBase' | ||
| import {defaultSxProp} from '../utils/defaultSxProp' | ||
| import {generateCustomSxProp} from './Button' | ||
| import {TooltipContext, Tooltip} from '../TooltipV2/Tooltip' | ||
| import {TooltipContext as TooltipContextV1} from '../Tooltip/Tooltip' | ||
|
|
||
| const IconButton = forwardRef(({sx: sxProp = defaultSxProp, icon: Icon, ...props}, forwardedRef): JSX.Element => { | ||
| let sxStyles = sxProp | ||
| // grap the button props that have associated data attributes in the styles | ||
| const {size} = props | ||
| const IconButton = forwardRef( | ||
| ( | ||
| { | ||
| sx: sxProp = defaultSxProp, | ||
| icon: Icon, | ||
| 'aria-label': ariaLabel, | ||
| description, | ||
| disabled, | ||
| tooltipDirection, | ||
| // This is planned to be a temporary prop until the default tooltip on icon buttons are fully rolled out. | ||
| disableTooltip = false, | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (soft blocking) scarier name please! (https://github.com/primer/react/pull/4224/files#r1489176900) |
||
| ...props | ||
| }, | ||
| forwardedRef, | ||
| ): JSX.Element => { | ||
| let sxStyles = sxProp | ||
| // grap the button props that have associated data attributes in the styles | ||
| const {size} = props | ||
|
|
||
| if (sxProp !== null && Object.keys(sxProp).length > 0) { | ||
| sxStyles = generateCustomSxProp({size}, sxProp) | ||
| } | ||
| if (sxProp !== null && Object.keys(sxProp).length > 0) { | ||
| sxStyles = generateCustomSxProp({size}, sxProp) | ||
| } | ||
|
|
||
| return ( | ||
| // @ts-expect-error StyledButton wants both Anchor and Button refs | ||
| <ButtonBase icon={Icon} data-component="IconButton" sx={sxStyles} type="button" {...props} ref={forwardedRef} /> | ||
| ) | ||
| }) as PolymorphicForwardRefComponent<'button' | 'a', IconButtonProps> | ||
| // If the icon button is already wrapped in a tooltip, do not add one. | ||
| const {tooltipId} = React.useContext(TooltipContext) // Tooltip v2 | ||
| const {tooltipId: tooltipIdV1} = React.useContext(TooltipContextV1) // Tooltip v1 | ||
|
|
||
| const hasExternalTooltip = tooltipId || tooltipIdV1 | ||
|
siddharthkp marked this conversation as resolved.
|
||
| const withoutTooltip = | ||
| disableTooltip || disabled || ariaLabel === undefined || ariaLabel === '' || hasExternalTooltip | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (non blocking question) is the check for empty/undefined aria-label temporary for smooth adoption or a permanent check?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Great question! There is a possibility of When an icon button has an <IconButton aria-labelledby="some-element-to-label" description="additional text" .../>
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sounds fair to me, up to you! |
||
|
|
||
| if (withoutTooltip) { | ||
| return ( | ||
| <ButtonBase | ||
| icon={Icon} | ||
| data-component="IconButton" | ||
| sx={sxStyles} | ||
| type="button" | ||
| aria-label={ariaLabel} | ||
| disabled={disabled} | ||
| {...props} | ||
| // @ts-expect-error StyledButton wants both Anchor and Button refs | ||
| ref={forwardedRef} | ||
| /> | ||
| ) | ||
| } else { | ||
| return ( | ||
| <Tooltip | ||
| ref={forwardedRef} | ||
| text={description ?? ariaLabel} | ||
| type={description ? undefined : 'label'} | ||
| direction={tooltipDirection} | ||
| > | ||
| <ButtonBase | ||
| icon={Icon} | ||
| data-component="IconButton" | ||
| sx={sxStyles} | ||
| type="button" | ||
| // If description is provided, we will use the tooltip to describe the button, so we need to keep the aria-label to label the button. | ||
| aria-label={description ? ariaLabel : undefined} | ||
| {...props} | ||
| /> | ||
| </Tooltip> | ||
| ) | ||
| } | ||
| }, | ||
| ) as PolymorphicForwardRefComponent<'button' | 'a', IconButtonProps> | ||
|
|
||
| export {IconButton} | ||
Uh oh!
There was an error while loading. Please reload this page.