diff --git a/.changeset/iconbutton-default-tooltip.md b/.changeset/iconbutton-default-tooltip.md
deleted file mode 100644
index 7fb2b407016..00000000000
--- a/.changeset/iconbutton-default-tooltip.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@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))
diff --git a/.changeset/improved-tooltip.md b/.changeset/improved-tooltip.md
deleted file mode 100644
index acaea20e976..00000000000
--- a/.changeset/improved-tooltip.md
+++ /dev/null
@@ -1,6 +0,0 @@
----
-'@primer/react': patch
----
-
-Accessibility and position fixes (backward compatible) for Tooltip ([#2006](https://github.com/primer/react/pull/2006))
-
diff --git a/docs/content/IconButton.mdx b/docs/content/IconButton.mdx
index 0eceefea19b..2af6b14024b 100644
--- a/docs/content/IconButton.mdx
+++ b/docs/content/IconButton.mdx
@@ -35,16 +35,6 @@ 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
-
-
-
-```
-
## API reference
Native `` HTML attributes are forwarded to the underlying React `button` component and are not listed below.
diff --git a/docs/content/Tooltip.md b/docs/content/Tooltip.md
new file mode 100644
index 00000000000..a8aea00bfa1
--- /dev/null
+++ b/docs/content/Tooltip.md
@@ -0,0 +1,34 @@
+---
+componentId: tooltip
+title: Tooltip
+status: Alpha
+---
+
+The Tooltip component adds a tooltip to add context to elements on the page.
+
+**_⚠️ Usage warning! ⚠️_**
+
+Tooltips as a UI pattern should be our last resort for conveying information because it is hidden by default and often with zero or little visual indicator of its existence.
+
+Before adding a tooltip, please consider: Is this information essential and necessary? Can the UI be made clearer? Can the information be shown on the page by default?
+
+**Attention:** we use aria-label for tooltip contents, because it is crucial that they are accessible to screen reader users. However, aria-label replaces the text content of an element in screen readers, so only use Tooltip on elements with no existing text content, or consider using `title` for supplemental information.
+
+## Default example
+
+```jsx live
+
+ Text with a tooltip
+
+```
+
+## Component props
+
+| Name | Type | Default | Description |
+| :--------- | :---------------- | :-----: | :------------------------------------------------------------------------------------------------------------------ |
+| align | String | | Can be either `left` or `right`. |
+| direction | String | | Can be one of `n`, `ne`, `e`, `se`, `s`, `sw`, `w`, `nw`. Sets where the tooltip renders in relation to the target. |
+| noDelay | Boolean | | When set to `true`, tooltip appears without any delay |
+| aria-label | String | | Text used in `aria-label` (for accessibility). |
+| wrap | Boolean | | Use `true` to allow text within tooltip to wrap. |
+| sx | SystemStyleObject | {} | Style to be applied to the component |
diff --git a/docs/content/Tooltip.mdx b/docs/content/Tooltip.mdx
deleted file mode 100644
index 1f6b49209cc..00000000000
--- a/docs/content/Tooltip.mdx
+++ /dev/null
@@ -1,194 +0,0 @@
----
-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'
-
-
-
-
-
-
-
-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
-
-
-
-```
-
-### As a description for a button with visible label
-
-```jsx live
-
- Save
-
-```
-
-### With direction
-
-Set direction of tooltip with `direction`. The tooltip is responsive and will automatically adjust direction to avoid cutting off.
-
-```jsx live
-
-
-
- North west
-
-
- North
-
-
- North east
-
-
-
-
- East
-
-
- West
-
-
-
-
- South west
-
-
- South
-
-
- South east
-
-
-
-```
-
-## Props
-
-### Tooltip
-
-
-
-
-
-
- Use text instead
- >
- }
- />
-
- Use aria-describedby or aria-labelledby
- >
- }
- />
-
-
-
- When set to true , tooltip appears without any delay
- >
- }
- />
-
- Use to allow text within tooltip to wrap. Deprecated: always set to true now.
- >
- }
- />
-
-
-
-## Status
-
-
-
-## Further reading
-
-- [Tooltip alternatives](https://primer.style/design/accessibility/tooltip-alternatives)
-
-## Related components
-
-- [IconButton](/IconButton)
diff --git a/package-lock.json b/package-lock.json
index ceb0be69e41..cb5ec85d317 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -9,7 +9,7 @@
"version": "35.2.1",
"license": "MIT",
"dependencies": {
- "@primer/behaviors": "^1.1.3",
+ "@primer/behaviors": "1.1.1",
"@primer/octicons-react": "16.1.1",
"@primer/primitives": "7.6.0",
"@radix-ui/react-polymorphic": "0.0.14",
@@ -5588,9 +5588,9 @@
}
},
"node_modules/@primer/behaviors": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/@primer/behaviors/-/behaviors-1.1.3.tgz",
- "integrity": "sha512-WpCcjAkXG7Lv3ZbaCUgASWKHnCi/pmuSEiyTmHHb6f5xhwk1mliixNL5ZZHtDN6RCcT3VnXUsyek4GopG2lbZQ=="
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@primer/behaviors/-/behaviors-1.1.1.tgz",
+ "integrity": "sha512-wvF1PYjyxKNTr6+5w4uR5Gkz53t1fsRDgKjWxDKk7wmlh0cwiILBo4dDFjjVhWRF1mBSjaIxxJGB4WGaP7ct2Q=="
},
"node_modules/@primer/octicons-react": {
"version": "16.1.1",
@@ -38627,9 +38627,9 @@
"dev": true
},
"@primer/behaviors": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/@primer/behaviors/-/behaviors-1.1.3.tgz",
- "integrity": "sha512-WpCcjAkXG7Lv3ZbaCUgASWKHnCi/pmuSEiyTmHHb6f5xhwk1mliixNL5ZZHtDN6RCcT3VnXUsyek4GopG2lbZQ=="
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@primer/behaviors/-/behaviors-1.1.1.tgz",
+ "integrity": "sha512-wvF1PYjyxKNTr6+5w4uR5Gkz53t1fsRDgKjWxDKk7wmlh0cwiILBo4dDFjjVhWRF1mBSjaIxxJGB4WGaP7ct2Q=="
},
"@primer/octicons-react": {
"version": "16.1.1",
diff --git a/package.json b/package.json
index 45b1490df10..c847db92cd2 100644
--- a/package.json
+++ b/package.json
@@ -78,7 +78,7 @@
"npm": ">=7"
},
"dependencies": {
- "@primer/behaviors": "^1.1.3",
+ "@primer/behaviors": "1.1.1",
"@primer/octicons-react": "16.1.1",
"@primer/primitives": "7.6.0",
"@radix-ui/react-polymorphic": "0.0.14",
diff --git a/src/Button/Button.stories.tsx b/src/Button/Button.stories.tsx
index 0f55c557a84..800915dc3a9 100644
--- a/src/Button/Button.stories.tsx
+++ b/src/Button/Button.stories.tsx
@@ -1,10 +1,9 @@
-import {BellIcon, EyeClosedIcon, EyeIcon, SearchIcon, TriangleDownIcon, XIcon} from '@primer/octicons-react'
+import {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',
@@ -94,35 +93,6 @@ export const iconButton = ({...args}: ButtonProps) => {
)
}
-export const iconButtonWithTooltip = ({...args}: ButtonProps) => {
- return (
- <>
-
- Default tooltip
-
-
-
- Custom tooltip text
-
-
-
-
-
- Custom tooltip direction
-
-
-
-
-
- Disable tooltip
-
-
-
-
- >
- )
-}
-
export const WatchCounterButton = ({...args}: ButtonProps) => {
const [count, setCount] = useState(0)
return (
diff --git a/src/Button/IconButton.tsx b/src/Button/IconButton.tsx
index e4e13a4758d..401650829fc 100644
--- a/src/Button/IconButton.tsx
+++ b/src/Button/IconButton.tsx
@@ -4,51 +4,22 @@ import {useTheme} from '../ThemeProvider'
import Box from '../Box'
import {IconButtonProps, StyledButton} from './types'
import {getBaseStyles, getSizeStyles, getVariantStyles} from './styles'
-import {Tooltip, TooltipContext} from '../Tooltip'
const IconButton = forwardRef((props, forwardedRef): JSX.Element => {
- const {
- variant = 'default',
- size = 'medium',
- sx: sxProp = {},
- icon: Icon,
- disableTooltip = false,
- 'aria-label': ariaLabel,
- ...rest
- } = props
+ const {variant = 'default', size = 'medium', sx: sxProp = {}, icon: Icon, ...rest} = props
const {theme} = useTheme()
-
const sxStyles = merge.all([
getBaseStyles(theme),
getSizeStyles(size, variant, true),
getVariantStyles(variant, theme),
sxProp as SxProp
])
-
- // If button is already wrapped in a Tooltip,
- // do not add another.
- const {tooltipId} = React.useContext(TooltipContext)
-
- if (tooltipId || disableTooltip) {
- return (
-
-
-
-
-
- )
- }
-
- // use Tooltip with type=label and skip aria-label on button
- // because the aria-labelledby is provided by the tooltip
return (
-
-
-
-
-
-
-
+
+
+
+
+
)
})
diff --git a/src/Button/types.ts b/src/Button/types.ts
index 147e1f7c812..7568bd3910d 100644
--- a/src/Button/types.ts
+++ b/src/Button/types.ts
@@ -48,8 +48,6 @@ export type ButtonProps = {
export type IconButtonProps = ButtonA11yProps & {
icon: React.FunctionComponent
- 'aria-label': string
- disableTooltip?: boolean
} & ButtonBaseProps
// adopted from React.AnchorHTMLAttributes
diff --git a/src/Tooltip.tsx b/src/Tooltip.tsx
new file mode 100644
index 00000000000..0e3baa22b2d
--- /dev/null
+++ b/src/Tooltip.tsx
@@ -0,0 +1,263 @@
+import classnames from 'classnames'
+import React from 'react'
+import styled from 'styled-components'
+import {get} from './constants'
+import sx, {SxProp} from './sx'
+import {ComponentProps} from './utils/types'
+
+const TooltipBase = styled.span`
+ position: relative;
+
+ &::before {
+ position: absolute;
+ z-index: 1000001;
+ display: none;
+ width: 0px;
+ height: 0px;
+ color: ${get('colors.neutral.emphasisPlus')};
+ pointer-events: none;
+ content: '';
+ border: 6px solid transparent;
+ opacity: 0;
+ }
+
+ &::after {
+ position: absolute;
+ z-index: 1000000;
+ display: none;
+ padding: 0.5em 0.75em;
+ font: normal normal 11px/1.5 ${get('fonts.normal')};
+ -webkit-font-smoothing: subpixel-antialiased;
+ color: ${get('colors.fg.onEmphasis')};
+ text-align: center;
+ text-decoration: none;
+ text-shadow: none;
+ text-transform: none;
+ letter-spacing: normal;
+ word-wrap: break-word;
+ white-space: pre;
+ pointer-events: none;
+ content: attr(aria-label);
+ background: ${get('colors.neutral.emphasisPlus')};
+ border-radius: ${get('radii.1')};
+ opacity: 0;
+ }
+
+ // delay animation for tooltip
+ @keyframes tooltip-appear {
+ from {
+ opacity: 0;
+ }
+
+ to {
+ opacity: 1;
+ }
+ }
+
+ &:hover,
+ &:active,
+ &:focus {
+ &::before,
+ &::after {
+ display: inline-block;
+ text-decoration: none;
+ animation-name: tooltip-appear;
+ animation-duration: 0.1s;
+ animation-fill-mode: forwards;
+ animation-timing-function: ease-in;
+ animation-delay: 0.4s;
+ }
+ }
+
+ &.tooltipped-no-delay:hover,
+ &.tooltipped-no-delay:active,
+ &.tooltipped-no-delay:focus {
+ &::before,
+ &::after {
+ animation-delay: 0s;
+ }
+ }
+
+ &.tooltipped-multiline:hover,
+ &.tooltipped-multiline:active,
+ &.tooltipped-multiline:focus {
+ &::after {
+ display: table-cell;
+ }
+ }
+
+ // Tooltipped south
+ &.tooltipped-s,
+ &.tooltipped-se,
+ &.tooltipped-sw {
+ &::after {
+ top: 100%;
+ right: 50%;
+ margin-top: 6px;
+ }
+
+ &::before {
+ top: auto;
+ right: 50%;
+ bottom: -7px;
+ margin-right: -6px;
+ border-bottom-color: ${get('colors.neutral.emphasisPlus')};
+ }
+ }
+
+ &.tooltipped-se {
+ &::after {
+ right: auto;
+ left: 50%;
+ margin-left: -${get('space.3')};
+ }
+ }
+
+ &.tooltipped-sw::after {
+ margin-right: -${get('space.3')};
+ }
+
+ // Tooltips above the object
+ &.tooltipped-n,
+ &.tooltipped-ne,
+ &.tooltipped-nw {
+ &::after {
+ right: 50%;
+ bottom: 100%;
+ margin-bottom: 6px;
+ }
+
+ &::before {
+ top: -7px;
+ right: 50%;
+ bottom: auto;
+ margin-right: -6px;
+ border-top-color: ${get('colors.neutral.emphasisPlus')};
+ }
+ }
+
+ &.tooltipped-ne {
+ &::after {
+ right: auto;
+ left: 50%;
+ margin-left: -${get('space.3')};
+ }
+ }
+
+ &.tooltipped-nw::after {
+ margin-right: -${get('space.3')};
+ }
+
+ // Move the tooltip body to the center of the object.
+ &.tooltipped-s::after,
+ &.tooltipped-n::after {
+ transform: translateX(50%);
+ }
+
+ // Tooltipped to the left
+ &.tooltipped-w {
+ &::after {
+ right: 100%;
+ bottom: 50%;
+ margin-right: 6px;
+ transform: translateY(50%);
+ }
+
+ &::before {
+ top: 50%;
+ bottom: 50%;
+ left: -7px;
+ margin-top: -6px;
+ border-left-color: ${get('colors.neutral.emphasisPlus')};
+ }
+ }
+
+ // tooltipped to the right
+ &.tooltipped-e {
+ &::after {
+ bottom: 50%;
+ left: 100%;
+ margin-left: 6px;
+ transform: translateY(50%);
+ }
+
+ &::before {
+ top: 50%;
+ right: -7px;
+ bottom: 50%;
+ margin-top: -6px;
+ border-right-color: ${get('colors.neutral.emphasisPlus')};
+ }
+ }
+
+ &.tooltipped-multiline {
+ &::after {
+ width: max-content;
+ max-width: 250px;
+ word-wrap: break-word;
+ white-space: pre-line;
+ border-collapse: separate;
+ }
+
+ &.tooltipped-s::after,
+ &.tooltipped-n::after {
+ right: auto;
+ left: 50%;
+ transform: translateX(-50%);
+ }
+
+ &.tooltipped-w::after,
+ &.tooltipped-e::after {
+ right: 100%;
+ }
+ }
+
+ &.tooltipped-align-right-2::after {
+ right: 0;
+ margin-right: 0;
+ }
+
+ &.tooltipped-align-right-2::before {
+ right: 15px;
+ }
+
+ &.tooltipped-align-left-2::after {
+ left: 0;
+ margin-left: 0;
+ }
+
+ &.tooltipped-align-left-2::before {
+ left: 10px;
+ }
+
+ ${sx};
+`
+
+export type TooltipProps = {
+ direction?: 'n' | 'ne' | 'e' | 'se' | 's' | 'sw' | 'w' | 'nw'
+ text?: string
+ noDelay?: boolean
+ align?: 'left' | 'right'
+ wrap?: boolean
+} & ComponentProps
+
+function Tooltip({direction = 'n', children, className, text, noDelay, align, wrap, ...rest}: TooltipProps) {
+ const classes = classnames(
+ className,
+ `tooltipped-${direction}`,
+ align && `tooltipped-align-${align}-2`,
+ noDelay && 'tooltipped-no-delay',
+ wrap && 'tooltipped-multiline'
+ )
+ return (
+
+ {children}
+
+ )
+}
+
+Tooltip.alignments = ['left', 'right']
+
+Tooltip.directions = ['n', 'ne', 'e', 'se', 's', 'sw', 'w', 'nw']
+
+export default Tooltip
diff --git a/src/Tooltip/index.tsx b/src/Tooltip/index.tsx
deleted file mode 100644
index bd92e1317aa..00000000000
--- a/src/Tooltip/index.tsx
+++ /dev/null
@@ -1,180 +0,0 @@
-import React from 'react'
-import {useSSRSafeId} from '@react-aria/ssr'
-import type {AnchorPosition, AnchorSide, AnchorAlignment} from '@primer/behaviors'
-import Box from '../Box'
-import {useAnchoredPosition, useProvidedRefOrCreate} from '../hooks'
-import {SxProp, merge, BetterSystemStyleObject} from '../sx'
-
-type TooltipDirection = 'nw' | 'n' | 'ne' | 'e' | 'se' | 's' | 'sw' | 'w'
-type TooltipAlign = 'left' | 'right'
-
-export type TooltipProps = {
- /** The text content of the tooltip. This should be brief and no longer than a sentence.
- * Marked as optional to support backward compatibility with aria-label. */
- text?: string
- /** @deprecated Use `text` instead */
- 'aria-label'?: string
- /** Direction relative to target */
- direction?: TooltipDirection
- /** @deprecated Use `direction` instead. Alignment relative to target. */
- align?: TooltipAlign
- /** Use aria-describedby or aria-labelledby */
- type?: 'description' | 'label'
- /** Tooltip target */
- children: React.ReactElement & {ref?: React.RefObject}
- /** When set to true, tooltip appears without any delay */
- noDelay?: boolean
- /** @deprecated Always set to true now. */
- wrap?: boolean
-} & SxProp
-
-// map tooltip direction to anchoredPosition props
-const directionToPosition: Record = {
- nw: {side: 'outside-top', align: 'start'},
- n: {side: 'outside-top', align: 'center'},
- ne: {side: 'outside-top', align: 'end'},
- e: {side: 'outside-right', align: 'center'},
- se: {side: 'outside-bottom', align: 'end'},
- s: {side: 'outside-bottom', align: 'center'},
- sw: {side: 'outside-bottom', align: 'start'},
- w: {side: 'outside-left', align: 'center'}
-}
-
-// map align to AnchorAlignment
-const alignToAnchorAlignment: Record = {left: 'start', right: 'end'}
-
-export const TooltipContext = React.createContext<{tooltipId?: string}>({})
-
-export const Tooltip: React.FC = ({
- text,
- children,
- direction = 'n',
- align,
- type = 'description',
- noDelay = false,
- sx = {},
- ...props
-}) => {
- const tooltipId = useSSRSafeId()
-
- const childRef = children.ref
- const anchorElementRef = useProvidedRefOrCreate(childRef)
- const tooltipRef = React.useRef(null)
-
- const child = React.cloneElement(children, {
- ref: anchorElementRef,
- [type === 'description' ? 'aria-describedby' : 'aria-labelledby']: tooltipId
- })
-
- const {position} = useAnchoredPosition({
- side: directionToPosition[direction].side,
- // support both algin and direction for backward compatibility
- align: align ? alignToAnchorAlignment[align] : directionToPosition[direction].align,
- floatingElementRef: tooltipRef,
- anchorElementRef
- })
-
- const tooltipText = text || props['aria-label']
-
- return (
-
- {child}
-
-
- )
-}
-
-const FloatingTooltip = React.forwardRef<
- HTMLDivElement,
- Pick & {id: string; position?: AnchorPosition}
->(({id, text, noDelay, position, sx = {}}, ref) => {
- const styles: BetterSystemStyleObject = {
- visibility: 'hidden',
- opacity: 0,
- transition: 'opacity 100ms ease-in',
- transitionDelay: noDelay ? '0ms' : '400ms',
-
- backgroundColor: 'neutral.emphasisPlus',
- color: 'fg.onEmphasis',
- borderRadius: 1,
- fontSize: 0,
- paddingY: 1,
- paddingX: 2,
- width: 'fit-content',
- maxWidth: '250px',
- textAlign: 'center',
- position: 'absolute',
- zIndex: 2,
- top: position?.top,
- left: position?.left,
-
- ':before': {
- content: '""',
- width: 0,
- height: 0,
- border: '5px solid transparent',
- position: 'absolute'
- },
-
- '&[data-side=outside-top]::before': {
- borderTop: '5px solid',
- borderTopColor: 'neutral.emphasisPlus',
- top: '100%'
- },
- '&[data-side=outside-bottom]::before': {
- borderBottom: '5px solid',
- borderBottomColor: 'neutral.emphasisPlus',
- top: '-10px'
- },
- '&[data-side=outside-left]::before': {
- borderLeft: '5px solid',
- borderLeftColor: 'neutral.emphasisPlus',
- top: 'calc(50% - 5px)',
- left: '100%'
- },
- '&[data-side=outside-right]::before': {
- borderRight: '5px solid',
- borderRightColor: 'neutral.emphasisPlus',
- top: 'calc(50% - 5px)',
- left: '-10px'
- },
-
- '&[data-align=start][data-side=outside-top]::before, &[data-align=start][data-side=outside-bottom]::before': {
- left: '8px'
- },
- '&[data-align=center][data-side=outside-top]::before, &[data-align=center][data-side=outside-bottom]::before': {
- left: 'calc(50% - 4px)'
- },
- '&[data-align=end][data-side=outside-top]::before, &[data-align=end][data-side=outside-bottom]::before': {
- left: 'calc(100% - 16px)'
- }
- }
-
- return (
- (styles, sx)}
- >
- {text}
-
- )
-})
diff --git a/src/_TextInputInnerAction.tsx b/src/_TextInputInnerAction.tsx
index d33d63f0f0b..255f8c842f6 100644
--- a/src/_TextInputInnerAction.tsx
+++ b/src/_TextInputInnerAction.tsx
@@ -41,7 +41,7 @@ const invisibleButtonStyleOverrides = {
const ConditionalTooltip: React.FC<{
['aria-label']?: string
- children: React.ReactElement
+ children: React.ReactNode
}> = ({'aria-label': ariaLabel, children}) => (
<>
{ariaLabel ? (
diff --git a/src/__tests__/ActionMenu.test.tsx b/src/__tests__/ActionMenu.test.tsx
index 29fee6e3066..7aa30c560ec 100644
--- a/src/__tests__/ActionMenu.test.tsx
+++ b/src/__tests__/ActionMenu.test.tsx
@@ -3,11 +3,10 @@ import 'babel-polyfill'
import {axe, toHaveNoViolations} from 'jest-axe'
import React from 'react'
import theme from '../theme'
-import {ActionMenu, ActionList, BaseStyles, ThemeProvider, SSRProvider, IconButton} from '..'
+import {ActionMenu, ActionList, BaseStyles, ThemeProvider, SSRProvider} from '..'
import {behavesAsComponent, checkExports, checkStoriesForAxeViolations} from '../utils/testing'
import {SingleSelection, MixedSelection} from '../stories/ActionMenu/examples.stories'
import '@testing-library/jest-dom'
-import {TriangleDownIcon} from '@primer/octicons-react'
expect.extend(toHaveNoViolations)
function Example(): JSX.Element {
@@ -137,32 +136,6 @@ describe('ActionMenu', () => {
cleanup()
})
- it('should open Menu on MenuAnchor click with IconButton', async () => {
- const component = HTMLRender(
-
-
-
-
-
-
-
-
-
- New file
- Copy link
-
-
-
-
-
-
- )
- const button = component.getByLabelText('Toggle Menu')
- fireEvent.click(button)
- expect(component.getByRole('menu')).toBeInTheDocument()
- cleanup()
- })
-
it('should have no axe violations', async () => {
const {container} = HTMLRender( )
const results = await axe(container)
diff --git a/src/__tests__/Button.test.tsx b/src/__tests__/Button.test.tsx
index fc8d0ca69dc..7e917445f9e 100644
--- a/src/__tests__/Button.test.tsx
+++ b/src/__tests__/Button.test.tsx
@@ -1,5 +1,5 @@
import React from 'react'
-import {SSRProvider, IconButton, Button} from '../'
+import {IconButton, Button} from '../Button'
import {behavesAsComponent} from '../utils/testing'
import {render, cleanup, fireEvent} from '@testing-library/react'
import {axe, toHaveNoViolations} from 'jest-axe'
@@ -91,21 +91,13 @@ describe('Button', () => {
})
it('styles icon only button to make it a square', () => {
- const container = render(
-
-
-
- )
+ const container = render( )
const IconOnlyButton = container.getByRole('button')
expect(IconOnlyButton).toHaveStyleRule('padding-right', '8px')
expect(IconOnlyButton).toMatchSnapshot()
})
it('makes sure icon button has an aria-label', () => {
- const container = render(
-
-
-
- )
+ const container = render( )
const IconOnlyButton = container.getByLabelText('Search button')
expect(IconOnlyButton).toBeTruthy()
})
diff --git a/src/__tests__/TextInput.test.tsx b/src/__tests__/TextInput.test.tsx
index 5a9fed253c5..1516917ca2b 100644
--- a/src/__tests__/TextInput.test.tsx
+++ b/src/__tests__/TextInput.test.tsx
@@ -1,5 +1,5 @@
import React from 'react'
-import {SSRProvider, TextInput} from '..'
+import {TextInput} from '..'
import {render, mount, behavesAsComponent, checkExports} from '../utils/testing'
import {render as HTMLRender, cleanup, fireEvent} from '@testing-library/react'
import {axe, toHaveNoViolations} from 'jest-axe'
@@ -68,13 +68,11 @@ describe('TextInput', () => {
const handleAction = jest.fn()
expect(
render(
-
- Clear}
- />
-
+ Clear}
+ />
)
).toMatchSnapshot()
})
@@ -83,17 +81,15 @@ describe('TextInput', () => {
const handleAction = jest.fn()
expect(
render(
-
-
- Clear
-
- }
- />
-
+
+ Clear
+
+ }
+ />
)
).toMatchSnapshot()
})
@@ -102,24 +98,22 @@ describe('TextInput', () => {
const handleAction = jest.fn()
expect(
render(
-
- }
- />
-
+ }
+ />
)
).toMatchSnapshot()
})
it('focuses the text input if you do not click the input element', () => {
const {container, getByLabelText} = HTMLRender(
-
+ <>
{/* eslint-disable-next-line jsx-a11y/label-has-for */}
Search
-
+ >
)
const icon = container.querySelector('svg')!
diff --git a/src/__tests__/Tooltip.test.tsx b/src/__tests__/Tooltip.test.tsx
index 92ecbc0f8bb..4a285eea46e 100644
--- a/src/__tests__/Tooltip.test.tsx
+++ b/src/__tests__/Tooltip.test.tsx
@@ -1,45 +1,52 @@
import React from 'react'
-import 'babel-polyfill'
-import {Tooltip, TooltipContext} from '../Tooltip'
-import {SSRProvider} from '..'
-import {behavesAsComponent, checkExports, checkStoriesForAxeViolations} from '../utils/testing'
+import Tooltip, {TooltipProps} from '../Tooltip'
+import {render, renderClasses, rendersClass, behavesAsComponent, checkExports} from '../utils/testing'
import {render as HTMLRender, cleanup} from '@testing-library/react'
import {axe, toHaveNoViolations} from 'jest-axe'
-import '@testing-library/jest-dom'
+import 'babel-polyfill'
expect.extend(toHaveNoViolations)
-const Fixture = () => {
- return (
-
-
- Save
-
-
- )
-}
-
describe('Tooltip', () => {
- behavesAsComponent({
- Component: Tooltip,
- options: {skipAs: true, skipSx: true},
- toRender: () =>
- })
+ behavesAsComponent({Component: Tooltip})
- checkExports('Tooltip', {default: undefined, Tooltip, TooltipContext})
+ checkExports('Tooltip', {
+ default: Tooltip
+ })
it('should have no axe violations', async () => {
- const {container} = HTMLRender( )
+ const {container} = HTMLRender( )
const results = await axe(container)
expect(results).toHaveNoViolations()
cleanup()
})
- it('tooltip should not be visible by default', () => {
- const component = HTMLRender( )
- expect(component.getByText('tooltip text')).not.toBeVisible()
- cleanup()
+ it('renders a with the "tooltipped" class', () => {
+ expect(render( ).type).toEqual('span')
+ expect(renderClasses( )).toContain('tooltipped-n')
+ })
+
+ it('respects the "align" prop', () => {
+ expect(rendersClass( , 'tooltipped-align-left-2')).toBe(true)
+ expect(rendersClass( , 'tooltipped-align-right-2')).toBe(true)
})
- checkStoriesForAxeViolations('Tooltip/fixtures')
- checkStoriesForAxeViolations('Tooltip/examples')
+ it('respects the "direction" prop', () => {
+ for (const direction of Tooltip.directions) {
+ expect(
+ rendersClass( , `tooltipped-${direction}`)
+ ).toBe(true)
+ }
+ })
+
+ it('respects the "noDelay" prop', () => {
+ expect(rendersClass( , 'tooltipped-no-delay')).toBe(true)
+ })
+
+ it('respects the "text" prop', () => {
+ expect(render( ).props['aria-label']).toEqual('hi')
+ })
+
+ it('respects the "wrap" prop', () => {
+ expect(rendersClass( , 'tooltipped-multiline')).toBe(true)
+ })
})
diff --git a/src/__tests__/Tooltip.types.test.tsx b/src/__tests__/Tooltip.types.test.tsx
index 59c0e45ac54..c9280121588 100644
--- a/src/__tests__/Tooltip.types.test.tsx
+++ b/src/__tests__/Tooltip.types.test.tsx
@@ -1,8 +1,7 @@
import React from 'react'
-import {Tooltip} from '../Tooltip'
+import Tooltip from '../Tooltip'
-export function shouldNotAcceptCallWithMissingProps() {
- // @ts-expect-error props missing
+export function shouldAcceptCallWithNoProps() {
return
}
diff --git a/src/__tests__/__snapshots__/Button.test.tsx.snap b/src/__tests__/__snapshots__/Button.test.tsx.snap
index bdeeeee95bf..f54c410ac25 100644
--- a/src/__tests__/__snapshots__/Button.test.tsx.snap
+++ b/src/__tests__/__snapshots__/Button.test.tsx.snap
@@ -324,7 +324,7 @@ exports[`Button styles icon only button to make it a square 1`] = `
}
-
- iconLabel
-
@@ -1853,90 +1981,6 @@ exports[`TextInput renders trailingAction text button with a tooltip 1`] = `
margin: 4px;
}
-.c3 {
- line-height: 1;
-}
-
-.c3:hover [data-component=tooltip],
-.c3:focus-within [data-component=tooltip] {
- visibility: visible;
- opacity: 1;
-}
-
-.c5 {
- visibility: hidden;
- opacity: 0;
- -webkit-transition: opacity 100ms ease-in;
- transition: opacity 100ms ease-in;
- -webkit-transition-delay: 400ms;
- transition-delay: 400ms;
- background-color: #24292f;
- color: #ffffff;
- border-radius: 3px;
- font-size: 12px;
- padding-top: 4px;
- padding-bottom: 4px;
- padding-left: 8px;
- padding-right: 8px;
- width: -webkit-fit-content;
- width: -moz-fit-content;
- width: fit-content;
- max-width: 250px;
- text-align: center;
- position: absolute;
- z-index: 2;
- display: inline-block;
-}
-
-.c5:before {
- content: "";
- width: 0;
- height: 0;
- border: 5px solid transparent;
- position: absolute;
-}
-
-.c5[data-side=outside-top]::before {
- border-top: 5px solid;
- border-top-color: #24292f;
- top: 100%;
-}
-
-.c5[data-side=outside-bottom]::before {
- border-bottom: 5px solid;
- border-bottom-color: #24292f;
- top: -10px;
-}
-
-.c5[data-side=outside-left]::before {
- border-left: 5px solid;
- border-left-color: #24292f;
- top: calc(50% - 5px);
- left: 100%;
-}
-
-.c5[data-side=outside-right]::before {
- border-right: 5px solid;
- border-right-color: #24292f;
- top: calc(50% - 5px);
- left: -10px;
-}
-
-.c5[data-align=start][data-side=outside-top]::before,
-.c5[data-align=start][data-side=outside-bottom]::before {
- left: 8px;
-}
-
-.c5[data-align=center][data-side=outside-top]::before,
-.c5[data-align=center][data-side=outside-bottom]::before {
- left: calc(50% - 4px);
-}
-
-.c5[data-align=end][data-side=outside-top]::before,
-.c5[data-align=end][data-side=outside-bottom]::before {
- left: calc(100% - 16px);
-}
-
.c4 {
border-radius: 6px;
border: 0;
@@ -2120,6 +2164,226 @@ exports[`TextInput renders trailingAction text button with a tooltip 1`] = `
outline: 0;
}
+.c3 {
+ position: relative;
+ display: inline-block;
+}
+
+.c3::before {
+ position: absolute;
+ z-index: 1000001;
+ display: none;
+ width: 0px;
+ height: 0px;
+ color: #24292f;
+ pointer-events: none;
+ content: '';
+ border: 6px solid transparent;
+ opacity: 0;
+}
+
+.c3::after {
+ position: absolute;
+ z-index: 1000000;
+ display: none;
+ padding: 0.5em 0.75em;
+ font: normal normal 11px/1.5 -apple-system,BlinkMacSystemFont,"Segoe UI",Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji";
+ -webkit-font-smoothing: subpixel-antialiased;
+ color: #ffffff;
+ text-align: center;
+ -webkit-text-decoration: none;
+ text-decoration: none;
+ text-shadow: none;
+ text-transform: none;
+ -webkit-letter-spacing: normal;
+ -moz-letter-spacing: normal;
+ -ms-letter-spacing: normal;
+ letter-spacing: normal;
+ word-wrap: break-word;
+ white-space: pre;
+ pointer-events: none;
+ content: attr(aria-label);
+ background: #24292f;
+ border-radius: 3px;
+ opacity: 0;
+}
+
+.c3:hover::before,
+.c3:active::before,
+.c3:focus::before,
+.c3:hover::after,
+.c3:active::after,
+.c3:focus::after {
+ display: inline-block;
+ -webkit-text-decoration: none;
+ text-decoration: none;
+ -webkit-animation-name: tooltip-appear;
+ animation-name: tooltip-appear;
+ -webkit-animation-duration: 0.1s;
+ animation-duration: 0.1s;
+ -webkit-animation-fill-mode: forwards;
+ animation-fill-mode: forwards;
+ -webkit-animation-timing-function: ease-in;
+ animation-timing-function: ease-in;
+ -webkit-animation-delay: 0.4s;
+ animation-delay: 0.4s;
+}
+
+.c3.tooltipped-no-delay:hover::before,
+.c3.tooltipped-no-delay:active::before,
+.c3.tooltipped-no-delay:focus::before,
+.c3.tooltipped-no-delay:hover::after,
+.c3.tooltipped-no-delay:active::after,
+.c3.tooltipped-no-delay:focus::after {
+ -webkit-animation-delay: 0s;
+ animation-delay: 0s;
+}
+
+.c3.tooltipped-multiline:hover::after,
+.c3.tooltipped-multiline:active::after,
+.c3.tooltipped-multiline:focus::after {
+ display: table-cell;
+}
+
+.c3.tooltipped-s::after,
+.c3.tooltipped-se::after,
+.c3.tooltipped-sw::after {
+ top: 100%;
+ right: 50%;
+ margin-top: 6px;
+}
+
+.c3.tooltipped-s::before,
+.c3.tooltipped-se::before,
+.c3.tooltipped-sw::before {
+ top: auto;
+ right: 50%;
+ bottom: -7px;
+ margin-right: -6px;
+ border-bottom-color: #24292f;
+}
+
+.c3.tooltipped-se::after {
+ right: auto;
+ left: 50%;
+ margin-left: -16px;
+}
+
+.c3.tooltipped-sw::after {
+ margin-right: -16px;
+}
+
+.c3.tooltipped-n::after,
+.c3.tooltipped-ne::after,
+.c3.tooltipped-nw::after {
+ right: 50%;
+ bottom: 100%;
+ margin-bottom: 6px;
+}
+
+.c3.tooltipped-n::before,
+.c3.tooltipped-ne::before,
+.c3.tooltipped-nw::before {
+ top: -7px;
+ right: 50%;
+ bottom: auto;
+ margin-right: -6px;
+ border-top-color: #24292f;
+}
+
+.c3.tooltipped-ne::after {
+ right: auto;
+ left: 50%;
+ margin-left: -16px;
+}
+
+.c3.tooltipped-nw::after {
+ margin-right: -16px;
+}
+
+.c3.tooltipped-s::after,
+.c3.tooltipped-n::after {
+ -webkit-transform: translateX(50%);
+ -ms-transform: translateX(50%);
+ transform: translateX(50%);
+}
+
+.c3.tooltipped-w::after {
+ right: 100%;
+ bottom: 50%;
+ margin-right: 6px;
+ -webkit-transform: translateY(50%);
+ -ms-transform: translateY(50%);
+ transform: translateY(50%);
+}
+
+.c3.tooltipped-w::before {
+ top: 50%;
+ bottom: 50%;
+ left: -7px;
+ margin-top: -6px;
+ border-left-color: #24292f;
+}
+
+.c3.tooltipped-e::after {
+ bottom: 50%;
+ left: 100%;
+ margin-left: 6px;
+ -webkit-transform: translateY(50%);
+ -ms-transform: translateY(50%);
+ transform: translateY(50%);
+}
+
+.c3.tooltipped-e::before {
+ top: 50%;
+ right: -7px;
+ bottom: 50%;
+ margin-top: -6px;
+ border-right-color: #24292f;
+}
+
+.c3.tooltipped-multiline::after {
+ width: -webkit-max-content;
+ width: -moz-max-content;
+ width: max-content;
+ max-width: 250px;
+ word-wrap: break-word;
+ white-space: pre-line;
+ border-collapse: separate;
+}
+
+.c3.tooltipped-multiline.tooltipped-s::after,
+.c3.tooltipped-multiline.tooltipped-n::after {
+ right: auto;
+ left: 50%;
+ -webkit-transform: translateX(-50%);
+ -ms-transform: translateX(-50%);
+ transform: translateX(-50%);
+}
+
+.c3.tooltipped-multiline.tooltipped-w::after,
+.c3.tooltipped-multiline.tooltipped-e::after {
+ right: 100%;
+}
+
+.c3.tooltipped-align-right-2::after {
+ right: 0;
+ margin-right: 0;
+}
+
+.c3.tooltipped-align-right-2::before {
+ right: 15px;
+}
+
+.c3.tooltipped-align-left-2::after {
+ left: 0;
+ margin-left: 0;
+}
+
+.c3.tooltipped-align-left-2::before {
+ left: 10px;
+}
+
@media (forced-colors:active) {
.c4:focus {
outline: solid 1px transparent;
@@ -2165,10 +2429,11 @@ exports[`TextInput renders trailingAction text button with a tooltip 1`] = `
className="c2 TextInput-action"
>
-
- Clear input
-
diff --git a/src/__tests__/__snapshots__/Tooltip.test.tsx.snap b/src/__tests__/__snapshots__/Tooltip.test.tsx.snap
index 83b5ed41551..25e5a7b14ab 100644
--- a/src/__tests__/__snapshots__/Tooltip.test.tsx.snap
+++ b/src/__tests__/__snapshots__/Tooltip.test.tsx.snap
@@ -2,104 +2,226 @@
exports[`Tooltip renders consistently 1`] = `
.c0 {
- line-height: 1;
+ position: relative;
}
-.c0:hover [data-component=tooltip],
-.c0:focus-within [data-component=tooltip] {
- visibility: visible;
- opacity: 1;
+.c0::before {
+ position: absolute;
+ z-index: 1000001;
+ display: none;
+ width: 0px;
+ height: 0px;
+ color: #24292f;
+ pointer-events: none;
+ content: '';
+ border: 6px solid transparent;
+ opacity: 0;
}
-.c1 {
- visibility: hidden;
- opacity: 0;
- -webkit-transition: opacity 100ms ease-in;
- transition: opacity 100ms ease-in;
- -webkit-transition-delay: 400ms;
- transition-delay: 400ms;
- background-color: #24292f;
+.c0::after {
+ position: absolute;
+ z-index: 1000000;
+ display: none;
+ padding: 0.5em 0.75em;
+ font: normal normal 11px/1.5 -apple-system,BlinkMacSystemFont,"Segoe UI",Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji";
+ -webkit-font-smoothing: subpixel-antialiased;
color: #ffffff;
- border-radius: 3px;
- font-size: 12px;
- padding-top: 4px;
- padding-bottom: 4px;
- padding-left: 8px;
- padding-right: 8px;
- width: -webkit-fit-content;
- width: -moz-fit-content;
- width: fit-content;
- max-width: 250px;
text-align: center;
- position: absolute;
- z-index: 2;
+ -webkit-text-decoration: none;
+ text-decoration: none;
+ text-shadow: none;
+ text-transform: none;
+ -webkit-letter-spacing: normal;
+ -moz-letter-spacing: normal;
+ -ms-letter-spacing: normal;
+ letter-spacing: normal;
+ word-wrap: break-word;
+ white-space: pre;
+ pointer-events: none;
+ content: attr(aria-label);
+ background: #24292f;
+ border-radius: 3px;
+ opacity: 0;
}
-.c1:before {
- content: "";
- width: 0;
- height: 0;
- border: 5px solid transparent;
- position: absolute;
+.c0:hover::before,
+.c0:active::before,
+.c0:focus::before,
+.c0:hover::after,
+.c0:active::after,
+.c0:focus::after {
+ display: inline-block;
+ -webkit-text-decoration: none;
+ text-decoration: none;
+ -webkit-animation-name: tooltip-appear;
+ animation-name: tooltip-appear;
+ -webkit-animation-duration: 0.1s;
+ animation-duration: 0.1s;
+ -webkit-animation-fill-mode: forwards;
+ animation-fill-mode: forwards;
+ -webkit-animation-timing-function: ease-in;
+ animation-timing-function: ease-in;
+ -webkit-animation-delay: 0.4s;
+ animation-delay: 0.4s;
}
-.c1[data-side=outside-top]::before {
- border-top: 5px solid;
- border-top-color: #24292f;
+.c0.tooltipped-no-delay:hover::before,
+.c0.tooltipped-no-delay:active::before,
+.c0.tooltipped-no-delay:focus::before,
+.c0.tooltipped-no-delay:hover::after,
+.c0.tooltipped-no-delay:active::after,
+.c0.tooltipped-no-delay:focus::after {
+ -webkit-animation-delay: 0s;
+ animation-delay: 0s;
+}
+
+.c0.tooltipped-multiline:hover::after,
+.c0.tooltipped-multiline:active::after,
+.c0.tooltipped-multiline:focus::after {
+ display: table-cell;
+}
+
+.c0.tooltipped-s::after,
+.c0.tooltipped-se::after,
+.c0.tooltipped-sw::after {
top: 100%;
+ right: 50%;
+ margin-top: 6px;
}
-.c1[data-side=outside-bottom]::before {
- border-bottom: 5px solid;
+.c0.tooltipped-s::before,
+.c0.tooltipped-se::before,
+.c0.tooltipped-sw::before {
+ top: auto;
+ right: 50%;
+ bottom: -7px;
+ margin-right: -6px;
border-bottom-color: #24292f;
- top: -10px;
}
-.c1[data-side=outside-left]::before {
- border-left: 5px solid;
+.c0.tooltipped-se::after {
+ right: auto;
+ left: 50%;
+ margin-left: -16px;
+}
+
+.c0.tooltipped-sw::after {
+ margin-right: -16px;
+}
+
+.c0.tooltipped-n::after,
+.c0.tooltipped-ne::after,
+.c0.tooltipped-nw::after {
+ right: 50%;
+ bottom: 100%;
+ margin-bottom: 6px;
+}
+
+.c0.tooltipped-n::before,
+.c0.tooltipped-ne::before,
+.c0.tooltipped-nw::before {
+ top: -7px;
+ right: 50%;
+ bottom: auto;
+ margin-right: -6px;
+ border-top-color: #24292f;
+}
+
+.c0.tooltipped-ne::after {
+ right: auto;
+ left: 50%;
+ margin-left: -16px;
+}
+
+.c0.tooltipped-nw::after {
+ margin-right: -16px;
+}
+
+.c0.tooltipped-s::after,
+.c0.tooltipped-n::after {
+ -webkit-transform: translateX(50%);
+ -ms-transform: translateX(50%);
+ transform: translateX(50%);
+}
+
+.c0.tooltipped-w::after {
+ right: 100%;
+ bottom: 50%;
+ margin-right: 6px;
+ -webkit-transform: translateY(50%);
+ -ms-transform: translateY(50%);
+ transform: translateY(50%);
+}
+
+.c0.tooltipped-w::before {
+ top: 50%;
+ bottom: 50%;
+ left: -7px;
+ margin-top: -6px;
border-left-color: #24292f;
- top: calc(50% - 5px);
+}
+
+.c0.tooltipped-e::after {
+ bottom: 50%;
left: 100%;
+ margin-left: 6px;
+ -webkit-transform: translateY(50%);
+ -ms-transform: translateY(50%);
+ transform: translateY(50%);
}
-.c1[data-side=outside-right]::before {
- border-right: 5px solid;
+.c0.tooltipped-e::before {
+ top: 50%;
+ right: -7px;
+ bottom: 50%;
+ margin-top: -6px;
border-right-color: #24292f;
- top: calc(50% - 5px);
- left: -10px;
}
-.c1[data-align=start][data-side=outside-top]::before,
-.c1[data-align=start][data-side=outside-bottom]::before {
- left: 8px;
+.c0.tooltipped-multiline::after {
+ width: -webkit-max-content;
+ width: -moz-max-content;
+ width: max-content;
+ max-width: 250px;
+ word-wrap: break-word;
+ white-space: pre-line;
+ border-collapse: separate;
+}
+
+.c0.tooltipped-multiline.tooltipped-s::after,
+.c0.tooltipped-multiline.tooltipped-n::after {
+ right: auto;
+ left: 50%;
+ -webkit-transform: translateX(-50%);
+ -ms-transform: translateX(-50%);
+ transform: translateX(-50%);
+}
+
+.c0.tooltipped-multiline.tooltipped-w::after,
+.c0.tooltipped-multiline.tooltipped-e::after {
+ right: 100%;
+}
+
+.c0.tooltipped-align-right-2::after {
+ right: 0;
+ margin-right: 0;
+}
+
+.c0.tooltipped-align-right-2::before {
+ right: 15px;
}
-.c1[data-align=center][data-side=outside-top]::before,
-.c1[data-align=center][data-side=outside-bottom]::before {
- left: calc(50% - 4px);
+.c0.tooltipped-align-left-2::after {
+ left: 0;
+ margin-left: 0;
}
-.c1[data-align=end][data-side=outside-top]::before,
-.c1[data-align=end][data-side=outside-bottom]::before {
- left: calc(100% - 16px);
+.c0.tooltipped-align-left-2::before {
+ left: 10px;
}
-
- Save
-
-
- tooltip text
-
-
+ className="c0 tooltipped-n"
+ role="tooltip"
+/>
`;
diff --git a/src/__tests__/hooks/useAnchoredPosition.test.tsx b/src/__tests__/hooks/useAnchoredPosition.test.tsx
index 4b7d38dc710..4038ca73c0e 100644
--- a/src/__tests__/hooks/useAnchoredPosition.test.tsx
+++ b/src/__tests__/hooks/useAnchoredPosition.test.tsx
@@ -23,7 +23,6 @@ it('should should return a position', () => {
expect(cb).toHaveBeenCalledTimes(2)
expect(cb.mock.calls[1][0]['position']).toMatchInlineSnapshot(`
Object {
- "anchorAlign": "start",
"anchorSide": "outside-bottom",
"left": 0,
"top": 4,
diff --git a/src/index.ts b/src/index.ts
index 435e8e9b295..f4ce005e4f7 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -143,7 +143,7 @@ export type {
} from './Timeline'
export {default as Token, IssueLabelToken, AvatarToken} from './Token'
export type {TokenProps} from './Token'
-export {Tooltip} from './Tooltip'
+export {default as Tooltip} from './Tooltip'
export type {TooltipProps} from './Tooltip'
export {default as Truncate} from './Truncate'
export type {TruncateProps} from './Truncate'
diff --git a/src/stories/ActionMenu/fixtures.stories.tsx b/src/stories/ActionMenu/fixtures.stories.tsx
index 0b077e45656..8e74c3677f9 100644
--- a/src/stories/ActionMenu/fixtures.stories.tsx
+++ b/src/stories/ActionMenu/fixtures.stories.tsx
@@ -269,7 +269,6 @@ export function MemexTableMenu(): JSX.Element {
width: 200,
display: 'flex',
justifyContent: 'space-between',
- alignItems: 'center',
p: 2,
border: '1px solid',
borderColor: 'border.default'
diff --git a/src/stories/Tooltip.stories.tsx b/src/stories/Tooltip.stories.tsx
new file mode 100644
index 00000000000..59d034282f1
--- /dev/null
+++ b/src/stories/Tooltip.stories.tsx
@@ -0,0 +1,37 @@
+import React from 'react'
+import {Meta} from '@storybook/react'
+import {BaseStyles, ThemeProvider, IconButton} from '..'
+import Box from '../Box'
+import Tooltip from '../Tooltip'
+import {SearchIcon} from '@primer/octicons-react'
+
+export default {
+ title: 'Tooltip/Default',
+ component: Tooltip,
+
+ decorators: [
+ Story => {
+ return (
+
+
+
+
+
+ )
+ }
+ ]
+} as Meta
+
+export const TextTooltip = () => (
+
+ Text with a tooltip
+
+)
+
+export const IconButtonTooltip = () => (
+
+
+
+
+
+)
diff --git a/src/stories/Tooltip/examples.stories.tsx b/src/stories/Tooltip/examples.stories.tsx
deleted file mode 100644
index 51217c340fd..00000000000
--- a/src/stories/Tooltip/examples.stories.tsx
+++ /dev/null
@@ -1,61 +0,0 @@
-import React from 'react'
-import {Meta} from '@storybook/react'
-import {CodeIcon, CrossReferenceIcon, ImageIcon, MentionIcon} from '@primer/octicons-react'
-import {IconButton, Box, IssueLabelToken, Tooltip} from '../..'
-
-export default {title: 'Composite components/Tooltip/examples', component: Tooltip} as Meta
-
-const IssueLabel = React.forwardRef(({text, fillColor}, ref) => {
- return (
-
- )
-})
-
-export const TokenWithTooltip = () => {
- return (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- )
-}
-
-export const ButtonWithTooltip = () => {
- return (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- )
-}
diff --git a/src/stories/Tooltip/fixtures.stories.tsx b/src/stories/Tooltip/fixtures.stories.tsx
deleted file mode 100644
index 401492fb3db..00000000000
--- a/src/stories/Tooltip/fixtures.stories.tsx
+++ /dev/null
@@ -1,108 +0,0 @@
-import React from 'react'
-import {Meta} from '@storybook/react'
-import {MentionIcon} from '@primer/octicons-react'
-import {Box, Button, IssueLabelToken, Tooltip} from '../..'
-
-export default {title: 'Composite components/Tooltip/fixtures', component: Tooltip} as Meta
-
-const IssueLabel = React.forwardRef(({text, fillColor}, ref) => {
- return (
-
- )
-})
-
-export const Direction = () => {
- return (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- )
-}
-
-export const Delay = () => {
- return (
-
-
- default, with delay
-
-
- no delay
-
-
- )
-}
-
-export const TypeLabel = () => {
- return (
-
-
-
-
-
- )
-}
-
-export const AcceptsSx = () => {
- return (
-
- Tooltip accepts sx prop
-
- )
-}
-
-export const BackwardCompatibility = () => {
- return (
-
-
- Has tooltip with aria-label
-
-
- Has tooltip with align:right
-
-
- Has tooltip with wrap
-
-
- )
-}
diff --git a/src/stories/behaviors-anchored-position.stories.tsx b/src/stories/behaviors-anchored-position.stories.tsx
deleted file mode 100644
index 2917b954022..00000000000
--- a/src/stories/behaviors-anchored-position.stories.tsx
+++ /dev/null
@@ -1,345 +0,0 @@
-import React from 'react'
-import {Meta} from '@storybook/react'
-import {SmileyIcon, KebabHorizontalIcon, TriangleDownIcon} from '@primer/octicons-react'
-import {
- BaseStyles,
- Box,
- ThemeProvider,
- Text,
- IconButton,
- PageLayout,
- Heading,
- ActionMenu,
- ActionList,
- Avatar,
- Label,
- LabelProps
-} from '..'
-import {useAnchoredPosition} from '../hooks'
-import type {AnchorAlignment} from '@primer/behaviors'
-
-export default {
- title: 'Behaviors/anchoredPosition',
- decorators: [
- // Note: For some reason, if you use ,
- // the component gets unmounted from the root every time a control changes!
- Story => {
- return (
-
- {Story()}
-
- )
- }
- ]
-} as Meta
-
-type TooltipProps = {
- children: string
- position?: {left: number; top: number; anchorAlign: AnchorAlignment}
- defaultVisible?: boolean
-}
-const Tooltip = React.forwardRef(({defaultVisible, position, children}, ref) => {
- return (
-
-
- {children}
-
- )
-})
-
-const LabelWithTooltip: React.FC = ({
- description,
- defaultVisible = false,
- ...props
-}) => {
- const labelRef = React.useRef(null)
- const tooltipRef = React.useRef(null)
-
- const {position} = useAnchoredPosition({
- side: 'outside-bottom',
- align: 'center',
- anchorElementRef: labelRef,
- floatingElementRef: tooltipRef
- })
-
- return (
-
-
- {props.children}
-
-
- {description}
-
-
- )
-}
-
-export const Tooltips = () => {
- const [optionsOpen, setOptionsOpen] = React.useState(false)
- const optionsButtonRef = React.useRef(null)
- const optionsTooltipRef = React.useRef(null)
- const {position: optionsTooltipPosition} = useAnchoredPosition({
- side: 'outside-bottom',
- align: 'start',
- anchorElementRef: optionsButtonRef,
- floatingElementRef: optionsTooltipRef
- })
-
- const [reactionsOpen, setReactionsOpen] = React.useState(false)
- const reactionButtonRef = React.useRef(null)
- const reactionTooltipRef = React.useRef(null)
- const {position: reactionTooltipPosition} = useAnchoredPosition({
- side: 'outside-bottom',
- align: 'start',
- anchorElementRef: reactionButtonRef,
- floatingElementRef: reactionTooltipRef
- })
-
- return (
- <>
-
-
-
-
- Input validation styles #1831
-
-
-
-
-
-
-
-
- setReactionsOpen(!reactionsOpen)}
- />
-
- Add reaction
-
-
-
-
-
- {['👍', '👎', '😄', '🎉', '😕', '❤️', '🚀', '👀'].map(emoji => (
- {emoji}
- ))}
-
-
-
-
-
- setOptionsOpen(!optionsOpen)}
- />
-
- Show options
-
-
-
-
-
- Copy link
- Quote reply
-
- Edit
-
-
-
-
-
-
- colebemis {' '}
- added {' '}
-
- bug
-
-
- collab
-
-
- blocked
-
-
- dependencies
-
-
- dependencies
-
-
-
-
-
-
- Assignees
- No one – assign yourself
-
-
-
- Labels
- None yet
-
-
-
-
- >
- )
-}
-
-export function MemexTableMenu(): JSX.Element {
- return (
- <>
-
-
- Primer teams backlog
-
-
-
-
-
- Title
- Assignees
- Status
- Labels
- Repository
-
-
- >
- )
-}
-
-const TableHeader: React.FC<{style?: Record}> = props => {
- return (
-
- {props.children}
-
-
-
-
-
-
-
- Sort ascending (123...)
- Sort descending (123...)
-
- Filter by values
- Group by values
-
- Delete file
-
-
-
-
- )
-}