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..d9dd6608e6a
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..9a57ae787e0
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..78a50c2bf71
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..2ce2acffba0
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..d9dd6608e6a
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..06b96eae488
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..761a31e4f20
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..497c9262997
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..06b96eae488
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 b7f50fdd03b..6e31e4fb9de 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -17,7 +17,7 @@
"@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",
@@ -6031,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.5.1",
diff --git a/package.json b/package.json
index 886af52e34a..49d6bb2f35c 100644
--- a/package.json
+++ b/package.json
@@ -102,7 +102,7 @@
"@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",
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..f923a02e616 100644
--- a/src/Button/Button.features.stories.tsx
+++ b/src/Button/Button.features.stories.tsx
@@ -1,6 +1,8 @@
import {EyeIcon, TriangleDownIcon, HeartIcon} from '@primer/octicons-react'
import React, {useState} from 'react'
import {Button} from '.'
+import {Tooltip} from '../drafts'
+import {ToggleSwitch, Text, Box, Dialog} from '../'
export default {
title: 'Components/Button/Features',
@@ -63,8 +65,61 @@ export const Block = () =>
export const Disabled = () =>
+export const Inactive = () => (
+
+)
+
export const Small = () =>
export const Medium = () =>
export const Large = () =>
+
+const InactiveButtonHover = () =>
+
+export const InactiveButtonWithTooltip = () => {
+ const [isOn, setIsOn] = React.useState(false)
+ const onClick = React.useCallback(() => {
+ setIsOn(!isOn)
+ }, [setIsOn, isOn])
+
+ return (
+
+
+ Simulate outage
+
+
+ {isOn ? (
+
+
+
+ ) : (
+
+ )}
+
+ )
+}
+
+export const InactiveButtonShowsDialog = () => {
+ const [isOpen, setIsOpen] = React.useState(false)
+ const onDialogClose = React.useCallback(() => {
+ setIsOpen(false)
+ }, [setIsOpen])
+ const onAnchorClick = React.useCallback(() => {
+ setIsOpen(true)
+ }, [setIsOpen])
+ return (
+
+
+ {isOpen && (
+
+ )}
+
+ )
+}
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..dfb478daddf 100644
--- a/src/Button/__tests__/__snapshots__/Button.test.tsx.snap
+++ b/src/Button/__tests__/__snapshots__/Button.test.tsx.snap
@@ -91,14 +91,13 @@ exports[`Button renders consistently 1`] = `
transition: none;
}
+.c0[data-inactive] {
+ cursor: auto;
+}
+
.c0:disabled {
cursor: not-allowed;
box-shadow: none;
- color: #8c959f;
-}
-
-.c0:disabled [data-component=ButtonCounter] {
- color: inherit;
}
.c0 [data-component=ButtonCounter] {
@@ -156,6 +155,12 @@ exports[`Button renders consistently 1`] = `
width: 100%;
}
+.c0[data-inactive]:not([disabled]):not([aria-disabled]) {
+ background-color: var(--button-inactive-bgColor,#eaeef2);
+ border: 0;
+ color: var(--button-inactive-fgColor,#57606a);
+}
+
.c0 [data-component="leadingVisual"] {
grid-area: leadingVisual;
}
@@ -194,16 +199,26 @@ exports[`Button renders consistently 1`] = `
margin-right: 8px;
}
-.c0:hover:not([disabled]) {
+.c0:hover:not([disabled]):not([aria-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([aria-disabled]):not([data-inactive]) {
background-color: hsla(220,14%,93%,1);
border-color: var(--button-default-borderColor-active,rgba(31,35,40,0.15));
}
+.c0:disabled,
+.c0[aria-disabled] {
+ color: #8c959f;
+}
+
+.c0:disabled [data-component=ButtonCounter],
+.c0[aria-disabled] [data-component=ButtonCounter] {
+ color: inherit;
+}
+
.c0[aria-expanded=true] {
background-color: hsla(220,14%,93%,1);
border-color: var(--button-default-borderColor-active,rgba(31,35,40,0.15));
@@ -326,14 +341,13 @@ exports[`Button respects block prop 1`] = `
transition: none;
}
+.c0[data-inactive] {
+ cursor: auto;
+}
+
.c0:disabled {
cursor: not-allowed;
box-shadow: none;
- color: primer.fg.disabled;
-}
-
-.c0:disabled [data-component=ButtonCounter] {
- color: inherit;
}
.c0 [data-component=ButtonCounter] {
@@ -391,6 +405,12 @@ exports[`Button respects block prop 1`] = `
width: 100%;
}
+.c0[data-inactive]:not([disabled]):not([aria-disabled]) {
+ background-color: var(--button-inactive-bgColor,undefined);
+ border: 0;
+ color: var(--button-inactive-fgColor,undefined);
+}
+
.c0 [data-component="leadingVisual"] {
grid-area: leadingVisual;
}
@@ -429,16 +449,26 @@ exports[`Button respects block prop 1`] = `
margin-right: 8px;
}
-.c0:hover:not([disabled]) {
+.c0:hover:not([disabled]):not([aria-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([aria-disabled]):not([data-inactive]) {
background-color: btn.activeBg;
border-color: var(--button-default-borderColor-active,undefined);
}
+.c0:disabled,
+.c0[aria-disabled] {
+ color: primer.fg.disabled;
+}
+
+.c0:disabled [data-component=ButtonCounter],
+.c0[aria-disabled] [data-component=ButtonCounter] {
+ color: inherit;
+}
+
.c0[aria-expanded=true] {
background-color: btn.activeBg;
border-color: var(--button-default-borderColor-active,undefined);
@@ -566,14 +596,13 @@ exports[`Button respects the alignContent prop 1`] = `
transition: none;
}
+.c0[data-inactive] {
+ cursor: auto;
+}
+
.c0:disabled {
cursor: not-allowed;
box-shadow: none;
- color: primer.fg.disabled;
-}
-
-.c0:disabled [data-component=ButtonCounter] {
- color: inherit;
}
.c0 [data-component=ButtonCounter] {
@@ -631,6 +660,12 @@ exports[`Button respects the alignContent prop 1`] = `
width: 100%;
}
+.c0[data-inactive]:not([disabled]):not([aria-disabled]) {
+ background-color: var(--button-inactive-bgColor,undefined);
+ border: 0;
+ color: var(--button-inactive-fgColor,undefined);
+}
+
.c0 [data-component="leadingVisual"] {
grid-area: leadingVisual;
}
@@ -669,16 +704,26 @@ exports[`Button respects the alignContent prop 1`] = `
margin-right: 8px;
}
-.c0:hover:not([disabled]) {
+.c0:hover:not([disabled]):not([aria-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([aria-disabled]):not([data-inactive]) {
background-color: btn.activeBg;
border-color: var(--button-default-borderColor-active,undefined);
}
+.c0:disabled,
+.c0[aria-disabled] {
+ color: primer.fg.disabled;
+}
+
+.c0:disabled [data-component=ButtonCounter],
+.c0[aria-disabled] [data-component=ButtonCounter] {
+ color: inherit;
+}
+
.c0[aria-expanded=true] {
background-color: btn.activeBg;
border-color: var(--button-default-borderColor-active,undefined);
@@ -805,14 +850,13 @@ exports[`Button respects the large size prop 1`] = `
transition: none;
}
+.c0[data-inactive] {
+ cursor: auto;
+}
+
.c0:disabled {
cursor: not-allowed;
box-shadow: none;
- color: primer.fg.disabled;
-}
-
-.c0:disabled [data-component=ButtonCounter] {
- color: inherit;
}
.c0 [data-component=ButtonCounter] {
@@ -870,6 +914,12 @@ exports[`Button respects the large size prop 1`] = `
width: 100%;
}
+.c0[data-inactive]:not([disabled]):not([aria-disabled]) {
+ background-color: var(--button-inactive-bgColor,undefined);
+ border: 0;
+ color: var(--button-inactive-fgColor,undefined);
+}
+
.c0 [data-component="leadingVisual"] {
grid-area: leadingVisual;
}
@@ -908,16 +958,26 @@ exports[`Button respects the large size prop 1`] = `
margin-right: 8px;
}
-.c0:hover:not([disabled]) {
+.c0:hover:not([disabled]):not([aria-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([aria-disabled]):not([data-inactive]) {
background-color: btn.activeBg;
border-color: var(--button-default-borderColor-active,undefined);
}
+.c0:disabled,
+.c0[aria-disabled] {
+ color: primer.fg.disabled;
+}
+
+.c0:disabled [data-component=ButtonCounter],
+.c0[aria-disabled] [data-component=ButtonCounter] {
+ color: inherit;
+}
+
.c0[aria-expanded=true] {
background-color: btn.activeBg;
border-color: var(--button-default-borderColor-active,undefined);
@@ -1045,14 +1105,13 @@ exports[`Button respects the small size prop 1`] = `
transition: none;
}
+.c0[data-inactive] {
+ cursor: auto;
+}
+
.c0:disabled {
cursor: not-allowed;
box-shadow: none;
- color: primer.fg.disabled;
-}
-
-.c0:disabled [data-component=ButtonCounter] {
- color: inherit;
}
.c0 [data-component=ButtonCounter] {
@@ -1110,6 +1169,12 @@ exports[`Button respects the small size prop 1`] = `
width: 100%;
}
+.c0[data-inactive]:not([disabled]):not([aria-disabled]) {
+ background-color: var(--button-inactive-bgColor,undefined);
+ border: 0;
+ color: var(--button-inactive-fgColor,undefined);
+}
+
.c0 [data-component="leadingVisual"] {
grid-area: leadingVisual;
}
@@ -1148,16 +1213,26 @@ exports[`Button respects the small size prop 1`] = `
margin-right: 8px;
}
-.c0:hover:not([disabled]) {
+.c0:hover:not([disabled]):not([aria-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([aria-disabled]):not([data-inactive]) {
background-color: btn.activeBg;
border-color: var(--button-default-borderColor-active,undefined);
}
+.c0:disabled,
+.c0[aria-disabled] {
+ color: primer.fg.disabled;
+}
+
+.c0:disabled [data-component=ButtonCounter],
+.c0[aria-disabled] [data-component=ButtonCounter] {
+ color: inherit;
+}
+
.c0[aria-expanded=true] {
background-color: btn.activeBg;
border-color: var(--button-default-borderColor-active,undefined);
@@ -1285,17 +1360,13 @@ exports[`Button styles danger button appropriately 1`] = `
transition: none;
}
+.c0[data-inactive] {
+ cursor: auto;
+}
+
.c0:disabled {
cursor: not-allowed;
box-shadow: none;
- color: btn.danger.disabledText;
- background-color: btn.danger.disabledBg;
- border-color: btn.danger.disabledBorder;
-}
-
-.c0:disabled [data-component=ButtonCounter] {
- color: btn.danger.disabledCounterFg;
- background-color: btn.danger.disabledCounterBg;
}
.c0 [data-component=ButtonCounter] {
@@ -1355,6 +1426,12 @@ exports[`Button styles danger button appropriately 1`] = `
width: 100%;
}
+.c0[data-inactive]:not([disabled]):not([aria-disabled]) {
+ background-color: var(--button-inactive-bgColor,undefined);
+ border: 0;
+ color: var(--button-inactive-fgColor,undefined);
+}
+
.c0 [data-component="leadingVisual"] {
grid-area: leadingVisual;
}
@@ -1393,25 +1470,38 @@ exports[`Button styles danger button appropriately 1`] = `
margin-right: 8px;
}
-.c0:hover:not([disabled]) {
+.c0:hover:not([disabled]):not([aria-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([aria-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([aria-disabled]):not([data-inactive]) {
color: btn.danger.selectedText;
background-color: btn.danger.selectedBg;
box-shadow: undefined;
border-color: btn.danger.selectedBorder;
}
+.c0:disabled,
+.c0[aria-disabled] {
+ color: btn.danger.disabledText;
+ background-color: btn.danger.disabledBg;
+ border-color: btn.danger.disabledBorder;
+}
+
+.c0:disabled [data-component=ButtonCounter],
+.c0[aria-disabled] [data-component=ButtonCounter] {
+ color: btn.danger.disabledCounterFg;
+ background-color: btn.danger.disabledCounterBg;
+}
+
.c0[aria-expanded=true] {
color: btn.danger.selectedText;
background-color: btn.danger.selectedBg;
@@ -1534,16 +1624,13 @@ exports[`Button styles invisible button appropriately 1`] = `
transition: none;
}
+.c0[data-inactive] {
+ cursor: auto;
+}
+
.c0:disabled {
cursor: not-allowed;
box-shadow: none;
- color: primer.fg.disabled;
-}
-
-.c0:disabled [data-component=ButtonCounter],
-.c0:disabled [data-component="leadingVisual"],
-.c0:disabled [data-component="trailingAction"] {
- color: inherit;
}
.c0 [data-component=ButtonCounter] {
@@ -1601,6 +1688,12 @@ exports[`Button styles invisible button appropriately 1`] = `
width: 100%;
}
+.c0[data-inactive]:not([disabled]):not([aria-disabled]) {
+ background-color: var(--button-inactive-bgColor,undefined);
+ border: 0;
+ color: var(--button-inactive-fgColor,undefined);
+}
+
.c0 [data-component="leadingVisual"] {
grid-area: leadingVisual;
color: fg.muted;
@@ -1649,6 +1742,20 @@ exports[`Button styles invisible button appropriately 1`] = `
background-color: actionListItem.default.activeBg;
}
+.c0:disabled,
+.c0[aria-disabled] {
+ color: primer.fg.disabled;
+}
+
+.c0:disabled [data-component=ButtonCounter],
+.c0[aria-disabled] [data-component=ButtonCounter],
+.c0:disabled [data-component="leadingVisual"],
+.c0[aria-disabled] [data-component="leadingVisual"],
+.c0:disabled [data-component="trailingAction"],
+.c0[aria-disabled] [data-component="trailingAction"] {
+ color: inherit;
+}
+
.c0[aria-expanded=true] {
background-color: actionListItem.default.selectedBg;
}
@@ -1788,15 +1895,13 @@ exports[`Button styles primary button appropriately 1`] = `
transition: none;
}
+.c0[data-inactive] {
+ cursor: auto;
+}
+
.c0:disabled {
cursor: not-allowed;
box-shadow: none;
- color: btn.primary.disabledText;
- background-color: btn.primary.disabledBg;
-}
-
-.c0:disabled [data-component=ButtonCounter] {
- color: inherit;
}
.c0 [data-component=ButtonCounter] {
@@ -1856,6 +1961,12 @@ exports[`Button styles primary button appropriately 1`] = `
width: 100%;
}
+.c0[data-inactive]:not([disabled]):not([aria-disabled]) {
+ background-color: var(--button-inactive-bgColor,undefined);
+ border: 0;
+ color: var(--button-inactive-fgColor,undefined);
+}
+
.c0 [data-component="leadingVisual"] {
grid-area: leadingVisual;
}
@@ -1894,7 +2005,7 @@ exports[`Button styles primary button appropriately 1`] = `
margin-right: 8px;
}
-.c0:hover:not([disabled]) {
+.c0:hover:not([disabled]):not([aria-disabled]):not([data-inactive]) {
color: btn.primary.hoverText;
background-color: btn.primary.hoverBg;
}
@@ -1907,11 +2018,22 @@ exports[`Button styles primary button appropriately 1`] = `
box-shadow: inset 0 0 0 3px;
}
-.c0:active:not([disabled]) {
+.c0:active:not([disabled]):not([aria-disabled]):not([data-inactive]) {
background-color: btn.primary.selectedBg;
box-shadow: undefined;
}
+.c0:disabled,
+.c0[aria-disabled] {
+ color: btn.primary.disabledText;
+ background-color: btn.primary.disabledBg;
+}
+
+.c0:disabled [data-component=ButtonCounter],
+.c0[aria-disabled] [data-component=ButtonCounter] {
+ color: inherit;
+}
+
.c0[aria-expanded=true] {
background-color: btn.primary.selectedBg;
box-shadow: undefined;
diff --git a/src/Button/styles.ts b/src/Button/styles.ts
index e373ffd7f07..263913dc6fd 100644
--- a/src/Button/styles.ts
+++ b/src/Button/styles.ts
@@ -7,15 +7,15 @@ 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([aria-disabled]):not([data-inactive])': {
backgroundColor: 'btn.hoverBg',
borderColor: `var(--button-default-borderColor-hover, ${theme?.colors.btn.hoverBorder})`,
},
- '&:active:not([disabled])': {
+ '&:active:not([disabled]):not([aria-disabled]):not([data-inactive])': {
backgroundColor: 'btn.activeBg',
borderColor: `var(--button-default-borderColor-active, ${theme?.colors.btn.activeBorder})`,
},
- '&:disabled': {
+ '&:disabled, &[aria-disabled]': {
color: 'primer.fg.disabled',
'[data-component=ButtonCounter]': {
color: 'inherit',
@@ -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([aria-disabled]):not([data-inactive])': {
color: 'btn.primary.hoverText',
backgroundColor: 'btn.primary.hoverBg',
},
@@ -44,11 +44,11 @@ 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([aria-disabled]):not([data-inactive])': {
backgroundColor: 'btn.primary.selectedBg',
boxShadow: `${theme?.shadows.btn.primary.selectedShadow}`,
},
- '&:disabled': {
+ '&:disabled, &[aria-disabled]': {
color: 'btn.primary.disabledText',
backgroundColor: 'btn.primary.disabledBg',
'[data-component=ButtonCounter]': {
@@ -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([aria-disabled]):not([data-inactive])': {
color: 'btn.danger.hoverText',
backgroundColor: 'btn.danger.hoverBg',
borderColor: 'btn.danger.hoverBorder',
@@ -78,13 +78,13 @@ export const getVariantStyles = (variant: VariantType = 'default', theme?: Theme
color: 'btn.danger.hoverCounterFg',
},
},
- '&:active:not([disabled])': {
+ '&:active:not([disabled]):not([aria-disabled]):not([data-inactive])': {
color: 'btn.danger.selectedText',
backgroundColor: 'btn.danger.selectedBg',
boxShadow: `${theme?.shadows.btn.danger.selectedShadow}`,
borderColor: 'btn.danger.selectedBorder',
},
- '&:disabled': {
+ '&:disabled, &[aria-disabled]': {
color: 'btn.danger.disabledText',
backgroundColor: 'btn.danger.disabledBg',
borderColor: 'btn.danger.disabledBorder',
@@ -115,7 +115,7 @@ export const getVariantStyles = (variant: VariantType = 'default', theme?: Theme
'&:active:not([disabled])': {
backgroundColor: 'actionListItem.default.activeBg',
},
- '&:disabled': {
+ '&:disabled, &[aria-disabled]': {
color: 'primer.fg.disabled',
'[data-component=ButtonCounter], [data-component="leadingVisual"], [data-component="trailingAction"]': {
color: 'inherit',
@@ -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([aria-disabled]):not([data-inactive])': {
color: 'btn.outline.hoverText',
backgroundColor: 'btn.outline.hoverBg',
borderColor: `${theme?.colors.btn.outline.hoverBorder}`,
@@ -162,14 +162,14 @@ export const getVariantStyles = (variant: VariantType = 'default', theme?: Theme
color: 'btn.outline.hoverCounterFg',
},
},
- '&:active:not([disabled])': {
+ '&:active:not([disabled]):not([aria-disabled]):not([data-inactive])': {
color: 'btn.outline.selectedText',
backgroundColor: 'btn.outline.selectedBg',
boxShadow: `${theme?.shadows.btn.outline.selectedShadow}`,
borderColor: `${theme?.colors.btn.outline.selectedBorder}`,
},
- '&:disabled': {
+ '&:disabled, &[aria-disabled]': {
color: 'btn.outline.disabledText',
backgroundColor: 'btn.outline.disabledBg',
borderColor: 'btn.border',
@@ -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,11 @@ export const getButtonStyles = (theme?: Theme) => {
'&[data-block="block"]': {
width: '100%',
},
+ '&[data-inactive]:not([disabled]):not([aria-disabled])': {
+ backgroundColor: `var(--button-inactive-bgColor, ${theme?.colors.btn.inactive.bg})`,
+ border: 0,
+ color: `var(--button-inactive-fgColor, ${theme?.colors.btn.inactive.text})`,
+ },
'[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..daedbbc002f 100644
--- a/src/Dialog/__snapshots__/Dialog.test.tsx.snap
+++ b/src/Dialog/__snapshots__/Dialog.test.tsx.snap
@@ -164,16 +164,13 @@ exports[`Dialog renders consistently 1`] = `
transition: none;
}
+.c1[data-inactive] {
+ cursor: auto;
+}
+
.c1:disabled {
cursor: not-allowed;
box-shadow: none;
- color: #8c959f;
-}
-
-.c1:disabled [data-component=ButtonCounter],
-.c1:disabled [data-component="leadingVisual"],
-.c1:disabled [data-component="trailingAction"] {
- color: inherit;
}
.c1 [data-component=ButtonCounter] {
@@ -231,6 +228,12 @@ exports[`Dialog renders consistently 1`] = `
width: 100%;
}
+.c1[data-inactive]:not([disabled]):not([aria-disabled]) {
+ background-color: var(--button-inactive-bgColor,#eaeef2);
+ border: 0;
+ color: var(--button-inactive-fgColor,#57606a);
+}
+
.c1 [data-component="leadingVisual"] {
grid-area: leadingVisual;
color: #656d76;
@@ -279,6 +282,20 @@ exports[`Dialog renders consistently 1`] = `
background-color: rgba(208,215,222,0.48);
}
+.c1:disabled,
+.c1[aria-disabled] {
+ color: #8c959f;
+}
+
+.c1:disabled [data-component=ButtonCounter],
+.c1[aria-disabled] [data-component=ButtonCounter],
+.c1:disabled [data-component="leadingVisual"],
+.c1[aria-disabled] [data-component="leadingVisual"],
+.c1:disabled [data-component="trailingAction"],
+.c1[aria-disabled] [data-component="trailingAction"] {
+ color: inherit;
+}
+
.c1[aria-expanded=true] {
background-color: rgba(208,215,222,0.24);
}
diff --git a/src/SelectPanel/__snapshots__/SelectPanel.test.tsx.snap b/src/SelectPanel/__snapshots__/SelectPanel.test.tsx.snap
index 7d696d62efc..e3ec074e3c9 100644
--- a/src/SelectPanel/__snapshots__/SelectPanel.test.tsx.snap
+++ b/src/SelectPanel/__snapshots__/SelectPanel.test.tsx.snap
@@ -105,14 +105,13 @@ exports[`SelectPanel renders consistently 1`] = `
transition: none;
}
+.c1[data-inactive] {
+ cursor: auto;
+}
+
.c1:disabled {
cursor: not-allowed;
box-shadow: none;
- color: #8c959f;
-}
-
-.c1:disabled [data-component=ButtonCounter] {
- color: inherit;
}
.c1 [data-component=ButtonCounter] {
@@ -170,6 +169,12 @@ exports[`SelectPanel renders consistently 1`] = `
width: 100%;
}
+.c1[data-inactive]:not([disabled]):not([aria-disabled]) {
+ background-color: var(--button-inactive-bgColor,#eaeef2);
+ border: 0;
+ color: var(--button-inactive-fgColor,#57606a);
+}
+
.c1 [data-component="leadingVisual"] {
grid-area: leadingVisual;
}
@@ -208,16 +213,26 @@ exports[`SelectPanel renders consistently 1`] = `
margin-right: 8px;
}
-.c1:hover:not([disabled]) {
+.c1:hover:not([disabled]):not([aria-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([aria-disabled]):not([data-inactive]) {
background-color: hsla(220,14%,93%,1);
border-color: var(--button-default-borderColor-active,rgba(31,35,40,0.15));
}
+.c1:disabled,
+.c1[aria-disabled] {
+ color: #8c959f;
+}
+
+.c1:disabled [data-component=ButtonCounter],
+.c1[aria-disabled] [data-component=ButtonCounter] {
+ color: inherit;
+}
+
.c1[aria-expanded=true] {
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 0893c7502ea..0cc2dc942f1 100644
--- a/src/__tests__/__snapshots__/ActionMenu.test.tsx.snap
+++ b/src/__tests__/__snapshots__/ActionMenu.test.tsx.snap
@@ -105,14 +105,13 @@ exports[`ActionMenu renders consistently 1`] = `
transition: none;
}
+.c1[data-inactive] {
+ cursor: auto;
+}
+
.c1:disabled {
cursor: not-allowed;
box-shadow: none;
- color: #8c959f;
-}
-
-.c1:disabled [data-component=ButtonCounter] {
- color: inherit;
}
.c1 [data-component=ButtonCounter] {
@@ -170,6 +169,12 @@ exports[`ActionMenu renders consistently 1`] = `
width: 100%;
}
+.c1[data-inactive]:not([disabled]):not([aria-disabled]) {
+ background-color: var(--button-inactive-bgColor,#eaeef2);
+ border: 0;
+ color: var(--button-inactive-fgColor,#57606a);
+}
+
.c1 [data-component="leadingVisual"] {
grid-area: leadingVisual;
}
@@ -208,16 +213,26 @@ exports[`ActionMenu renders consistently 1`] = `
margin-right: 8px;
}
-.c1:hover:not([disabled]) {
+.c1:hover:not([disabled]):not([aria-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([aria-disabled]):not([data-inactive]) {
background-color: hsla(220,14%,93%,1);
border-color: var(--button-default-borderColor-active,rgba(31,35,40,0.15));
}
+.c1:disabled,
+.c1[aria-disabled] {
+ color: #8c959f;
+}
+
+.c1:disabled [data-component=ButtonCounter],
+.c1[aria-disabled] [data-component=ButtonCounter] {
+ color: inherit;
+}
+
.c1[aria-expanded=true] {
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..1486e65ed4e 100644
--- a/src/__tests__/__snapshots__/TextInput.test.tsx.snap
+++ b/src/__tests__/__snapshots__/TextInput.test.tsx.snap
@@ -1779,16 +1779,13 @@ exports[`TextInput renders trailingAction icon button 1`] = `
transition: none;
}
+.c5[data-inactive] {
+ cursor: auto;
+}
+
.c5:disabled {
cursor: not-allowed;
box-shadow: none;
- color: #8c959f;
-}
-
-.c5:disabled [data-component=ButtonCounter],
-.c5:disabled [data-component="leadingVisual"],
-.c5:disabled [data-component="trailingAction"] {
- color: inherit;
}
.c5 [data-component=ButtonCounter] {
@@ -1846,6 +1843,12 @@ exports[`TextInput renders trailingAction icon button 1`] = `
width: 100%;
}
+.c5[data-inactive]:not([disabled]):not([aria-disabled]) {
+ background-color: var(--button-inactive-bgColor,#eaeef2);
+ border: 0;
+ color: var(--button-inactive-fgColor,#57606a);
+}
+
.c5 [data-component="leadingVisual"] {
grid-area: leadingVisual;
color: #656d76;
@@ -1894,6 +1897,20 @@ exports[`TextInput renders trailingAction icon button 1`] = `
background-color: rgba(208,215,222,0.48);
}
+.c5:disabled,
+.c5[aria-disabled] {
+ color: #8c959f;
+}
+
+.c5:disabled [data-component=ButtonCounter],
+.c5[aria-disabled] [data-component=ButtonCounter],
+.c5:disabled [data-component="leadingVisual"],
+.c5[aria-disabled] [data-component="leadingVisual"],
+.c5:disabled [data-component="trailingAction"],
+.c5[aria-disabled] [data-component="trailingAction"] {
+ color: inherit;
+}
+
.c5[aria-expanded=true] {
background-color: rgba(208,215,222,0.24);
}
@@ -2432,16 +2449,13 @@ exports[`TextInput renders trailingAction text button 1`] = `
transition: none;
}
+.c4[data-inactive] {
+ cursor: auto;
+}
+
.c4:disabled {
cursor: not-allowed;
box-shadow: none;
- color: #8c959f;
-}
-
-.c4:disabled [data-component=ButtonCounter],
-.c4:disabled [data-component="leadingVisual"],
-.c4:disabled [data-component="trailingAction"] {
- color: inherit;
}
.c4 [data-component=ButtonCounter] {
@@ -2499,6 +2513,12 @@ exports[`TextInput renders trailingAction text button 1`] = `
width: 100%;
}
+.c4[data-inactive]:not([disabled]):not([aria-disabled]) {
+ background-color: var(--button-inactive-bgColor,#eaeef2);
+ border: 0;
+ color: var(--button-inactive-fgColor,#57606a);
+}
+
.c4 [data-component="leadingVisual"] {
grid-area: leadingVisual;
color: #656d76;
@@ -2547,6 +2567,20 @@ exports[`TextInput renders trailingAction text button 1`] = `
background-color: rgba(208,215,222,0.48);
}
+.c4:disabled,
+.c4[aria-disabled] {
+ color: #8c959f;
+}
+
+.c4:disabled [data-component=ButtonCounter],
+.c4[aria-disabled] [data-component=ButtonCounter],
+.c4:disabled [data-component="leadingVisual"],
+.c4[aria-disabled] [data-component="leadingVisual"],
+.c4:disabled [data-component="trailingAction"],
+.c4[aria-disabled] [data-component="trailingAction"] {
+ color: inherit;
+}
+
.c4[aria-expanded=true] {
background-color: rgba(208,215,222,0.24);
}
@@ -2837,16 +2871,13 @@ 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;
- color: #8c959f;
-}
-
-.c5:disabled [data-component=ButtonCounter],
-.c5:disabled [data-component="leadingVisual"],
-.c5:disabled [data-component="trailingAction"] {
- color: inherit;
}
.c5 [data-component=ButtonCounter] {
@@ -2904,6 +2935,12 @@ exports[`TextInput renders trailingAction text button with a tooltip 1`] = `
width: 100%;
}
+.c5[data-inactive]:not([disabled]):not([aria-disabled]) {
+ background-color: var(--button-inactive-bgColor,#eaeef2);
+ border: 0;
+ color: var(--button-inactive-fgColor,#57606a);
+}
+
.c5 [data-component="leadingVisual"] {
grid-area: leadingVisual;
color: #656d76;
@@ -2952,6 +2989,20 @@ exports[`TextInput renders trailingAction text button with a tooltip 1`] = `
background-color: rgba(208,215,222,0.48);
}
+.c5:disabled,
+.c5[aria-disabled] {
+ color: #8c959f;
+}
+
+.c5:disabled [data-component=ButtonCounter],
+.c5[aria-disabled] [data-component=ButtonCounter],
+.c5:disabled [data-component="leadingVisual"],
+.c5[aria-disabled] [data-component="leadingVisual"],
+.c5:disabled [data-component="trailingAction"],
+.c5[aria-disabled] [data-component="trailingAction"] {
+ color: inherit;
+}
+
.c5[aria-expanded=true] {
background-color: rgba(208,215,222,0.24);
}
diff --git a/src/drafts/Button2/Button.docs.json b/src/drafts/Button2/Button.docs.json
index b8a24e7a045..5b27f9006e2 100644
--- a/src/drafts/Button2/Button.docs.json
+++ b/src/drafts/Button2/Button.docs.json
@@ -32,6 +32,11 @@
"type": "React.ComponentType",
"description": "An icon 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/drafts/Button2/Button.features.stories.tsx b/src/drafts/Button2/Button.features.stories.tsx
index ab0fce76123..0acd2902e1e 100644
--- a/src/drafts/Button2/Button.features.stories.tsx
+++ b/src/drafts/Button2/Button.features.stories.tsx
@@ -34,6 +34,8 @@ export const Block = () =>
export const Disabled = () =>
+export const Inactive = () =>
+
export const Small = () =>
export const Medium = () =>
diff --git a/src/drafts/Button2/Button.stories.tsx b/src/drafts/Button2/Button.stories.tsx
index 32a841e66c4..8c3975485b8 100644
--- a/src/drafts/Button2/Button.stories.tsx
+++ b/src/drafts/Button2/Button.stories.tsx
@@ -6,55 +6,6 @@ import {OcticonArgType} from '../../utils/story-helpers'
export default {
title: 'Drafts/Components/Button',
- argTypes: {
- size: {
- control: {
- type: 'radio',
- },
- options: ['small', 'medium', 'large'],
- },
- disabled: {
- control: {
- type: 'boolean',
- },
- },
- variant: {
- control: {
- type: 'radio',
- },
- options: ['default', 'primary', 'danger', 'invisible', 'outline'],
- },
- alignContent: {
- control: {
- type: 'radio',
- },
- options: ['center', 'start'],
- },
- block: {
- control: {
- type: 'boolean',
- },
- },
- leadingIcon: OcticonArgType([EyeClosedIcon, EyeIcon, SearchIcon, XIcon, HeartIcon]),
- trailingIcon: OcticonArgType([EyeClosedIcon, EyeIcon, SearchIcon, XIcon, HeartIcon]),
- trailingAction: OcticonArgType([TriangleDownIcon]),
- trailingVisualCount: {
- control: {
- type: 'number',
- },
- },
- },
- args: {
- block: false,
- size: 'medium',
- disabled: false,
- variant: 'default',
- alignContent: 'center',
- trailingIcon: null,
- leadingIcon: null,
- trailingAction: null,
- trailingVisualCount: undefined,
- },
} as Meta
export const Playground: StoryFn = args => {
@@ -67,4 +18,61 @@ export const Playground: StoryFn = args => {
)
}
+Playground.argTypes = {
+ size: {
+ control: {
+ type: 'radio',
+ },
+ options: ['small', 'medium', 'large'],
+ },
+ disabled: {
+ control: {
+ type: 'boolean',
+ },
+ },
+ inactive: {
+ control: {
+ type: 'boolean',
+ },
+ },
+ variant: {
+ control: {
+ type: 'radio',
+ },
+ options: ['default', 'primary', 'danger', 'invisible', 'outline'],
+ },
+ alignContent: {
+ control: {
+ type: 'radio',
+ },
+ options: ['center', 'start'],
+ },
+ block: {
+ control: {
+ type: 'boolean',
+ },
+ },
+ leadingIcon: OcticonArgType([EyeClosedIcon, EyeIcon, SearchIcon, XIcon, HeartIcon]),
+ trailingIcon: OcticonArgType([EyeClosedIcon, EyeIcon, SearchIcon, XIcon, HeartIcon]),
+ trailingAction: OcticonArgType([TriangleDownIcon]),
+ trailingVisualCount: {
+ control: {
+ type: 'number',
+ },
+ },
+}
+
+Playground.args = {
+ block: false,
+ size: 'medium',
+ disabled: false,
+ inactive: false,
+ variant: 'default',
+ alignContent: 'center',
+ trailingIcon: null,
+ leadingIcon: null,
+ trailingAction: null,
+ trailingVisualCount: undefined,
+}
+
export const Default = () =>
diff --git a/src/drafts/Button2/Button.tsx b/src/drafts/Button2/Button.tsx
index d3cce915480..9f02953aae3 100644
--- a/src/drafts/Button2/Button.tsx
+++ b/src/drafts/Button2/Button.tsx
@@ -8,11 +8,11 @@ import {BetterSystemStyleObject} from '../../sx'
const ButtonComponent = forwardRef(({children, sx: sxProp = defaultSxProp, ...props}, forwardedRef): JSX.Element => {
let sxStyles = sxProp
- // grap the button props that have associated data attributes in the styles
- const {block, size, leadingIcon, trailingIcon, trailingAction} = props
+ // grab the button props that have associated data attributes in the styles
+ const {block, size, leadingIcon, trailingIcon, trailingAction, inactive} = props
if (sxProp !== null && Object.keys(sxProp).length > 0) {
- sxStyles = generateCustomSxProp({block, size, leadingIcon, trailingIcon, trailingAction}, sxProp)
+ sxStyles = generateCustomSxProp({block, size, leadingIcon, trailingIcon, trailingAction, inactive}, sxProp)
}
return (
@@ -63,16 +63,17 @@ sx={{
// We need to make sure we append the customCSSSelector to the original class selector. i.e & - > &[data-attribute="Icon"][data-size="small"]
*/
export function generateCustomSxProp(
- props: Partial>,
+ props: Partial>,
providedSx: BetterSystemStyleObject,
) {
// Possible data attributes: data-size, data-block, data-no-visuals
const size = props.size && props.size !== 'medium' ? `[data-size="${props.size}"]` : '' // medium is a default size therefore it doesn't have a data attribute that used for styling
const block = props.block ? `[data-block="block"]` : ''
const noVisuals = props.leadingIcon || props.trailingIcon || props.trailingAction ? '' : '[data-no-visuals="true"]'
+ const inactive = props.inactive ? '[data-inactive="true"]' : ''
// this is custom selector. We need to make sure we add the data attributes to the base css class (& -> &[data-attributename="value"]])
- const cssSelector = `&${size}${block}${noVisuals}` // &[data-size="small"][data-block="block"][data-no-visuals="true"]
+ const cssSelector = `&${size}${block}${noVisuals}${inactive}` // &[data-size="small"][data-block="block"][data-no-visuals="true"]
const customSxProp: {
[key: string]: BetterSystemStyleObject
diff --git a/src/drafts/Button2/ButtonBase.tsx b/src/drafts/Button2/ButtonBase.tsx
index 44125f7dbd0..84c30c19863 100644
--- a/src/drafts/Button2/ButtonBase.tsx
+++ b/src/drafts/Button2/ButtonBase.tsx
@@ -19,6 +19,7 @@ const ButtonBase = forwardRef(
size = 'medium',
alignContent = 'center',
block = false,
+ inactive,
...rest
} = props
@@ -27,8 +28,8 @@ const ButtonBase = forwardRef(
const {theme} = useTheme()
const baseStyles = useMemo(() => {
- return merge.all([getButtonStyles(theme), getVariantStyles(variant, theme)])
- }, [theme, variant])
+ return merge.all([getButtonStyles(theme), getVariantStyles(variant, inactive, theme)])
+ }, [inactive, theme, variant])
const sxStyles = useMemo(() => {
return merge(baseStyles, sxProp)
}, [baseStyles, sxProp])
@@ -66,6 +67,7 @@ const ButtonBase = forwardRef(
data-block={block ? 'block' : null}
data-size={size === 'small' || size === 'large' ? size : undefined}
data-no-visuals={!LeadingIcon && !TrailingIcon && !TrailingAction ? true : undefined}
+ data-inactive={inactive ? true : undefined}
>
{Icon ? (
diff --git a/src/drafts/Button2/styles.ts b/src/drafts/Button2/styles.ts
index 474a9e81991..86c9574c74b 100644
--- a/src/drafts/Button2/styles.ts
+++ b/src/drafts/Button2/styles.ts
@@ -1,21 +1,21 @@
import {VariantType, AlignContent} from './types'
import {Theme} from '../../ThemeProvider'
-export const getVariantStyles = (variant: VariantType = 'default', theme?: Theme) => {
+export const getVariantStyles = (variant: VariantType = 'default', inactive?: boolean, theme?: Theme) => {
const style = {
default: {
color: 'btn.text',
backgroundColor: 'btn.bg',
boxShadow: `${theme?.shadows.btn.shadow}, ${theme?.shadows.btn.insetShadow}`,
- '&:hover:not([disabled])': {
+ '&:hover:not([disabled]):not([aria-disabled]):not([data-inactive])': {
backgroundColor: 'btn.hoverBg',
borderColor: 'btn.hoverBorder',
},
- '&:active:not([disabled])': {
+ '&:active:not([disabled]):not([aria-disabled]):not([data-inactive])': {
backgroundColor: 'btn.activeBg',
borderColor: 'btn.activeBorder',
},
- '&:disabled': {
+ '&:disabled, &[aria-disabled]': {
color: 'primer.fg.disabled',
'[data-component=ButtonCounter]': {
color: 'inherit',
@@ -31,7 +31,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([aria-disabled]):not([data-inactive])': {
color: 'btn.primary.hoverText',
backgroundColor: 'btn.primary.hoverBg',
},
@@ -41,11 +41,11 @@ 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([aria-disabled]):not([data-inactive])': {
backgroundColor: 'btn.primary.selectedBg',
boxShadow: `${theme?.shadows.btn.primary.selectedShadow}`,
},
- '&:disabled': {
+ '&:disabled, &[aria-disabled]': {
color: 'btn.primary.disabledText',
backgroundColor: 'btn.primary.disabledBg',
'[data-component=ButtonCounter]': {
@@ -65,7 +65,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([aria-disabled]):not([data-inactive])': {
color: 'btn.danger.hoverText',
backgroundColor: 'btn.danger.hoverBg',
borderColor: 'btn.danger.hoverBorder',
@@ -75,13 +75,13 @@ export const getVariantStyles = (variant: VariantType = 'default', theme?: Theme
color: 'btn.danger.hoverText',
},
},
- '&:active:not([disabled])': {
+ '&:active:not([disabled]):not([aria-disabled]):not([data-inactive])': {
color: 'btn.danger.selectedText',
backgroundColor: 'btn.danger.selectedBg',
boxShadow: `${theme?.shadows.btn.danger.selectedShadow}`,
borderColor: 'btn.danger.selectedBorder',
},
- '&:disabled': {
+ '&:disabled, &[aria-disabled]': {
color: 'btn.danger.disabledText',
backgroundColor: 'btn.danger.disabledBg',
borderColor: 'btn.danger.disabledBorder',
@@ -106,13 +106,13 @@ export const getVariantStyles = (variant: VariantType = 'default', theme?: Theme
backgroundColor: 'transparent',
borderColor: 'transparent',
boxShadow: 'none',
- '&:hover:not([disabled])': {
+ '&:hover:not([disabled]):not([data-inactive])': {
backgroundColor: 'btn.hoverBg',
},
- '&:active:not([disabled])': {
+ '&:active:not([disabled]):not([aria-disabled]):not([data-inactive])': {
backgroundColor: 'btn.selectedBg',
},
- '&:disabled': {
+ '&:disabled, &[aria-disabled]': {
color: 'primer.fg.disabled',
'[data-component=ButtonCounter]': {
color: 'inherit',
@@ -143,7 +143,7 @@ export const getVariantStyles = (variant: VariantType = 'default', theme?: Theme
borderColor: 'btn.border',
backgroundColor: 'btn.bg',
- '&:hover:not([disabled])': {
+ '&:hover:not([disabled]):not([aria-disabled]):not([data-inactive])': {
color: 'btn.outline.hoverText',
backgroundColor: 'btn.outline.hoverBg',
borderColor: 'btn.outline.hoverBorder',
@@ -153,14 +153,14 @@ export const getVariantStyles = (variant: VariantType = 'default', theme?: Theme
color: 'inherit',
},
},
- '&:active:not([disabled])': {
+ '&:active:not([disabled]):not([aria-disabled]):not([data-inactive])': {
color: 'btn.outline.selectedText',
backgroundColor: 'btn.outline.selectedBg',
boxShadow: `${theme?.shadows.btn.outline.selectedShadow}`,
borderColor: 'btn.outline.selectedBorder',
},
- '&:disabled': {
+ '&:disabled, &[aria-disabled]': {
color: 'btn.outline.disabledText',
backgroundColor: 'btn.outline.disabledBg',
borderColor: 'btn.border',
@@ -181,6 +181,7 @@ export const getVariantStyles = (variant: VariantType = 'default', theme?: Theme
},
},
}
+
return style[variant]
}
@@ -217,6 +218,9 @@ export const getBaseStyles = (theme?: Theme) => ({
'&:active': {
transition: 'none',
},
+ '&[data-inactive]': {
+ cursor: 'auto',
+ },
'&:disabled': {
cursor: 'not-allowed',
boxShadow: 'none',
@@ -282,6 +286,11 @@ export const getButtonStyles = (theme?: Theme) => {
'&[data-block="block"]': {
width: '100%',
},
+ '&[data-inactive]:not([disabled]):not([aria-disabled])': {
+ backgroundColor: `var(--button-inactive-bgColor, ${theme?.colors.btn.inactive.bg})`,
+ border: 0,
+ color: `var(--button-inactive-fgColor, ${theme?.colors.btn.inactive.text})`,
+ },
'[data-component="leadingVisual"]': {
gridArea: 'leadingVisual',
},
diff --git a/src/drafts/Button2/types.ts b/src/drafts/Button2/types.ts
index 7194fd0d354..b1dcdd065ff 100644
--- a/src/drafts/Button2/types.ts
+++ b/src/drafts/Button2/types.ts
@@ -31,6 +31,11 @@ export type ButtonBaseProps = {
* Items that are disabled can not be clicked, selected, or navigated through.
*/
disabled?: boolean
+ /**
+ * Whether the button looks visually disabled, but can still accept all the same
+ * interactions as an enabled button.
+ */
+ inactive?: boolean
/**
* Allow button width to fill its container.
*/