-
Notifications
You must be signed in to change notification settings - Fork 2.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(web-components): add Tooltip component (#32852)
- Loading branch information
1 parent
b9672fa
commit 70d3d23
Showing
15 changed files
with
803 additions
and
1 deletion.
There are no files selected for viewing
7 changes: 7 additions & 0 deletions
7
change/@fluentui-web-components-037cbaec-4ee8-4f24-8cb7-1ae8ea8546f4.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
{ | ||
"type": "prerelease", | ||
"comment": "feat: add Tooltip component", | ||
"packageName": "@fluentui/web-components", | ||
"email": "[email protected]", | ||
"dependentChangeType": "patch" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
<script id="anchor-polyfill" type="module"> | ||
if (!CSS.supports('anchor-name: --foo')) { | ||
const { default: applyPolyfill } = await import( | ||
'https://unpkg.com/@oddbird/css-anchor-positioning/dist/css-anchor-positioning-fn.js' | ||
); | ||
window.CSS_ANCHOR_POLYFILL = applyPolyfill; | ||
} | ||
</script> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
import { FluentDesignSystem } from '../fluent-design-system.js'; | ||
import { definition } from './tooltip.definition.js'; | ||
|
||
definition.define(FluentDesignSystem.registry); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
export { definition as TooltipDefinition } from './tooltip.definition.js'; | ||
export { Tooltip } from './tooltip.js'; | ||
export { TooltipPositioningOption } from './tooltip.options.js'; | ||
export { styles as TooltipStyles } from './tooltip.styles.js'; | ||
export { template as TooltipTemplate } from './tooltip.template.js'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
import { FluentDesignSystem } from '../fluent-design-system.js'; | ||
import { Tooltip } from './tooltip.js'; | ||
import { styles } from './tooltip.styles.js'; | ||
import { template } from './tooltip.template.js'; | ||
|
||
/** | ||
* The {@link Tooltip } custom element definition. | ||
* | ||
* @public | ||
* @remarks | ||
* HTML Element: `<fluent-tooltip>` | ||
*/ | ||
export const definition = Tooltip.compose({ | ||
name: `${FluentDesignSystem.prefix}-tooltip`, | ||
template, | ||
styles, | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
import type { ValuesOf } from '../utils/typings.js'; | ||
|
||
/** | ||
* The TooltipPositioning options and their corresponding CSS values | ||
* @public | ||
*/ | ||
export const TooltipPositioningOption = { | ||
'above-start': 'block-start span-inline-end', | ||
above: 'block-start', | ||
'above-end': 'block-start span-inline-start', | ||
'below-start': 'block-end span-inline-end', | ||
below: 'block-end', | ||
'below-end': 'block-end span-inline-start', | ||
'before-top': 'inline-start span-block-end', | ||
before: 'inline-start', | ||
'before-bottom': 'inline-start span-block-start', | ||
'after-top': 'inline-end span-block-end', | ||
after: 'inline-end', | ||
'after-bottom': 'inline-end span-block-start', | ||
} as const; | ||
|
||
/** | ||
* The TooltipPositioning type | ||
* @public | ||
*/ | ||
export type TooltipPositioningOption = ValuesOf<typeof TooltipPositioningOption>; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,160 @@ | ||
import { test } from '@playwright/test'; | ||
import { expect, fixtureURL } from '../helpers.tests.js'; | ||
import type { Tooltip } from './tooltip.js'; | ||
import type { TooltipPositioningOption } from './tooltip.options.js'; | ||
|
||
test.describe('Tooltip', () => { | ||
test.beforeEach(async ({ page }) => { | ||
await page.goto(fixtureURL('components-tooltip--docs')); | ||
await page.waitForFunction(() => customElements.whenDefined('fluent-tooltip')); | ||
|
||
await page.setContent(/* html */ ` | ||
<div style="position: absolute; inset: 200px"> | ||
<button id="target">Target</button> | ||
<fluent-tooltip anchor="target">This is a tooltip</fluent-tooltip> | ||
</div> | ||
`); | ||
}); | ||
|
||
/** | ||
* ARIA APG Tooltip Pattern {@link https://www.w3.org/WAI/ARIA/apg/patterns/tooltip/ } | ||
* ESC dismisses the tooltip. | ||
* The element that serves as the tooltip container has role tooltip. | ||
* The element that triggers the tooltip references the tooltip element with aria-describedby. | ||
*/ | ||
test('escape key should hide the tooltip', async ({ page }) => { | ||
const element = page.locator('fluent-tooltip'); | ||
const button = page.locator('button'); | ||
|
||
await button.focus(); | ||
await expect(element).toBeVisible(); | ||
await page.keyboard.press('Escape'); | ||
await expect(element).toBeHidden(); | ||
}); | ||
|
||
test('should have the role set to `tooltip`', async ({ page }) => { | ||
const element = page.locator('fluent-tooltip'); | ||
await expect(element).toHaveJSProperty('elementInternals.role', 'tooltip'); | ||
}); | ||
|
||
test('should have the `aria-describedby` attribute set to the tooltip id', async ({ page }) => { | ||
const element = page.locator('fluent-tooltip'); | ||
const button = page.locator('button'); | ||
|
||
await expect(element).toHaveAttribute('id'); | ||
const id = await element.evaluate((node: Tooltip) => node.id); | ||
await expect(button).toHaveAttribute('aria-describedby', id); | ||
}); | ||
|
||
test('should not be visible by default', async ({ page }) => { | ||
const element = page.locator('fluent-tooltip'); | ||
await expect(element).toBeHidden(); | ||
}); | ||
|
||
test('should show the tooltip on hover', async ({ page }) => { | ||
const element = page.locator('fluent-tooltip'); | ||
const button = page.locator('button'); | ||
|
||
await expect(element).toBeHidden(); | ||
await button.hover(); | ||
await expect(element).toBeVisible(); | ||
}); | ||
|
||
test('should show the tooltip on focus', async ({ page }) => { | ||
const element = page.locator('fluent-tooltip'); | ||
const button = page.locator('button'); | ||
|
||
await expect(element).toBeHidden(); | ||
await button.focus(); | ||
await expect(element).toBeVisible(); | ||
await button.blur(); | ||
await expect(element).toBeHidden(); | ||
}); | ||
|
||
test('default placement should be set to `above`', async ({ page }) => { | ||
const element = page.locator('fluent-tooltip'); | ||
const button = page.locator('button'); | ||
await expect(element).not.toHaveAttribute('positioning', 'above'); | ||
|
||
// show the element to get the position | ||
await button.focus(); | ||
await expect(element).toBeVisible(); | ||
|
||
const buttonTop = await button.evaluate((node: HTMLElement) => node.getBoundingClientRect().top); | ||
const elementBottom = await element.evaluate((node: HTMLElement) => node.getBoundingClientRect().bottom); | ||
|
||
await expect(buttonTop).toBeGreaterThan(elementBottom); | ||
}); | ||
|
||
test('position should be set to `above` when `positioning` is set to `above`', async ({ page }) => { | ||
const element = page.locator('fluent-tooltip'); | ||
const button = page.locator('button'); | ||
|
||
await element.evaluate((node: Tooltip) => { | ||
node.positioning = 'above' as TooltipPositioningOption; | ||
}); | ||
await expect(element).toHaveAttribute('positioning', 'above'); | ||
|
||
// show the element to get the position | ||
await button.focus(); | ||
|
||
const buttonTop = await button.evaluate((node: HTMLElement) => node.getBoundingClientRect().top); | ||
const elementBottom = await element.evaluate((node: HTMLElement) => node.getBoundingClientRect().bottom); | ||
|
||
await expect(buttonTop).toBeGreaterThan(elementBottom); | ||
}); | ||
|
||
test('position should be set to `below` when `positioning` is set to `below`', async ({ page }) => { | ||
const element = page.locator('fluent-tooltip'); | ||
const button = page.locator('button'); | ||
|
||
await element.evaluate((node: Tooltip) => { | ||
node.positioning = 'below' as TooltipPositioningOption; | ||
}); | ||
await expect(element).toHaveAttribute('positioning', 'below'); | ||
|
||
// show the element to get the position | ||
await button.focus(); | ||
|
||
const buttonBottom = await button.evaluate((node: HTMLElement) => node.getBoundingClientRect().bottom); | ||
const elementTop = await element.evaluate((node: HTMLElement) => node.getBoundingClientRect().top); | ||
|
||
await expect(buttonBottom).toBeLessThan(elementTop); | ||
}); | ||
|
||
test('position should be set to `before` when `positioning` is set to `before`', async ({ page }) => { | ||
const element = page.locator('fluent-tooltip'); | ||
const button = page.locator('button'); | ||
|
||
await element.evaluate((node: Tooltip) => { | ||
node.positioning = 'before' as TooltipPositioningOption; | ||
}); | ||
await expect(element).toHaveAttribute('positioning', 'before'); | ||
|
||
// show the element to get the position | ||
await button.focus(); | ||
|
||
const buttonLeft = await button.evaluate((node: HTMLElement) => node.getBoundingClientRect().left); | ||
const elementRight = await element.evaluate((node: HTMLElement) => node.getBoundingClientRect().right); | ||
|
||
await expect(buttonLeft).toBeGreaterThan(elementRight); | ||
}); | ||
|
||
test('position should be set to `after` when `positioning` is set to `after`', async ({ page }) => { | ||
const element = page.locator('fluent-tooltip'); | ||
const button = page.locator('button'); | ||
|
||
await element.evaluate((node: Tooltip) => { | ||
node.positioning = 'after' as TooltipPositioningOption; | ||
}); | ||
await expect(element).toHaveAttribute('positioning', 'after'); | ||
|
||
// show the element to get the position | ||
await button.focus(); | ||
|
||
const buttonRight = await button.evaluate((node: HTMLElement) => node.getBoundingClientRect().right); | ||
const elementLeft = await element.evaluate((node: HTMLElement) => node.getBoundingClientRect().left); | ||
|
||
await expect(buttonRight).toBeLessThan(elementLeft); | ||
}); | ||
}); |
Oops, something went wrong.