Skip to content

Commit ef4027f

Browse files
authored
chore: update S2 picker "isLoading" API to "loadingState" for consistency (#8255)
* replace isLoading with loadingState for consistency * adding codemod and updating migration * fix tests * fix chromatic story * forgot to remove outdated snapshot
1 parent da7e608 commit ef4027f

File tree

9 files changed

+76
-61
lines changed

9 files changed

+76
-61
lines changed

.storybook-s2/docs/Migrating.jsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,7 @@ export function Migrating() {
277277
<li className={style({font: 'body', marginY: 8})}>Change <Code>validationState="invalid"</Code> to <Code>isInvalid</Code></li>
278278
<li className={style({font: 'body', marginY: 8})}>Remove <Code>validationState="valid"</Code> (it is no longer supported in Spectrum 2)</li>
279279
<li className={style({font: 'body', marginY: 8})}>Update <Code>Item</Code> to be a <Code>PickerItem</Code></li>
280+
<li className={style({font: 'body', marginY: 8})}>Change <Code>isLoading</Code> to <Code>loadingState</Code> and provide the appropriate loading state.</li>
280281
</ul>
281282

282283
<H3>ProgressBar</H3>

packages/@react-spectrum/s2/chromatic/Picker.stories.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ export const ContextualHelp = {
7272

7373
export const EmptyAndLoading = {
7474
render: () => (
75-
<Picker label="loading" isLoading>
75+
<Picker label="loading" loadingState="loading">
7676
{[]}
7777
</Picker>
7878
),

packages/@react-spectrum/s2/src/Picker.tsx

Lines changed: 16 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,11 @@ import {
2727
ListLayout,
2828
Provider,
2929
SectionProps,
30-
SelectStateContext,
3130
SelectValue,
3231
UNSTABLE_ListBoxLoadingSentinel,
3332
Virtualizer
3433
} from 'react-aria-components';
35-
import {AsyncLoadable, FocusableRef, FocusableRefValue, HelpTextProps, PressEvent, RefObject, SpectrumLabelableProps} from '@react-types/shared';
34+
import {AsyncLoadable, FocusableRef, FocusableRefValue, HelpTextProps, LoadingState, PressEvent, RefObject, SpectrumLabelableProps} from '@react-types/shared';
3635
import {baseColor, edgeToText, focusRing, style} from '../style' with {type: 'macro'};
3736
import {centerBaseline} from './CenterBaseline';
3837
import {
@@ -71,7 +70,7 @@ import {PressResponder} from '@react-aria/interactions';
7170
import {pressScale} from './pressScale';
7271
import {ProgressCircle} from './ProgressCircle';
7372
import {raw} from '../style/style-macro' with {type: 'macro'};
74-
import React, {createContext, forwardRef, ReactNode, useContext, useRef, useState} from 'react';
73+
import React, {createContext, forwardRef, ReactNode, useContext, useMemo, useRef, useState} from 'react';
7574
import {useFocusableRef} from '@react-spectrum/utils';
7675
import {useGlobalListeners, useSlotId} from '@react-aria/utils';
7776
import {useLocalizedStringFormatter} from '@react-aria/i18n';
@@ -100,8 +99,7 @@ export interface PickerProps<T extends object> extends
10099
HelpTextProps,
101100
Pick<ListBoxProps<T>, 'items'>,
102101
Pick<AriaPopoverProps, 'shouldFlip'>,
103-
AsyncLoadable
104-
{
102+
Pick<AsyncLoadable, 'onLoadMore'> {
105103
/** The contents of the collection. */
106104
children: ReactNode | ((item: T) => ReactNode),
107105
/**
@@ -117,7 +115,9 @@ export interface PickerProps<T extends object> extends
117115
*/
118116
align?: 'start' | 'end',
119117
/** Width of the menu. By default, matches width of the trigger. Note that the minimum width of the dropdown is always equal to the trigger's width. */
120-
menuWidth?: number
118+
menuWidth?: number,
119+
/** The current loading state of the Picker. */
120+
loadingState?: LoadingState
121121
}
122122

123123
interface PickerButtonProps extends PickerStyleProps, ButtonRenderProps {}
@@ -232,7 +232,7 @@ const iconStyles = style({
232232
value: 'currentColor'
233233
},
234234
color: {
235-
isInitialLoad: 'disabled'
235+
isLoading: 'disabled'
236236
}
237237
});
238238

@@ -286,7 +286,7 @@ export const Picker = /*#__PURE__*/ (forwardRef as forwardRefType)(function Pick
286286
UNSAFE_style,
287287
placeholder = stringFormatter.format('picker.placeholder'),
288288
isQuiet,
289-
isLoading,
289+
loadingState,
290290
onLoadMore,
291291
...pickerProps
292292
} = props;
@@ -304,12 +304,13 @@ export const Picker = /*#__PURE__*/ (forwardRef as forwardRefType)(function Pick
304304
}
305305

306306
let renderer;
307-
let spinnerId = useSlotId([isLoading]);
307+
let showButtonSpinner = useMemo(() => loadingState === 'loading', [loadingState]);
308+
let spinnerId = useSlotId([showButtonSpinner]);
308309

309310
let listBoxLoadingCircle = (
310311
<UNSTABLE_ListBoxLoadingSentinel
311312
className={loadingWrapperStyles}
312-
isLoading={isLoading}
313+
isLoading={loadingState === 'loadingMore'}
313314
onLoadMore={onLoadMore}>
314315
<PickerProgressCircle size={size} aria-label={stringFormatter.format('table.loadingMore')} />
315316
</UNSTABLE_ListBoxLoadingSentinel>
@@ -360,7 +361,7 @@ export const Picker = /*#__PURE__*/ (forwardRef as forwardRefType)(function Pick
360361
{label}
361362
</FieldLabel>
362363
<PickerButton
363-
isLoading={isLoading}
364+
loadingState={loadingState}
364365
isOpen={isOpen}
365366
isQuiet={isQuiet}
366367
isFocusVisible={isFocusVisible}
@@ -454,7 +455,7 @@ function PickerProgressCircle(props) {
454455
);
455456
}
456457

457-
interface PickerButtonInnerProps<T extends object> extends PickerStyleProps, Omit<AriaSelectRenderProps, 'isRequired' | 'isFocused'>, Pick<PickerProps<T>, 'isLoading'> {
458+
interface PickerButtonInnerProps<T extends object> extends PickerStyleProps, Omit<AriaSelectRenderProps, 'isRequired' | 'isFocused'>, Pick<PickerProps<T>, 'loadingState'> {
458459
loadingCircle: ReactNode,
459460
buttonRef: RefObject<HTMLButtonElement | null>
460461
}
@@ -468,13 +469,10 @@ const PickerButton = createHideableComponent(function PickerButton<T extends obj
468469
size,
469470
isInvalid,
470471
isDisabled,
471-
isLoading,
472+
loadingState,
472473
loadingCircle,
473474
buttonRef
474475
} = props;
475-
let state = useContext(SelectStateContext);
476-
// If it is the initial load, the collection either hasn't been formed or only has the loader so apply the disabled style
477-
let isInitialLoad = (state?.collection.size == null || state?.collection.size <= 1) && isLoading;
478476

479477
// For mouse interactions, pickers open on press start. When the popover underlay appears
480478
// it covers the trigger button, causing onPressEnd to fire immediately and no press scaling
@@ -538,10 +536,10 @@ const PickerButton = createHideableComponent(function PickerButton<T extends obj
538536
}}
539537
</SelectValue>
540538
{isInvalid && <FieldErrorIcon isDisabled={isDisabled} />}
541-
{isInitialLoad && !isOpen && loadingCircle}
539+
{loadingState === 'loading' && !isOpen && loadingCircle}
542540
<ChevronIcon
543541
size={size}
544-
className={iconStyles({isInitialLoad})} />
542+
className={iconStyles({isLoading: loadingState === 'loading'})} />
545543
{isFocusVisible && isQuiet && <span className={quietFocusLine} /> }
546544
{isInvalid && !isDisabled && !isQuiet &&
547545
// @ts-ignore known limitation detecting functions from the theme

packages/@react-spectrum/s2/stories/Picker.stories.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -251,7 +251,7 @@ const AsyncPicker = (args: any) => {
251251
});
252252

253253
return (
254-
<Picker {...args} isLoading={list.isLoading} onLoadMore={list.loadMore} items={list.items}>
254+
<Picker {...args} loadingState={list.loadingState} onLoadMore={list.loadMore} items={list.items}>
255255
{(item: Character) => <PickerItem id={item.name} textValue={item.name}>{item.name}</PickerItem>}
256256
</Picker>
257257
);
@@ -279,7 +279,7 @@ let list = useAsyncList({
279279
280280
return (
281281
<Picker
282-
isLoading={list.isLoading}
282+
loadingState={list.loadingState}
283283
onLoadMore={list.loadMore}
284284
items={list.items}>
285285
{item => <PickerItem id={item.name} textValue={item.name}>{item.name}</PickerItem>}

packages/@react-spectrum/s2/test/Picker.test.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,13 @@ import {User} from '@react-aria/test-utils';
1818
describe('Picker', () => {
1919
let testUtilUser = new User();
2020
function DynamicPicker(props) {
21-
let {items, isLoading, onLoadMore, ...otherProps} = props;
21+
let {items, loadingState, onLoadMore, ...otherProps} = props;
2222
return (
2323
<Picker
2424
{...otherProps}
2525
label="Test picker"
2626
items={items}
27-
isLoading={isLoading}
27+
loadingState={loadingState}
2828
onLoadMore={onLoadMore}>
2929
{(item: any) => <PickerItem id={item.name} textValue={item.name}>{item.name}</PickerItem>}
3030
</Picker>
@@ -55,7 +55,7 @@ describe('Picker', () => {
5555
});
5656

5757
let tree = render(
58-
<Picker label="test" isLoading onLoadMore={onLoadMore}>
58+
<Picker label="test" loadingState="loadingMore" onLoadMore={onLoadMore}>
5959
<PickerItem>Chocolate</PickerItem>
6060
<PickerItem>Mint</PickerItem>
6161
<PickerItem>Strawberry</PickerItem>
@@ -104,7 +104,7 @@ describe('Picker', () => {
104104
expect(option).toHaveAttribute('aria-posinset', `${index + 1}`);
105105
}
106106

107-
tree.rerender(<DynamicPicker items={items} isLoading />);
107+
tree.rerender(<DynamicPicker items={items} loadingState="loadingMore" />);
108108
options = selectTester.options();
109109
for (let [index, option] of options.entries()) {
110110
expect(option).toHaveAttribute('aria-posinset', `${index + 1}`);

packages/dev/codemods/src/s1-to-s2/__tests__/__snapshots__/picker.test.ts.snap

Lines changed: 19 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -7,38 +7,24 @@ let options = [
77
{id: 2, name: 'Vanilla'}
88
];
99
<div>
10-
<Picker
10+
<Picker
1111
label="Ice Cream"
1212
items={options}>
1313
{(item) => <PickerItem id={item.id}>{item.name}</PickerItem>}
1414
</Picker>
15-
<Picker
15+
<Picker
1616
label="Ice Cream"
1717
items={options}>
1818
{(item) => <PickerItem id={item.id}>{item.name}</PickerItem>}
1919
</Picker>
20-
<Picker
20+
<Picker
2121
label="Ice Cream"
2222
items={options}>
2323
{(item) => <PickerItem>{item.name}</PickerItem>}
2424
</Picker>
2525
</div>"
2626
`;
2727

28-
exports[`Keeps isLoading and onLoadMore 1`] = `
29-
"import { PickerItem, Picker } from "@react-spectrum/s2";
30-
<>
31-
<Picker label="Ice Cream" isLoading onLoadMore={() => {}}>
32-
<PickerItem>Red Panda</PickerItem>
33-
<PickerItem>Cat</PickerItem>
34-
</Picker>
35-
<Picker label="Ice Cream" isLoading={false} onLoadMore={() => {}}>
36-
<PickerItem>Red Panda</PickerItem>
37-
<PickerItem>Cat</PickerItem>
38-
</Picker>
39-
</>"
40-
`;
41-
4228
exports[`Static - Converts menuWidth to px value 1`] = `
4329
"import { PickerItem, Picker } from "@react-spectrum/s2";
4430
let menuWidth = 'size-10';
@@ -162,3 +148,19 @@ exports[`handles sections 1`] = `
162148
</PickerSection>
163149
</Picker>"
164150
`;
151+
152+
exports[`replaces isLoading with loadingState and keeps onLoadMore 1`] = `
153+
"import { PickerItem, Picker } from "@react-spectrum/s2";
154+
<>
155+
// TODO(S2-upgrade): Replace boolean passed to isLoading with appropriate loadingState.
156+
<Picker label="Ice Cream" loadingState onLoadMore={() => {}}>
157+
<PickerItem>Red Panda</PickerItem>
158+
<PickerItem>Cat</PickerItem>
159+
</Picker>
160+
// TODO(S2-upgrade): Replace boolean passed to isLoading with appropriate loadingState.
161+
<Picker label="Ice Cream" loadingState={false} onLoadMore={() => {}}>
162+
<PickerItem>Red Panda</PickerItem>
163+
<PickerItem>Cat</PickerItem>
164+
</Picker>
165+
</>"
166+
`;

packages/dev/codemods/src/s1-to-s2/__tests__/picker.test.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,17 +33,17 @@ let options = [
3333
{id: 2, name: 'Vanilla'}
3434
];
3535
<div>
36-
<Picker
36+
<Picker
3737
label="Ice Cream"
3838
items={options}>
3939
{(item) => <Item id={item.id}>{item.name}</Item>}
4040
</Picker>
41-
<Picker
41+
<Picker
4242
label="Ice Cream"
4343
items={options}>
4444
{(item) => <Item key={item.id}>{item.name}</Item>}
4545
</Picker>
46-
<Picker
46+
<Picker
4747
label="Ice Cream"
4848
items={options}>
4949
{(item) => <Item>{item.name}</Item>}
@@ -149,7 +149,7 @@ import {Picker, Section, Item} from '@adobe/react-spectrum';
149149
</Picker>
150150
`);
151151

152-
test('Keeps isLoading and onLoadMore', `
152+
test('replaces isLoading with loadingState and keeps onLoadMore', `
153153
import {Picker, Section, Item} from '@adobe/react-spectrum';
154154
<>
155155
<Picker label="Ice Cream" isLoading onLoadMore={() => {}}>

packages/dev/codemods/src/s1-to-s2/src/codemods/components/Picker/transform.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import {
22
convertDimensionValueToPx,
33
removeProp,
4+
updatePropName,
45
updatePropNameAndValue
56
} from '../../shared/transforms';
67
import {NodePath} from '@babel/traverse';
@@ -12,6 +13,7 @@ import * as t from '@babel/types';
1213
* - Remove isQuiet (it is no longer supported in Spectrum 2).
1314
* - Change validationState="invalid" to isInvalid.
1415
* - Remove validationState="valid" (it is no longer supported in Spectrum 2).
16+
* - Replace isLoading with loadingState.
1517
*/
1618
export default function transformPicker(path: NodePath<t.JSXElement>) {
1719
// Change menuWidth value from a DimensionValue to a pixel value
@@ -30,4 +32,11 @@ export default function transformPicker(path: NodePath<t.JSXElement>) {
3032

3133
// Remove validationState="valid"
3234
removeProp(path, {propName: 'validationState', propValue: 'valid'});
33-
}
35+
36+
// Replace isLoading with loadingState
37+
updatePropName(path, {
38+
oldPropName: 'isLoading',
39+
newPropName: 'loadingState',
40+
comment: 'Replace boolean passed to isLoading with appropriate loadingState.'
41+
});
42+
}

0 commit comments

Comments
 (0)