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
+
+
+
+
+
+
+
+
+ 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
+
+
+
+
+
+
+
+)
+
+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 `