Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/iconbutton-default-tooltip.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@primer/react': minor
---

[IconButton](https://primer.style/react/IconButton) now has a tooltip by default, it can be [customised by wrapping in a Tooltip](https://primer.style/react/IconButton#customize-description--tooltip-text) ([#2006](https://github.com/primer/react/pull/2006))
6 changes: 6 additions & 0 deletions .changeset/improved-tooltip.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@primer/react': patch
---

Accessibility and position fixes (backward compatible) for Tooltip ([#2006](https://github.com/primer/react/pull/2006))

10 changes: 10 additions & 0 deletions docs/content/IconButton.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,16 @@ A separate component called `IconButton` is used if the action shows only an ico
</>
```

### Customize description / tooltip text

To add description for the button, wrap `IconButton` in a `Tooltip`. Make sure you pass `aria-label` to the button as well.

```jsx live
<Tooltip text="You have no unread notifications">
<IconButton icon={BellIcon} aria-label="Notifications" />
</Tooltip>
```

## API reference

Native `<button>` HTML attributes are forwarded to the underlying React `button` component and are not listed below.
Expand Down
34 changes: 0 additions & 34 deletions docs/content/Tooltip.md

This file was deleted.

194 changes: 194 additions & 0 deletions docs/content/Tooltip.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
---
componentId: tooltip
title: Tooltip
status: Alpha
source: https://github.com/primer/react/tree/main/src/Tooltip
storybook: '/react/storybook?path=/story/composite-components-tooltip'
description: Use tooltips to add context to elements on the page.
---

Tooltip only appears on mouse hover or keyboard focus and contain a label or description text. Use tooltips sparingly and as a last resort. [Consider these alternatives](https://primer.style/design/accessibility/tooltip-alternatives).

import {Tooltip, IconButton, Button} from '@primer/react'
import {BellIcon, MentionIcon} from '@primer/octicons-react'
import InlineCode from '@primer/gatsby-theme-doctocat/src/components/inline-code'

<Box
sx={{
display: 'flex',
justifyContent: 'center',
border: '1px solid',
borderColor: 'border.default',
borderRadius: 2,
padding: 6,
paddingTop: 8,
marginBottom: 3
}}
>
<Tooltip text="You have no unread notifications" sx={{visibility: 'visible', opacity: 1}}>
<IconButton icon={BellIcon} aria-label="Notifications" />
</Tooltip>
</Box>

When using a tooltip, follow the provided guidelines to avoid accessibility issues:

- Tooltip text should be brief and to the point.
- Tooltips should contain only **non-essential text**. Tooltips can easily be missed and are not accessible on touch devices so never use tooltips to convey critical information.

## Examples

### As a description for icon-only button

If the tooltip content provides supplementary description, wrap the target in a `Tooltip`. The trigger element should also have a concise accessible label via `aria-label`.

```jsx live
<Tooltip text="Directly mention a team or user">
<IconButton aria-label="Mentions" icon={MentionIcon} variant="invisible" />
</Tooltip>
```

### As a description for a button with visible label

```jsx live
<Tooltip text="This will immediately impact all organization members">
<Button variant="primary">Save</Button>
</Tooltip>
```

### With direction

Set direction of tooltip with `direction`. The tooltip is responsive and will automatically adjust direction to avoid cutting off.

```jsx live
<Box
sx={{
display: 'flex',
flexDirection: 'column',
gap: 8,
marginY: 4,
'[data-component=tooltip]': {visibility: 'visible', opacity: 1}
}}
>
<Box sx={{display: 'flex', justifyContent: 'space-between', gap: 1}}>
<Tooltip direction="nw" text="Tooltip text">
<Button>North west</Button>
</Tooltip>
<Tooltip direction="n" text="Tooltip text">
<Button>North</Button>
</Tooltip>
<Tooltip direction="ne" text="Tooltip text">
<Button>North east</Button>
</Tooltip>
</Box>
<Box sx={{display: 'flex', justifyContent: 'space-between', gap: 1}}>
<Tooltip direction="e" text="Tooltip text">
<Button>East</Button>
</Tooltip>
<Tooltip direction="w" text="Tooltip text">
<Button>West</Button>
</Tooltip>
</Box>
<Box sx={{display: 'flex', justifyContent: 'space-between', gap: 1}}>
<Tooltip direction="sw" text="Tooltip text">
<Button>South west</Button>
</Tooltip>
<Tooltip direction="s" text="Tooltip text">
<Button>South</Button>
</Tooltip>
<Tooltip direction="se" text="Tooltip text">
<Button>South east</Button>
</Tooltip>
</Box>
</Box>
```

## Props

### Tooltip

<PropsTable>
<PropsTableRow name="children" required type="React.ReactNode" description="Tooltip target, single element" />

<PropsTableRow
name="text"
type="string"
description="The text content of the tooltip. This should be brief and no longer than a sentence"
/>
<PropsTableRow
deprecated
name="aria-label"
type="string"
description={
<>
Use <InlineCode>text</InlineCode> instead
</>
}
/>
<PropsTableRow
name="type"
type="'description' | 'label'"
defaultValue="'description'"
description={
<>
Use <InlineCode>aria-describedby</InlineCode> or <InlineCode>aria-labelledby</InlineCode>
</>
}
/>
<PropsTableRow
name="direction"
type="'nw' | 'n' | 'ne' | 'e' | 'se' | 's' | 'sw' | 'w'"
defaultValue="'n'"
description="Sets where the tooltip renders in relation to the target"
/>
<PropsTableRow name="align" deprecated type="'left' | 'right'" description="Use direction instead. Alignment relative to target" />
<PropsTableRow
name="noDelay"
type="boolean"
defaultValue="false"
description={
<>
When set to <InlineCode>true</InlineCode>, tooltip appears without any delay
</>
}
/>
<PropsTableRow
name="wrap"
type="boolean"
deprecated
description={
<>
Use to allow text within tooltip to wrap. Deprecated: always set to <InlineCode>true</InlineCode> now.
</>
}
/>
<PropsTableSxRow />
</PropsTable>

## Status

<ComponentChecklist
items={{
propsDocumented: true,
noUnnecessaryDeps: true,
adaptsToThemes: true,
adaptsToScreenSizes: true,
fullTestCoverage: true,
usedInProduction: true,
usageExamplesDocumented: true,
hasStorybookStories: true,
designReviewed: false,
a11yReviewed: false,
stableApi: true,
addressedApiFeedback: false,
hasDesignGuidelines: false,
hasFigmaComponent: true
}}
/>

## Further reading

- [Tooltip alternatives](https://primer.style/design/accessibility/tooltip-alternatives)

## Related components

- [IconButton](/IconButton)
14 changes: 7 additions & 7 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@
"npm": ">=7"
},
"dependencies": {
"@primer/behaviors": "1.1.1",
"@primer/behaviors": "^1.1.3",
"@primer/octicons-react": "16.1.1",
"@primer/primitives": "7.6.0",
"@radix-ui/react-polymorphic": "0.0.14",
Expand Down
32 changes: 31 additions & 1 deletion src/Button/Button.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import {EyeClosedIcon, EyeIcon, SearchIcon, TriangleDownIcon, XIcon} from '@primer/octicons-react'
import {BellIcon, EyeClosedIcon, EyeIcon, SearchIcon, TriangleDownIcon, XIcon} from '@primer/octicons-react'
import {Meta} from '@storybook/react'
import React, {useState} from 'react'
import {Button, ButtonProps, IconButton} from '.'
import {BaseStyles, ThemeProvider} from '..'
import Box from '../Box'
import {Tooltip} from '../Tooltip'

export default {
title: 'Composite components/Button',
Expand Down Expand Up @@ -93,6 +94,35 @@ export const iconButton = ({...args}: ButtonProps) => {
)
}

export const iconButtonWithTooltip = ({...args}: ButtonProps) => {
return (
<>
<Box sx={{mt: 4, display: 'grid', gridTemplateColumns: '1fr 1fr', rowGap: 2}}>
Default tooltip
<span>
<IconButton icon={BellIcon} aria-label="Notifications" {...args} />
</span>
Custom tooltip text
<span>
<Tooltip text="You have no unread notifications">
<IconButton icon={BellIcon} aria-label="Notifications" {...args} />
</Tooltip>
</span>
Custom tooltip direction
<span>
<Tooltip text="Notifications" direction="e">
<IconButton icon={BellIcon} aria-label="Notifications" {...args} />
</Tooltip>
</span>
Disable tooltip
<span>
<IconButton icon={BellIcon} aria-label="Notifications" disableTooltip {...args} />
</span>
</Box>
</>
)
}

export const WatchCounterButton = ({...args}: ButtonProps) => {
const [count, setCount] = useState(0)
return (
Expand Down
Loading