diff --git a/.changeset/metal-cycles-appear.md b/.changeset/metal-cycles-appear.md new file mode 100644 index 00000000000..5f6a4cf64cb --- /dev/null +++ b/.changeset/metal-cycles-appear.md @@ -0,0 +1,5 @@ +--- +"@primer/react": minor +--- + +IconButton: Add `keyshortcuts` prop to allow labelling and describing support for keyboard shortcut (through tooltips) diff --git a/.playwright/snapshots/components/IconButton.test.ts-snapshots/IconButton-Keyshortcuts-dark-colorblind-linux.png b/.playwright/snapshots/components/IconButton.test.ts-snapshots/IconButton-Keyshortcuts-dark-colorblind-linux.png new file mode 100644 index 00000000000..a948549ddd2 Binary files /dev/null and b/.playwright/snapshots/components/IconButton.test.ts-snapshots/IconButton-Keyshortcuts-dark-colorblind-linux.png differ diff --git a/.playwright/snapshots/components/IconButton.test.ts-snapshots/IconButton-Keyshortcuts-dark-dimmed-linux.png b/.playwright/snapshots/components/IconButton.test.ts-snapshots/IconButton-Keyshortcuts-dark-dimmed-linux.png new file mode 100644 index 00000000000..92621ebc4a8 Binary files /dev/null and b/.playwright/snapshots/components/IconButton.test.ts-snapshots/IconButton-Keyshortcuts-dark-dimmed-linux.png differ diff --git a/.playwright/snapshots/components/IconButton.test.ts-snapshots/IconButton-Keyshortcuts-dark-high-contrast-linux.png b/.playwright/snapshots/components/IconButton.test.ts-snapshots/IconButton-Keyshortcuts-dark-high-contrast-linux.png new file mode 100644 index 00000000000..45ba9851146 Binary files /dev/null and b/.playwright/snapshots/components/IconButton.test.ts-snapshots/IconButton-Keyshortcuts-dark-high-contrast-linux.png differ diff --git a/.playwright/snapshots/components/IconButton.test.ts-snapshots/IconButton-Keyshortcuts-dark-linux.png b/.playwright/snapshots/components/IconButton.test.ts-snapshots/IconButton-Keyshortcuts-dark-linux.png new file mode 100644 index 00000000000..a948549ddd2 Binary files /dev/null and b/.playwright/snapshots/components/IconButton.test.ts-snapshots/IconButton-Keyshortcuts-dark-linux.png differ diff --git a/.playwright/snapshots/components/IconButton.test.ts-snapshots/IconButton-Keyshortcuts-dark-tritanopia-linux.png b/.playwright/snapshots/components/IconButton.test.ts-snapshots/IconButton-Keyshortcuts-dark-tritanopia-linux.png new file mode 100644 index 00000000000..a948549ddd2 Binary files /dev/null and b/.playwright/snapshots/components/IconButton.test.ts-snapshots/IconButton-Keyshortcuts-dark-tritanopia-linux.png differ diff --git a/.playwright/snapshots/components/IconButton.test.ts-snapshots/IconButton-Keyshortcuts-light-colorblind-linux.png b/.playwright/snapshots/components/IconButton.test.ts-snapshots/IconButton-Keyshortcuts-light-colorblind-linux.png new file mode 100644 index 00000000000..294988f9740 Binary files /dev/null and b/.playwright/snapshots/components/IconButton.test.ts-snapshots/IconButton-Keyshortcuts-light-colorblind-linux.png differ diff --git a/.playwright/snapshots/components/IconButton.test.ts-snapshots/IconButton-Keyshortcuts-light-high-contrast-linux.png b/.playwright/snapshots/components/IconButton.test.ts-snapshots/IconButton-Keyshortcuts-light-high-contrast-linux.png new file mode 100644 index 00000000000..dab1a2bb5ea Binary files /dev/null and b/.playwright/snapshots/components/IconButton.test.ts-snapshots/IconButton-Keyshortcuts-light-high-contrast-linux.png differ diff --git a/.playwright/snapshots/components/IconButton.test.ts-snapshots/IconButton-Keyshortcuts-light-linux.png b/.playwright/snapshots/components/IconButton.test.ts-snapshots/IconButton-Keyshortcuts-light-linux.png new file mode 100644 index 00000000000..294988f9740 Binary files /dev/null and b/.playwright/snapshots/components/IconButton.test.ts-snapshots/IconButton-Keyshortcuts-light-linux.png differ diff --git a/.playwright/snapshots/components/IconButton.test.ts-snapshots/IconButton-Keyshortcuts-light-tritanopia-linux.png b/.playwright/snapshots/components/IconButton.test.ts-snapshots/IconButton-Keyshortcuts-light-tritanopia-linux.png new file mode 100644 index 00000000000..294988f9740 Binary files /dev/null and b/.playwright/snapshots/components/IconButton.test.ts-snapshots/IconButton-Keyshortcuts-light-tritanopia-linux.png differ diff --git a/.playwright/snapshots/components/IconButton.test.ts-snapshots/IconButton-Keyshortcuts-on-Description-dark-colorblind-linux.png b/.playwright/snapshots/components/IconButton.test.ts-snapshots/IconButton-Keyshortcuts-on-Description-dark-colorblind-linux.png new file mode 100644 index 00000000000..a9df5acaeb1 Binary files /dev/null and b/.playwright/snapshots/components/IconButton.test.ts-snapshots/IconButton-Keyshortcuts-on-Description-dark-colorblind-linux.png differ diff --git a/.playwright/snapshots/components/IconButton.test.ts-snapshots/IconButton-Keyshortcuts-on-Description-dark-dimmed-linux.png b/.playwright/snapshots/components/IconButton.test.ts-snapshots/IconButton-Keyshortcuts-on-Description-dark-dimmed-linux.png new file mode 100644 index 00000000000..91b18e01673 Binary files /dev/null and b/.playwright/snapshots/components/IconButton.test.ts-snapshots/IconButton-Keyshortcuts-on-Description-dark-dimmed-linux.png differ diff --git a/.playwright/snapshots/components/IconButton.test.ts-snapshots/IconButton-Keyshortcuts-on-Description-dark-high-contrast-linux.png b/.playwright/snapshots/components/IconButton.test.ts-snapshots/IconButton-Keyshortcuts-on-Description-dark-high-contrast-linux.png new file mode 100644 index 00000000000..ab00467787c Binary files /dev/null and b/.playwright/snapshots/components/IconButton.test.ts-snapshots/IconButton-Keyshortcuts-on-Description-dark-high-contrast-linux.png differ diff --git a/.playwright/snapshots/components/IconButton.test.ts-snapshots/IconButton-Keyshortcuts-on-Description-dark-linux.png b/.playwright/snapshots/components/IconButton.test.ts-snapshots/IconButton-Keyshortcuts-on-Description-dark-linux.png new file mode 100644 index 00000000000..a9df5acaeb1 Binary files /dev/null and b/.playwright/snapshots/components/IconButton.test.ts-snapshots/IconButton-Keyshortcuts-on-Description-dark-linux.png differ diff --git a/.playwright/snapshots/components/IconButton.test.ts-snapshots/IconButton-Keyshortcuts-on-Description-dark-tritanopia-linux.png b/.playwright/snapshots/components/IconButton.test.ts-snapshots/IconButton-Keyshortcuts-on-Description-dark-tritanopia-linux.png new file mode 100644 index 00000000000..a9df5acaeb1 Binary files /dev/null and b/.playwright/snapshots/components/IconButton.test.ts-snapshots/IconButton-Keyshortcuts-on-Description-dark-tritanopia-linux.png differ diff --git a/.playwright/snapshots/components/IconButton.test.ts-snapshots/IconButton-Keyshortcuts-on-Description-light-colorblind-linux.png b/.playwright/snapshots/components/IconButton.test.ts-snapshots/IconButton-Keyshortcuts-on-Description-light-colorblind-linux.png new file mode 100644 index 00000000000..318d5545c21 Binary files /dev/null and b/.playwright/snapshots/components/IconButton.test.ts-snapshots/IconButton-Keyshortcuts-on-Description-light-colorblind-linux.png differ diff --git a/.playwright/snapshots/components/IconButton.test.ts-snapshots/IconButton-Keyshortcuts-on-Description-light-high-contrast-linux.png b/.playwright/snapshots/components/IconButton.test.ts-snapshots/IconButton-Keyshortcuts-on-Description-light-high-contrast-linux.png new file mode 100644 index 00000000000..bdd1e750e45 Binary files /dev/null and b/.playwright/snapshots/components/IconButton.test.ts-snapshots/IconButton-Keyshortcuts-on-Description-light-high-contrast-linux.png differ diff --git a/.playwright/snapshots/components/IconButton.test.ts-snapshots/IconButton-Keyshortcuts-on-Description-light-linux.png b/.playwright/snapshots/components/IconButton.test.ts-snapshots/IconButton-Keyshortcuts-on-Description-light-linux.png new file mode 100644 index 00000000000..318d5545c21 Binary files /dev/null and b/.playwright/snapshots/components/IconButton.test.ts-snapshots/IconButton-Keyshortcuts-on-Description-light-linux.png differ diff --git a/.playwright/snapshots/components/IconButton.test.ts-snapshots/IconButton-Keyshortcuts-on-Description-light-tritanopia-linux.png b/.playwright/snapshots/components/IconButton.test.ts-snapshots/IconButton-Keyshortcuts-on-Description-light-tritanopia-linux.png new file mode 100644 index 00000000000..318d5545c21 Binary files /dev/null and b/.playwright/snapshots/components/IconButton.test.ts-snapshots/IconButton-Keyshortcuts-on-Description-light-tritanopia-linux.png differ diff --git a/e2e/components/IconButton.test.ts b/e2e/components/IconButton.test.ts index 8b1e3aac1e6..86a6b3d1c26 100644 --- a/e2e/components/IconButton.test.ts +++ b/e2e/components/IconButton.test.ts @@ -308,4 +308,67 @@ test.describe('IconButton', () => { }) } }) + test.describe('Keyshortcuts', () => { + for (const theme of themes) { + test.describe(theme, () => { + test('default @vrt', async ({page}) => { + await visit(page, { + id: 'components-iconbutton-features--keyshortcuts', + globals: { + colorScheme: theme, + }, + }) + + // Default state + await page.keyboard.press('Tab') // focus on icon button + expect(await page.screenshot({animations: 'disabled'})).toMatchSnapshot( + `IconButton.Keyshortcuts.${theme}.png`, + ) + }) + + test('axe @aat', async ({page}) => { + await visit(page, { + id: 'components-iconbutton-features--keyshortcuts', + globals: { + colorScheme: theme, + }, + }) + await page.keyboard.press('Tab') // focus on icon button + await expect(page).toHaveNoViolations() + }) + }) + } + }) + + test.describe('Keyshortcuts on Description', () => { + for (const theme of themes) { + test.describe(theme, () => { + test('default @vrt', async ({page}) => { + await visit(page, { + id: 'components-iconbutton-features--keyshortcuts-on-description', + globals: { + colorScheme: theme, + }, + }) + + // Default state + await page.keyboard.press('Tab') // focus on icon button + expect(await page.screenshot({animations: 'disabled'})).toMatchSnapshot( + `IconButton.Keyshortcuts on Description.${theme}.png`, + ) + }) + + test('axe @aat', async ({page}) => { + await visit(page, { + id: 'components-iconbutton-features--keyshortcuts-on-description', + globals: { + colorScheme: theme, + }, + }) + await page.keyboard.press('Tab') // focus on icon button + await expect(page).toHaveNoViolations() + }) + }) + } + }) }) diff --git a/packages/react/src/Button/IconButton.docs.json b/packages/react/src/Button/IconButton.docs.json index bf8978b87f0..d460a90835a 100644 --- a/packages/react/src/Button/IconButton.docs.json +++ b/packages/react/src/Button/IconButton.docs.json @@ -42,10 +42,17 @@ "defaultValue": "", "description": "Use an aria label to describe the functionality of the button. Please refer to [our guidance on alt text](https://primer.style/guides/accessibility/alternative-text-for-images) for tips on writing good alternative text." }, + { + "name": "keyshortcuts", + "type": "string", + "defaultValue": "", + "required": false, + "description": "Keyboard shortcuts that trigger the button. Keyboard shortcut will be appended to the accessible name or description (depending on the tooltip type) of the button with a comma (i.e. Bold, Command+B) and it will be displayed in the tooltip." + }, { "name": "sx", "type": "SystemStyleObject" } ], "subcomponents": [] -} \ No newline at end of file +} diff --git a/packages/react/src/Button/IconButton.features.stories.tsx b/packages/react/src/Button/IconButton.features.stories.tsx index dc81b62bdfa..745eb362574 100644 --- a/packages/react/src/Button/IconButton.features.stories.tsx +++ b/packages/react/src/Button/IconButton.features.stories.tsx @@ -1,4 +1,4 @@ -import {HeartIcon, InboxIcon, ChevronDownIcon} from '@primer/octicons-react' +import {HeartIcon, InboxIcon, ChevronDownIcon, BoldIcon} from '@primer/octicons-react' import React from 'react' import {IconButton} from '.' import {ActionMenu} from '../ActionMenu' @@ -88,3 +88,17 @@ export const AsAMenuAnchor = () => ( ) + +export const KeyshortcutsOnDescription = () => ( + +) + +export const Keyshortcuts = () => ( + +) diff --git a/packages/react/src/Button/IconButton.tsx b/packages/react/src/Button/IconButton.tsx index e2ae3db8108..79a8b4d8ae3 100644 --- a/packages/react/src/Button/IconButton.tsx +++ b/packages/react/src/Button/IconButton.tsx @@ -18,6 +18,7 @@ const IconButton = forwardRef( tooltipDirection, // This is planned to be a temporary prop until the default tooltip on icon buttons are fully rolled out. unsafeDisableTooltip = false, + keyshortcuts, ...props }, forwardedRef, @@ -53,10 +54,13 @@ const IconButton = forwardRef( /> ) } else { + // Does it have keyshortcuts? + const tooltipSuffix = keyshortcuts ? `, ${keyshortcuts}` : '' + const tooltipText = description ?? ariaLabel return ( @@ -65,6 +69,7 @@ const IconButton = forwardRef( data-component="IconButton" sx={sxStyles} type="button" + aria-keyshortcuts={keyshortcuts ?? undefined} // If description is provided, we will use the tooltip to describe the button, so we need to keep the aria-label to label the button. aria-label={description ? ariaLabel : undefined} {...props} diff --git a/packages/react/src/Button/__tests__/Button.test.tsx b/packages/react/src/Button/__tests__/Button.test.tsx index 87b4e6b3b06..882e5f1cdce 100644 --- a/packages/react/src/Button/__tests__/Button.test.tsx +++ b/packages/react/src/Button/__tests__/Button.test.tsx @@ -138,4 +138,49 @@ describe('Button', () => { expect(triggerEl).toHaveAttribute('aria-labelledby', tooltipEl.id) expect(triggerEl).not.toHaveAttribute('aria-label') }) + it('should render aria-keyshorts on an icon button when keyshortcuts prop is passed', () => { + const {getByRole} = render( + , + ) + const triggerEl = getByRole('button') + expect(triggerEl).toHaveAttribute('aria-keyshortcuts', 'Command+H') + }) + it('should append the keyshortcuts to the tooltip text that labels the icon button when keyshortcuts prop is passed', () => { + const {getByRole, getByText} = render( + , + ) + const triggerEl = getByRole('button') + const tooltipEl = getByText('Heart, Command+H') + expect(tooltipEl).toBeInTheDocument() + expect(triggerEl).toHaveAttribute('aria-labelledby', tooltipEl.id) + }) + it('should render aria-keyshorts on an icon button when keyshortcuts prop is passed (Description Type)', () => { + const {getByRole, getByText} = render( + , + ) + const triggerEl = getByRole('button') + const tooltipEl = getByText('Love is all around, Command+H') + expect(triggerEl).toHaveAttribute('aria-describedby', tooltipEl.id) + }) + it('should append the keyshortcuts to the tooltip text that describes the icon button when keyshortcuts prop is passed (Description Type)', () => { + const {getByRole, getByText} = render( + , + ) + const triggerEl = getByRole('button') + const tooltipEl = getByText('Love is all around, Command+H') + expect(tooltipEl).toBeInTheDocument() + expect(triggerEl).toHaveAttribute('aria-describedby', tooltipEl.id) + }) }) diff --git a/packages/react/src/Button/types.ts b/packages/react/src/Button/types.ts index f1552d8b474..1efe6cb026a 100644 --- a/packages/react/src/Button/types.ts +++ b/packages/react/src/Button/types.ts @@ -85,6 +85,7 @@ export type IconButtonProps = ButtonA11yProps & { unsafeDisableTooltip?: boolean description?: string tooltipDirection?: TooltipDirection + keyshortcuts?: string } & Omit // adopted from React.AnchorHTMLAttributes diff --git a/script/generate-e2e-tests.js b/script/generate-e2e-tests.js index 555b9157766..4c8d1524b2a 100644 --- a/script/generate-e2e-tests.js +++ b/script/generate-e2e-tests.js @@ -624,6 +624,14 @@ const components = new Map([ id: 'components-iconbutton-features--small', name: 'Small', }, + { + id: 'components-iconbutton-features--keyshortcuts', + name: 'Keyshortcuts', + }, + { + id: 'components-iconbutton-features--keyshortcuts-on-description', + name: 'Keyshortcuts on Description', + }, ], }, ],