diff --git a/.changeset/soft-pans-punch.md b/.changeset/soft-pans-punch.md new file mode 100644 index 00000000000..e0a29220089 --- /dev/null +++ b/.changeset/soft-pans-punch.md @@ -0,0 +1,7 @@ +--- +'@primer/react': minor +--- + +Adds an 'inactive' state to buttons. An inactive button looks disabled and has aria-disabled, but it can still be clicked and focused. This was added to support buttons that are broken due to availability issues, but can't be removed from the page. + + diff --git a/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Inactive-dark-colorblind-linux.png b/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Inactive-dark-colorblind-linux.png new file mode 100644 index 00000000000..1e3a0c45717 Binary files /dev/null and b/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Inactive-dark-colorblind-linux.png differ diff --git a/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Inactive-dark-dimmed-linux.png b/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Inactive-dark-dimmed-linux.png new file mode 100644 index 00000000000..20fa18b1f09 Binary files /dev/null and b/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Inactive-dark-dimmed-linux.png differ diff --git a/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Inactive-dark-high-contrast-linux.png b/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Inactive-dark-high-contrast-linux.png new file mode 100644 index 00000000000..d9fea495638 Binary files /dev/null and b/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Inactive-dark-high-contrast-linux.png differ diff --git a/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Inactive-dark-linux.png b/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Inactive-dark-linux.png new file mode 100644 index 00000000000..1e3a0c45717 Binary files /dev/null and b/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Inactive-dark-linux.png differ diff --git a/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Inactive-dark-tritanopia-linux.png b/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Inactive-dark-tritanopia-linux.png new file mode 100644 index 00000000000..1e3a0c45717 Binary files /dev/null and b/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Inactive-dark-tritanopia-linux.png differ diff --git a/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Inactive-light-colorblind-linux.png b/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Inactive-light-colorblind-linux.png new file mode 100644 index 00000000000..aac9a555acf Binary files /dev/null and b/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Inactive-light-colorblind-linux.png differ diff --git a/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Inactive-light-high-contrast-linux.png b/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Inactive-light-high-contrast-linux.png new file mode 100644 index 00000000000..f8942b5bac8 Binary files /dev/null and b/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Inactive-light-high-contrast-linux.png differ diff --git a/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Inactive-light-linux.png b/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Inactive-light-linux.png new file mode 100644 index 00000000000..3bc974ffe65 Binary files /dev/null and b/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Inactive-light-linux.png differ diff --git a/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Inactive-light-tritanopia-linux.png b/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Inactive-light-tritanopia-linux.png new file mode 100644 index 00000000000..aac9a555acf Binary files /dev/null and b/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Inactive-light-tritanopia-linux.png differ diff --git a/e2e/components/Button.test.ts b/e2e/components/Button.test.ts index d9a4755ab1a..cc8eee590ef 100644 --- a/e2e/components/Button.test.ts +++ b/e2e/components/Button.test.ts @@ -445,6 +445,40 @@ test.describe('Button', () => { } }) + test.describe('Inactive', () => { + for (const theme of themes) { + test.describe(theme, () => { + test('default @vrt', async ({page}) => { + await visit(page, { + id: 'components-button-features--inactive', + globals: { + colorScheme: theme, + }, + }) + + // Default state + expect(await page.screenshot()).toMatchSnapshot(`Button.Inactive.${theme}.png`) + }) + + test('axe @aat', async ({page}) => { + await visit(page, { + id: 'components-button-features--inactive', + globals: { + colorScheme: theme, + }, + }) + await expect(page).toHaveNoViolations({ + rules: { + 'color-contrast': { + enabled: theme !== 'dark_dimmed', + }, + }, + }) + }) + }) + } + }) + test.describe('Dev: Invisible Variants', () => { for (const theme of themes) { test.describe(theme, () => { diff --git a/package-lock.json b/package-lock.json index f690e86a6eb..e8c310fee41 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,7 +17,8 @@ "@oddbird/popover-polyfill": "^0.3.1", "@primer/behaviors": "^1.5.1", "@primer/octicons-react": "^19.8.0", - "@primer/primitives": "^7.11.11", + "@primer/primitives": "7.15.3", + "@react-aria/ssr": "^3.5.0", "@styled-system/css": "^5.1.5", "@styled-system/props": "^5.1.5", "@styled-system/theme-get": "^5.1.2", @@ -6030,9 +6031,9 @@ } }, "node_modules/@primer/primitives": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@primer/primitives/-/primitives-7.15.4.tgz", - "integrity": "sha512-471hL6pkcGuPS4G0urQ0TRZYo3ukAaVhUtvlsi1mZLofmX+EF+9iQL/iau06JfaQkm5NNP236+F7yyxXra9EjA==" + "version": "7.15.3", + "resolved": "https://registry.npmjs.org/@primer/primitives/-/primitives-7.15.3.tgz", + "integrity": "sha512-BFxFKwa0Bkr+esqbXU5Yt91z/58J2MPoW1cYtp0j2rUYus4lIZnczX7+ZYb7j4BqpfY/88q9Vn+BRwW/Sx4eIA==" }, "node_modules/@primer/view-components": { "version": "0.14.0", @@ -6716,6 +6717,20 @@ "@babel/runtime": "^7.13.10" } }, + "node_modules/@react-aria/ssr": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/@react-aria/ssr/-/ssr-3.9.0.tgz", + "integrity": "sha512-Bz6BqP6ZorCme9tSWHZVmmY+s7AU8l6Vl2NUYmBzezD//fVHHfFo4lFBn5tBuAaJEm3AuCLaJQ6H2qhxNSb7zg==", + "dependencies": { + "@swc/helpers": "^0.5.0" + }, + "engines": { + "node": ">= 12" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, "node_modules/@react-dnd/asap": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@react-dnd/asap/-/asap-4.0.0.tgz", @@ -9047,17 +9062,6 @@ } } }, - "node_modules/@storybook/builder-webpack5/node_modules/@swc/helpers": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.1.tgz", - "integrity": "sha512-sJ902EfIzn1Fa+qYmjdQqh8tPsoxyBz+8yBKC2HKUxyezKJFwPGOn7pv4WY6QuQW//ySQi5lJjA/ZT9sNWWNTg==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, "node_modules/@storybook/builder-webpack5/node_modules/@types/node": { "version": "16.18.38", "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.38.tgz", @@ -12022,17 +12026,6 @@ } } }, - "node_modules/@storybook/test-runner/node_modules/@swc/helpers": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.1.tgz", - "integrity": "sha512-sJ902EfIzn1Fa+qYmjdQqh8tPsoxyBz+8yBKC2HKUxyezKJFwPGOn7pv4WY6QuQW//ySQi5lJjA/ZT9sNWWNTg==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, "node_modules/@storybook/test-runner/node_modules/@swc/jest": { "version": "0.2.26", "resolved": "https://registry.npmjs.org/@swc/jest/-/jest-0.2.26.tgz", @@ -13222,6 +13215,14 @@ "node": ">=10" } }, + "node_modules/@swc/helpers": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.3.tgz", + "integrity": "sha512-FaruWX6KdudYloq1AHD/4nU+UsMTdNE8CKyrseXWEcgjDAbvkwJg2QGPAnfIJLIWsjZOSPLOAykK6fuYp4vp4A==", + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@testing-library/dom": { "version": "9.3.1", "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-9.3.1.tgz", @@ -40079,8 +40080,7 @@ "node_modules/tslib": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", - "dev": true + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" }, "node_modules/tsutils": { "version": "3.21.0", diff --git a/package.json b/package.json index 46e2f4c4463..efba074a25b 100644 --- a/package.json +++ b/package.json @@ -102,7 +102,8 @@ "@oddbird/popover-polyfill": "^0.3.1", "@primer/behaviors": "^1.5.1", "@primer/octicons-react": "^19.8.0", - "@primer/primitives": "^7.11.11", + "@primer/primitives": "7.15.3", + "@react-aria/ssr": "^3.5.0", "@styled-system/css": "^5.1.5", "@styled-system/props": "^5.1.5", "@styled-system/theme-get": "^5.1.2", diff --git a/src/Button/Button.docs.json b/src/Button/Button.docs.json index e6f74974f5b..e2ab4c6b3d1 100644 --- a/src/Button/Button.docs.json +++ b/src/Button/Button.docs.json @@ -50,6 +50,11 @@ "type": "React.ElementType", "description": "A visual to display after the button text." }, + { + "name": "inactive", + "type": "boolean", + "description": "Whether the button looks visually disabled, but can still accept all the same interactions as an enabled button.\n This is intended to be used when a system error such as an outage prevents the button from performing its usual action.\n Inactive styles are slightly different from disabled styles because inactive buttons need to have an accessible color contrast ratio. This is because inactive buttons can have tooltips or perform an action such as opening a dialog explaining why it's inactive.\n If both `disabled` and `inactive` are true, `disabled` takes precedence." + }, { "name": "as", "type": "React.ElementType", diff --git a/src/Button/Button.features.stories.tsx b/src/Button/Button.features.stories.tsx index 96197d0692e..195093642ca 100644 --- a/src/Button/Button.features.stories.tsx +++ b/src/Button/Button.features.stories.tsx @@ -63,6 +63,12 @@ export const Block = () => export const Disabled = () => +export const Inactive = () => ( + +) + export const Small = () => export const Medium = () => diff --git a/src/Button/Button.stories.tsx b/src/Button/Button.stories.tsx index 00456a231bb..793d5884195 100644 --- a/src/Button/Button.stories.tsx +++ b/src/Button/Button.stories.tsx @@ -26,6 +26,11 @@ Playground.argTypes = { type: 'boolean', }, }, + inactive: { + control: { + type: 'boolean', + }, + }, variant: { control: { type: 'radio', @@ -51,6 +56,7 @@ Playground.args = { block: false, size: 'medium', disabled: false, + inactive: false, variant: 'default', alignContent: 'center', trailingVisual: null, diff --git a/src/Button/ButtonBase.tsx b/src/Button/ButtonBase.tsx index 8ee3b3ff6f3..1e05138e5c2 100644 --- a/src/Button/ButtonBase.tsx +++ b/src/Button/ButtonBase.tsx @@ -21,6 +21,7 @@ const ButtonBase = forwardRef( size = 'medium', alignContent = 'center', block = false, + inactive, ...rest } = props @@ -68,6 +69,7 @@ const ButtonBase = forwardRef( data-block={block ? 'block' : null} data-size={size === 'small' || size === 'large' ? size : undefined} data-no-visuals={!LeadingVisual && !TrailingVisual && !TrailingAction ? true : undefined} + data-inactive={inactive ? true : undefined} > {Icon ? ( diff --git a/src/Button/IconButton.docs.json b/src/Button/IconButton.docs.json index c49de7a2661..ad1529202a8 100644 --- a/src/Button/IconButton.docs.json +++ b/src/Button/IconButton.docs.json @@ -24,6 +24,11 @@ "defaultValue": "", "description": "Changes the size of the icon button component" }, + { + "name": "inactive", + "type": "boolean", + "description": "Whether the button looks visually disabled, but can still accept all the same interactions as an enabled button." + }, { "name": "icon", "type": "Component", diff --git a/src/Button/IconButton.stories.tsx b/src/Button/IconButton.stories.tsx index cc8b1f5b78a..a60e2fc9340 100644 --- a/src/Button/IconButton.stories.tsx +++ b/src/Button/IconButton.stories.tsx @@ -23,6 +23,11 @@ Playground.argTypes = { type: 'boolean', }, }, + inactive: { + control: { + type: 'boolean', + }, + }, variant: { control: { type: 'radio', @@ -34,6 +39,7 @@ Playground.argTypes = { Playground.args = { size: 'medium', disabled: false, + inactive: false, variant: 'default', 'aria-label': 'Icon button description', icon: XIcon, diff --git a/src/Button/__tests__/__snapshots__/Button.test.tsx.snap b/src/Button/__tests__/__snapshots__/Button.test.tsx.snap index 1d0805e2466..67da6d5e057 100644 --- a/src/Button/__tests__/__snapshots__/Button.test.tsx.snap +++ b/src/Button/__tests__/__snapshots__/Button.test.tsx.snap @@ -91,6 +91,10 @@ exports[`Button renders consistently 1`] = ` transition: none; } +.c0[data-inactive] { + cursor: auto; +} + .c0:disabled { cursor: not-allowed; box-shadow: none; @@ -156,6 +160,16 @@ exports[`Button renders consistently 1`] = ` width: 100%; } +.c0[data-inactive]:not([disabled]) { + background-color: var(--button-inactive-bgColor,#eaeef2); + border-color: var(--button-inactive-bgColor,#eaeef2); + color: var(--button-inactive-fgColor,#57606a); +} + +.c0[data-inactive]:not([disabled]):focus-visible { + box-shadow: none; +} + .c0 [data-component="leadingVisual"] { grid-area: leadingVisual; } @@ -194,12 +208,12 @@ exports[`Button renders consistently 1`] = ` margin-right: 8px; } -.c0:hover:not([disabled]) { +.c0:hover:not([disabled]):not([data-inactive]) { background-color: #f3f4f6; border-color: var(--button-default-borderColor-hover,rgba(31,35,40,0.15)); } -.c0:active:not([disabled]) { +.c0:active:not([disabled]):not([data-inactive]) { background-color: hsla(220,14%,93%,1); border-color: var(--button-default-borderColor-active,rgba(31,35,40,0.15)); } @@ -326,6 +340,10 @@ exports[`Button respects block prop 1`] = ` transition: none; } +.c0[data-inactive] { + cursor: auto; +} + .c0:disabled { cursor: not-allowed; box-shadow: none; @@ -391,6 +409,16 @@ exports[`Button respects block prop 1`] = ` width: 100%; } +.c0[data-inactive]:not([disabled]) { + background-color: var(--button-inactive-bgColor,undefined); + border-color: var(--button-inactive-bgColor,undefined); + color: var(--button-inactive-fgColor,undefined); +} + +.c0[data-inactive]:not([disabled]):focus-visible { + box-shadow: none; +} + .c0 [data-component="leadingVisual"] { grid-area: leadingVisual; } @@ -429,12 +457,12 @@ exports[`Button respects block prop 1`] = ` margin-right: 8px; } -.c0:hover:not([disabled]) { +.c0:hover:not([disabled]):not([data-inactive]) { background-color: btn.hoverBg; border-color: var(--button-default-borderColor-hover,undefined); } -.c0:active:not([disabled]) { +.c0:active:not([disabled]):not([data-inactive]) { background-color: btn.activeBg; border-color: var(--button-default-borderColor-active,undefined); } @@ -566,6 +594,10 @@ exports[`Button respects the alignContent prop 1`] = ` transition: none; } +.c0[data-inactive] { + cursor: auto; +} + .c0:disabled { cursor: not-allowed; box-shadow: none; @@ -631,6 +663,16 @@ exports[`Button respects the alignContent prop 1`] = ` width: 100%; } +.c0[data-inactive]:not([disabled]) { + background-color: var(--button-inactive-bgColor,undefined); + border-color: var(--button-inactive-bgColor,undefined); + color: var(--button-inactive-fgColor,undefined); +} + +.c0[data-inactive]:not([disabled]):focus-visible { + box-shadow: none; +} + .c0 [data-component="leadingVisual"] { grid-area: leadingVisual; } @@ -669,12 +711,12 @@ exports[`Button respects the alignContent prop 1`] = ` margin-right: 8px; } -.c0:hover:not([disabled]) { +.c0:hover:not([disabled]):not([data-inactive]) { background-color: btn.hoverBg; border-color: var(--button-default-borderColor-hover,undefined); } -.c0:active:not([disabled]) { +.c0:active:not([disabled]):not([data-inactive]) { background-color: btn.activeBg; border-color: var(--button-default-borderColor-active,undefined); } @@ -805,6 +847,10 @@ exports[`Button respects the large size prop 1`] = ` transition: none; } +.c0[data-inactive] { + cursor: auto; +} + .c0:disabled { cursor: not-allowed; box-shadow: none; @@ -870,6 +916,16 @@ exports[`Button respects the large size prop 1`] = ` width: 100%; } +.c0[data-inactive]:not([disabled]) { + background-color: var(--button-inactive-bgColor,undefined); + border-color: var(--button-inactive-bgColor,undefined); + color: var(--button-inactive-fgColor,undefined); +} + +.c0[data-inactive]:not([disabled]):focus-visible { + box-shadow: none; +} + .c0 [data-component="leadingVisual"] { grid-area: leadingVisual; } @@ -908,12 +964,12 @@ exports[`Button respects the large size prop 1`] = ` margin-right: 8px; } -.c0:hover:not([disabled]) { +.c0:hover:not([disabled]):not([data-inactive]) { background-color: btn.hoverBg; border-color: var(--button-default-borderColor-hover,undefined); } -.c0:active:not([disabled]) { +.c0:active:not([disabled]):not([data-inactive]) { background-color: btn.activeBg; border-color: var(--button-default-borderColor-active,undefined); } @@ -1045,6 +1101,10 @@ exports[`Button respects the small size prop 1`] = ` transition: none; } +.c0[data-inactive] { + cursor: auto; +} + .c0:disabled { cursor: not-allowed; box-shadow: none; @@ -1110,6 +1170,16 @@ exports[`Button respects the small size prop 1`] = ` width: 100%; } +.c0[data-inactive]:not([disabled]) { + background-color: var(--button-inactive-bgColor,undefined); + border-color: var(--button-inactive-bgColor,undefined); + color: var(--button-inactive-fgColor,undefined); +} + +.c0[data-inactive]:not([disabled]):focus-visible { + box-shadow: none; +} + .c0 [data-component="leadingVisual"] { grid-area: leadingVisual; } @@ -1148,12 +1218,12 @@ exports[`Button respects the small size prop 1`] = ` margin-right: 8px; } -.c0:hover:not([disabled]) { +.c0:hover:not([disabled]):not([data-inactive]) { background-color: btn.hoverBg; border-color: var(--button-default-borderColor-hover,undefined); } -.c0:active:not([disabled]) { +.c0:active:not([disabled]):not([data-inactive]) { background-color: btn.activeBg; border-color: var(--button-default-borderColor-active,undefined); } @@ -1285,6 +1355,10 @@ exports[`Button styles danger button appropriately 1`] = ` transition: none; } +.c0[data-inactive] { + cursor: auto; +} + .c0:disabled { cursor: not-allowed; box-shadow: none; @@ -1355,6 +1429,16 @@ exports[`Button styles danger button appropriately 1`] = ` width: 100%; } +.c0[data-inactive]:not([disabled]) { + background-color: var(--button-inactive-bgColor,undefined); + border-color: var(--button-inactive-bgColor,undefined); + color: var(--button-inactive-fgColor,undefined); +} + +.c0[data-inactive]:not([disabled]):focus-visible { + box-shadow: none; +} + .c0 [data-component="leadingVisual"] { grid-area: leadingVisual; } @@ -1393,19 +1477,19 @@ exports[`Button styles danger button appropriately 1`] = ` margin-right: 8px; } -.c0:hover:not([disabled]) { +.c0:hover:not([disabled]):not([data-inactive]) { color: btn.danger.hoverText; background-color: btn.danger.hoverBg; border-color: btn.danger.hoverBorder; box-shadow: undefined; } -.c0:hover:not([disabled]) [data-component=ButtonCounter] { +.c0:hover:not([disabled]):not([data-inactive]) [data-component=ButtonCounter] { background-color: btn.danger.hoverCounterBg; color: btn.danger.hoverCounterFg; } -.c0:active:not([disabled]) { +.c0:active:not([disabled]):not([data-inactive]) { color: btn.danger.selectedText; background-color: btn.danger.selectedBg; box-shadow: undefined; @@ -1534,6 +1618,10 @@ exports[`Button styles invisible button appropriately 1`] = ` transition: none; } +.c0[data-inactive] { + cursor: auto; +} + .c0:disabled { cursor: not-allowed; box-shadow: none; @@ -1601,6 +1689,16 @@ exports[`Button styles invisible button appropriately 1`] = ` width: 100%; } +.c0[data-inactive]:not([disabled]) { + background-color: var(--button-inactive-bgColor,undefined); + border-color: var(--button-inactive-bgColor,undefined); + color: var(--button-inactive-fgColor,undefined); +} + +.c0[data-inactive]:not([disabled]):focus-visible { + box-shadow: none; +} + .c0 [data-component="leadingVisual"] { grid-area: leadingVisual; color: fg.muted; @@ -1788,6 +1886,10 @@ exports[`Button styles primary button appropriately 1`] = ` transition: none; } +.c0[data-inactive] { + cursor: auto; +} + .c0:disabled { cursor: not-allowed; box-shadow: none; @@ -1856,6 +1958,16 @@ exports[`Button styles primary button appropriately 1`] = ` width: 100%; } +.c0[data-inactive]:not([disabled]) { + background-color: var(--button-inactive-bgColor,undefined); + border-color: var(--button-inactive-bgColor,undefined); + color: var(--button-inactive-fgColor,undefined); +} + +.c0[data-inactive]:not([disabled]):focus-visible { + box-shadow: none; +} + .c0 [data-component="leadingVisual"] { grid-area: leadingVisual; } @@ -1894,7 +2006,7 @@ exports[`Button styles primary button appropriately 1`] = ` margin-right: 8px; } -.c0:hover:not([disabled]) { +.c0:hover:not([disabled]):not([data-inactive]) { color: btn.primary.hoverText; background-color: btn.primary.hoverBg; } @@ -1907,7 +2019,7 @@ exports[`Button styles primary button appropriately 1`] = ` box-shadow: inset 0 0 0 3px; } -.c0:active:not([disabled]) { +.c0:active:not([disabled]):not([data-inactive]) { background-color: btn.primary.selectedBg; box-shadow: undefined; } diff --git a/src/Button/styles.ts b/src/Button/styles.ts index e373ffd7f07..81a9d0c606b 100644 --- a/src/Button/styles.ts +++ b/src/Button/styles.ts @@ -7,11 +7,11 @@ export const getVariantStyles = (variant: VariantType = 'default', theme?: Theme color: 'btn.text', backgroundColor: 'btn.bg', boxShadow: `${theme?.shadows.btn.shadow}, ${theme?.shadows.btn.insetShadow}`, - '&:hover:not([disabled])': { + '&:hover:not([disabled]):not([data-inactive])': { backgroundColor: 'btn.hoverBg', borderColor: `var(--button-default-borderColor-hover, ${theme?.colors.btn.hoverBorder})`, }, - '&:active:not([disabled])': { + '&:active:not([disabled]):not([data-inactive])': { backgroundColor: 'btn.activeBg', borderColor: `var(--button-default-borderColor-active, ${theme?.colors.btn.activeBorder})`, }, @@ -34,7 +34,7 @@ export const getVariantStyles = (variant: VariantType = 'default', theme?: Theme backgroundColor: 'btn.primary.bg', borderColor: 'btn.primary.border', boxShadow: `${theme?.shadows.btn.primary.shadow}`, - '&:hover:not([disabled])': { + '&:hover:not([disabled]):not([data-inactive])': { color: 'btn.primary.hoverText', backgroundColor: 'btn.primary.hoverBg', }, @@ -44,7 +44,7 @@ export const getVariantStyles = (variant: VariantType = 'default', theme?: Theme '&:focus-visible:not([disabled])': { boxShadow: 'inset 0 0 0 3px', }, - '&:active:not([disabled])': { + '&:active:not([disabled]):not([data-inactive])': { backgroundColor: 'btn.primary.selectedBg', boxShadow: `${theme?.shadows.btn.primary.selectedShadow}`, }, @@ -68,7 +68,7 @@ export const getVariantStyles = (variant: VariantType = 'default', theme?: Theme color: 'btn.danger.text', backgroundColor: 'btn.bg', boxShadow: `${theme?.shadows.btn.shadow}`, - '&:hover:not([disabled])': { + '&:hover:not([disabled]):not([data-inactive])': { color: 'btn.danger.hoverText', backgroundColor: 'btn.danger.hoverBg', borderColor: 'btn.danger.hoverBorder', @@ -78,7 +78,7 @@ export const getVariantStyles = (variant: VariantType = 'default', theme?: Theme color: 'btn.danger.hoverCounterFg', }, }, - '&:active:not([disabled])': { + '&:active:not([disabled]):not([data-inactive])': { color: 'btn.danger.selectedText', backgroundColor: 'btn.danger.selectedBg', boxShadow: `${theme?.shadows.btn.danger.selectedShadow}`, @@ -152,7 +152,7 @@ export const getVariantStyles = (variant: VariantType = 'default', theme?: Theme borderColor: `var(--button-default-borderColor-rest, ${theme?.colors.btn.border})`, backgroundColor: 'btn.bg', - '&:hover:not([disabled])': { + '&:hover:not([disabled]):not([data-inactive])': { color: 'btn.outline.hoverText', backgroundColor: 'btn.outline.hoverBg', borderColor: `${theme?.colors.btn.outline.hoverBorder}`, @@ -162,7 +162,7 @@ export const getVariantStyles = (variant: VariantType = 'default', theme?: Theme color: 'btn.outline.hoverCounterFg', }, }, - '&:active:not([disabled])': { + '&:active:not([disabled]):not([data-inactive])': { color: 'btn.outline.selectedText', backgroundColor: 'btn.outline.selectedBg', boxShadow: `${theme?.shadows.btn.outline.selectedShadow}`, @@ -190,6 +190,7 @@ export const getVariantStyles = (variant: VariantType = 'default', theme?: Theme }, }, } + return style[variant] } @@ -226,6 +227,9 @@ export const getBaseStyles = (theme?: Theme) => ({ '&:active': { transition: 'none', }, + '&[data-inactive]': { + cursor: 'auto', + }, '&:disabled': { cursor: 'not-allowed', boxShadow: 'none', @@ -291,6 +295,14 @@ export const getButtonStyles = (theme?: Theme) => { '&[data-block="block"]': { width: '100%', }, + '&[data-inactive]:not([disabled])': { + backgroundColor: `var(--button-inactive-bgColor, ${theme?.colors.btn.inactive.bg})`, + borderColor: `var(--button-inactive-bgColor, ${theme?.colors.btn.inactive.bg})`, + color: `var(--button-inactive-fgColor, ${theme?.colors.btn.inactive.text})`, + }, + '&[data-inactive]:not([disabled]):focus-visible': { + boxShadow: 'none', + }, '[data-component="leadingVisual"]': { gridArea: 'leadingVisual', }, diff --git a/src/Button/types.ts b/src/Button/types.ts index d2f3c252099..49ad7969eb9 100644 --- a/src/Button/types.ts +++ b/src/Button/types.ts @@ -35,6 +35,11 @@ export type ButtonBaseProps = { * Allow button width to fill its container. */ block?: boolean + /** + * Whether the button looks visually disabled, but can still accept all the same + * interactions as an enabled button. + */ + inactive?: boolean } & SxProp & React.ButtonHTMLAttributes diff --git a/src/Dialog/__snapshots__/Dialog.test.tsx.snap b/src/Dialog/__snapshots__/Dialog.test.tsx.snap index cb0b5cfda3a..f3aaf3ab027 100644 --- a/src/Dialog/__snapshots__/Dialog.test.tsx.snap +++ b/src/Dialog/__snapshots__/Dialog.test.tsx.snap @@ -164,6 +164,10 @@ exports[`Dialog renders consistently 1`] = ` transition: none; } +.c1[data-inactive] { + cursor: auto; +} + .c1:disabled { cursor: not-allowed; box-shadow: none; @@ -231,6 +235,16 @@ exports[`Dialog renders consistently 1`] = ` width: 100%; } +.c1[data-inactive]:not([disabled]) { + background-color: var(--button-inactive-bgColor,#eaeef2); + border-color: var(--button-inactive-bgColor,#eaeef2); + color: var(--button-inactive-fgColor,#57606a); +} + +.c1[data-inactive]:not([disabled]):focus-visible { + box-shadow: none; +} + .c1 [data-component="leadingVisual"] { grid-area: leadingVisual; color: #656d76; diff --git a/src/SelectPanel/__snapshots__/SelectPanel.test.tsx.snap b/src/SelectPanel/__snapshots__/SelectPanel.test.tsx.snap index a1682f981cd..0b105cd6a03 100644 --- a/src/SelectPanel/__snapshots__/SelectPanel.test.tsx.snap +++ b/src/SelectPanel/__snapshots__/SelectPanel.test.tsx.snap @@ -105,6 +105,10 @@ exports[`SelectPanel renders consistently 1`] = ` transition: none; } +.c1[data-inactive] { + cursor: auto; +} + .c1:disabled { cursor: not-allowed; box-shadow: none; @@ -170,6 +174,16 @@ exports[`SelectPanel renders consistently 1`] = ` width: 100%; } +.c1[data-inactive]:not([disabled]) { + background-color: var(--button-inactive-bgColor,#eaeef2); + border-color: var(--button-inactive-bgColor,#eaeef2); + color: var(--button-inactive-fgColor,#57606a); +} + +.c1[data-inactive]:not([disabled]):focus-visible { + box-shadow: none; +} + .c1 [data-component="leadingVisual"] { grid-area: leadingVisual; } @@ -208,12 +222,12 @@ exports[`SelectPanel renders consistently 1`] = ` margin-right: 8px; } -.c1:hover:not([disabled]) { +.c1:hover:not([disabled]):not([data-inactive]) { background-color: #f3f4f6; border-color: var(--button-default-borderColor-hover,rgba(31,35,40,0.15)); } -.c1:active:not([disabled]) { +.c1:active:not([disabled]):not([data-inactive]) { background-color: hsla(220,14%,93%,1); border-color: var(--button-default-borderColor-active,rgba(31,35,40,0.15)); } diff --git a/src/__tests__/__snapshots__/ActionMenu.test.tsx.snap b/src/__tests__/__snapshots__/ActionMenu.test.tsx.snap index 5f5cd763cca..67221dbfe60 100644 --- a/src/__tests__/__snapshots__/ActionMenu.test.tsx.snap +++ b/src/__tests__/__snapshots__/ActionMenu.test.tsx.snap @@ -105,6 +105,10 @@ exports[`ActionMenu renders consistently 1`] = ` transition: none; } +.c1[data-inactive] { + cursor: auto; +} + .c1:disabled { cursor: not-allowed; box-shadow: none; @@ -170,6 +174,16 @@ exports[`ActionMenu renders consistently 1`] = ` width: 100%; } +.c1[data-inactive]:not([disabled]) { + background-color: var(--button-inactive-bgColor,#eaeef2); + border-color: var(--button-inactive-bgColor,#eaeef2); + color: var(--button-inactive-fgColor,#57606a); +} + +.c1[data-inactive]:not([disabled]):focus-visible { + box-shadow: none; +} + .c1 [data-component="leadingVisual"] { grid-area: leadingVisual; } @@ -208,12 +222,12 @@ exports[`ActionMenu renders consistently 1`] = ` margin-right: 8px; } -.c1:hover:not([disabled]) { +.c1:hover:not([disabled]):not([data-inactive]) { background-color: #f3f4f6; border-color: var(--button-default-borderColor-hover,rgba(31,35,40,0.15)); } -.c1:active:not([disabled]) { +.c1:active:not([disabled]):not([data-inactive]) { background-color: hsla(220,14%,93%,1); border-color: var(--button-default-borderColor-active,rgba(31,35,40,0.15)); } diff --git a/src/__tests__/__snapshots__/TextInput.test.tsx.snap b/src/__tests__/__snapshots__/TextInput.test.tsx.snap index 1a431197282..e891006f9dd 100644 --- a/src/__tests__/__snapshots__/TextInput.test.tsx.snap +++ b/src/__tests__/__snapshots__/TextInput.test.tsx.snap @@ -1779,6 +1779,10 @@ exports[`TextInput renders trailingAction icon button 1`] = ` transition: none; } +.c5[data-inactive] { + cursor: auto; +} + .c5:disabled { cursor: not-allowed; box-shadow: none; @@ -1846,6 +1850,16 @@ exports[`TextInput renders trailingAction icon button 1`] = ` width: 100%; } +.c5[data-inactive]:not([disabled]) { + background-color: var(--button-inactive-bgColor,#eaeef2); + border-color: var(--button-inactive-bgColor,#eaeef2); + color: var(--button-inactive-fgColor,#57606a); +} + +.c5[data-inactive]:not([disabled]):focus-visible { + box-shadow: none; +} + .c5 [data-component="leadingVisual"] { grid-area: leadingVisual; color: #656d76; @@ -2432,6 +2446,10 @@ exports[`TextInput renders trailingAction text button 1`] = ` transition: none; } +.c4[data-inactive] { + cursor: auto; +} + .c4:disabled { cursor: not-allowed; box-shadow: none; @@ -2499,6 +2517,16 @@ exports[`TextInput renders trailingAction text button 1`] = ` width: 100%; } +.c4[data-inactive]:not([disabled]) { + background-color: var(--button-inactive-bgColor,#eaeef2); + border-color: var(--button-inactive-bgColor,#eaeef2); + color: var(--button-inactive-fgColor,#57606a); +} + +.c4[data-inactive]:not([disabled]):focus-visible { + box-shadow: none; +} + .c4 [data-component="leadingVisual"] { grid-area: leadingVisual; color: #656d76; @@ -2837,6 +2865,10 @@ exports[`TextInput renders trailingAction text button with a tooltip 1`] = ` transition: none; } +.c5[data-inactive] { + cursor: auto; +} + .c5:disabled { cursor: not-allowed; box-shadow: none; @@ -2904,6 +2936,16 @@ exports[`TextInput renders trailingAction text button with a tooltip 1`] = ` width: 100%; } +.c5[data-inactive]:not([disabled]) { + background-color: var(--button-inactive-bgColor,#eaeef2); + border-color: var(--button-inactive-bgColor,#eaeef2); + color: var(--button-inactive-fgColor,#57606a); +} + +.c5[data-inactive]:not([disabled]):focus-visible { + box-shadow: none; +} + .c5 [data-component="leadingVisual"] { grid-area: leadingVisual; color: #656d76;