Skip to content

Commit 991839c

Browse files
mperrottisiddharthkpkhiga8broccolinisoup
authored
Un-revert "Add loading prop for Button and IconButton (#3582)" (#4485)
* Revert "Revert "Add `loading` prop for `Button` and `IconButton` (#3582)" (#4…" This reverts commit c01901f. * only overrides btn label when loading * update tooltip tests to accomodate loading message aria-describedby * import missing modules in IconButton stories * makes aria-labelledby prop remain set when when button is in a loading state * Update packages/react/src/Button/Button.examples.stories.tsx Co-authored-by: Kate Higa <[email protected]> * Update packages/react/src/Button/Button.examples.stories.tsx Co-authored-by: Kate Higa <[email protected]> * updates textinput snapshots * addresses remaining PR feedback * appeases the linter, updates snaps * leaves loading prop undefined so we do not always render the wrapper * test(vrt): update snapshots * trying again without changing colors on faux-disabled buttons * replaces Status with AriaStatus * replaces one more Status with AriaStatus * rms added aria-disabled styles * test(vrt): update snapshots * Update icon button tests to make it work with the loading state * add story with leading visual and count * misc bugfixes: - ensures counter stays rendered even when no children are passed - preserves space between elements that are children of span[data-component=text] - adds story and VRT for buttons with a trailing action but no leading/trailing visuals * test(vrt): update snapshots --------- Co-authored-by: Siddharth Kshetrapal <[email protected]> Co-authored-by: Kate Higa <[email protected]> Co-authored-by: mperrotti <[email protected]> Co-authored-by: Armagan Ersoz <[email protected]>
1 parent 8574027 commit 991839c

File tree

91 files changed

+1063
-304
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

91 files changed

+1063
-304
lines changed

.changeset/lazy-jobs-pump.md

Lines changed: 5 additions & 0 deletions

e2e/components/Button.test.ts

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -479,6 +479,184 @@ test.describe('Button', () => {
479479
}
480480
})
481481

482+
test.describe('Loading', () => {
483+
for (const theme of themes) {
484+
test.describe(theme, () => {
485+
test('default @vrt', async ({page}) => {
486+
await visit(page, {
487+
id: 'components-button-features--loading',
488+
globals: {
489+
colorScheme: theme,
490+
},
491+
})
492+
493+
// Default state
494+
expect(await page.screenshot({animations: 'disabled'})).toMatchSnapshot(`Button.Loading.${theme}.png`)
495+
})
496+
497+
test('axe @aat', async ({page}) => {
498+
await visit(page, {
499+
id: 'components-button-features--loading',
500+
globals: {
501+
colorScheme: theme,
502+
},
503+
})
504+
await expect(page).toHaveNoViolations({
505+
rules: {
506+
'color-contrast': {
507+
enabled: theme !== 'dark_dimmed',
508+
},
509+
},
510+
})
511+
})
512+
})
513+
}
514+
})
515+
516+
test.describe('Loading Custom Announcement', () => {
517+
for (const theme of themes) {
518+
test.describe(theme, () => {
519+
test('default @vrt', async ({page}) => {
520+
await visit(page, {
521+
id: 'components-button-features--loading-custom-announcement',
522+
globals: {
523+
colorScheme: theme,
524+
},
525+
})
526+
527+
// Default state
528+
expect(await page.screenshot({animations: 'disabled'})).toMatchSnapshot(
529+
`Button.Loading Custom Announcement.${theme}.png`,
530+
)
531+
})
532+
533+
test('axe @aat', async ({page}) => {
534+
await visit(page, {
535+
id: 'components-button-features--loading-custom-announcement',
536+
globals: {
537+
colorScheme: theme,
538+
},
539+
})
540+
await expect(page).toHaveNoViolations({
541+
rules: {
542+
'color-contrast': {
543+
enabled: theme !== 'dark_dimmed',
544+
},
545+
},
546+
})
547+
})
548+
})
549+
}
550+
})
551+
552+
test.describe('Loading With Leading Visual', () => {
553+
for (const theme of themes) {
554+
test.describe(theme, () => {
555+
test('default @vrt', async ({page}) => {
556+
await visit(page, {
557+
id: 'components-button-features--loading-with-leading-visual',
558+
globals: {
559+
colorScheme: theme,
560+
},
561+
})
562+
563+
// Default state
564+
expect(await page.screenshot({animations: 'disabled'})).toMatchSnapshot(
565+
`Button.Loading With Leading Visual.${theme}.png`,
566+
)
567+
})
568+
569+
test('axe @aat', async ({page}) => {
570+
await visit(page, {
571+
id: 'components-button-features--loading-with-leading-visual',
572+
globals: {
573+
colorScheme: theme,
574+
},
575+
})
576+
await expect(page).toHaveNoViolations({
577+
rules: {
578+
'color-contrast': {
579+
enabled: theme !== 'dark_dimmed',
580+
},
581+
},
582+
})
583+
})
584+
})
585+
}
586+
})
587+
588+
test.describe('Loading With Trailing Visual', () => {
589+
for (const theme of themes) {
590+
test.describe(theme, () => {
591+
test('default @vrt', async ({page}) => {
592+
await visit(page, {
593+
id: 'components-button-features--loading-with-trailing-visual',
594+
globals: {
595+
colorScheme: theme,
596+
},
597+
})
598+
599+
// Default state
600+
expect(await page.screenshot({animations: 'disabled'})).toMatchSnapshot(
601+
`Button.Loading With Trailing Visual.${theme}.png`,
602+
)
603+
})
604+
605+
test('axe @aat', async ({page}) => {
606+
await visit(page, {
607+
id: 'components-button-features--loading-with-trailing-visual',
608+
globals: {
609+
colorScheme: theme,
610+
},
611+
})
612+
await expect(page).toHaveNoViolations({
613+
rules: {
614+
'color-contrast': {
615+
enabled: theme !== 'dark_dimmed',
616+
},
617+
},
618+
})
619+
})
620+
})
621+
}
622+
})
623+
624+
test.describe('Loading With Trailing Action', () => {
625+
for (const theme of themes) {
626+
test.describe(theme, () => {
627+
test('default @vrt', async ({page}) => {
628+
await visit(page, {
629+
id: 'components-button-features--loading-with-trailing-action',
630+
globals: {
631+
colorScheme: theme,
632+
},
633+
})
634+
635+
// Default state
636+
expect(await page.screenshot({animations: 'disabled'})).toMatchSnapshot(
637+
`Button.Loading With Trailing Action.${theme}.png`,
638+
)
639+
})
640+
641+
test('axe @aat', async ({page}) => {
642+
await visit(page, {
643+
id: 'components-button-features--loading-with-trailing-action',
644+
globals: {
645+
colorScheme: theme,
646+
},
647+
})
648+
await expect(page).toHaveNoViolations({
649+
rules: {
650+
'color-contrast': {
651+
enabled: theme !== 'dark_dimmed',
652+
},
653+
},
654+
})
655+
})
656+
})
657+
}
658+
})
659+
482660
test.describe('Dev: Invisible Variants', () => {
483661
for (const theme of themes) {
484662
test.describe(theme, () => {

package-lock.json

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/react/src/Button/Button.docs.json

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,6 @@
3232
"type": "number | string",
3333
"description": "For counter buttons, the number to display."
3434
},
35-
{
36-
"name": "disabled",
37-
"type": "boolean",
38-
"description": "Items that are disabled can not be clicked, selected, or navigated through."
39-
},
4035
{
4136
"name": "inactive",
4237
"type": "boolean",
@@ -53,6 +48,17 @@
5348
"type": "React.ElementType",
5449
"description": "A visual to display before the button text."
5550
},
51+
{
52+
"name": "loading",
53+
"type": "boolean",
54+
"description": "When true, the button is in a loading state."
55+
},
56+
{
57+
"name": "loadingAnnouncement",
58+
"type": "string",
59+
"description": "The content to announce to screen readers when loading. This requires `loading` prop to be true"
60+
},
61+
5662
{
5763
"name": "ref",
5864
"type": "React.RefObject<HTMLButtonElement>"
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import React from 'react'
2+
import type {Meta} from '@storybook/react'
3+
import {Button} from '.'
4+
import {DownloadIcon} from '@primer/octicons-react'
5+
import {Banner} from '../drafts'
6+
import {AriaStatus} from '../live-region'
7+
8+
const meta: Meta<typeof Button> = {
9+
title: 'Components/Button/Examples',
10+
} as Meta<typeof Button>
11+
12+
export default meta
13+
14+
export const LoadingStatusAnnouncementSuccessful = () => {
15+
const [loading, setLoading] = React.useState(false)
16+
const [success, setSuccess] = React.useState(false)
17+
18+
const resolveAction = async () => {
19+
setLoading(true)
20+
await new Promise(resolve => setTimeout(resolve, 1500))
21+
setLoading(false)
22+
23+
return await true
24+
}
25+
26+
const onClick = (resolveType: 'error' | 'success') => async () => {
27+
const actionResult = await resolveAction()
28+
29+
if (resolveType === 'error') {
30+
setSuccess(!actionResult)
31+
return
32+
}
33+
34+
setSuccess(actionResult)
35+
}
36+
37+
return (
38+
<>
39+
<AriaStatus>{!loading && success ? 'Export completed' : null}</AriaStatus>
40+
<Button loading={loading} leadingVisual={DownloadIcon} onClick={onClick('success')}>
41+
Export (success)
42+
</Button>
43+
</>
44+
)
45+
}
46+
47+
export const LoadingStatusAnnouncementError = () => {
48+
const [loading, setLoading] = React.useState(false)
49+
const [error, setError] = React.useState(false)
50+
51+
const resolveAction = async () => {
52+
setLoading(true)
53+
await new Promise(resolve => setTimeout(resolve, 1500))
54+
setLoading(false)
55+
56+
return await true
57+
}
58+
59+
const onClick = (resolveType: 'error' | 'success') => async () => {
60+
const actionResult = await resolveAction()
61+
62+
if (resolveType === 'error') {
63+
setError(actionResult)
64+
return
65+
}
66+
67+
setError(!actionResult)
68+
}
69+
70+
return (
71+
<>
72+
{!loading && error ? <Banner title="Export failed" variant="critical" /> : null}
73+
74+
<Button loading={loading} leadingVisual={DownloadIcon} onClick={onClick('error')}>
75+
Export (error)
76+
</Button>
77+
</>
78+
)
79+
}

packages/react/src/Button/Button.features.stories.tsx

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {EyeIcon, TriangleDownIcon, HeartIcon, CommentIcon} from '@primer/octicons-react'
1+
import {EyeIcon, TriangleDownIcon, HeartIcon, DownloadIcon, CommentIcon} from '@primer/octicons-react'
22
import React, {useState} from 'react'
33
import {Button} from '.'
44
import {Stack} from '../Stack/Stack'
@@ -141,6 +141,46 @@ export const Medium = () => <Button size="medium">Default</Button>
141141

142142
export const Large = () => <Button size="large">Default</Button>
143143

144+
export const Loading = () => <Button loading>Default</Button>
145+
146+
export const LoadingCustomAnnouncement = () => (
147+
<Button loading loadingAnnouncement="This is a custom loading announcement">
148+
Default
149+
</Button>
150+
)
151+
152+
export const LoadingWithLeadingVisual = () => (
153+
<Button loading leadingVisual={DownloadIcon}>
154+
Export
155+
</Button>
156+
)
157+
158+
export const LoadingWithTrailingVisual = () => (
159+
<Button loading trailingVisual={DownloadIcon}>
160+
Export
161+
</Button>
162+
)
163+
164+
export const LoadingWithTrailingAction = () => (
165+
<Button loading trailingAction={TriangleDownIcon}>
166+
Export dropdown
167+
</Button>
168+
)
169+
170+
export const LoadingTrigger = () => {
171+
const [isLoading, setIsLoading] = useState(false)
172+
173+
const handleClick = () => {
174+
setIsLoading(true)
175+
}
176+
177+
return (
178+
<Button loading={isLoading} onClick={handleClick} leadingVisual={DownloadIcon}>
179+
Export
180+
</Button>
181+
)
182+
}
183+
144184
export const LabelWrap = () => {
145185
return (
146186
<Stack style={{width: '200px'}}>

packages/react/src/Button/Button.stories.tsx

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,11 +48,21 @@ Playground.argTypes = {
4848
type: 'boolean',
4949
},
5050
},
51+
loading: {
52+
control: {
53+
type: 'boolean',
54+
},
55+
},
5156
labelWrap: {
5257
control: {
5358
type: 'boolean',
5459
},
5560
},
61+
count: {
62+
control: {
63+
type: 'number',
64+
},
65+
},
5666
leadingVisual: OcticonArgType([EyeClosedIcon, EyeIcon, SearchIcon, XIcon, HeartIcon]),
5767
trailingVisual: OcticonArgType([EyeClosedIcon, EyeIcon, SearchIcon, XIcon, HeartIcon]),
5868
trailingAction: OcticonArgType([TriangleDownIcon]),
@@ -64,6 +74,7 @@ Playground.args = {
6474
inactive: false,
6575
variant: 'default',
6676
alignContent: 'center',
77+
loading: false,
6778
trailingVisual: null,
6879
leadingVisual: null,
6980
trailingAction: null,

0 commit comments

Comments
 (0)