Skip to content

Commit

Permalink
Revert "Revert "Add loading prop for Button and IconButton (#3582
Browse files Browse the repository at this point in the history
…)" (#4…"

This reverts commit c01901f.
  • Loading branch information
siddharthkp authored Apr 10, 2024
1 parent cb5bf80 commit 60b3253
Show file tree
Hide file tree
Showing 89 changed files with 1,114 additions and 292 deletions.
5 changes: 5 additions & 0 deletions .changeset/lazy-jobs-pump.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@primer/react": minor
---

Add `loading` state to `Button` and `IconButton`
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
142 changes: 142 additions & 0 deletions e2e/components/Button.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -479,6 +479,148 @@ test.describe('Button', () => {
}
})

test.describe('Loading', () => {
for (const theme of themes) {
test.describe(theme, () => {
test('default @vrt', async ({page}) => {
await visit(page, {
id: 'components-button-features--loading',
globals: {
colorScheme: theme,
},
})

// Default state
expect(await page.screenshot({animations: 'disabled'})).toMatchSnapshot(`Button.Loading.${theme}.png`)
})

test('axe @aat', async ({page}) => {
await visit(page, {
id: 'components-button-features--loading',
globals: {
colorScheme: theme,
},
})
await expect(page).toHaveNoViolations({
rules: {
'color-contrast': {
enabled: theme !== 'dark_dimmed',
},
},
})
})
})
}
})

test.describe('Loading Custom Announcement', () => {
for (const theme of themes) {
test.describe(theme, () => {
test('default @vrt', async ({page}) => {
await visit(page, {
id: 'components-button-features--loading-custom-announcement',
globals: {
colorScheme: theme,
},
})

// Default state
expect(await page.screenshot({animations: 'disabled'})).toMatchSnapshot(
`Button.Loading Custom Announcement.${theme}.png`,
)
})

test('axe @aat', async ({page}) => {
await visit(page, {
id: 'components-button-features--loading-custom-announcement',
globals: {
colorScheme: theme,
},
})
await expect(page).toHaveNoViolations({
rules: {
'color-contrast': {
enabled: theme !== 'dark_dimmed',
},
},
})
})
})
}
})

test.describe('Loading With Leading Visual', () => {
for (const theme of themes) {
test.describe(theme, () => {
test('default @vrt', async ({page}) => {
await visit(page, {
id: 'components-button-features--loading-with-leading-visual',
globals: {
colorScheme: theme,
},
})

// Default state
expect(await page.screenshot({animations: 'disabled'})).toMatchSnapshot(
`Button.Loading With Leading Visual.${theme}.png`,
)
})

test('axe @aat', async ({page}) => {
await visit(page, {
id: 'components-button-features--loading-with-leading-visual',
globals: {
colorScheme: theme,
},
})
await expect(page).toHaveNoViolations({
rules: {
'color-contrast': {
enabled: theme !== 'dark_dimmed',
},
},
})
})
})
}
})

test.describe('Loading With Trailing Visual', () => {
for (const theme of themes) {
test.describe(theme, () => {
test('default @vrt', async ({page}) => {
await visit(page, {
id: 'components-button-features--loading-with-trailing-visual',
globals: {
colorScheme: theme,
},
})

// Default state
expect(await page.screenshot({animations: 'disabled'})).toMatchSnapshot(
`Button.Loading With Trailing Visual.${theme}.png`,
)
})

test('axe @aat', async ({page}) => {
await visit(page, {
id: 'components-button-features--loading-with-trailing-visual',
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, () => {
Expand Down
1 change: 1 addition & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

35 changes: 23 additions & 12 deletions packages/react/src/Button/Button.docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,9 @@
"description": "For counter buttons, the number to display."
},
{
"name": "variant",
"type": "'default'\n| 'primary'\n| 'danger'\n| 'invisible'",
"defaultValue": "'default'",
"description": "Change the visual style of the button."
},
{
"name": "size",
"type": "'small'\n| 'medium'\n| 'large'",
"defaultValue": "'medium'"
"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": "leadingIcon",
Expand All @@ -40,6 +34,22 @@
"type": "React.ElementType",
"description": "A visual to display before the button text."
},
{
"name": "loading",
"type": "boolean",
"description": "When true, the button is in a loading state."
},
{
"name": "loadingAnnouncement",
"type": "string",
"description": "The content to announce to screen readers when loading. This requires `loading` prop to be true"
},

{
"name": "size",
"type": "'small'\n| 'medium'\n| 'large'",
"defaultValue": "'medium'"
},
{
"name": "trailingIcon",
"type": "React.ComponentType<OcticonProps>",
Expand All @@ -52,9 +62,10 @@
"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": "variant",
"type": "'default'\n| 'primary'\n| 'danger'\n| 'invisible'",
"defaultValue": "'default'",
"description": "Change the visual style of the button."
},
{
"name": "as",
Expand Down
78 changes: 78 additions & 0 deletions packages/react/src/Button/Button.examples.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import React from 'react'
import type {Meta} from '@storybook/react'
import {Button} from '.'
import {DownloadIcon} from '@primer/octicons-react'
import {VisuallyHidden} from '../internal/components/VisuallyHidden'

const meta: Meta<typeof Button> = {
title: 'Components/Button/Examples',
} as Meta<typeof Button>

export default meta

export const LoadingStatusAnnouncementSuccessful = () => {
const [loading, setLoading] = React.useState(false)
const [success, setSuccess] = React.useState(false)

const resolveAction = async () => {
setLoading(true)
await new Promise(resolve => setTimeout(resolve, 1500))
setLoading(false)

return await true
}

const onClick = (resolveType: 'error' | 'success') => async () => {
const actionResult = await resolveAction()

if (resolveType === 'error') {
setSuccess(!actionResult)
return
}

setSuccess(actionResult)
}

return (
<>
<VisuallyHidden aria-live="polite">{!loading && success ? 'Export completed' : null}</VisuallyHidden>
<Button loading={loading} leadingVisual={DownloadIcon} onClick={onClick('error')}>
Export (success)
</Button>
</>
)
}

export const LoadingStatusAnnouncementError = () => {
const [loading, setLoading] = React.useState(false)
const [error, setError] = React.useState(false)

const resolveAction = async () => {
setLoading(true)
await new Promise(resolve => setTimeout(resolve, 1500))
setLoading(false)

return await true
}

const onClick = (resolveType: 'error' | 'success') => async () => {
const actionResult = await resolveAction()

if (resolveType === 'error') {
setError(actionResult)
return
}

setError(!actionResult)
}

return (
<>
<VisuallyHidden aria-live="polite">{!loading && error ? 'Export failed' : null}</VisuallyHidden>

<Button loading={loading} leadingVisual={DownloadIcon} onClick={onClick('error')}>
Export (error)
</Button>
</>
)
}
36 changes: 35 additions & 1 deletion packages/react/src/Button/Button.features.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {EyeIcon, TriangleDownIcon, HeartIcon} from '@primer/octicons-react'
import {EyeIcon, TriangleDownIcon, HeartIcon, DownloadIcon} from '@primer/octicons-react'
import React, {useState} from 'react'
import {Button} from '.'

Expand Down Expand Up @@ -96,3 +96,37 @@ export const Small = () => <Button size="small">Default</Button>
export const Medium = () => <Button size="medium">Default</Button>

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

export const Loading = () => <Button loading>Default</Button>

export const LoadingCustomAnnouncement = () => (
<Button loading loadingAnnouncement="This is a custom loading announcement">
Default
</Button>
)

export const LoadingWithLeadingVisual = () => (
<Button loading leadingVisual={DownloadIcon}>
Export
</Button>
)

export const LoadingWithTrailingVisual = () => (
<Button loading trailingVisual={DownloadIcon}>
Export
</Button>
)

export const LoadingTrigger = () => {
const [isLoading, setIsLoading] = useState(false)

const handleClick = () => {
setIsLoading(true)
}

return (
<Button loading={isLoading} onClick={handleClick} leadingVisual={DownloadIcon}>
Export
</Button>
)
}
11 changes: 11 additions & 0 deletions packages/react/src/Button/Button.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,16 @@ Playground.argTypes = {
type: 'boolean',
},
},
loading: {
control: {
type: 'boolean',
},
},
count: {
control: {
type: 'number',
},
},
leadingVisual: OcticonArgType([EyeClosedIcon, EyeIcon, SearchIcon, XIcon, HeartIcon]),
trailingVisual: OcticonArgType([EyeClosedIcon, EyeIcon, SearchIcon, XIcon, HeartIcon]),
trailingAction: OcticonArgType([TriangleDownIcon]),
Expand All @@ -59,6 +69,7 @@ Playground.args = {
inactive: false,
variant: 'default',
alignContent: 'center',
loading: false,
trailingVisual: null,
leadingVisual: null,
trailingAction: null,
Expand Down
Loading

0 comments on commit 60b3253

Please sign in to comment.