diff --git a/.changeset/blue-melons-arrive.md b/.changeset/blue-melons-arrive.md new file mode 100644 index 00000000000..80b9572885d --- /dev/null +++ b/.changeset/blue-melons-arrive.md @@ -0,0 +1,7 @@ +--- +'@primer/react': minor +--- + +Tooltip: Release Tooltip v2 as a draft/experimental + + diff --git a/.playwright/snapshots/components/Tooltip--experimental.test.ts-snapshots/Tooltip--experimental-Anchor-Has-Margin-dark-colorblind-linux.png b/.playwright/snapshots/components/Tooltip--experimental.test.ts-snapshots/Tooltip--experimental-Anchor-Has-Margin-dark-colorblind-linux.png new file mode 100644 index 00000000000..d83f0d9f39b Binary files /dev/null and b/.playwright/snapshots/components/Tooltip--experimental.test.ts-snapshots/Tooltip--experimental-Anchor-Has-Margin-dark-colorblind-linux.png differ diff --git a/.playwright/snapshots/components/Tooltip--experimental.test.ts-snapshots/Tooltip--experimental-Anchor-Has-Margin-dark-dimmed-linux.png b/.playwright/snapshots/components/Tooltip--experimental.test.ts-snapshots/Tooltip--experimental-Anchor-Has-Margin-dark-dimmed-linux.png new file mode 100644 index 00000000000..a8bb5c4f192 Binary files /dev/null and b/.playwright/snapshots/components/Tooltip--experimental.test.ts-snapshots/Tooltip--experimental-Anchor-Has-Margin-dark-dimmed-linux.png differ diff --git a/.playwright/snapshots/components/Tooltip--experimental.test.ts-snapshots/Tooltip--experimental-Anchor-Has-Margin-dark-high-contrast-linux.png b/.playwright/snapshots/components/Tooltip--experimental.test.ts-snapshots/Tooltip--experimental-Anchor-Has-Margin-dark-high-contrast-linux.png new file mode 100644 index 00000000000..1850dc25179 Binary files /dev/null and b/.playwright/snapshots/components/Tooltip--experimental.test.ts-snapshots/Tooltip--experimental-Anchor-Has-Margin-dark-high-contrast-linux.png differ diff --git a/.playwright/snapshots/components/Tooltip--experimental.test.ts-snapshots/Tooltip--experimental-Anchor-Has-Margin-dark-linux.png b/.playwright/snapshots/components/Tooltip--experimental.test.ts-snapshots/Tooltip--experimental-Anchor-Has-Margin-dark-linux.png new file mode 100644 index 00000000000..3132b718e14 Binary files /dev/null and b/.playwright/snapshots/components/Tooltip--experimental.test.ts-snapshots/Tooltip--experimental-Anchor-Has-Margin-dark-linux.png differ diff --git a/.playwright/snapshots/components/Tooltip--experimental.test.ts-snapshots/Tooltip--experimental-Anchor-Has-Margin-dark-tritanopia-linux.png b/.playwright/snapshots/components/Tooltip--experimental.test.ts-snapshots/Tooltip--experimental-Anchor-Has-Margin-dark-tritanopia-linux.png new file mode 100644 index 00000000000..d83f0d9f39b Binary files /dev/null and b/.playwright/snapshots/components/Tooltip--experimental.test.ts-snapshots/Tooltip--experimental-Anchor-Has-Margin-dark-tritanopia-linux.png differ diff --git a/.playwright/snapshots/components/Tooltip--experimental.test.ts-snapshots/Tooltip--experimental-Anchor-Has-Margin-light-colorblind-linux.png b/.playwright/snapshots/components/Tooltip--experimental.test.ts-snapshots/Tooltip--experimental-Anchor-Has-Margin-light-colorblind-linux.png new file mode 100644 index 00000000000..4fee4f98353 Binary files /dev/null and b/.playwright/snapshots/components/Tooltip--experimental.test.ts-snapshots/Tooltip--experimental-Anchor-Has-Margin-light-colorblind-linux.png differ diff --git a/.playwright/snapshots/components/Tooltip--experimental.test.ts-snapshots/Tooltip--experimental-Anchor-Has-Margin-light-high-contrast-linux.png b/.playwright/snapshots/components/Tooltip--experimental.test.ts-snapshots/Tooltip--experimental-Anchor-Has-Margin-light-high-contrast-linux.png new file mode 100644 index 00000000000..8657dfda75e Binary files /dev/null and b/.playwright/snapshots/components/Tooltip--experimental.test.ts-snapshots/Tooltip--experimental-Anchor-Has-Margin-light-high-contrast-linux.png differ diff --git a/.playwright/snapshots/components/Tooltip--experimental.test.ts-snapshots/Tooltip--experimental-Anchor-Has-Margin-light-linux.png b/.playwright/snapshots/components/Tooltip--experimental.test.ts-snapshots/Tooltip--experimental-Anchor-Has-Margin-light-linux.png new file mode 100644 index 00000000000..9fd679b76ba Binary files /dev/null and b/.playwright/snapshots/components/Tooltip--experimental.test.ts-snapshots/Tooltip--experimental-Anchor-Has-Margin-light-linux.png differ diff --git a/.playwright/snapshots/components/Tooltip--experimental.test.ts-snapshots/Tooltip--experimental-Anchor-Has-Margin-light-tritanopia-linux.png b/.playwright/snapshots/components/Tooltip--experimental.test.ts-snapshots/Tooltip--experimental-Anchor-Has-Margin-light-tritanopia-linux.png new file mode 100644 index 00000000000..4fee4f98353 Binary files /dev/null and b/.playwright/snapshots/components/Tooltip--experimental.test.ts-snapshots/Tooltip--experimental-Anchor-Has-Margin-light-tritanopia-linux.png differ diff --git a/.playwright/snapshots/components/Tooltip--experimental.test.ts-snapshots/Tooltip--experimental-Calculated-Direction-dark-colorblind-linux.png b/.playwright/snapshots/components/Tooltip--experimental.test.ts-snapshots/Tooltip--experimental-Calculated-Direction-dark-colorblind-linux.png new file mode 100644 index 00000000000..bb856ed22cd Binary files /dev/null and b/.playwright/snapshots/components/Tooltip--experimental.test.ts-snapshots/Tooltip--experimental-Calculated-Direction-dark-colorblind-linux.png differ diff --git a/.playwright/snapshots/components/Tooltip--experimental.test.ts-snapshots/Tooltip--experimental-Calculated-Direction-dark-dimmed-linux.png b/.playwright/snapshots/components/Tooltip--experimental.test.ts-snapshots/Tooltip--experimental-Calculated-Direction-dark-dimmed-linux.png new file mode 100644 index 00000000000..dc2ed1cad1b Binary files /dev/null and b/.playwright/snapshots/components/Tooltip--experimental.test.ts-snapshots/Tooltip--experimental-Calculated-Direction-dark-dimmed-linux.png differ diff --git a/.playwright/snapshots/components/Tooltip--experimental.test.ts-snapshots/Tooltip--experimental-Calculated-Direction-dark-high-contrast-linux.png b/.playwright/snapshots/components/Tooltip--experimental.test.ts-snapshots/Tooltip--experimental-Calculated-Direction-dark-high-contrast-linux.png new file mode 100644 index 00000000000..1fdbd7a042a Binary files /dev/null and b/.playwright/snapshots/components/Tooltip--experimental.test.ts-snapshots/Tooltip--experimental-Calculated-Direction-dark-high-contrast-linux.png differ diff --git a/.playwright/snapshots/components/Tooltip--experimental.test.ts-snapshots/Tooltip--experimental-Calculated-Direction-dark-linux.png b/.playwright/snapshots/components/Tooltip--experimental.test.ts-snapshots/Tooltip--experimental-Calculated-Direction-dark-linux.png new file mode 100644 index 00000000000..009d2d73107 Binary files /dev/null and b/.playwright/snapshots/components/Tooltip--experimental.test.ts-snapshots/Tooltip--experimental-Calculated-Direction-dark-linux.png differ diff --git a/.playwright/snapshots/components/Tooltip--experimental.test.ts-snapshots/Tooltip--experimental-Calculated-Direction-dark-tritanopia-linux.png b/.playwright/snapshots/components/Tooltip--experimental.test.ts-snapshots/Tooltip--experimental-Calculated-Direction-dark-tritanopia-linux.png new file mode 100644 index 00000000000..bb856ed22cd Binary files /dev/null and b/.playwright/snapshots/components/Tooltip--experimental.test.ts-snapshots/Tooltip--experimental-Calculated-Direction-dark-tritanopia-linux.png differ diff --git a/.playwright/snapshots/components/Tooltip--experimental.test.ts-snapshots/Tooltip--experimental-Calculated-Direction-light-colorblind-linux.png b/.playwright/snapshots/components/Tooltip--experimental.test.ts-snapshots/Tooltip--experimental-Calculated-Direction-light-colorblind-linux.png new file mode 100644 index 00000000000..8aa918e728c Binary files /dev/null and b/.playwright/snapshots/components/Tooltip--experimental.test.ts-snapshots/Tooltip--experimental-Calculated-Direction-light-colorblind-linux.png differ diff --git a/.playwright/snapshots/components/Tooltip--experimental.test.ts-snapshots/Tooltip--experimental-Calculated-Direction-light-high-contrast-linux.png b/.playwright/snapshots/components/Tooltip--experimental.test.ts-snapshots/Tooltip--experimental-Calculated-Direction-light-high-contrast-linux.png new file mode 100644 index 00000000000..97eaa316a60 Binary files /dev/null and b/.playwright/snapshots/components/Tooltip--experimental.test.ts-snapshots/Tooltip--experimental-Calculated-Direction-light-high-contrast-linux.png differ diff --git a/.playwright/snapshots/components/Tooltip--experimental.test.ts-snapshots/Tooltip--experimental-Calculated-Direction-light-linux.png b/.playwright/snapshots/components/Tooltip--experimental.test.ts-snapshots/Tooltip--experimental-Calculated-Direction-light-linux.png new file mode 100644 index 00000000000..096e6ffd2fa Binary files /dev/null and b/.playwright/snapshots/components/Tooltip--experimental.test.ts-snapshots/Tooltip--experimental-Calculated-Direction-light-linux.png differ diff --git a/.playwright/snapshots/components/Tooltip--experimental.test.ts-snapshots/Tooltip--experimental-Calculated-Direction-light-tritanopia-linux.png b/.playwright/snapshots/components/Tooltip--experimental.test.ts-snapshots/Tooltip--experimental-Calculated-Direction-light-tritanopia-linux.png new file mode 100644 index 00000000000..8aa918e728c Binary files /dev/null and b/.playwright/snapshots/components/Tooltip--experimental.test.ts-snapshots/Tooltip--experimental-Calculated-Direction-light-tritanopia-linux.png differ diff --git a/.playwright/snapshots/components/Tooltip--experimental.test.ts-snapshots/Tooltip--experimental-Default-dark-colorblind-linux.png b/.playwright/snapshots/components/Tooltip--experimental.test.ts-snapshots/Tooltip--experimental-Default-dark-colorblind-linux.png new file mode 100644 index 00000000000..cad231f0dd5 Binary files /dev/null and b/.playwright/snapshots/components/Tooltip--experimental.test.ts-snapshots/Tooltip--experimental-Default-dark-colorblind-linux.png differ diff --git a/.playwright/snapshots/components/Tooltip--experimental.test.ts-snapshots/Tooltip--experimental-Default-dark-dimmed-linux.png b/.playwright/snapshots/components/Tooltip--experimental.test.ts-snapshots/Tooltip--experimental-Default-dark-dimmed-linux.png new file mode 100644 index 00000000000..a3a8b0d29d1 Binary files /dev/null and b/.playwright/snapshots/components/Tooltip--experimental.test.ts-snapshots/Tooltip--experimental-Default-dark-dimmed-linux.png differ diff --git a/.playwright/snapshots/components/Tooltip--experimental.test.ts-snapshots/Tooltip--experimental-Default-dark-high-contrast-linux.png b/.playwright/snapshots/components/Tooltip--experimental.test.ts-snapshots/Tooltip--experimental-Default-dark-high-contrast-linux.png new file mode 100644 index 00000000000..7e761a8871b Binary files /dev/null and b/.playwright/snapshots/components/Tooltip--experimental.test.ts-snapshots/Tooltip--experimental-Default-dark-high-contrast-linux.png differ diff --git a/.playwright/snapshots/components/Tooltip--experimental.test.ts-snapshots/Tooltip--experimental-Default-dark-linux.png b/.playwright/snapshots/components/Tooltip--experimental.test.ts-snapshots/Tooltip--experimental-Default-dark-linux.png new file mode 100644 index 00000000000..40b13c2ee78 Binary files /dev/null and b/.playwright/snapshots/components/Tooltip--experimental.test.ts-snapshots/Tooltip--experimental-Default-dark-linux.png differ diff --git a/.playwright/snapshots/components/Tooltip--experimental.test.ts-snapshots/Tooltip--experimental-Default-dark-tritanopia-linux.png b/.playwright/snapshots/components/Tooltip--experimental.test.ts-snapshots/Tooltip--experimental-Default-dark-tritanopia-linux.png new file mode 100644 index 00000000000..cad231f0dd5 Binary files /dev/null and b/.playwright/snapshots/components/Tooltip--experimental.test.ts-snapshots/Tooltip--experimental-Default-dark-tritanopia-linux.png differ diff --git a/.playwright/snapshots/components/Tooltip--experimental.test.ts-snapshots/Tooltip--experimental-Default-light-colorblind-linux.png b/.playwright/snapshots/components/Tooltip--experimental.test.ts-snapshots/Tooltip--experimental-Default-light-colorblind-linux.png new file mode 100644 index 00000000000..84bb7b94f7f Binary files /dev/null and b/.playwright/snapshots/components/Tooltip--experimental.test.ts-snapshots/Tooltip--experimental-Default-light-colorblind-linux.png differ diff --git a/.playwright/snapshots/components/Tooltip--experimental.test.ts-snapshots/Tooltip--experimental-Default-light-high-contrast-linux.png b/.playwright/snapshots/components/Tooltip--experimental.test.ts-snapshots/Tooltip--experimental-Default-light-high-contrast-linux.png new file mode 100644 index 00000000000..2f71c246501 Binary files /dev/null and b/.playwright/snapshots/components/Tooltip--experimental.test.ts-snapshots/Tooltip--experimental-Default-light-high-contrast-linux.png differ diff --git a/.playwright/snapshots/components/Tooltip--experimental.test.ts-snapshots/Tooltip--experimental-Default-light-linux.png b/.playwright/snapshots/components/Tooltip--experimental.test.ts-snapshots/Tooltip--experimental-Default-light-linux.png new file mode 100644 index 00000000000..ca70b0e3002 Binary files /dev/null and b/.playwright/snapshots/components/Tooltip--experimental.test.ts-snapshots/Tooltip--experimental-Default-light-linux.png differ diff --git a/.playwright/snapshots/components/Tooltip--experimental.test.ts-snapshots/Tooltip--experimental-Default-light-tritanopia-linux.png b/.playwright/snapshots/components/Tooltip--experimental.test.ts-snapshots/Tooltip--experimental-Default-light-tritanopia-linux.png new file mode 100644 index 00000000000..84bb7b94f7f Binary files /dev/null and b/.playwright/snapshots/components/Tooltip--experimental.test.ts-snapshots/Tooltip--experimental-Default-light-tritanopia-linux.png differ diff --git a/.playwright/snapshots/components/Tooltip--experimental.test.ts-snapshots/Tooltip--experimental-Icon-Button-With-Description-dark-colorblind-linux.png b/.playwright/snapshots/components/Tooltip--experimental.test.ts-snapshots/Tooltip--experimental-Icon-Button-With-Description-dark-colorblind-linux.png new file mode 100644 index 00000000000..330e3a01d4f Binary files /dev/null and b/.playwright/snapshots/components/Tooltip--experimental.test.ts-snapshots/Tooltip--experimental-Icon-Button-With-Description-dark-colorblind-linux.png differ diff --git a/.playwright/snapshots/components/Tooltip--experimental.test.ts-snapshots/Tooltip--experimental-Icon-Button-With-Description-dark-dimmed-linux.png b/.playwright/snapshots/components/Tooltip--experimental.test.ts-snapshots/Tooltip--experimental-Icon-Button-With-Description-dark-dimmed-linux.png new file mode 100644 index 00000000000..79fb5224d59 Binary files /dev/null and b/.playwright/snapshots/components/Tooltip--experimental.test.ts-snapshots/Tooltip--experimental-Icon-Button-With-Description-dark-dimmed-linux.png differ diff --git a/.playwright/snapshots/components/Tooltip--experimental.test.ts-snapshots/Tooltip--experimental-Icon-Button-With-Description-dark-high-contrast-linux.png b/.playwright/snapshots/components/Tooltip--experimental.test.ts-snapshots/Tooltip--experimental-Icon-Button-With-Description-dark-high-contrast-linux.png new file mode 100644 index 00000000000..1d80122a97a Binary files /dev/null and b/.playwright/snapshots/components/Tooltip--experimental.test.ts-snapshots/Tooltip--experimental-Icon-Button-With-Description-dark-high-contrast-linux.png differ diff --git a/.playwright/snapshots/components/Tooltip--experimental.test.ts-snapshots/Tooltip--experimental-Icon-Button-With-Description-dark-linux.png b/.playwright/snapshots/components/Tooltip--experimental.test.ts-snapshots/Tooltip--experimental-Icon-Button-With-Description-dark-linux.png new file mode 100644 index 00000000000..9ef1a7f8b4f Binary files /dev/null and b/.playwright/snapshots/components/Tooltip--experimental.test.ts-snapshots/Tooltip--experimental-Icon-Button-With-Description-dark-linux.png differ diff --git a/.playwright/snapshots/components/Tooltip--experimental.test.ts-snapshots/Tooltip--experimental-Icon-Button-With-Description-dark-tritanopia-linux.png b/.playwright/snapshots/components/Tooltip--experimental.test.ts-snapshots/Tooltip--experimental-Icon-Button-With-Description-dark-tritanopia-linux.png new file mode 100644 index 00000000000..330e3a01d4f Binary files /dev/null and b/.playwright/snapshots/components/Tooltip--experimental.test.ts-snapshots/Tooltip--experimental-Icon-Button-With-Description-dark-tritanopia-linux.png differ diff --git a/.playwright/snapshots/components/Tooltip--experimental.test.ts-snapshots/Tooltip--experimental-Icon-Button-With-Description-light-colorblind-linux.png b/.playwright/snapshots/components/Tooltip--experimental.test.ts-snapshots/Tooltip--experimental-Icon-Button-With-Description-light-colorblind-linux.png new file mode 100644 index 00000000000..7a5c9768591 Binary files /dev/null and b/.playwright/snapshots/components/Tooltip--experimental.test.ts-snapshots/Tooltip--experimental-Icon-Button-With-Description-light-colorblind-linux.png differ diff --git a/.playwright/snapshots/components/Tooltip--experimental.test.ts-snapshots/Tooltip--experimental-Icon-Button-With-Description-light-high-contrast-linux.png b/.playwright/snapshots/components/Tooltip--experimental.test.ts-snapshots/Tooltip--experimental-Icon-Button-With-Description-light-high-contrast-linux.png new file mode 100644 index 00000000000..094f8d312bf Binary files /dev/null and b/.playwright/snapshots/components/Tooltip--experimental.test.ts-snapshots/Tooltip--experimental-Icon-Button-With-Description-light-high-contrast-linux.png differ diff --git a/.playwright/snapshots/components/Tooltip--experimental.test.ts-snapshots/Tooltip--experimental-Icon-Button-With-Description-light-linux.png b/.playwright/snapshots/components/Tooltip--experimental.test.ts-snapshots/Tooltip--experimental-Icon-Button-With-Description-light-linux.png new file mode 100644 index 00000000000..21619e3c79c Binary files /dev/null and b/.playwright/snapshots/components/Tooltip--experimental.test.ts-snapshots/Tooltip--experimental-Icon-Button-With-Description-light-linux.png differ diff --git a/.playwright/snapshots/components/Tooltip--experimental.test.ts-snapshots/Tooltip--experimental-Icon-Button-With-Description-light-tritanopia-linux.png b/.playwright/snapshots/components/Tooltip--experimental.test.ts-snapshots/Tooltip--experimental-Icon-Button-With-Description-light-tritanopia-linux.png new file mode 100644 index 00000000000..7a5c9768591 Binary files /dev/null and b/.playwright/snapshots/components/Tooltip--experimental.test.ts-snapshots/Tooltip--experimental-Icon-Button-With-Description-light-tritanopia-linux.png differ diff --git a/.playwright/snapshots/components/Tooltip--experimental.test.ts-snapshots/Tooltip--experimental-Label-Type-dark-colorblind-linux.png b/.playwright/snapshots/components/Tooltip--experimental.test.ts-snapshots/Tooltip--experimental-Label-Type-dark-colorblind-linux.png new file mode 100644 index 00000000000..5b80a53345e Binary files /dev/null and b/.playwright/snapshots/components/Tooltip--experimental.test.ts-snapshots/Tooltip--experimental-Label-Type-dark-colorblind-linux.png differ diff --git a/.playwright/snapshots/components/Tooltip--experimental.test.ts-snapshots/Tooltip--experimental-Label-Type-dark-dimmed-linux.png b/.playwright/snapshots/components/Tooltip--experimental.test.ts-snapshots/Tooltip--experimental-Label-Type-dark-dimmed-linux.png new file mode 100644 index 00000000000..fcc1766d12f Binary files /dev/null and b/.playwright/snapshots/components/Tooltip--experimental.test.ts-snapshots/Tooltip--experimental-Label-Type-dark-dimmed-linux.png differ diff --git a/.playwright/snapshots/components/Tooltip--experimental.test.ts-snapshots/Tooltip--experimental-Label-Type-dark-high-contrast-linux.png b/.playwright/snapshots/components/Tooltip--experimental.test.ts-snapshots/Tooltip--experimental-Label-Type-dark-high-contrast-linux.png new file mode 100644 index 00000000000..3a218b7f43d Binary files /dev/null and b/.playwright/snapshots/components/Tooltip--experimental.test.ts-snapshots/Tooltip--experimental-Label-Type-dark-high-contrast-linux.png differ diff --git a/.playwright/snapshots/components/Tooltip--experimental.test.ts-snapshots/Tooltip--experimental-Label-Type-dark-linux.png b/.playwright/snapshots/components/Tooltip--experimental.test.ts-snapshots/Tooltip--experimental-Label-Type-dark-linux.png new file mode 100644 index 00000000000..87a08fc9903 Binary files /dev/null and b/.playwright/snapshots/components/Tooltip--experimental.test.ts-snapshots/Tooltip--experimental-Label-Type-dark-linux.png differ diff --git a/.playwright/snapshots/components/Tooltip--experimental.test.ts-snapshots/Tooltip--experimental-Label-Type-dark-tritanopia-linux.png b/.playwright/snapshots/components/Tooltip--experimental.test.ts-snapshots/Tooltip--experimental-Label-Type-dark-tritanopia-linux.png new file mode 100644 index 00000000000..5b80a53345e Binary files /dev/null and b/.playwright/snapshots/components/Tooltip--experimental.test.ts-snapshots/Tooltip--experimental-Label-Type-dark-tritanopia-linux.png differ diff --git a/.playwright/snapshots/components/Tooltip--experimental.test.ts-snapshots/Tooltip--experimental-Label-Type-light-colorblind-linux.png b/.playwright/snapshots/components/Tooltip--experimental.test.ts-snapshots/Tooltip--experimental-Label-Type-light-colorblind-linux.png new file mode 100644 index 00000000000..91e1e1c5962 Binary files /dev/null and b/.playwright/snapshots/components/Tooltip--experimental.test.ts-snapshots/Tooltip--experimental-Label-Type-light-colorblind-linux.png differ diff --git a/.playwright/snapshots/components/Tooltip--experimental.test.ts-snapshots/Tooltip--experimental-Label-Type-light-high-contrast-linux.png b/.playwright/snapshots/components/Tooltip--experimental.test.ts-snapshots/Tooltip--experimental-Label-Type-light-high-contrast-linux.png new file mode 100644 index 00000000000..962ca3360d2 Binary files /dev/null and b/.playwright/snapshots/components/Tooltip--experimental.test.ts-snapshots/Tooltip--experimental-Label-Type-light-high-contrast-linux.png differ diff --git a/.playwright/snapshots/components/Tooltip--experimental.test.ts-snapshots/Tooltip--experimental-Label-Type-light-linux.png b/.playwright/snapshots/components/Tooltip--experimental.test.ts-snapshots/Tooltip--experimental-Label-Type-light-linux.png new file mode 100644 index 00000000000..b22d8589d44 Binary files /dev/null and b/.playwright/snapshots/components/Tooltip--experimental.test.ts-snapshots/Tooltip--experimental-Label-Type-light-linux.png differ diff --git a/.playwright/snapshots/components/Tooltip--experimental.test.ts-snapshots/Tooltip--experimental-Label-Type-light-tritanopia-linux.png b/.playwright/snapshots/components/Tooltip--experimental.test.ts-snapshots/Tooltip--experimental-Label-Type-light-tritanopia-linux.png new file mode 100644 index 00000000000..91e1e1c5962 Binary files /dev/null and b/.playwright/snapshots/components/Tooltip--experimental.test.ts-snapshots/Tooltip--experimental-Label-Type-light-tritanopia-linux.png differ diff --git a/docs/content/drafts/Tooltip.mdx b/docs/content/drafts/Tooltip.mdx new file mode 100644 index 00000000000..237c49470da --- /dev/null +++ b/docs/content/drafts/Tooltip.mdx @@ -0,0 +1,76 @@ +--- +componentId: tooltip +title: Tooltip +status: Alpha +--- + +import data from '../../../src/drafts/Tooltip/Tooltip.docs.json' + +The Tooltip component is used to add context to interactive 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? See [Tooltip alternatives](https://primer.style/design/accessibility/tooltip-alternatives) for more accessible alternatives. + +Tooltip should only be used on an element that is interactive such as a button or a link. + +## Examples + +### Default (For additional context) + +Default tooltip is suitable for interactive controls that require additional context. + +```jsx live + + + +``` + +### As a label + +Tooltip can be used to label interactive controls that has no visible text label such as interactive icon links. + +```jsx live + + + + + +``` + +### With direction + +```jsx live + + + + + + + + + + + + + + + + + + + + + + + + + + +``` + +## Props + + diff --git a/e2e/components/Tooltip--experimental.test.ts b/e2e/components/Tooltip--experimental.test.ts new file mode 100644 index 00000000000..d497145e047 --- /dev/null +++ b/e2e/components/Tooltip--experimental.test.ts @@ -0,0 +1,175 @@ +import {test, expect} from '@playwright/test' +import {visit} from '../test-helpers/storybook' +import {themes} from '../test-helpers/themes' + +test.describe('Tooltip--experimental', () => { + test.describe('Default', () => { + for (const theme of themes) { + test.describe(theme, () => { + test('default @vrt', async ({page}) => { + await visit(page, { + id: 'drafts-components-tooltip--default', + globals: { + colorScheme: theme, + }, + }) + + // Default state + await page.keyboard.press('Tab') + expect(await page.screenshot({animations: 'disabled'})).toMatchSnapshot( + `Tooltip--experimental.Default.${theme}.png`, + { + threshold: 0.1, + }, + ) + }) + + test('axe @aat', async ({page}) => { + await visit(page, { + id: 'drafts-components-tooltip--default', + globals: { + colorScheme: theme, + }, + }) + await expect(page).toHaveNoViolations() + }) + }) + } + }) + + test.describe('Anchor Has Margin', () => { + for (const theme of themes) { + test.describe(theme, () => { + test('default @vrt', async ({page}) => { + await visit(page, { + id: 'drafts-components-tooltip-features--anchor-has-margin', + globals: { + colorScheme: theme, + }, + }) + + // Default state + await page.keyboard.press('Tab') + expect(await page.screenshot({animations: 'disabled'})).toMatchSnapshot( + `Tooltip--experimental.Anchor Has Margin.${theme}.png`, + { + threshold: 0.1, + }, + ) + }) + + test('axe @aat', async ({page}) => { + await visit(page, { + id: 'drafts-components-tooltip-features--anchor-has-margin', + globals: { + colorScheme: theme, + }, + }) + await expect(page).toHaveNoViolations() + }) + }) + } + }) + + test.describe('Calculated Direction', () => { + for (const theme of themes) { + test.describe(theme, () => { + test('default @vrt', async ({page}) => { + await visit(page, { + id: 'drafts-components-tooltip-features--calculated-direction', + globals: { + colorScheme: theme, + }, + }) + + // Default state + await page.keyboard.press('Tab') + expect(await page.screenshot({animations: 'disabled'})).toMatchSnapshot( + `Tooltip--experimental.Calculated Direction.${theme}.png`, + { + threshold: 0.1, + }, + ) + }) + + test('axe @aat', async ({page}) => { + await visit(page, { + id: 'drafts-components-tooltip-features--calculated-direction', + globals: { + colorScheme: theme, + }, + }) + await expect(page).toHaveNoViolations() + }) + }) + } + }) + + test.describe('Icon Button With Description', () => { + for (const theme of themes) { + test.describe(theme, () => { + test('default @vrt', async ({page}) => { + await visit(page, { + id: 'drafts-components-tooltip-features--icon-button-with-description', + globals: { + colorScheme: theme, + }, + }) + + // Default state + await page.keyboard.press('Tab') + expect(await page.screenshot({animations: 'disabled'})).toMatchSnapshot( + `Tooltip--experimental.Icon Button With Description.${theme}.png`, + { + threshold: 0.1, + }, + ) + }) + + test('axe @aat', async ({page}) => { + await visit(page, { + id: 'drafts-components-tooltip-features--icon-button-with-description', + globals: { + colorScheme: theme, + }, + }) + await expect(page).toHaveNoViolations() + }) + }) + } + }) + + test.describe('Label Type', () => { + for (const theme of themes) { + test.describe(theme, () => { + test('default @vrt', async ({page}) => { + await visit(page, { + id: 'drafts-components-tooltip-features--label-type', + globals: { + colorScheme: theme, + }, + }) + + // Default state + await page.keyboard.press('Tab') + expect(await page.screenshot({animations: 'disabled'})).toMatchSnapshot( + `Tooltip--experimental.Label Type.${theme}.png`, + { + threshold: 0.1, + }, + ) + }) + + test('axe @aat', async ({page}) => { + await visit(page, { + id: 'drafts-components-tooltip-features--label-type', + globals: { + colorScheme: theme, + }, + }) + await expect(page).toHaveNoViolations() + }) + }) + } + }) +}) diff --git a/jest.config.js b/jest.config.js index 33620627bd5..0ca24b1d7d9 100644 --- a/jest.config.js +++ b/jest.config.js @@ -2,6 +2,7 @@ 'use strict' +const path = require('node:path') const {REACT_VERSION_17} = process.env /** @@ -17,12 +18,21 @@ module.exports = { collectCoverageFrom: ['src/**/*.{js,jsx,ts,tsx}', '!src/stories/**', '!**/*.stories.{js,jsx,ts,tsx}'], moduleNameMapper: { '\\.css$': 'jest-css-modules', + // We need to specify this package subpath because it does not provide a `require` conditional export path + '@oddbird/popover-polyfill/fn': path.join( + __dirname, + 'node_modules', + '@oddbird', + 'popover-polyfill', + 'dist', + 'popover-fn.js', + ), }, setupFiles: ['/src/utils/test-helpers.tsx'], setupFilesAfterEnv: ['/src/utils/test-matchers.tsx', '/src/utils/test-deprecations.tsx'], testMatch: ['/(src|codemods)/**/*.test.[jt]s?(x)', '!**/*.types.test.[jt]s?(x)'], transformIgnorePatterns: [ - 'node_modules/(?!@github/combobox-nav|@koddsson/textarea-caret|@github/[a-z-]+-element|@lit-labs/react)', + 'node_modules/(?!@github/combobox-nav|@koddsson/textarea-caret|@github/[a-z-]+-element|@lit-labs/react|@oddbird/popover-polyfill)', ], watchPlugins: ['jest-watch-typeahead/filename', 'jest-watch-typeahead/testname'], } diff --git a/package-lock.json b/package-lock.json index 8e3ee50473c..5b21142c515 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,6 +14,7 @@ "@github/paste-markdown": "^1.4.0", "@github/relative-time-element": "^4.1.2", "@lit-labs/react": "^1.1.1", + "@oddbird/popover-polyfill": "0.2.2", "@primer/behaviors": "^1.3.4", "@primer/octicons-react": "^19.7.0", "@primer/primitives": "^7.11.11", @@ -6066,8 +6067,7 @@ "node_modules/@oddbird/popover-polyfill": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/@oddbird/popover-polyfill/-/popover-polyfill-0.2.2.tgz", - "integrity": "sha512-ko7x+PDZA9bHwA6hSfxjL1IhBP91JukfZq/NAe85u9rT0akFn9RKvSXymX/mS7S2mfNjE+Zw9JdLUPGvPabQAA==", - "dev": true + "integrity": "sha512-ko7x+PDZA9bHwA6hSfxjL1IhBP91JukfZq/NAe85u9rT0akFn9RKvSXymX/mS7S2mfNjE+Zw9JdLUPGvPabQAA==" }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", @@ -6245,6 +6245,12 @@ "@primer/behaviors": "^1.3.4" } }, + "node_modules/@primer/view-components/node_modules/@oddbird/popover-polyfill": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@oddbird/popover-polyfill/-/popover-polyfill-0.1.1.tgz", + "integrity": "sha512-X9gxiwKtN1ZumOoe9KRLpe37nshLtwHm/IJflIxgjanXz/FqKb0DQ7BlWu+iqUn/O0/jWYgkKnTLtsC9JlgwQg==", + "dev": true + }, "node_modules/@react-aria/ssr": { "version": "3.5.0", "resolved": "https://registry.npmjs.org/@react-aria/ssr/-/ssr-3.5.0.tgz", diff --git a/package.json b/package.json index df326df263a..e05f9a718b0 100644 --- a/package.json +++ b/package.json @@ -99,6 +99,7 @@ "@github/paste-markdown": "^1.4.0", "@github/relative-time-element": "^4.1.2", "@lit-labs/react": "^1.1.1", + "@oddbird/popover-polyfill": "0.2.2", "@primer/behaviors": "^1.3.4", "@primer/octicons-react": "^19.7.0", "@primer/primitives": "^7.11.11", diff --git a/rollup.config.js b/rollup.config.js index 2689076a07d..a32f34a765b 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -63,6 +63,7 @@ const ESM_ONLY = new Set([ '@github/paste-markdown', '@github/relative-time-element', '@lit-labs/react', + '@oddbird/popover-polyfill', ]) const dependencies = [ ...Object.keys(packageJson.peerDependencies ?? {}), diff --git a/script/generate-e2e-tests.js b/script/generate-e2e-tests.js index 8ab57ab8525..848737fdfed 100644 --- a/script/generate-e2e-tests.js +++ b/script/generate-e2e-tests.js @@ -1404,6 +1404,45 @@ const components = new Map([ ], }, ], + [ + 'Tooltip--experimental', + { + stories: [ + { + id: 'drafts-components-tooltip--default', + name: 'Default', + }, + { + id: 'drafts-components-tooltip-features--anchor-has-margin', + name: 'Anchor Has Margin', + }, + { + id: 'drafts-components-tooltip-features--calculated-direction', + name: 'Calculated Direction', + }, + { + id: 'drafts-components-tooltip-features--icon-button-with-description', + name: 'Icon Button With Description', + }, + { + id: 'drafts-components-tooltip-features--label-type', + name: 'Label Type', + }, + { + id: 'drafts-components-tooltip-features--multiline-text', + name: 'Multiline Text', + }, + { + id: 'drafts-components-tooltip-features--on-action-menu-anchor', + name: 'On Action Menu Anchor', + }, + { + id: 'drafts-components-tooltip-examples--files-page', + name: 'Files Page Example', + }, + ], + }, + ], [ 'TreeView', { diff --git a/src/ActionMenu/ActionMenu.tsx b/src/ActionMenu/ActionMenu.tsx index 31b68990978..724532ada11 100644 --- a/src/ActionMenu/ActionMenu.tsx +++ b/src/ActionMenu/ActionMenu.tsx @@ -9,6 +9,7 @@ import {Button, ButtonProps} from '../Button' import {useId} from '../hooks/useId' import {MandateProps} from '../utils/types' import {ForwardRefComponent as PolymorphicForwardRefComponent} from '../utils/polymorphic' +import {Tooltip} from '../drafts/Tooltip/Tooltip' export type MenuContextProps = Pick< AnchoredOverlayProps, @@ -48,16 +49,47 @@ const Menu: React.FC> = ({ const anchorRef = useProvidedRefOrCreate(externalAnchorRef) const anchorId = useId() let renderAnchor: AnchoredOverlayProps['renderAnchor'] = null - // 🚨 Hack for good API! // we strip out Anchor from children and pass it to AnchoredOverlay to render // with additional props for accessibility + // 🚨 Accounting for Tooltip wrapping ActionMenu.Button or being a direct child of ActionMenu.Anchor. const contents = React.Children.map(children, child => { - if (child.type === MenuButton || child.type === Anchor) { + // Is ActionMenu.Button wrapped with Tooltip? If this is the case, our anchor is the tooltip's trigger (ActionMenu.Button's grandchild) + if (child.type === Tooltip) { + // tooltip trigger + const anchorChildren = child.props.children + if (anchorChildren.type === MenuButton) { + renderAnchor = anchorProps => { + // We need to attach the anchor props to the tooltip trigger (ActionMenu.Button's grandchild) not the tooltip itself. + const triggerButton = React.cloneElement(anchorChildren, {...anchorProps}) + return React.cloneElement(child, {children: triggerButton, ref: anchorRef}) + } + } + return null + } else if (child.type === Anchor) { + const anchorChildren = child.props.children + const isWrappedWithTooltip = anchorChildren !== undefined ? anchorChildren.type === Tooltip : false + if (isWrappedWithTooltip) { + if (anchorChildren.props.children !== null) { + renderAnchor = anchorProps => { + // ActionMenu.Anchor's children can be wrapped with Tooltip. If this is the case, our anchor is the tooltip's trigger + const tooltipTrigger = anchorChildren.props.children + // We need to attach the anchor props to the tooltip trigger not the tooltip itself. + const tooltipTriggerEl = React.cloneElement(tooltipTrigger, {...anchorProps}) + const tooltip = React.cloneElement(anchorChildren, {children: tooltipTriggerEl}) + return React.cloneElement(child, {children: tooltip, ref: anchorRef}) + } + } + } else { + renderAnchor = anchorProps => React.cloneElement(child, anchorProps) + } + return null + } else if (child.type === MenuButton) { renderAnchor = anchorProps => React.cloneElement(child, anchorProps) return null + } else { + return child } - return child }) return ( diff --git a/src/__tests__/ActionMenu.test.tsx b/src/__tests__/ActionMenu.test.tsx index bf33d15c491..aa9d4024b07 100644 --- a/src/__tests__/ActionMenu.test.tsx +++ b/src/__tests__/ActionMenu.test.tsx @@ -3,7 +3,8 @@ import userEvent from '@testing-library/user-event' import {axe, toHaveNoViolations} from 'jest-axe' import React from 'react' import theme from '../theme' -import {ActionMenu, ActionList, BaseStyles, ThemeProvider, SSRProvider} from '..' +import {ActionMenu, ActionList, BaseStyles, ThemeProvider, SSRProvider, Tooltip, Button} from '..' +import {Tooltip as TooltipV2} from '../drafts/Tooltip/Tooltip' import {behavesAsComponent, checkExports} from '../utils/testing' import {SingleSelect} from '../ActionMenu/ActionMenu.features.stories' import {MixedSelection} from '../ActionMenu/ActionMenu.examples.stories' @@ -37,6 +38,46 @@ function Example(): JSX.Element { ) } +function ExampleWithTooltip(): JSX.Element { + return ( + + + + + + Toggle Menu + + + New file + + + + + + + + ) +} + +function ExampleWithTooltipV2(actionMenuTrigger: React.ReactElement): JSX.Element { + return ( + + + + + {actionMenuTrigger} + + + New file + + + + + + + ) +} + describe('ActionMenu', () => { behavesAsComponent({ Component: ActionList, @@ -244,4 +285,83 @@ describe('ActionMenu', () => { const results = await axe(container) expect(results).toHaveNoViolations() }) + + it('should open menu on menu button click and it is wrapped with tooltip', async () => { + const component = HTMLRender() + const button = component.getByRole('button') + + const user = userEvent.setup() + await user.click(button) + + expect(component.getByRole('menu')).toBeInTheDocument() + }) + + it('should open menu on menu button click and it is wrapped with tooltip v2', async () => { + const component = HTMLRender( + ExampleWithTooltipV2( + + Toggle Menu + , + ), + ) + const button = component.getByRole('button') + + const user = userEvent.setup() + await user.click(button) + + expect(component.getByRole('menu')).toBeInTheDocument() + }) + + it('should display tooltip when menu button is focused', async () => { + const component = HTMLRender() + const button = component.getByRole('button') + button.focus() + expect(component.getByRole('tooltip')).toBeInTheDocument() + }) + + it('should display tooltip v2 when menu button is focused', async () => { + const component = HTMLRender( + ExampleWithTooltipV2( + + Toggle Menu + , + ), + ) + const button = component.getByRole('button') + button.focus() + expect(component.getByRole('tooltip')).toBeInTheDocument() + }) + + it('should open menu on menu anchor click and it is wrapped with tooltip v2', async () => { + const component = HTMLRender( + ExampleWithTooltipV2( + + + + + , + ), + ) + const button = component.getByRole('button') + + const user = userEvent.setup() + await user.click(button) + + expect(component.getByRole('menu')).toBeInTheDocument() + }) + + it('should display tooltip v2 and menu anchor is focused', async () => { + const component = HTMLRender( + ExampleWithTooltipV2( + + + + + , + ), + ) + const button = component.getByRole('button') + button.focus() + expect(component.getByRole('tooltip')).toBeInTheDocument() + }) }) diff --git a/src/__tests__/__snapshots__/exports.test.ts.snap b/src/__tests__/__snapshots__/exports.test.ts.snap index 2cf204be6c4..28fbb4963c9 100644 --- a/src/__tests__/__snapshots__/exports.test.ts.snap +++ b/src/__tests__/__snapshots__/exports.test.ts.snap @@ -153,6 +153,8 @@ exports[`@primer/react/drafts should not update exports without a semver change "SegmentedControl", "SplitPageLayout", "Table", + "Tooltip", + "TooltipContext", "TreeView", "UnderlineNav", "callbackCancelledResult", diff --git a/src/drafts/Tooltip/Tooltip.docs.json b/src/drafts/Tooltip/Tooltip.docs.json new file mode 100644 index 00000000000..cfed6a9ae3f --- /dev/null +++ b/src/drafts/Tooltip/Tooltip.docs.json @@ -0,0 +1,31 @@ +{ + "id": "tooltip", + "name": "Tooltip", + "status": "draft", + "a11yReviewed": false, + "stories": [], + "props": [ + { + "name": "direction", + "type": "'n' | 'ne' | 'e' | 'se' | 's' | 'sw' | 'w' | 'nw'", + "defaultValue": "s", + "description": "Sets where the tooltip renders in relation to the target." + }, + { + "name": "text", + "type": "string", + "description": "The text to be displayed in the tooltip" + }, + { + "name": "type", + "type": "'label' | 'description'", + "defaultValue": "description", + "description": "The type of tooltip. `label` is used for labelling the element that triggers tooltip. `description` is used for describing or adding a suplementary information to the element that triggers the tooltip." + }, + { + "name": "sx", + "type": "SystemStyleObject" + } + ], + "subcomponents": [] +} diff --git a/src/drafts/Tooltip/Tooltip.examples.stories.tsx b/src/drafts/Tooltip/Tooltip.examples.stories.tsx new file mode 100644 index 00000000000..dde9a5a65ad --- /dev/null +++ b/src/drafts/Tooltip/Tooltip.examples.stories.tsx @@ -0,0 +1,179 @@ +import React from 'react' +import {Button, IconButton, Breadcrumbs, ActionMenu, ActionList, NavList} from '../..' +import {PageHeader} from '../../PageHeader' +import {Tooltip} from './Tooltip' +import { + GitBranchIcon, + KebabHorizontalIcon, + TriangleDownIcon, + CheckIcon, + PeopleIcon, + SmileyIcon, + EyeIcon, + CommentIcon, +} from '@primer/octicons-react' +import {default as VisuallyHidden} from '../../_VisuallyHidden' + +export default { + title: 'Drafts/Components/Tooltip/Examples', + component: Tooltip, +} + +export const FilesPage = () => ( + + + Files + + + + + + + + + + alert('Main')}> + + + + main default + + alert('Branch 1')}>branch-1 + alert('Branch 2')}>branch-2 + + + + + + + + + + + alert('Download')}>Download + + + alert('Jump to line')}> + Jump to line + L + + + alert('Copy path')}> + Copy path + ⌘⇧. + + alert('Copy permalink')}> + Copy permalink + ⌘⇧, + + + + alert('Show code folding buttons')}> + Show code folding buttons + + alert('Wrap lines')}>Wrap lines + alert('Center content')}>Center content + + + alert('Delete file clicked')}> + Delete file + ⌘D + + + + + + + + + react + src + PageHeader + + PageHeader.tsx + + + PageHeader.tsx + + + +) + +FilesPage.parameters = { + viewport: { + defaultViewport: 'small', + }, +} + +export const Hyperlist = () => ( + + + + + + Assigned to me + + + + + + + Created by me + + + + + + + Mentioned + + + + + + Recent activity + + +) diff --git a/src/drafts/Tooltip/Tooltip.features.stories.tsx b/src/drafts/Tooltip/Tooltip.features.stories.tsx new file mode 100644 index 00000000000..d216e5e5413 --- /dev/null +++ b/src/drafts/Tooltip/Tooltip.features.stories.tsx @@ -0,0 +1,175 @@ +import React from 'react' +import {IconButton, Button, Box, Link, StyledOcticon, ActionMenu, ActionList} from '../..' +import {Tooltip} from './Tooltip' +import {SearchIcon, BookIcon, CheckIcon, TriangleDownIcon, GitBranchIcon} from '@primer/octicons-react' + +export default { + title: 'Drafts/Components/Tooltip/Features', + component: Tooltip, +} + +export const AnchorHasMargin = () => ( + + + + + +) + +export const LabelType = () => ( + + + + + + + +) + +// As a supplementary description for a button +export const DescriptionType = () => ( + + + + + +) + +// As a supplementary description for an IconButton +export const IconButtonWithDescription = () => ( + + + + + +) + +export const AllDirections = () => ( + + + + + + + + + + + + + + + + + + + + + + + + + + +) + +export const MultilineText = () => ( + + + + + +) + +export const CalculatedDirection = () => ( + + + + + + + + + +) + +export const OnActionMenuAnchor = () => ( + + + + + + + + + + alert('Main')}> + + + + main default + + alert('Branch 1')}>branch-1 + alert('Branch 2')}>branch-2 + + + + + + ActionMenu.Button w/ t + + + + alert('Main')}> + + + + main default + + alert('Branch 1')}>branch-1 + alert('Branch 2')}>branch-2 + + + + + + + + + + alert('Main')}> + + + + main default + + alert('Branch 1')}>branch-1 + alert('Branch 2')}>branch-2 + + + + + ActionMenu.Button + + + + alert('Main')}> + + + + main default + + alert('Branch 1')}>branch-1 + alert('Branch 2')}>branch-2 + + + + +) diff --git a/src/drafts/Tooltip/Tooltip.playground.stories.tsx b/src/drafts/Tooltip/Tooltip.playground.stories.tsx new file mode 100644 index 00000000000..c0e303e55e4 --- /dev/null +++ b/src/drafts/Tooltip/Tooltip.playground.stories.tsx @@ -0,0 +1,29 @@ +import React from 'react' +import {Button, Box} from '../..' +import {Tooltip} from './Tooltip' +import {Meta, StoryFn} from '@storybook/react' + +export default { + title: 'Drafts/Components/Tooltip/Playground', + component: Tooltip, + + args: { + text: 'This is the tooltip text', + direction: 's', + type: 'description', + }, +} as Meta + +// Description type, north direction by default +export const Playground: StoryFn = args => { + // this is a hack to remove the `type` prop from the args because for this example type label is not a valid choice and violates accessibility + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const {type, ...rest} = args + return ( + + + + + + ) +} diff --git a/src/drafts/Tooltip/Tooltip.stories.tsx b/src/drafts/Tooltip/Tooltip.stories.tsx new file mode 100644 index 00000000000..3c489bc1b88 --- /dev/null +++ b/src/drafts/Tooltip/Tooltip.stories.tsx @@ -0,0 +1,17 @@ +import React from 'react' +import {Button, Box} from '../..' +import {Tooltip} from './Tooltip' + +export default { + title: 'Drafts/Components/Tooltip', + component: Tooltip, +} + +// Description type, north direction by default +export const Default = () => ( + + + + + +) diff --git a/src/drafts/Tooltip/Tooltip.tsx b/src/drafts/Tooltip/Tooltip.tsx new file mode 100644 index 00000000000..feddfb6bea3 --- /dev/null +++ b/src/drafts/Tooltip/Tooltip.tsx @@ -0,0 +1,324 @@ +import React, {Children, useEffect, useRef, useState} from 'react' +import Box from '../../Box' +import sx, {SxProp} from '../../sx' +import {useId, useProvidedRefOrCreate} from '../../hooks' +import {invariant} from '../../utils/invariant' +import {warning} from '../../utils/warning' +import styled from 'styled-components' +import {get} from '../../constants' +import {ComponentProps} from '../../utils/types' +import {getAnchoredPosition} from '@primer/behaviors' +import type {AnchorSide, AnchorAlignment} from '@primer/behaviors' +import {isSupported, apply} from '@oddbird/popover-polyfill/fn' + +// Reusable styles to use for :popover-open (Chrome, Edge) and \:popover-open (Safari, Firefox) classes +const popoverStyles = ` + padding: 0.5em 0.75em; + width: max-content; + height: fit-content; + margin: auto; + clip: auto; + white-space: normal; + /* for scrollbar */ + overflow: visible; +` + +const animationStyles = ` + animation-name: tooltip-appear; + animation-duration: 0.1s; + animation-fill-mode: forwards; + animation-timing-function: ease-in; + animation-delay: 0s; +` + +const StyledTooltip = styled.div` + /* tooltip element should be rendered visually hidden when it is not opened. */ + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + position: fixed; + font: normal normal 11px/1.5 ${get('fonts.normal')}; + -webkit-font-smoothing: subpixel-antialiased; + color: ${get('colors.fg.onEmphasis')}; + text-align: center; + + word-wrap: break-word; + background: ${get('colors.neutral.emphasisPlus')}; //bg--emphasis-color + border-radius: ${get('radii.2')}; + border: 0; + opacity: 0; + max-width: 250px; + inset: auto; + + @media (forced-colors: active) { + outline: 1px solid transparent; + } + /* pollyfil */ + z-index: 2147483647; + display: block; + + /* class name in chrome is :popover-open */ + &:popover-open { + ${popoverStyles} + } + + /* class name in firefox and safari is \:popover-open */ + &.\\:popover-open { + ${popoverStyles} + } + + // This is needed to keep the tooltip open when the user leaves the trigger element to hover tooltip + &::after { + position: absolute; + display: block; + right: 0; + left: 0; + height: 8px; + content: ''; + } + + /* South, East, Southeast, Southwest after */ + &[data-direction='n']::after, + &[data-direction='ne']::after, + &[data-direction='nw']::after { + top: 100%; + } + &[data-direction='s']::after, + &[data-direction='se']::after, + &[data-direction='sw']::after { + bottom: 100%; + } + + &[data-direction='w']::after { + position: absolute; + display: block; + height: 100%; + width: 8px; + content: ''; + bottom: 0; + left: 100%; + } + /* East before and after */ + &[data-direction='e']::after { + position: absolute; + display: block; + height: 100%; + width: 8px; + content: ''; + bottom: 0; + right: 100%; + margin-left: -8px; + } + + /* Animation definition */ + @keyframes tooltip-appear { + from { + opacity: 0; + } + to { + opacity: 1; + } + } + /* Animation styles */ + &:popover-open, + &:popover-open::before { + ${animationStyles} + } + + /* Animation styles */ + &.\\:popover-open, + &.\\:popover-open::before { + ${animationStyles} + } + + ${sx}; +` + +type TooltipDirection = 'nw' | 'n' | 'ne' | 'e' | 'se' | 's' | 'sw' | 'w' +export type TooltipProps = React.PropsWithChildren< + { + direction?: TooltipDirection + text?: string + type?: 'label' | 'description' + } & SxProp & + ComponentProps +> + +export type TriggerPropsType = { + 'aria-describedby'?: string + 'aria-labelledby'?: string + 'aria-label'?: string + onBlur?: React.FocusEventHandler + onFocus?: React.FocusEventHandler + onMouseEnter?: React.MouseEventHandler + ref?: React.RefObject +} + +// 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 anchoredPosition props to tooltip direction +const positionToDirection: Record = { + 'outside-top-start': 'nw', + 'outside-top-center': 'n', + 'outside-top-end': 'ne', + 'outside-right-center': 'e', + 'outside-bottom-end': 'se', + 'outside-bottom-center': 's', + 'outside-bottom-start': 'sw', + 'outside-left-center': 'w', +} + +// The list is from GitHub's custom-axe-rules https://github.com/github/github/blob/master/app/assets/modules/github/axe-custom-rules.ts#L3 +const interactiveElements = [ + 'a[href]', + 'button:not(:disabled)', + 'summary', + 'select', + 'input:not([type=hidden])', + 'textarea', +] + +const isInteractive = (element: HTMLElement) => { + return ( + interactiveElements.some(selector => element.matches(selector)) || + (element.hasAttribute('role') && element.getAttribute('role') === 'button') + ) +} +export const TooltipContext = React.createContext<{tooltipId?: string}>({}) + +export const Tooltip = React.forwardRef( + ({direction = 's', text, type = 'description', children, ...rest}: TooltipProps, forwardedRef) => { + const tooltipId = useId() + const child = Children.only(children) + const triggerRef = useProvidedRefOrCreate(forwardedRef as React.RefObject) + const tooltipElRef = useRef(null) + + const [calculatedDirection, setCalculatedDirection] = useState(direction) + + const openTooltip = () => { + if (tooltipElRef.current && triggerRef.current && !tooltipElRef.current.matches(':popover-open')) { + tooltipElRef.current.showPopover() + } + } + const closeTooltip = () => { + if (tooltipElRef.current && triggerRef.current && tooltipElRef.current.matches(':popover-open')) { + tooltipElRef.current.hidePopover() + } + } + + useEffect(() => { + if (!tooltipElRef.current || !triggerRef.current) return + /* + * ACCESSIBILITY CHECKS + */ + // Has trigger element or any of its children interactive elements? + const isTriggerInteractive = isInteractive(triggerRef.current) + const triggerChildren = triggerRef.current.childNodes + const hasInteractiveChild = Array.from(triggerChildren).some(child => { + return child instanceof HTMLElement && isInteractive(child) + }) + invariant( + isTriggerInteractive || hasInteractiveChild, + 'The `Tooltip` component expects a single React element that contains interactive content. Consider using a ` + +) + +function ExampleWithActionMenu(actionMenuTrigger: React.ReactElement): JSX.Element { + return ( + + + + + {actionMenuTrigger} + + + New file + + + + + + + ) +} + +describe('Tooltip', () => { + checkStoriesForAxeViolations('Tooltip.features', '../drafts/Tooltip/') + + it('renders `data-direction="s"` by default', () => { + const {getByText} = HTMLRender() + expect(getByText('Tooltip text')).toHaveAttribute('data-direction', 's') + }) + it('renders `data-direction` attribute with the correct value when the `direction` prop is specified', () => { + const {getByText} = HTMLRender() + expect(getByText('Tooltip text')).toHaveAttribute('data-direction', 'n') + }) + it('should label the trigger element by its tooltip when the tooltip type is label', () => { + const {getByRole, getByText} = HTMLRender() + const triggerEL = getByRole('button') + const tooltipEl = getByText('Tooltip text') + expect(triggerEL).toHaveAttribute('aria-labelledby', tooltipEl.id) + }) + it('should render aria-hidden on the tooltip element when the tooltip is label type', () => { + const {getByText} = HTMLRender() + expect(getByText('Tooltip text')).toHaveAttribute('aria-hidden', 'true') + }) + it('should describe the trigger element by its tooltip when the tooltip type is description (by default)', () => { + const {getByRole, getByText} = HTMLRender() + const triggerEL = getByRole('button') + const tooltipEl = getByText('Tooltip text') + expect(triggerEL).toHaveAttribute('aria-describedby', tooltipEl.id) + }) + it('should render the tooltip element with role="tooltip" when the tooltip type is description (by default)', () => { + const {getByText} = HTMLRender() + expect(getByText('Tooltip text')).toHaveAttribute('role', 'tooltip') + }) + + it('should spread the accessibility attributes correctly on the trigger (ActionMenu.Button) when tooltip is used in an action menu', () => { + const {getByRole, getByText} = HTMLRender( + ExampleWithActionMenu( + + Toggle Menu + , + ), + ) + const menuButton = getByRole('button') + const tooltip = getByText('Additional context about the menu button') + expect(menuButton).toHaveAttribute('aria-describedby', tooltip.id) + expect(menuButton).toHaveAttribute('aria-haspopup', 'true') + }) + + it('should spread the accessibility attributes correctly on the trigger (Button) when tooltip is used in an action menu', () => { + const {getByRole, getByText} = HTMLRender( + ExampleWithActionMenu( + + + + + , + ), + ) + const menuButton = getByRole('button') + const tooltip = getByText('Additional context about the menu button') + expect(menuButton).toHaveAttribute('aria-describedby', tooltip.id) + expect(menuButton).toHaveAttribute('aria-haspopup', 'true') + }) +}) diff --git a/src/drafts/Tooltip/index.ts b/src/drafts/Tooltip/index.ts new file mode 100644 index 00000000000..ba15f407377 --- /dev/null +++ b/src/drafts/Tooltip/index.ts @@ -0,0 +1 @@ +export * from './Tooltip' diff --git a/src/drafts/index.ts b/src/drafts/index.ts index c58163e46eb..54e66cbd629 100644 --- a/src/drafts/index.ts +++ b/src/drafts/index.ts @@ -55,6 +55,7 @@ export * from '../TreeView' export * from '../NavList' export * from '../SegmentedControl' export * from '../SplitPageLayout' +export * from './Tooltip' // CSS Experiment // export * from './CSSComponent' diff --git a/src/hooks/index.ts b/src/hooks/index.ts index 97de8c06c0d..01d1d97c4aa 100644 --- a/src/hooks/index.ts +++ b/src/hooks/index.ts @@ -14,3 +14,4 @@ export {useMenuInitialFocus} from './useMenuInitialFocus' export {useMenuKeyboardNavigation} from './useMenuKeyboardNavigation' export {useMnemonics} from './useMnemonics' export {useRefObjectAsForwardedRef} from './useRefObjectAsForwardedRef' +export {useId} from './useId'