Skip to content

Commit 10c31a7

Browse files
authored
Revert RAC Pending Button (#6986)
Revert RAC Pending Button
1 parent 68403fe commit 10c31a7

File tree

13 files changed

+61
-535
lines changed

13 files changed

+61
-535
lines changed

packages/@react-aria/live-announcer/src/LiveAnnouncer.tsx

Lines changed: 36 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -15,20 +15,21 @@ type Assertiveness = 'assertive' | 'polite';
1515
/* Inspired by https://github.com/AlmeroSteyn/react-aria-live */
1616
const LIVEREGION_TIMEOUT_DELAY = 7000;
1717

18+
let liveAnnouncer: LiveAnnouncer | null = null;
19+
1820
/**
1921
* Announces the message using screen reader technology.
2022
*/
2123
export function announce(
2224
message: string,
2325
assertiveness: Assertiveness = 'assertive',
24-
timeout = LIVEREGION_TIMEOUT_DELAY,
25-
mode: 'message' | 'ids' = 'message'
26+
timeout = LIVEREGION_TIMEOUT_DELAY
2627
) {
2728
if (!liveAnnouncer) {
2829
liveAnnouncer = new LiveAnnouncer();
2930
}
3031

31-
liveAnnouncer.announce(message, assertiveness, timeout, mode);
32+
liveAnnouncer.announce(message, assertiveness, timeout);
3233
}
3334

3435
/**
@@ -57,36 +58,34 @@ export function destroyAnnouncer() {
5758
// is simple enough to implement without React, so that's what we do here.
5859
// See this discussion for more details: https://github.com/reactwg/react-18/discussions/125#discussioncomment-2382638
5960
class LiveAnnouncer {
60-
node: HTMLElement | null = null;
61-
assertiveLog: HTMLElement | null = null;
62-
politeLog: HTMLElement | null = null;
61+
node: HTMLElement | null;
62+
assertiveLog: HTMLElement;
63+
politeLog: HTMLElement;
6364

6465
constructor() {
65-
if (typeof document !== 'undefined') {
66-
this.node = document.createElement('div');
67-
this.node.dataset.liveAnnouncer = 'true';
68-
// copied from VisuallyHidden
69-
Object.assign(this.node.style, {
70-
border: 0,
71-
clip: 'rect(0 0 0 0)',
72-
clipPath: 'inset(50%)',
73-
height: '1px',
74-
margin: '-1px',
75-
overflow: 'hidden',
76-
padding: 0,
77-
position: 'absolute',
78-
width: '1px',
79-
whiteSpace: 'nowrap'
80-
});
81-
82-
this.assertiveLog = this.createLog('assertive');
83-
this.node.appendChild(this.assertiveLog);
84-
85-
this.politeLog = this.createLog('polite');
86-
this.node.appendChild(this.politeLog);
87-
88-
document.body.prepend(this.node);
89-
}
66+
this.node = document.createElement('div');
67+
this.node.dataset.liveAnnouncer = 'true';
68+
// copied from VisuallyHidden
69+
Object.assign(this.node.style, {
70+
border: 0,
71+
clip: 'rect(0 0 0 0)',
72+
clipPath: 'inset(50%)',
73+
height: '1px',
74+
margin: '-1px',
75+
overflow: 'hidden',
76+
padding: 0,
77+
position: 'absolute',
78+
width: '1px',
79+
whiteSpace: 'nowrap'
80+
});
81+
82+
this.assertiveLog = this.createLog('assertive');
83+
this.node.appendChild(this.assertiveLog);
84+
85+
this.politeLog = this.createLog('polite');
86+
this.node.appendChild(this.politeLog);
87+
88+
document.body.prepend(this.node);
9089
}
9190

9291
createLog(ariaLive: string) {
@@ -106,24 +105,18 @@ class LiveAnnouncer {
106105
this.node = null;
107106
}
108107

109-
announce(message: string, assertiveness = 'assertive', timeout = LIVEREGION_TIMEOUT_DELAY, mode: 'message' | 'ids' = 'message') {
108+
announce(message: string, assertiveness = 'assertive', timeout = LIVEREGION_TIMEOUT_DELAY) {
110109
if (!this.node) {
111110
return;
112111
}
113112

114113
let node = document.createElement('div');
115-
if (mode === 'message') {
116-
node.textContent = message;
117-
} else {
118-
// To read an aria-labelledby, the element must have an appropriate role, such as img.
119-
node.setAttribute('role', 'img');
120-
node.setAttribute('aria-labelledby', message);
121-
}
114+
node.textContent = message;
122115

123116
if (assertiveness === 'assertive') {
124-
this.assertiveLog?.appendChild(node);
117+
this.assertiveLog.appendChild(node);
125118
} else {
126-
this.politeLog?.appendChild(node);
119+
this.politeLog.appendChild(node);
127120
}
128121

129122
if (message !== '') {
@@ -138,16 +131,12 @@ class LiveAnnouncer {
138131
return;
139132
}
140133

141-
if ((!assertiveness || assertiveness === 'assertive') && this.assertiveLog) {
134+
if (!assertiveness || assertiveness === 'assertive') {
142135
this.assertiveLog.innerHTML = '';
143136
}
144137

145-
if ((!assertiveness || assertiveness === 'polite') && this.politeLog) {
138+
if (!assertiveness || assertiveness === 'polite') {
146139
this.politeLog.innerHTML = '';
147140
}
148141
}
149142
}
150-
151-
// singleton, setup immediately so that the DOM is primed for the first announcement as soon as possible
152-
// Safari has a race condition where the first announcement is not read if we wait until the first announce call
153-
let liveAnnouncer: LiveAnnouncer | null = new LiveAnnouncer();

packages/@react-spectrum/autocomplete/test/SearchAutocomplete.test.js

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
*/
1212

1313
jest.mock('@react-aria/live-announcer');
14-
import {act, fireEvent, pointerMap, render, simulateDesktop, simulateMobile, waitFor, within} from '@react-spectrum/test-utils-internal';
14+
import {act, fireEvent, pointerMap, render, screen, simulateDesktop, simulateMobile, waitFor, within} from '@react-spectrum/test-utils-internal';
1515
import {announce} from '@react-aria/live-announcer';
1616
import {Button} from '@react-spectrum/button';
1717
import Filter from '@spectrum-icons/workflow/Filter';
@@ -3228,9 +3228,7 @@ describe('SearchAutocomplete', function () {
32283228

32293229
let listbox = getByRole('listbox');
32303230
expect(listbox).toBeVisible();
3231-
expect(announce).toHaveBeenCalledTimes(2);
3232-
expect(announce).toHaveBeenNthCalledWith(1, '3 options available.');
3233-
expect(announce).toHaveBeenNthCalledWith(2, 'One');
3231+
expect(screen.getAllByRole('log')).toHaveLength(2);
32343232
platformMock.mockRestore();
32353233
});
32363234

packages/@react-spectrum/color/test/ColorPicker.test.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ describe('ColorPicker', function () {
4040

4141
let button = getByRole('button');
4242
expect(button).toHaveTextContent('Fill');
43-
expect(within(button).getByLabelText('vibrant red')).toBeInTheDocument();
43+
expect(within(button).getByRole('img')).toHaveAttribute('aria-label', 'vibrant red');
4444

4545
await user.click(button);
4646

@@ -67,7 +67,7 @@ describe('ColorPicker', function () {
6767
act(() => dialog.focus());
6868
await user.keyboard('{Escape}');
6969
act(() => {jest.runAllTimers();});
70-
expect(within(button).getByLabelText('dark vibrant blue')).toBeInTheDocument();
70+
expect(within(button).getByRole('img')).toHaveAttribute('aria-label', 'dark vibrant blue');
7171
});
7272

7373
it('should have default value of black', async function () {
@@ -81,7 +81,7 @@ describe('ColorPicker', function () {
8181

8282
let button = getByRole('button');
8383
expect(button).toHaveTextContent('Fill');
84-
expect(within(button).getByLabelText('black')).toBeInTheDocument();
84+
expect(within(button).getByRole('img')).toHaveAttribute('aria-label', 'black');
8585

8686
await user.click(button);
8787

@@ -132,6 +132,6 @@ describe('ColorPicker', function () {
132132
act(() => getByRole('dialog').focus());
133133
await user.keyboard('{Escape}');
134134
act(() => {jest.runAllTimers();});
135-
expect(within(button).getByLabelText('vibrant orange')).toBeInTheDocument();
135+
expect(within(button).getByRole('img')).toHaveAttribute('aria-label', 'vibrant orange');
136136
});
137137
});

packages/@react-spectrum/combobox/test/ComboBox.test.js

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
*/
1212

1313
jest.mock('@react-aria/live-announcer');
14-
import {act, fireEvent, pointerMap, render, simulateDesktop, simulateMobile, waitFor, within} from '@react-spectrum/test-utils-internal';
14+
import {act, fireEvent, pointerMap, render, screen, simulateDesktop, simulateMobile, waitFor, within} from '@react-spectrum/test-utils-internal';
1515
import {announce} from '@react-aria/live-announcer';
1616
import {Button} from '@react-spectrum/button';
1717
import {chain} from '@react-aria/utils';
@@ -5149,9 +5149,7 @@ describe('ComboBox', function () {
51495149

51505150
let listbox = getByRole('listbox');
51515151
expect(listbox).toBeVisible();
5152-
expect(announce).toHaveBeenCalledTimes(2);
5153-
expect(announce).toHaveBeenNthCalledWith(1, '3 options available.');
5154-
expect(announce).toHaveBeenNthCalledWith(2, 'One');
5152+
expect(screen.getAllByRole('log')).toHaveLength(2);
51555153
platformMock.mockRestore();
51565154
});
51575155

packages/@react-spectrum/provider/test/Provider.test.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,13 @@
1010
* governing permissions and limitations under the License.
1111
*/
1212

13-
import {act, fireEvent, pointerMap, render} from '@react-spectrum/test-utils-internal';
13+
// needs to be imported first
14+
// eslint-disable-next-line
15+
import MatchMediaMock from 'jest-matchmedia-mock';
1416
// eslint-disable-next-line rsp-rules/sort-imports
17+
import {act, fireEvent, pointerMap, render} from '@react-spectrum/test-utils-internal';
1518
import {ActionButton, Button} from '@react-spectrum/button';
1619
import {Checkbox} from '@react-spectrum/checkbox';
17-
import MatchMediaMock from 'jest-matchmedia-mock';
1820
import {Provider} from '../';
1921
// eslint-disable-next-line rulesdir/useLayoutEffectRule
2022
import React, {useLayoutEffect, useRef} from 'react';

packages/react-aria-components/docs/Button.mdx

Lines changed: 0 additions & 132 deletions
Original file line numberDiff line numberDiff line change
@@ -159,138 +159,6 @@ A `Button` can be disabled using the `isDisabled` prop.
159159

160160
</details>
161161

162-
## Pending
163-
164-
A `Button` can be put into the pending state using the `isPending` prop.
165-
Both a `Text` and [ProgressBar](ProgressBar.html) component are required to show the pending state correctly.
166-
Make sure to internationalize the label you pass to the [ProgressBar](ProgressBar.html) component.
167-
168-
```tsx example
169-
import {useEffect, useRef, useState} from 'react';
170-
import {ProgressBar, Text} from 'react-aria-components';
171-
172-
function PendingButton(props) {
173-
let [isPending, setPending] = useState(false);
174-
175-
let timeout = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);
176-
let handlePress = (e) => {
177-
setPending(true);
178-
timeout.current = setTimeout(() => {
179-
setPending(false);
180-
timeout.current = undefined;
181-
}, 5000);
182-
};
183-
184-
useEffect(() => {
185-
return () => {
186-
clearTimeout(timeout.current);
187-
};
188-
}, []);
189-
190-
return (
191-
<Button
192-
{...props}
193-
isPending={isPending}
194-
onPress={handlePress}>
195-
{({isPending}) => (
196-
<>
197-
<Text className={isPending ? 'pending' : undefined}>Click me</Text>
198-
<ProgressBar
199-
aria-label="loading"
200-
isIndeterminate
201-
className={['spinner', (isPending ? 'spinner-pending' : '')].join(' ')}>
202-
<span className={'loader'} />
203-
</ProgressBar>
204-
</>
205-
)}
206-
</Button>
207-
);
208-
}
209-
<PendingButton />
210-
```
211-
212-
<details>
213-
<summary style={{fontWeight: 'bold'}}><ChevronRight size="S" /> Show CSS</summary>
214-
215-
```css
216-
@keyframes load {
217-
99% {
218-
visibility: hidden;
219-
}
220-
221-
100% {
222-
visibility: visible;
223-
}
224-
}
225-
226-
@keyframes hidden {
227-
99% {
228-
visibility: visible;
229-
}
230-
231-
100% {
232-
visibility: hidden;
233-
}
234-
}
235-
236-
.react-aria-Button {
237-
position: relative;
238-
}
239-
240-
.spinner {
241-
position: absolute;
242-
top: 50%;
243-
left: 50%;
244-
transform: translate(-50%, -50%);
245-
visibility: hidden;
246-
}
247-
.spinner-pending {
248-
animation: 1s load;
249-
animation-fill-mode: forwards;
250-
}
251-
252-
.pending {
253-
animation: 1s hidden;
254-
animation-fill-mode: forwards;
255-
visibility: visible;
256-
}
257-
258-
.loader {
259-
width: 20px;
260-
height: 20px;
261-
border: 3px solid var(--background-color);
262-
border-radius: 50%;
263-
display: inline-block;
264-
position: relative;
265-
box-sizing: border-box;
266-
animation: rotation 1s linear infinite;
267-
}
268-
.loader::after {
269-
content: '';
270-
box-sizing: border-box;
271-
position: absolute;
272-
left: 50%;
273-
top: 50%;
274-
transform: translate(-50%, -50%);
275-
width: 20px;
276-
height: 20px;
277-
border-radius: 50%;
278-
border: 3px solid;
279-
border-color: var(--purple-400) transparent;
280-
}
281-
282-
@keyframes rotation {
283-
0% {
284-
transform: rotate(0deg);
285-
}
286-
100% {
287-
transform: rotate(360deg);
288-
}
289-
}
290-
```
291-
292-
</details>
293-
294162
## Link buttons
295163

296164
The `Button` component always represents a button semantically. To create a link that visually looks like a button, use the [Link](Link.html) component instead. You can reuse the same styles you apply to the `Button` component on the `Link`.

packages/react-aria-components/package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,6 @@
4444
"@react-aria/dnd": "^3.7.2",
4545
"@react-aria/focus": "^3.18.2",
4646
"@react-aria/interactions": "^3.22.2",
47-
"@react-aria/live-announcer": "^3.3.4",
4847
"@react-aria/menu": "^3.15.3",
4948
"@react-aria/toolbar": "3.0.0-beta.8",
5049
"@react-aria/tree": "3.0.0-alpha.5",

0 commit comments

Comments
 (0)