diff --git a/.changeset/smooth-tips-breathe.md b/.changeset/smooth-tips-breathe.md new file mode 100644 index 00000000000..e0334b3f905 --- /dev/null +++ b/.changeset/smooth-tips-breathe.md @@ -0,0 +1,7 @@ +--- +'@primer/react': minor +--- + +Supports inactive ActionList items by letting users pass the required message to the `inactiveText` prop. + + diff --git a/.playwright/snapshots/components/ActionList.test.ts-snapshots/ActionList-Inactive-Item-dark-colorblind-linux.png b/.playwright/snapshots/components/ActionList.test.ts-snapshots/ActionList-Inactive-Item-dark-colorblind-linux.png new file mode 100644 index 00000000000..87595e3c722 Binary files /dev/null and b/.playwright/snapshots/components/ActionList.test.ts-snapshots/ActionList-Inactive-Item-dark-colorblind-linux.png differ diff --git a/.playwright/snapshots/components/ActionList.test.ts-snapshots/ActionList-Inactive-Item-dark-dimmed-linux.png b/.playwright/snapshots/components/ActionList.test.ts-snapshots/ActionList-Inactive-Item-dark-dimmed-linux.png new file mode 100644 index 00000000000..7b4e0285b60 Binary files /dev/null and b/.playwright/snapshots/components/ActionList.test.ts-snapshots/ActionList-Inactive-Item-dark-dimmed-linux.png differ diff --git a/.playwright/snapshots/components/ActionList.test.ts-snapshots/ActionList-Inactive-Item-dark-high-contrast-linux.png b/.playwright/snapshots/components/ActionList.test.ts-snapshots/ActionList-Inactive-Item-dark-high-contrast-linux.png new file mode 100644 index 00000000000..62d4093d37a Binary files /dev/null and b/.playwright/snapshots/components/ActionList.test.ts-snapshots/ActionList-Inactive-Item-dark-high-contrast-linux.png differ diff --git a/.playwright/snapshots/components/ActionList.test.ts-snapshots/ActionList-Inactive-Item-dark-linux.png b/.playwright/snapshots/components/ActionList.test.ts-snapshots/ActionList-Inactive-Item-dark-linux.png new file mode 100644 index 00000000000..a938d9402b8 Binary files /dev/null and b/.playwright/snapshots/components/ActionList.test.ts-snapshots/ActionList-Inactive-Item-dark-linux.png differ diff --git a/.playwright/snapshots/components/ActionList.test.ts-snapshots/ActionList-Inactive-Item-dark-tritanopia-linux.png b/.playwright/snapshots/components/ActionList.test.ts-snapshots/ActionList-Inactive-Item-dark-tritanopia-linux.png new file mode 100644 index 00000000000..87595e3c722 Binary files /dev/null and b/.playwright/snapshots/components/ActionList.test.ts-snapshots/ActionList-Inactive-Item-dark-tritanopia-linux.png differ diff --git a/.playwright/snapshots/components/ActionList.test.ts-snapshots/ActionList-Inactive-Item-light-colorblind-linux.png b/.playwright/snapshots/components/ActionList.test.ts-snapshots/ActionList-Inactive-Item-light-colorblind-linux.png new file mode 100644 index 00000000000..ada03906c18 Binary files /dev/null and b/.playwright/snapshots/components/ActionList.test.ts-snapshots/ActionList-Inactive-Item-light-colorblind-linux.png differ diff --git a/.playwright/snapshots/components/ActionList.test.ts-snapshots/ActionList-Inactive-Item-light-high-contrast-linux.png b/.playwright/snapshots/components/ActionList.test.ts-snapshots/ActionList-Inactive-Item-light-high-contrast-linux.png new file mode 100644 index 00000000000..d022a52fba9 Binary files /dev/null and b/.playwright/snapshots/components/ActionList.test.ts-snapshots/ActionList-Inactive-Item-light-high-contrast-linux.png differ diff --git a/.playwright/snapshots/components/ActionList.test.ts-snapshots/ActionList-Inactive-Item-light-linux.png b/.playwright/snapshots/components/ActionList.test.ts-snapshots/ActionList-Inactive-Item-light-linux.png new file mode 100644 index 00000000000..24db9886990 Binary files /dev/null and b/.playwright/snapshots/components/ActionList.test.ts-snapshots/ActionList-Inactive-Item-light-linux.png differ diff --git a/.playwright/snapshots/components/ActionList.test.ts-snapshots/ActionList-Inactive-Item-light-tritanopia-linux.png b/.playwright/snapshots/components/ActionList.test.ts-snapshots/ActionList-Inactive-Item-light-tritanopia-linux.png new file mode 100644 index 00000000000..ada03906c18 Binary files /dev/null and b/.playwright/snapshots/components/ActionList.test.ts-snapshots/ActionList-Inactive-Item-light-tritanopia-linux.png differ diff --git a/.playwright/snapshots/components/ActionList.test.ts-snapshots/ActionList-Inactive-Multiselect-dark-colorblind-linux.png b/.playwright/snapshots/components/ActionList.test.ts-snapshots/ActionList-Inactive-Multiselect-dark-colorblind-linux.png new file mode 100644 index 00000000000..f0521ec0123 Binary files /dev/null and b/.playwright/snapshots/components/ActionList.test.ts-snapshots/ActionList-Inactive-Multiselect-dark-colorblind-linux.png differ diff --git a/.playwright/snapshots/components/ActionList.test.ts-snapshots/ActionList-Inactive-Multiselect-dark-dimmed-linux.png b/.playwright/snapshots/components/ActionList.test.ts-snapshots/ActionList-Inactive-Multiselect-dark-dimmed-linux.png new file mode 100644 index 00000000000..69e67439356 Binary files /dev/null and b/.playwright/snapshots/components/ActionList.test.ts-snapshots/ActionList-Inactive-Multiselect-dark-dimmed-linux.png differ diff --git a/.playwright/snapshots/components/ActionList.test.ts-snapshots/ActionList-Inactive-Multiselect-dark-high-contrast-linux.png b/.playwright/snapshots/components/ActionList.test.ts-snapshots/ActionList-Inactive-Multiselect-dark-high-contrast-linux.png new file mode 100644 index 00000000000..0246471fcfb Binary files /dev/null and b/.playwright/snapshots/components/ActionList.test.ts-snapshots/ActionList-Inactive-Multiselect-dark-high-contrast-linux.png differ diff --git a/.playwright/snapshots/components/ActionList.test.ts-snapshots/ActionList-Inactive-Multiselect-dark-linux.png b/.playwright/snapshots/components/ActionList.test.ts-snapshots/ActionList-Inactive-Multiselect-dark-linux.png new file mode 100644 index 00000000000..db63349c2e6 Binary files /dev/null and b/.playwright/snapshots/components/ActionList.test.ts-snapshots/ActionList-Inactive-Multiselect-dark-linux.png differ diff --git a/.playwright/snapshots/components/ActionList.test.ts-snapshots/ActionList-Inactive-Multiselect-dark-tritanopia-linux.png b/.playwright/snapshots/components/ActionList.test.ts-snapshots/ActionList-Inactive-Multiselect-dark-tritanopia-linux.png new file mode 100644 index 00000000000..f0521ec0123 Binary files /dev/null and b/.playwright/snapshots/components/ActionList.test.ts-snapshots/ActionList-Inactive-Multiselect-dark-tritanopia-linux.png differ diff --git a/.playwright/snapshots/components/ActionList.test.ts-snapshots/ActionList-Inactive-Multiselect-light-colorblind-linux.png b/.playwright/snapshots/components/ActionList.test.ts-snapshots/ActionList-Inactive-Multiselect-light-colorblind-linux.png new file mode 100644 index 00000000000..cad4a3046cf Binary files /dev/null and b/.playwright/snapshots/components/ActionList.test.ts-snapshots/ActionList-Inactive-Multiselect-light-colorblind-linux.png differ diff --git a/.playwright/snapshots/components/ActionList.test.ts-snapshots/ActionList-Inactive-Multiselect-light-high-contrast-linux.png b/.playwright/snapshots/components/ActionList.test.ts-snapshots/ActionList-Inactive-Multiselect-light-high-contrast-linux.png new file mode 100644 index 00000000000..46f3f32bbb6 Binary files /dev/null and b/.playwright/snapshots/components/ActionList.test.ts-snapshots/ActionList-Inactive-Multiselect-light-high-contrast-linux.png differ diff --git a/.playwright/snapshots/components/ActionList.test.ts-snapshots/ActionList-Inactive-Multiselect-light-linux.png b/.playwright/snapshots/components/ActionList.test.ts-snapshots/ActionList-Inactive-Multiselect-light-linux.png new file mode 100644 index 00000000000..bb4b6717633 Binary files /dev/null and b/.playwright/snapshots/components/ActionList.test.ts-snapshots/ActionList-Inactive-Multiselect-light-linux.png differ diff --git a/.playwright/snapshots/components/ActionList.test.ts-snapshots/ActionList-Inactive-Multiselect-light-tritanopia-linux.png b/.playwright/snapshots/components/ActionList.test.ts-snapshots/ActionList-Inactive-Multiselect-light-tritanopia-linux.png new file mode 100644 index 00000000000..cad4a3046cf Binary files /dev/null and b/.playwright/snapshots/components/ActionList.test.ts-snapshots/ActionList-Inactive-Multiselect-light-tritanopia-linux.png differ diff --git a/.playwright/snapshots/components/ActionMenu.test.ts-snapshots/ActionMenu-Inactive-Items-dark-colorblind-default-vrt-1-linux.png b/.playwright/snapshots/components/ActionMenu.test.ts-snapshots/ActionMenu-Inactive-Items-dark-colorblind-default-vrt-1-linux.png new file mode 100644 index 00000000000..e96b6b1f646 Binary files /dev/null and b/.playwright/snapshots/components/ActionMenu.test.ts-snapshots/ActionMenu-Inactive-Items-dark-colorblind-default-vrt-1-linux.png differ diff --git a/.playwright/snapshots/components/ActionMenu.test.ts-snapshots/ActionMenu-Inactive-Items-dark-default-vrt-1-linux.png b/.playwright/snapshots/components/ActionMenu.test.ts-snapshots/ActionMenu-Inactive-Items-dark-default-vrt-1-linux.png new file mode 100644 index 00000000000..722a3de6e46 Binary files /dev/null and b/.playwright/snapshots/components/ActionMenu.test.ts-snapshots/ActionMenu-Inactive-Items-dark-default-vrt-1-linux.png differ diff --git a/.playwright/snapshots/components/ActionMenu.test.ts-snapshots/ActionMenu-Inactive-Items-dark-dimmed-default-vrt-1-linux.png b/.playwright/snapshots/components/ActionMenu.test.ts-snapshots/ActionMenu-Inactive-Items-dark-dimmed-default-vrt-1-linux.png new file mode 100644 index 00000000000..7d70f8b6a33 Binary files /dev/null and b/.playwright/snapshots/components/ActionMenu.test.ts-snapshots/ActionMenu-Inactive-Items-dark-dimmed-default-vrt-1-linux.png differ diff --git a/.playwright/snapshots/components/ActionMenu.test.ts-snapshots/ActionMenu-Inactive-Items-dark-high-contrast-default-vrt-1-linux.png b/.playwright/snapshots/components/ActionMenu.test.ts-snapshots/ActionMenu-Inactive-Items-dark-high-contrast-default-vrt-1-linux.png new file mode 100644 index 00000000000..336e9c5a737 Binary files /dev/null and b/.playwright/snapshots/components/ActionMenu.test.ts-snapshots/ActionMenu-Inactive-Items-dark-high-contrast-default-vrt-1-linux.png differ diff --git a/.playwright/snapshots/components/ActionMenu.test.ts-snapshots/ActionMenu-Inactive-Items-dark-tritanopia-default-vrt-1-linux.png b/.playwright/snapshots/components/ActionMenu.test.ts-snapshots/ActionMenu-Inactive-Items-dark-tritanopia-default-vrt-1-linux.png new file mode 100644 index 00000000000..cc8b89efd33 Binary files /dev/null and b/.playwright/snapshots/components/ActionMenu.test.ts-snapshots/ActionMenu-Inactive-Items-dark-tritanopia-default-vrt-1-linux.png differ diff --git a/.playwright/snapshots/components/ActionMenu.test.ts-snapshots/ActionMenu-Inactive-Items-light-colorblind-default-vrt-1-linux.png b/.playwright/snapshots/components/ActionMenu.test.ts-snapshots/ActionMenu-Inactive-Items-light-colorblind-default-vrt-1-linux.png new file mode 100644 index 00000000000..de804b70eaa Binary files /dev/null and b/.playwright/snapshots/components/ActionMenu.test.ts-snapshots/ActionMenu-Inactive-Items-light-colorblind-default-vrt-1-linux.png differ diff --git a/.playwright/snapshots/components/ActionMenu.test.ts-snapshots/ActionMenu-Inactive-Items-light-default-vrt-1-linux.png b/.playwright/snapshots/components/ActionMenu.test.ts-snapshots/ActionMenu-Inactive-Items-light-default-vrt-1-linux.png new file mode 100644 index 00000000000..9e7fda8048b Binary files /dev/null and b/.playwright/snapshots/components/ActionMenu.test.ts-snapshots/ActionMenu-Inactive-Items-light-default-vrt-1-linux.png differ diff --git a/.playwright/snapshots/components/ActionMenu.test.ts-snapshots/ActionMenu-Inactive-Items-light-high-contrast-default-vrt-1-linux.png b/.playwright/snapshots/components/ActionMenu.test.ts-snapshots/ActionMenu-Inactive-Items-light-high-contrast-default-vrt-1-linux.png new file mode 100644 index 00000000000..f10ab247f93 Binary files /dev/null and b/.playwright/snapshots/components/ActionMenu.test.ts-snapshots/ActionMenu-Inactive-Items-light-high-contrast-default-vrt-1-linux.png differ diff --git a/.playwright/snapshots/components/ActionMenu.test.ts-snapshots/ActionMenu-Inactive-Items-light-tritanopia-default-vrt-1-linux.png b/.playwright/snapshots/components/ActionMenu.test.ts-snapshots/ActionMenu-Inactive-Items-light-tritanopia-default-vrt-1-linux.png new file mode 100644 index 00000000000..de804b70eaa Binary files /dev/null and b/.playwright/snapshots/components/ActionMenu.test.ts-snapshots/ActionMenu-Inactive-Items-light-tritanopia-default-vrt-1-linux.png differ diff --git a/e2e/components/ActionList.test.ts b/e2e/components/ActionList.test.ts index 48c37ede40a..4d9898be49c 100644 --- a/e2e/components/ActionList.test.ts +++ b/e2e/components/ActionList.test.ts @@ -454,4 +454,64 @@ test.describe('ActionList', () => { }) } }) + + test.describe('Inactive Item', () => { + for (const theme of themes) { + test.describe(theme, () => { + test('default @vrt', async ({page}) => { + await visit(page, { + id: 'components-actionlist-features--inactive-item', + globals: { + colorScheme: theme, + }, + }) + + // Default state + expect(await page.screenshot({animations: 'disabled'})).toMatchSnapshot( + `ActionList.Inactive Item.${theme}.png`, + ) + }) + + test('axe @aat', async ({page}) => { + await visit(page, { + id: 'components-actionlist-features--inactive-item', + globals: { + colorScheme: theme, + }, + }) + await expect(page).toHaveNoViolations() + }) + }) + } + }) + + test.describe('Inactive Multiselect', () => { + for (const theme of themes) { + test.describe(theme, () => { + test('default @vrt', async ({page}) => { + await visit(page, { + id: 'components-actionlist-features--inactive-multiselect', + globals: { + colorScheme: theme, + }, + }) + + // Default state + expect(await page.screenshot({animations: 'disabled'})).toMatchSnapshot( + `ActionList.Inactive Multiselect.${theme}.png`, + ) + }) + + test('axe @aat', async ({page}) => { + await visit(page, { + id: 'components-actionlist-features--inactive-multiselect', + globals: { + colorScheme: theme, + }, + }) + await expect(page).toHaveNoViolations() + }) + }) + } + }) }) diff --git a/e2e/components/ActionMenu.test.ts b/e2e/components/ActionMenu.test.ts index dddbaee08f5..fa67e91f547 100644 --- a/e2e/components/ActionMenu.test.ts +++ b/e2e/components/ActionMenu.test.ts @@ -31,6 +31,41 @@ test.describe('ActionMenu', () => { } }) + test.describe('Inactive Items', () => { + for (const theme of themes) { + test.describe(theme, () => { + test('default @vrt', async ({page}) => { + await visit(page, { + id: 'components-actionmenu-features--links-and-actions', + globals: { + colorScheme: theme, + }, + }) + + // Default state + expect(await page.screenshot({animations: 'disabled'})).toMatchSnapshot( + `ActionMenu.Links And Actions.${theme}.png`, + ) + + // Open menu + await page.locator('button', {hasText: 'Open menu'}).waitFor() + await page.getByRole('button', {name: 'Open menu'}).click() + expect(await page.screenshot({animations: 'disabled'})).toMatchSnapshot() + }) + + test('axe @aat', async ({page}) => { + await visit(page, { + id: 'components-actionmenu-features--links-and-actions', + globals: { + colorScheme: theme, + }, + }) + await expect(page).toHaveNoViolations() + }) + }) + } + }) + test.describe('Links And Actions', () => { for (const theme of themes) { test.describe(theme, () => { diff --git a/src/ActionList/ActionList.docs.json b/src/ActionList/ActionList.docs.json index 2049f3dc674..5e948f84e08 100644 --- a/src/ActionList/ActionList.docs.json +++ b/src/ActionList/ActionList.docs.json @@ -62,7 +62,7 @@ "name": "onSelect", "type": "(event: React.MouseEvent | React.KeyboardEvent) => void", "defaultValue": "", - "description": "Callback that is called when the item is selected using either the mouse or keyboard. `event.preventDefault()` will prevent a menu from closing when within an ``" + "description": "Callback that is called when the item is selected using either the mouse or keyboard. `event.preventDefault()` will prevent a menu from closing when within an ``. This is not called for disabled or inactive items." }, { "name": "selected", @@ -82,6 +82,12 @@ "defaultValue": "false", "description": "Items that are disabled can not be clicked, selected, or navigated to." }, + { + "name": "inactiveText", + "type": "string", + "defaultValue": "", + "description": "Text describing why the item is inactive. This may be used when an item's usual functionality is unavailable due to a system error such as a database outage." + }, { "name": "role", "type": "AriaRole", diff --git a/src/ActionList/ActionList.examples.stories.tsx b/src/ActionList/ActionList.examples.stories.tsx index 6fc11247d58..3b68a14b0bb 100644 --- a/src/ActionList/ActionList.examples.stories.tsx +++ b/src/ActionList/ActionList.examples.stories.tsx @@ -453,6 +453,43 @@ export function AllCombinations(): JSX.Element { + + L + B + TBlock description + + + L + B + TInline description + + + + + + L + I + Tinline description + + + + + + + + + L + B + TBlock description + + + + + + L + B + TBlock description + + + + + + I + B + Tinline description + Block description + + + + diff --git a/src/ActionList/ActionList.features.stories.tsx b/src/ActionList/ActionList.features.stories.tsx index fe3527f79d3..1281d0c5881 100644 --- a/src/ActionList/ActionList.features.stories.tsx +++ b/src/ActionList/ActionList.features.stories.tsx @@ -269,6 +269,25 @@ export const SingleSelect = () => { ) } +export const InactiveSingleSelect = () => { + const [selectedIndex, setSelectedIndex] = React.useState(1) + return ( + + + Inactive item + + setSelectedIndex(1)} + > + Item 2 + + + ) +} + export const MultiSelect = () => { const [selectedIndices, setSelectedIndices] = React.useState([0]) const handleSelect = (index: number) => { @@ -322,6 +341,32 @@ export const DisabledMultiselect = () => ( ) +export const InactiveMultiselect = () => { + const [selectedIndices, setSelectedIndices] = React.useState([0]) + const handleSelect = (index: number) => { + if (selectedIndices.includes(index)) { + setSelectedIndices(selectedIndices.filter(i => i !== index)) + } else { + setSelectedIndices([...selectedIndices, index]) + } + } + return ( + + + Inactive item + + handleSelect(1)} + > + Item 2 + + + ) +} + export const DisabledItem = () => { const [selectedIndex, setSelectedIndex] = React.useState(0) return ( @@ -346,6 +391,22 @@ export const DisabledItem = () => { ) } +export const InactiveItem = () => { + return ( + + {projects.map((project, index) => ( + + + + + {project.name} + {project.scope} + + ))} + + ) +} + export const Links = () => ( <> diff --git a/src/ActionList/ActionList.stories.tsx b/src/ActionList/ActionList.stories.tsx index de6f66ae6f5..8a4b68e1373 100644 --- a/src/ActionList/ActionList.stories.tsx +++ b/src/ActionList/ActionList.stories.tsx @@ -120,6 +120,11 @@ ItemPlayground.argTypes = { type: 'boolean', }, }, + inactiveText: { + control: { + type: 'text', + }, + }, variant: { control: 'radio', options: ['default', 'danger'], @@ -155,6 +160,7 @@ ItemPlayground.args = { selected: false, active: false, disabled: false, + inactiveText: '', variant: 'default', role: 'listitem', id: 'item-1', diff --git a/src/ActionList/ActionList.test.tsx b/src/ActionList/ActionList.test.tsx index f7635fd0b50..b3d8c814c41 100644 --- a/src/ActionList/ActionList.test.tsx +++ b/src/ActionList/ActionList.test.tsx @@ -1,4 +1,5 @@ import {render as HTMLRender, waitFor, fireEvent} from '@testing-library/react' +import userEvent from '@testing-library/user-event' import {axe} from 'jest-axe' import React from 'react' import theme from '../theme' @@ -31,6 +32,7 @@ const projects = [ {name: 'Primer Backlog', scope: 'GitHub'}, {name: 'Primer React', scope: 'github/primer'}, {name: 'Disabled Project', scope: 'github/primer', disabled: true}, + {name: 'Inactive Project', scope: 'github/primer', inactiveText: 'Unavailable due to an outage'}, ] function SingleSelectListStory(): JSX.Element { const [selectedIndex, setSelectedIndex] = React.useState(0) @@ -45,6 +47,7 @@ function SingleSelectListStory(): JSX.Element { aria-selected={index === selectedIndex} onSelect={() => setSelectedIndex(index)} disabled={project.disabled} + inactiveText={project.inactiveText} > {project.name} @@ -123,6 +126,24 @@ describe('ActionList', () => { expect(options[2]).toHaveAttribute('aria-selected', 'false') }) + it('should skip onSelect on inactive items', async () => { + const component = HTMLRender() + const options = await waitFor(() => component.getAllByRole('option')) + + expect(options[0]).toHaveAttribute('aria-selected', 'true') + expect(options[3]).toHaveAttribute('aria-selected', 'false') + + fireEvent.click(options[3]) + + expect(options[0]).toHaveAttribute('aria-selected', 'true') + expect(options[3]).toHaveAttribute('aria-selected', 'false') + + fireEvent.keyPress(options[3], {key: 'Enter', charCode: 13}) + + expect(options[0]).toHaveAttribute('aria-selected', 'true') + expect(options[3]).toHaveAttribute('aria-selected', 'false') + }) + it('should throw when selected is provided without a selectionVariant on parent', async () => { // we expect console.error to be called, so we suppress that in the test const mockError = jest.spyOn(console, 'error').mockImplementation(() => jest.fn()) @@ -154,6 +175,20 @@ describe('ActionList', () => { expect(option).toBeInTheDocument() }) + it('should focus the button around the leading visual when tabbing to an inactive item', async () => { + const component = HTMLRender() + const inactiveOptionButton = await waitFor(() => + component.getByRole('button', {description: projects[3].inactiveText}), + ) + const inactiveIndex = projects.findIndex(project => 'inactiveText' in project) + + for (let i = 0; i < inactiveIndex; i++) { + await userEvent.tab() + } + + expect(inactiveOptionButton).toHaveFocus() + }) + it('should call onClick for a link item', async () => { const onClick = jest.fn() const component = HTMLRender( diff --git a/src/ActionList/Item.tsx b/src/ActionList/Item.tsx index 97d3eaea55c..13e5d264999 100644 --- a/src/ActionList/Item.tsx +++ b/src/ActionList/Item.tsx @@ -1,6 +1,8 @@ import React from 'react' import styled from 'styled-components' +import {AlertIcon} from '@primer/octicons-react' import Box, {BoxProps} from '../Box' +import {Tooltip, TooltipProps} from '../drafts/Tooltip/Tooltip' import {useId} from '../hooks/useId' import {useSlots} from '../hooks/useSlots' import sx, {BetterSystemStyleObject, merge, SxProp} from '../sx' @@ -13,15 +15,41 @@ import {GroupContext} from './Group' import {ActionListProps, ListContext} from './List' import {Selection} from './Selection' import {ActionListItemProps, getVariantStyles, ItemContext, TEXT_ROW_HEIGHT} from './shared' -import {LeadingVisual, TrailingVisual} from './Visuals' +import {LeadingVisual, TrailingVisual, VisualProps} from './Visuals' const LiBox = styled.li(sx) +const InactiveIndicator: React.FC<{ + labelId: string + text: TooltipProps['text'] + visualComponent: React.FC> +}> = ({labelId, text, visualComponent: VisualComponent}) => ( + + + + + + + +) + export const Item = React.forwardRef( ( { variant = 'default', disabled = false, + inactiveText, selected = undefined, active = false, onSelect: onSelectUser, @@ -48,6 +76,8 @@ export const Item = React.forwardRef( } = React.useContext(ListContext) const {selectionVariant: groupSelectionVariant} = React.useContext(GroupContext) const {container, afterSelect, selectionAttribute} = React.useContext(ActionListContainerContext) + const inactive = Boolean(inactiveText) + const showInactiveIndicator = inactive && container === undefined const onSelect = React.useCallback( ( @@ -104,9 +134,9 @@ export const Item = React.forwardRef( marginX: listVariant === 'inset' ? 2 : 0, borderRadius: 2, transition: 'background 33.333ms linear', - color: getVariantStyles(variant, disabled).color, + color: getVariantStyles(variant, disabled, inactive).color, cursor: 'pointer', - '&[aria-disabled]': { + '&[aria-disabled], &[data-inactive]': { cursor: 'not-allowed', '[data-component="ActionList.Checkbox"]': { cursor: 'not-allowed', @@ -125,9 +155,9 @@ export const Item = React.forwardRef( marginY: 'unset', '@media (hover: hover) and (pointer: fine)': { - ':hover:not([aria-disabled])': { + ':hover:not([aria-disabled]):not([data-inactive])': { backgroundColor: `actionListItem.${variant}.hoverBg`, - color: getVariantStyles(variant, disabled).hoverColor, + color: getVariantStyles(variant, disabled, inactive).hoverColor, boxShadow: `inset 0 0 0 max(1px, 0.0625rem) ${theme?.colors.actionListItem.default.activeBorder}`, }, '&:focus-visible, > a:focus-visible': { @@ -135,9 +165,9 @@ export const Item = React.forwardRef( border: `2 solid`, boxShadow: `0 0 0 2px ${theme?.colors.accent.emphasis}`, }, - ':active:not([aria-disabled])': { + ':active:not([aria-disabled]):not([data-inactive])': { backgroundColor: `actionListItem.${variant}.activeBg`, - color: getVariantStyles(variant, disabled).hoverColor, + color: getVariantStyles(variant, disabled, inactive).hoverColor, }, }, @@ -167,10 +197,11 @@ export const Item = React.forwardRef( // hide divider after dividers & group header, with higher importance! '[data-component="ActionList.Divider"] + &': {'--divider-color': 'transparent !important'}, // hide border on current and previous item - '&:hover:not([aria-disabled]), &:focus:not([aria-disabled]), &[data-focus-visible-added]:not([aria-disabled])': { - '--divider-color': 'transparent', - }, - '&:hover:not([aria-disabled]) + &, &[data-focus-visible-added] + li': { + '&:hover:not([aria-disabled]):not([data-inactive]), &:focus:not([aria-disabled]):not([data-inactive]), &[data-focus-visible-added]:not([aria-disabled]):not([data-inactive])': + { + '--divider-color': 'transparent', + }, + '&:hover:not([aria-disabled]):not([data-inactive]) + &, &[data-focus-visible-added] + li': { '--divider-color': 'transparent', }, ...(active ? activeStyles : {}), @@ -178,26 +209,27 @@ export const Item = React.forwardRef( const clickHandler = React.useCallback( (event: React.MouseEvent) => { - if (disabled) return + if (disabled || inactive) return onSelect(event, afterSelect) }, - [onSelect, disabled, afterSelect], + [onSelect, disabled, inactive, afterSelect], ) const keyPressHandler = React.useCallback( (event: React.KeyboardEvent) => { - if (disabled) return + if (disabled || inactive) return if ([' ', 'Enter'].includes(event.key)) { onSelect(event, afterSelect) } }, - [onSelect, disabled, afterSelect], + [onSelect, disabled, inactive, afterSelect], ) const itemId = useId(id) const labelId = `${itemId}--label` const inlineDescriptionId = `${itemId}--inline-description` const blockDescriptionId = `${itemId}--block-description` + const inactiveWarningId = inactive && !showInactiveIndicator ? `${itemId}--warning-message` : undefined const ItemWrapper = _PrivateItemWrapper || React.Fragment @@ -205,9 +237,12 @@ export const Item = React.forwardRef( onClick: clickHandler, onKeyPress: keyPressHandler, 'aria-disabled': disabled ? true : undefined, - tabIndex: disabled ? undefined : 0, + 'data-inactive': inactive ? true : undefined, + tabIndex: disabled || showInactiveIndicator ? undefined : 0, 'aria-labelledby': `${labelId} ${slots.inlineDescription ? inlineDescriptionId : ''}`, - 'aria-describedby': slots.blockDescription ? blockDescriptionId : undefined, + 'aria-describedby': slots.blockDescription + ? [blockDescriptionId, inactiveWarningId].join(' ') + : inactiveWarningId, ...(selectionAttribute && {[selectionAttribute]: selected}), role: role || itemRole, id: itemId, @@ -218,7 +253,9 @@ export const Item = React.forwardRef( const wrapperProps = _PrivateItemWrapper ? menuItemProps : {} return ( - + (styles, sxProp)} @@ -228,12 +265,29 @@ export const Item = React.forwardRef( > - {slots.leadingVisual} + { + // If we're showing an inactive indicator and a leading visual has been passed, + // replace the leading visual with the inactive indicator. + // + // Inactive items without a leading visual place the inactive indicator in the + // trailing visual slot. This preserves the left alignment of item text. + showInactiveIndicator && slots.leadingVisual ? ( + // using a non-null assertion for `inactiveText` since we check for it in `showInactiveIndicator` + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + + ) : ( + // If it's not inactive, just render the leading visual slot + slots.leadingVisual + ) + } - + ( {slots.inlineDescription} - {slots.trailingVisual} + { + // If we're showing an inactive indicator and a leading visual has NOT been passed, + // replace the trailing visual with the inactive indicator. + // + // This preserves the left alignment of item text. + showInactiveIndicator && !slots.leadingVisual ? ( + // using a non-null assertion for `inactiveText` since we check for it in `showInactiveIndicator` + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + + ) : ( + // If it's not inactive, or it has a leading visual that can be replaced, + // just render the trailing visual slot. + slots.trailingVisual + ) + } + { + // If the item is inactive, but it's not in an overlay (e.g. ActionMenu, SelectPanel), + // render the inactive warning message directly in the item. + inactive && container ? ( + + {inactiveText} + + ) : null + } {slots.blockDescription} diff --git a/src/ActionList/LinkItem.tsx b/src/ActionList/LinkItem.tsx index d4decbe2292..978c040bf9f 100644 --- a/src/ActionList/LinkItem.tsx +++ b/src/ActionList/LinkItem.tsx @@ -4,6 +4,7 @@ import Link from '../Link' import {SxProp, merge} from '../sx' import {Item} from './Item' import {ActionListItemProps} from './shared' +import {Box} from '..' // adopted from React.AnchorHTMLAttributes type LinkProps = { @@ -19,9 +20,10 @@ type LinkProps = { } // LinkItem does not support selected, variants, etc. -export type ActionListLinkItemProps = Pick & LinkProps +export type ActionListLinkItemProps = Pick & + LinkProps -export const LinkItem = React.forwardRef(({sx = {}, active, as: Component, ...props}, forwardedRef) => { +export const LinkItem = React.forwardRef(({sx = {}, active, inactiveText, as: Component, ...props}, forwardedRef) => { const styles = { // occupy full size of Item paddingX: 2, @@ -39,12 +41,18 @@ export const LinkItem = React.forwardRef(({sx = {}, active, as: Component, ...pr { const clickHandler = (event: React.MouseEvent) => { onClick && onClick(event) props.onClick && props.onClick(event as React.MouseEvent) } - return ( + return inactiveText ? ( + + {children} + + ) : ( +export type VisualProps = SxProp & React.HTMLAttributes export const LeadingVisualContainer: React.FC> = ({sx = {}, ...props}) => { return ( @@ -30,15 +30,15 @@ export const LeadingVisualContainer: React.FC> = ({sx = {}, ...props}) => { - const {variant, disabled} = React.useContext(ItemContext) + const {variant, disabled, inactive} = React.useContext(ItemContext) return ( > = ({s export type ActionListTrailingVisualProps = VisualProps export const TrailingVisual: React.FC> = ({sx = {}, ...props}) => { - const {variant, disabled} = React.useContext(ItemContext) + const {variant, disabled, inactive} = React.useContext(ItemContext) return ( > = ({ { height: '20px', // match height of text row flexShrink: 0, - color: getVariantStyles(variant, disabled).annotationColor, + color: getVariantStyles(variant, disabled, inactive).annotationColor, marginLeft: 2, fontWeight: 'initial', '[data-variant="danger"]:hover &, [data-variant="danger"]:active &': { - color: getVariantStyles(variant, disabled).hoverColor, + color: getVariantStyles(variant, disabled, inactive).hoverColor, }, }, sx as SxProp, diff --git a/src/ActionList/shared.ts b/src/ActionList/shared.ts index 10b79112915..e69478eb17b 100644 --- a/src/ActionList/shared.ts +++ b/src/ActionList/shared.ts @@ -9,6 +9,7 @@ export type ActionListItemProps = { children?: React.ReactNode /** * Callback that will trigger both on click selection and keyboard selection. + * This is not called for disabled or inactive items. */ onSelect?: (event: React.MouseEvent | React.KeyboardEvent) => void /** @@ -38,6 +39,11 @@ export type ActionListItemProps = { * id to attach to the root element of the Item */ id?: string + /** + * Text describing why the item is inactive. This may be used when an item's usual functionality + * is unavailable due to a system error such as a database outage. + */ + inactiveText?: string /** * Private API for use internally only. Used by LinkItem to wrap contents in an anchor */ @@ -57,6 +63,7 @@ type MenuItemProps = { export type ItemContext = Pick & { inlineDescriptionId?: string blockDescriptionId?: string + inactive?: boolean } export const ItemContext = React.createContext({}) @@ -64,6 +71,7 @@ export const ItemContext = React.createContext({}) export const getVariantStyles = ( variant: ActionListItemProps['variant'], disabled: ActionListItemProps['disabled'], + inactive?: boolean, ) => { if (disabled) { return { @@ -73,6 +81,14 @@ export const getVariantStyles = ( } } + if (inactive) { + return { + color: 'fg.muted', + iconColor: 'fg.muted', + annotationColor: 'fg.muted', + } + } + switch (variant) { case 'danger': return { diff --git a/src/ActionMenu/ActionMenu.features.stories.tsx b/src/ActionMenu/ActionMenu.features.stories.tsx index 2e0eb571078..686bbc9d9ca 100644 --- a/src/ActionMenu/ActionMenu.features.stories.tsx +++ b/src/ActionMenu/ActionMenu.features.stories.tsx @@ -122,3 +122,115 @@ export const MultiSelect = () => { ) } + +export const InactiveItems = () => ( + + Open menu + + + alert('Workflows clicked')} inactiveText="Unavailable due to an outage"> + Workflows + + + + + alert('Archived items clicked')} inactiveText="Unavailable due to an outage"> + Archived items + + + + + + Settings + + + + + alert('Make a copy clicked')} inactiveText="Unavailable due to an outage"> + Make a copy + + + + + + + + What's new + + + + + + Give feedback + + + + + + GitHub Docs + + + + + + + + +) + +// TODO: Uncomment this story when we have inactive buttons +// +// export const OnlyInactiveItems = () => ( +// +// Open menu +// +// +// alert('Workflows clicked')} inactiveText="Unavailable due to an outage"> +// Workflows +// +// +// +// +// alert('Archived items clicked')} inactiveText="Unavailable due to an outage"> +// Archived items +// +// +// +// +// +// Settings +// +// +// +// +// alert('Make a copy clicked')} inactiveText="Unavailable due to an outage"> +// Make a copy +// +// +// +// +// +// +// +// What's new +// +// +// +// +// +// Give feedback +// +// +// +// +// +// GitHub Docs +// +// +// +// +// +// +// +// +// ) diff --git a/src/NavList/NavList.stories.tsx b/src/NavList/NavList.stories.tsx index 3a7f51e8c51..d8623c56e3d 100644 --- a/src/NavList/NavList.stories.tsx +++ b/src/NavList/NavList.stories.tsx @@ -199,4 +199,29 @@ export const WithReloads: Story = () => { ) } +export const WithInactiveItems: Story = () => ( + + + + + Item 1 + + + Item 2 + + + Sub item 1 + + + Sub item 2 + + + + Item 3 + + + + +) + export default meta diff --git a/src/NavList/NavList.tsx b/src/NavList/NavList.tsx index 0a7687bb204..d3524872a41 100644 --- a/src/NavList/NavList.tsx +++ b/src/NavList/NavList.tsx @@ -51,6 +51,7 @@ export type NavListItemProps = { defaultOpen?: boolean href?: string 'aria-current'?: 'page' | 'step' | 'location' | 'date' | 'time' | 'true' | 'false' | boolean + inactiveText?: string } & SxProp const Item = React.forwardRef( diff --git a/src/NavList/__snapshots__/NavList.test.tsx.snap b/src/NavList/__snapshots__/NavList.test.tsx.snap index c9d6cf86b56..c6a9f6aafbc 100644 --- a/src/NavList/__snapshots__/NavList.test.tsx.snap +++ b/src/NavList/__snapshots__/NavList.test.tsx.snap @@ -65,11 +65,13 @@ exports[`NavList renders a simple list 1`] = ` background-color: rgba(208,215,222,0.24); } -.c2[aria-disabled] { +.c2[aria-disabled], +.c2[data-inactive] { cursor: not-allowed; } -.c2[aria-disabled] [data-component="ActionList.Checkbox"] { +.c2[aria-disabled] [data-component="ActionList.Checkbox"], +.c2[data-inactive] [data-component="ActionList.Checkbox"] { cursor: not-allowed; background-color: var(--color-input-disabled-bg,rgba(175,184,193,0.2)); border-color: var(--color-input-disabled-bg,rgba(175,184,193,0.2)); @@ -98,13 +100,13 @@ exports[`NavList renders a simple list 1`] = ` --divider-color: transparent !important; } -.c2:hover:not([aria-disabled]), -.c2:focus:not([aria-disabled]), -.c2[data-focus-visible-added]:not([aria-disabled]) { +.c2:hover:not([aria-disabled]):not([data-inactive]), +.c2:focus:not([aria-disabled]):not([data-inactive]), +.c2[data-focus-visible-added]:not([aria-disabled]):not([data-inactive]) { --divider-color: transparent; } -.c2:hover:not([aria-disabled]) + .c1, +.c2:hover:not([aria-disabled]):not([data-inactive]) + .c1, .c2[data-focus-visible-added] + li { --divider-color: transparent; } @@ -152,11 +154,13 @@ exports[`NavList renders a simple list 1`] = ` margin-bottom: unset; } -.c7[aria-disabled] { +.c7[aria-disabled], +.c7[data-inactive] { cursor: not-allowed; } -.c7[aria-disabled] [data-component="ActionList.Checkbox"] { +.c7[aria-disabled] [data-component="ActionList.Checkbox"], +.c7[data-inactive] [data-component="ActionList.Checkbox"] { cursor: not-allowed; background-color: var(--color-input-disabled-bg,rgba(175,184,193,0.2)); border-color: var(--color-input-disabled-bg,rgba(175,184,193,0.2)); @@ -185,13 +189,13 @@ exports[`NavList renders a simple list 1`] = ` --divider-color: transparent !important; } -.c7:hover:not([aria-disabled]), -.c7:focus:not([aria-disabled]), -.c7[data-focus-visible-added]:not([aria-disabled]) { +.c7:hover:not([aria-disabled]):not([data-inactive]), +.c7:focus:not([aria-disabled]):not([data-inactive]), +.c7[data-focus-visible-added]:not([aria-disabled]):not([data-inactive]) { --divider-color: transparent; } -.c7:hover:not([aria-disabled]) + .c1, +.c7:hover:not([aria-disabled]):not([data-inactive]) + .c1, .c7[data-focus-visible-added] + li { --divider-color: transparent; } @@ -250,7 +254,7 @@ exports[`NavList renders a simple list 1`] = ` } @media (hover:hover) and (pointer:fine) { - .c2:hover:not([aria-disabled]) { + .c2:hover:not([aria-disabled]):not([data-inactive]) { background-color: rgba(208,215,222,0.32); color: #1F2328; box-shadow: inset 0 0 0 max(1px,0.0625rem) rgba(0,0,0,0); @@ -263,7 +267,7 @@ exports[`NavList renders a simple list 1`] = ` box-shadow: 0 0 0 2px #0969da; } - .c2:active:not([aria-disabled]) { + .c2:active:not([aria-disabled]):not([data-inactive]) { background-color: rgba(208,215,222,0.48); color: #1F2328; } @@ -276,7 +280,7 @@ exports[`NavList renders a simple list 1`] = ` } @media (hover:hover) and (pointer:fine) { - .c7:hover:not([aria-disabled]) { + .c7:hover:not([aria-disabled]):not([data-inactive]) { background-color: rgba(208,215,222,0.32); color: #1F2328; box-shadow: inset 0 0 0 max(1px,0.0625rem) rgba(0,0,0,0); @@ -289,7 +293,7 @@ exports[`NavList renders a simple list 1`] = ` box-shadow: 0 0 0 2px #0969da; } - .c7:active:not([aria-disabled]) { + .c7:active:not([aria-disabled]):not([data-inactive]) { background-color: rgba(208,215,222,0.48); color: #1F2328; } @@ -485,11 +489,13 @@ exports[`NavList renders with groups 1`] = ` background-color: rgba(208,215,222,0.24); } -.c6[aria-disabled] { +.c6[aria-disabled], +.c6[data-inactive] { cursor: not-allowed; } -.c6[aria-disabled] [data-component="ActionList.Checkbox"] { +.c6[aria-disabled] [data-component="ActionList.Checkbox"], +.c6[data-inactive] [data-component="ActionList.Checkbox"] { cursor: not-allowed; background-color: var(--color-input-disabled-bg,rgba(175,184,193,0.2)); border-color: var(--color-input-disabled-bg,rgba(175,184,193,0.2)); @@ -518,13 +524,13 @@ exports[`NavList renders with groups 1`] = ` --divider-color: transparent !important; } -.c6:hover:not([aria-disabled]), -.c6:focus:not([aria-disabled]), -.c6[data-focus-visible-added]:not([aria-disabled]) { +.c6:hover:not([aria-disabled]):not([data-inactive]), +.c6:focus:not([aria-disabled]):not([data-inactive]), +.c6[data-focus-visible-added]:not([aria-disabled]):not([data-inactive]) { --divider-color: transparent; } -.c6:hover:not([aria-disabled]) + .c5, +.c6:hover:not([aria-disabled]):not([data-inactive]) + .c5, .c6[data-focus-visible-added] + li { --divider-color: transparent; } @@ -572,11 +578,13 @@ exports[`NavList renders with groups 1`] = ` margin-bottom: unset; } -.c11[aria-disabled] { +.c11[aria-disabled], +.c11[data-inactive] { cursor: not-allowed; } -.c11[aria-disabled] [data-component="ActionList.Checkbox"] { +.c11[aria-disabled] [data-component="ActionList.Checkbox"], +.c11[data-inactive] [data-component="ActionList.Checkbox"] { cursor: not-allowed; background-color: var(--color-input-disabled-bg,rgba(175,184,193,0.2)); border-color: var(--color-input-disabled-bg,rgba(175,184,193,0.2)); @@ -605,13 +613,13 @@ exports[`NavList renders with groups 1`] = ` --divider-color: transparent !important; } -.c11:hover:not([aria-disabled]), -.c11:focus:not([aria-disabled]), -.c11[data-focus-visible-added]:not([aria-disabled]) { +.c11:hover:not([aria-disabled]):not([data-inactive]), +.c11:focus:not([aria-disabled]):not([data-inactive]), +.c11[data-focus-visible-added]:not([aria-disabled]):not([data-inactive]) { --divider-color: transparent; } -.c11:hover:not([aria-disabled]) + .c5, +.c11:hover:not([aria-disabled]):not([data-inactive]) + .c5, .c11[data-focus-visible-added] + li { --divider-color: transparent; } @@ -670,7 +678,7 @@ exports[`NavList renders with groups 1`] = ` } @media (hover:hover) and (pointer:fine) { - .c6:hover:not([aria-disabled]) { + .c6:hover:not([aria-disabled]):not([data-inactive]) { background-color: rgba(208,215,222,0.32); color: #1F2328; box-shadow: inset 0 0 0 max(1px,0.0625rem) rgba(0,0,0,0); @@ -683,7 +691,7 @@ exports[`NavList renders with groups 1`] = ` box-shadow: 0 0 0 2px #0969da; } - .c6:active:not([aria-disabled]) { + .c6:active:not([aria-disabled]):not([data-inactive]) { background-color: rgba(208,215,222,0.48); color: #1F2328; } @@ -696,7 +704,7 @@ exports[`NavList renders with groups 1`] = ` } @media (hover:hover) and (pointer:fine) { - .c11:hover:not([aria-disabled]) { + .c11:hover:not([aria-disabled]):not([data-inactive]) { background-color: rgba(208,215,222,0.32); color: #1F2328; box-shadow: inset 0 0 0 max(1px,0.0625rem) rgba(0,0,0,0); @@ -709,7 +717,7 @@ exports[`NavList renders with groups 1`] = ` box-shadow: 0 0 0 2px #0969da; } - .c11:active:not([aria-disabled]) { + .c11:active:not([aria-disabled]):not([data-inactive]) { background-color: rgba(208,215,222,0.48); color: #1F2328; } @@ -897,7 +905,7 @@ exports[`NavList.Item with NavList.SubNav does not have active styles if SubNav --divider-color: transparent !important; } -.c14:hover:not([aria-disabled]) + .c3, +.c14:hover:not([aria-disabled]):not([data-inactive]) + .c3, .c14[data-focus-visible-added] + li { --divider-color: transparent; } @@ -936,11 +944,13 @@ exports[`NavList.Item with NavList.SubNav does not have active styles if SubNav background-color: rgba(208,215,222,0.24); } -.c11[aria-disabled] { +.c11[aria-disabled], +.c11[data-inactive] { cursor: not-allowed; } -.c11[aria-disabled] [data-component="ActionList.Checkbox"] { +.c11[aria-disabled] [data-component="ActionList.Checkbox"], +.c11[data-inactive] [data-component="ActionList.Checkbox"] { cursor: not-allowed; background-color: var(--color-input-disabled-bg,rgba(175,184,193,0.2)); border-color: var(--color-input-disabled-bg,rgba(175,184,193,0.2)); @@ -969,13 +979,13 @@ exports[`NavList.Item with NavList.SubNav does not have active styles if SubNav --divider-color: transparent !important; } -.c11:hover:not([aria-disabled]), -.c11:focus:not([aria-disabled]), -.c11[data-focus-visible-added]:not([aria-disabled]) { +.c11:hover:not([aria-disabled]):not([data-inactive]), +.c11:focus:not([aria-disabled]):not([data-inactive]), +.c11[data-focus-visible-added]:not([aria-disabled]):not([data-inactive]) { --divider-color: transparent; } -.c11:hover:not([aria-disabled]) + .c3, +.c11:hover:not([aria-disabled]):not([data-inactive]) + .c3, .c11[data-focus-visible-added] + li { --divider-color: transparent; } @@ -1023,11 +1033,13 @@ exports[`NavList.Item with NavList.SubNav does not have active styles if SubNav font-weight: 600; } -.c4[aria-disabled] { +.c4[aria-disabled], +.c4[data-inactive] { cursor: not-allowed; } -.c4[aria-disabled] [data-component="ActionList.Checkbox"] { +.c4[aria-disabled] [data-component="ActionList.Checkbox"], +.c4[data-inactive] [data-component="ActionList.Checkbox"] { cursor: not-allowed; background-color: var(--color-input-disabled-bg,rgba(175,184,193,0.2)); border-color: var(--color-input-disabled-bg,rgba(175,184,193,0.2)); @@ -1056,13 +1068,13 @@ exports[`NavList.Item with NavList.SubNav does not have active styles if SubNav --divider-color: transparent !important; } -.c4:hover:not([aria-disabled]), -.c4:focus:not([aria-disabled]), -.c4[data-focus-visible-added]:not([aria-disabled]) { +.c4:hover:not([aria-disabled]):not([data-inactive]), +.c4:focus:not([aria-disabled]):not([data-inactive]), +.c4[data-focus-visible-added]:not([aria-disabled]):not([data-inactive]) { --divider-color: transparent; } -.c4:hover:not([aria-disabled]) + .c3, +.c4:hover:not([aria-disabled]):not([data-inactive]) + .c3, .c4[data-focus-visible-added] + li { --divider-color: transparent; } @@ -1131,7 +1143,7 @@ exports[`NavList.Item with NavList.SubNav does not have active styles if SubNav } @media (hover:hover) and (pointer:fine) { - .c11:hover:not([aria-disabled]) { + .c11:hover:not([aria-disabled]):not([data-inactive]) { background-color: rgba(208,215,222,0.32); color: #1F2328; box-shadow: inset 0 0 0 max(1px,0.0625rem) rgba(0,0,0,0); @@ -1144,7 +1156,7 @@ exports[`NavList.Item with NavList.SubNav does not have active styles if SubNav box-shadow: 0 0 0 2px #0969da; } - .c11:active:not([aria-disabled]) { + .c11:active:not([aria-disabled]):not([data-inactive]) { background-color: rgba(208,215,222,0.48); color: #1F2328; } @@ -1157,7 +1169,7 @@ exports[`NavList.Item with NavList.SubNav does not have active styles if SubNav } @media (hover:hover) and (pointer:fine) { - .c4:hover:not([aria-disabled]) { + .c4:hover:not([aria-disabled]):not([data-inactive]) { background-color: rgba(208,215,222,0.32); color: #1F2328; box-shadow: inset 0 0 0 max(1px,0.0625rem) rgba(0,0,0,0); @@ -1170,7 +1182,7 @@ exports[`NavList.Item with NavList.SubNav does not have active styles if SubNav box-shadow: 0 0 0 2px #0969da; } - .c4:active:not([aria-disabled]) { + .c4:active:not([aria-disabled]):not([data-inactive]) { background-color: rgba(208,215,222,0.48); color: #1F2328; } @@ -1351,7 +1363,7 @@ exports[`NavList.Item with NavList.SubNav has active styles if SubNav contains t --divider-color: transparent !important; } -.c14:hover:not([aria-disabled]) + .c3, +.c14:hover:not([aria-disabled]):not([data-inactive]) + .c3, .c14[data-focus-visible-added] + li { --divider-color: transparent; } @@ -1390,11 +1402,13 @@ exports[`NavList.Item with NavList.SubNav has active styles if SubNav contains t background-color: rgba(208,215,222,0.24); } -.c11[aria-disabled] { +.c11[aria-disabled], +.c11[data-inactive] { cursor: not-allowed; } -.c11[aria-disabled] [data-component="ActionList.Checkbox"] { +.c11[aria-disabled] [data-component="ActionList.Checkbox"], +.c11[data-inactive] [data-component="ActionList.Checkbox"] { cursor: not-allowed; background-color: var(--color-input-disabled-bg,rgba(175,184,193,0.2)); border-color: var(--color-input-disabled-bg,rgba(175,184,193,0.2)); @@ -1423,13 +1437,13 @@ exports[`NavList.Item with NavList.SubNav has active styles if SubNav contains t --divider-color: transparent !important; } -.c11:hover:not([aria-disabled]), -.c11:focus:not([aria-disabled]), -.c11[data-focus-visible-added]:not([aria-disabled]) { +.c11:hover:not([aria-disabled]):not([data-inactive]), +.c11:focus:not([aria-disabled]):not([data-inactive]), +.c11[data-focus-visible-added]:not([aria-disabled]):not([data-inactive]) { --divider-color: transparent; } -.c11:hover:not([aria-disabled]) + .c3, +.c11:hover:not([aria-disabled]):not([data-inactive]) + .c3, .c11[data-focus-visible-added] + li { --divider-color: transparent; } @@ -1449,7 +1463,7 @@ exports[`NavList.Item with NavList.SubNav has active styles if SubNav contains t --divider-color: transparent !important; } -.c15:hover:not([aria-disabled]) + .c3, +.c15:hover:not([aria-disabled]):not([data-inactive]) + .c3, .c15[data-focus-visible-added] + li { --divider-color: transparent; } @@ -1487,11 +1501,13 @@ exports[`NavList.Item with NavList.SubNav has active styles if SubNav contains t background-color: rgba(208,215,222,0.24); } -.c4[aria-disabled] { +.c4[aria-disabled], +.c4[data-inactive] { cursor: not-allowed; } -.c4[aria-disabled] [data-component="ActionList.Checkbox"] { +.c4[aria-disabled] [data-component="ActionList.Checkbox"], +.c4[data-inactive] [data-component="ActionList.Checkbox"] { cursor: not-allowed; background-color: var(--color-input-disabled-bg,rgba(175,184,193,0.2)); border-color: var(--color-input-disabled-bg,rgba(175,184,193,0.2)); @@ -1520,13 +1536,13 @@ exports[`NavList.Item with NavList.SubNav has active styles if SubNav contains t --divider-color: transparent !important; } -.c4:hover:not([aria-disabled]), -.c4:focus:not([aria-disabled]), -.c4[data-focus-visible-added]:not([aria-disabled]) { +.c4:hover:not([aria-disabled]):not([data-inactive]), +.c4:focus:not([aria-disabled]):not([data-inactive]), +.c4[data-focus-visible-added]:not([aria-disabled]):not([data-inactive]) { --divider-color: transparent; } -.c4:hover:not([aria-disabled]) + .c3, +.c4:hover:not([aria-disabled]):not([data-inactive]) + .c3, .c4[data-focus-visible-added] + li { --divider-color: transparent; } @@ -1606,7 +1622,7 @@ exports[`NavList.Item with NavList.SubNav has active styles if SubNav contains t } @media (hover:hover) and (pointer:fine) { - .c11:hover:not([aria-disabled]) { + .c11:hover:not([aria-disabled]):not([data-inactive]) { background-color: rgba(208,215,222,0.32); color: #1F2328; box-shadow: inset 0 0 0 max(1px,0.0625rem) rgba(0,0,0,0); @@ -1619,7 +1635,7 @@ exports[`NavList.Item with NavList.SubNav has active styles if SubNav contains t box-shadow: 0 0 0 2px #0969da; } - .c11:active:not([aria-disabled]) { + .c11:active:not([aria-disabled]):not([data-inactive]) { background-color: rgba(208,215,222,0.48); color: #1F2328; } @@ -1640,7 +1656,7 @@ exports[`NavList.Item with NavList.SubNav has active styles if SubNav contains t } @media (hover:hover) and (pointer:fine) { - .c4:hover:not([aria-disabled]) { + .c4:hover:not([aria-disabled]):not([data-inactive]) { background-color: rgba(208,215,222,0.32); color: #1F2328; box-shadow: inset 0 0 0 max(1px,0.0625rem) rgba(0,0,0,0); @@ -1653,7 +1669,7 @@ exports[`NavList.Item with NavList.SubNav has active styles if SubNav contains t box-shadow: 0 0 0 2px #0969da; } - .c4:active:not([aria-disabled]) { + .c4:active:not([aria-disabled]):not([data-inactive]) { background-color: rgba(208,215,222,0.48); color: #1F2328; } diff --git a/src/__tests__/__snapshots__/Autocomplete.test.tsx.snap b/src/__tests__/__snapshots__/Autocomplete.test.tsx.snap index 080ff22b479..11df12386a1 100644 --- a/src/__tests__/__snapshots__/Autocomplete.test.tsx.snap +++ b/src/__tests__/__snapshots__/Autocomplete.test.tsx.snap @@ -687,11 +687,13 @@ exports[`snapshots renders a menu that contains an item to add to the menu 1`] = margin-bottom: unset; } -.c3[aria-disabled] { +.c3[aria-disabled], +.c3[data-inactive] { cursor: not-allowed; } -.c3[aria-disabled] [data-component="ActionList.Checkbox"] { +.c3[aria-disabled] [data-component="ActionList.Checkbox"], +.c3[data-inactive] [data-component="ActionList.Checkbox"] { cursor: not-allowed; background-color: var(--color-input-disabled-bg,rgba(175,184,193,0.2)); border-color: var(--color-input-disabled-bg,rgba(175,184,193,0.2)); @@ -720,13 +722,13 @@ exports[`snapshots renders a menu that contains an item to add to the menu 1`] = --divider-color: transparent !important; } -.c3:hover:not([aria-disabled]), -.c3:focus:not([aria-disabled]), -.c3[data-focus-visible-added]:not([aria-disabled]) { +.c3:hover:not([aria-disabled]):not([data-inactive]), +.c3:focus:not([aria-disabled]):not([data-inactive]), +.c3[data-focus-visible-added]:not([aria-disabled]):not([data-inactive]) { --divider-color: transparent; } -.c3:hover:not([aria-disabled]) + .c2, +.c3:hover:not([aria-disabled]):not([data-inactive]) + .c2, .c3[data-focus-visible-added] + li { --divider-color: transparent; } @@ -735,13 +737,13 @@ exports[`snapshots renders a menu that contains an item to add to the menu 1`] = --divider-color: transparent !important; } -.c10:hover:not([aria-disabled]) + .c2, +.c10:hover:not([aria-disabled]):not([data-inactive]) + .c2, .c10[data-focus-visible-added] + li { --divider-color: transparent; } @media (hover:hover) and (pointer:fine) { - .c3:hover:not([aria-disabled]) { + .c3:hover:not([aria-disabled]):not([data-inactive]) { background-color: rgba(208,215,222,0.32); color: #1F2328; box-shadow: inset 0 0 0 max(1px,0.0625rem) rgba(0,0,0,0); @@ -754,7 +756,7 @@ exports[`snapshots renders a menu that contains an item to add to the menu 1`] = box-shadow: 0 0 0 2px #0969da; } - .c3:active:not([aria-disabled]) { + .c3:active:not([aria-disabled]):not([data-inactive]) { background-color: rgba(208,215,222,0.48); color: #1F2328; } @@ -1426,11 +1428,13 @@ exports[`snapshots renders a multiselect input 1`] = ` margin-bottom: unset; } -.c3[aria-disabled] { +.c3[aria-disabled], +.c3[data-inactive] { cursor: not-allowed; } -.c3[aria-disabled] [data-component="ActionList.Checkbox"] { +.c3[aria-disabled] [data-component="ActionList.Checkbox"], +.c3[data-inactive] [data-component="ActionList.Checkbox"] { cursor: not-allowed; background-color: var(--color-input-disabled-bg,rgba(175,184,193,0.2)); border-color: var(--color-input-disabled-bg,rgba(175,184,193,0.2)); @@ -1459,19 +1463,19 @@ exports[`snapshots renders a multiselect input 1`] = ` --divider-color: transparent !important; } -.c3:hover:not([aria-disabled]), -.c3:focus:not([aria-disabled]), -.c3[data-focus-visible-added]:not([aria-disabled]) { +.c3:hover:not([aria-disabled]):not([data-inactive]), +.c3:focus:not([aria-disabled]):not([data-inactive]), +.c3[data-focus-visible-added]:not([aria-disabled]):not([data-inactive]) { --divider-color: transparent; } -.c3:hover:not([aria-disabled]) + .c2, +.c3:hover:not([aria-disabled]):not([data-inactive]) + .c2, .c3[data-focus-visible-added] + li { --divider-color: transparent; } @media (hover:hover) and (pointer:fine) { - .c3:hover:not([aria-disabled]) { + .c3:hover:not([aria-disabled]):not([data-inactive]) { background-color: rgba(208,215,222,0.32); color: #1F2328; box-shadow: inset 0 0 0 max(1px,0.0625rem) rgba(0,0,0,0); @@ -1484,7 +1488,7 @@ exports[`snapshots renders a multiselect input 1`] = ` box-shadow: 0 0 0 2px #0969da; } - .c3:active:not([aria-disabled]) { + .c3:active:not([aria-disabled]):not([data-inactive]) { background-color: rgba(208,215,222,0.48); color: #1F2328; } @@ -2138,11 +2142,13 @@ exports[`snapshots renders a multiselect input with selected menu items 1`] = ` margin-bottom: unset; } -.c3[aria-disabled] { +.c3[aria-disabled], +.c3[data-inactive] { cursor: not-allowed; } -.c3[aria-disabled] [data-component="ActionList.Checkbox"] { +.c3[aria-disabled] [data-component="ActionList.Checkbox"], +.c3[data-inactive] [data-component="ActionList.Checkbox"] { cursor: not-allowed; background-color: #656d76; border-color: #656d76; @@ -2171,13 +2177,13 @@ exports[`snapshots renders a multiselect input with selected menu items 1`] = ` --divider-color: transparent !important; } -.c3:hover:not([aria-disabled]), -.c3:focus:not([aria-disabled]), -.c3[data-focus-visible-added]:not([aria-disabled]) { +.c3:hover:not([aria-disabled]):not([data-inactive]), +.c3:focus:not([aria-disabled]):not([data-inactive]), +.c3[data-focus-visible-added]:not([aria-disabled]):not([data-inactive]) { --divider-color: transparent; } -.c3:hover:not([aria-disabled]) + .c2, +.c3:hover:not([aria-disabled]):not([data-inactive]) + .c2, .c3[data-focus-visible-added] + li { --divider-color: transparent; } @@ -2214,11 +2220,13 @@ exports[`snapshots renders a multiselect input with selected menu items 1`] = ` margin-bottom: unset; } -.c8[aria-disabled] { +.c8[aria-disabled], +.c8[data-inactive] { cursor: not-allowed; } -.c8[aria-disabled] [data-component="ActionList.Checkbox"] { +.c8[aria-disabled] [data-component="ActionList.Checkbox"], +.c8[data-inactive] [data-component="ActionList.Checkbox"] { cursor: not-allowed; background-color: var(--color-input-disabled-bg,rgba(175,184,193,0.2)); border-color: var(--color-input-disabled-bg,rgba(175,184,193,0.2)); @@ -2247,19 +2255,19 @@ exports[`snapshots renders a multiselect input with selected menu items 1`] = ` --divider-color: transparent !important; } -.c8:hover:not([aria-disabled]), -.c8:focus:not([aria-disabled]), -.c8[data-focus-visible-added]:not([aria-disabled]) { +.c8:hover:not([aria-disabled]):not([data-inactive]), +.c8:focus:not([aria-disabled]):not([data-inactive]), +.c8[data-focus-visible-added]:not([aria-disabled]):not([data-inactive]) { --divider-color: transparent; } -.c8:hover:not([aria-disabled]) + .c2, +.c8:hover:not([aria-disabled]):not([data-inactive]) + .c2, .c8[data-focus-visible-added] + li { --divider-color: transparent; } @media (hover:hover) and (pointer:fine) { - .c3:hover:not([aria-disabled]) { + .c3:hover:not([aria-disabled]):not([data-inactive]) { background-color: rgba(208,215,222,0.32); color: #1F2328; box-shadow: inset 0 0 0 max(1px,0.0625rem) rgba(0,0,0,0); @@ -2272,7 +2280,7 @@ exports[`snapshots renders a multiselect input with selected menu items 1`] = ` box-shadow: 0 0 0 2px #0969da; } - .c3:active:not([aria-disabled]) { + .c3:active:not([aria-disabled]):not([data-inactive]) { background-color: rgba(208,215,222,0.48); color: #1F2328; } @@ -2285,7 +2293,7 @@ exports[`snapshots renders a multiselect input with selected menu items 1`] = ` } @media (hover:hover) and (pointer:fine) { - .c8:hover:not([aria-disabled]) { + .c8:hover:not([aria-disabled]):not([data-inactive]) { background-color: rgba(208,215,222,0.32); color: #1F2328; box-shadow: inset 0 0 0 max(1px,0.0625rem) rgba(0,0,0,0); @@ -2298,7 +2306,7 @@ exports[`snapshots renders a multiselect input with selected menu items 1`] = ` box-shadow: 0 0 0 2px #0969da; } - .c8:active:not([aria-disabled]) { + .c8:active:not([aria-disabled]):not([data-inactive]) { background-color: rgba(208,215,222,0.48); color: #1F2328; } @@ -2868,11 +2876,13 @@ exports[`snapshots renders a single select input 1`] = ` margin-bottom: unset; } -.c3[aria-disabled] { +.c3[aria-disabled], +.c3[data-inactive] { cursor: not-allowed; } -.c3[aria-disabled] [data-component="ActionList.Checkbox"] { +.c3[aria-disabled] [data-component="ActionList.Checkbox"], +.c3[data-inactive] [data-component="ActionList.Checkbox"] { cursor: not-allowed; background-color: var(--color-input-disabled-bg,rgba(175,184,193,0.2)); border-color: var(--color-input-disabled-bg,rgba(175,184,193,0.2)); @@ -2901,19 +2911,19 @@ exports[`snapshots renders a single select input 1`] = ` --divider-color: transparent !important; } -.c3:hover:not([aria-disabled]), -.c3:focus:not([aria-disabled]), -.c3[data-focus-visible-added]:not([aria-disabled]) { +.c3:hover:not([aria-disabled]):not([data-inactive]), +.c3:focus:not([aria-disabled]):not([data-inactive]), +.c3[data-focus-visible-added]:not([aria-disabled]):not([data-inactive]) { --divider-color: transparent; } -.c3:hover:not([aria-disabled]) + .c2, +.c3:hover:not([aria-disabled]):not([data-inactive]) + .c2, .c3[data-focus-visible-added] + li { --divider-color: transparent; } @media (hover:hover) and (pointer:fine) { - .c3:hover:not([aria-disabled]) { + .c3:hover:not([aria-disabled]):not([data-inactive]) { background-color: rgba(208,215,222,0.32); color: #1F2328; box-shadow: inset 0 0 0 max(1px,0.0625rem) rgba(0,0,0,0); @@ -2926,7 +2936,7 @@ exports[`snapshots renders a single select input 1`] = ` box-shadow: 0 0 0 2px #0969da; } - .c3:active:not([aria-disabled]) { + .c3:active:not([aria-disabled]):not([data-inactive]) { background-color: rgba(208,215,222,0.48); color: #1F2328; } @@ -3319,11 +3329,13 @@ exports[`snapshots renders with a custom text input component 1`] = ` margin-bottom: unset; } -.c3[aria-disabled] { +.c3[aria-disabled], +.c3[data-inactive] { cursor: not-allowed; } -.c3[aria-disabled] [data-component="ActionList.Checkbox"] { +.c3[aria-disabled] [data-component="ActionList.Checkbox"], +.c3[data-inactive] [data-component="ActionList.Checkbox"] { cursor: not-allowed; background-color: var(--color-input-disabled-bg,rgba(175,184,193,0.2)); border-color: var(--color-input-disabled-bg,rgba(175,184,193,0.2)); @@ -3352,19 +3364,19 @@ exports[`snapshots renders with a custom text input component 1`] = ` --divider-color: transparent !important; } -.c3:hover:not([aria-disabled]), -.c3:focus:not([aria-disabled]), -.c3[data-focus-visible-added]:not([aria-disabled]) { +.c3:hover:not([aria-disabled]):not([data-inactive]), +.c3:focus:not([aria-disabled]):not([data-inactive]), +.c3[data-focus-visible-added]:not([aria-disabled]):not([data-inactive]) { --divider-color: transparent; } -.c3:hover:not([aria-disabled]) + .c2, +.c3:hover:not([aria-disabled]):not([data-inactive]) + .c2, .c3[data-focus-visible-added] + li { --divider-color: transparent; } @media (hover:hover) and (pointer:fine) { - .c3:hover:not([aria-disabled]) { + .c3:hover:not([aria-disabled]):not([data-inactive]) { background-color: rgba(208,215,222,0.32); color: #1F2328; box-shadow: inset 0 0 0 max(1px,0.0625rem) rgba(0,0,0,0); @@ -3377,7 +3389,7 @@ exports[`snapshots renders with a custom text input component 1`] = ` box-shadow: 0 0 0 2px #0969da; } - .c3:active:not([aria-disabled]) { + .c3:active:not([aria-disabled]):not([data-inactive]) { background-color: rgba(208,215,222,0.48); color: #1F2328; } diff --git a/src/drafts/Tooltip/Tooltip.tsx b/src/drafts/Tooltip/Tooltip.tsx index 417f8e03bab..6570e939bf0 100644 --- a/src/drafts/Tooltip/Tooltip.tsx +++ b/src/drafts/Tooltip/Tooltip.tsx @@ -273,9 +273,9 @@ export const Tooltip = React.forwardRef( React.cloneElement(child as React.ReactElement, { ref: triggerRef, // If it is a type description, we use tooltip to describe the trigger - 'aria-describedby': type === 'description' ? `tooltip-${tooltipId}` : undefined, + 'aria-describedby': type === 'description' ? `tooltip-${tooltipId}` : child.props['aria-describedby'], // If it is a label type, we use tooltip to label the trigger - 'aria-labelledby': type === 'label' ? `tooltip-${tooltipId}` : undefined, + 'aria-labelledby': type === 'label' ? `tooltip-${tooltipId}` : child.props['aria-labelledby'], onBlur: (event: React.FocusEvent) => { closeTooltip() child.props.onBlur?.(event)