Skip to content

Commit 3fe09f6

Browse files
committed
add loading spinners to RAC stories
1 parent f6ba68a commit 3fe09f6

File tree

7 files changed

+99
-52
lines changed

7 files changed

+99
-52
lines changed

packages/react-aria-components/example/index.css

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -474,3 +474,9 @@ input {
474474
[aria-autocomplete][data-focus-visible]{
475475
outline: 3px solid blue;
476476
}
477+
478+
.spinner {
479+
position: absolute;
480+
top: 50%;
481+
left: 50%;
482+
}

packages/react-aria-components/stories/ComboBox.stories.tsx

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

1313
import {Button, Collection, ComboBox, Input, Label, ListBox, ListLayout, Popover, useFilter, Virtualizer} from 'react-aria-components';
14-
import {MyListBoxItem} from './utils';
14+
import {LoadingSpinner, MyListBoxItem} from './utils';
1515
import React, {useMemo, useState} from 'react';
1616
import styles from '../example/index.css';
1717
import {UNSTABLE_ListBoxLoadingIndicator} from '../src/ListBox';
@@ -238,6 +238,14 @@ export const VirtualizedComboBox = () => {
238238
);
239239
};
240240

241+
let renderEmptyState = ({isLoading}) => {
242+
return (
243+
<div style={{height: 30, width: '100%'}}>
244+
{isLoading ? <LoadingSpinner style={{height: 20, width: 20, transform: 'translate(-50%, -50%)'}} /> : 'No results'}
245+
</div>
246+
);
247+
};
248+
241249
interface Character {
242250
name: string,
243251
height: number,
@@ -252,7 +260,7 @@ export const AsyncVirtualizedDynamicCombobox = () => {
252260
cursor = cursor.replace(/^http:\/\//i, 'https://');
253261
}
254262

255-
await new Promise(resolve => setTimeout(resolve, 4000));
263+
await new Promise(resolve => setTimeout(resolve, 2000));
256264
let res = await fetch(cursor || `https://swapi.py4e.com/api/people/?search=${filterText}`, {signal});
257265
let json = await res.json();
258266

@@ -264,17 +272,18 @@ export const AsyncVirtualizedDynamicCombobox = () => {
264272
});
265273

266274
return (
267-
<ComboBox items={list.items} inputValue={list.filterText} onInputChange={list.setFilterText} isLoading={list.isLoading} onLoadMore={list.loadMore}>
275+
<ComboBox items={list.items} inputValue={list.filterText} onInputChange={list.setFilterText} isLoading={list.isLoading} onLoadMore={list.loadMore} allowsEmptyCollection>
268276
<Label style={{display: 'block'}}>Async Virtualized Dynamic ComboBox</Label>
269-
<div style={{display: 'flex'}}>
277+
<div style={{display: 'flex', position: 'relative'}}>
270278
<Input />
279+
{list.isLoading && <LoadingSpinner style={{left: '130px', top: '0px', height: 20, width: 20}} />}
271280
<Button>
272281
<span aria-hidden="true" style={{padding: '0 2px'}}></span>
273282
</Button>
274283
</div>
275284
<Popover>
276285
<Virtualizer layout={new ListLayout({rowHeight: 25})}>
277-
<ListBox<Character> className={styles.menu}>
286+
<ListBox<Character> className={styles.menu} renderEmptyState={({isLoading}) => renderEmptyState({isLoading})}>
278287
{item => <MyListBoxItem id={item.name}>{item.name}</MyListBoxItem>}
279288
</ListBox>
280289
</Virtualizer>
@@ -285,10 +294,8 @@ export const AsyncVirtualizedDynamicCombobox = () => {
285294

286295
const MyListBoxLoaderIndicator = () => {
287296
return (
288-
<UNSTABLE_ListBoxLoadingIndicator>
289-
<span>
290-
Load more spinner
291-
</span>
297+
<UNSTABLE_ListBoxLoadingIndicator style={{height: 30, width: '100%'}}>
298+
<LoadingSpinner style={{height: 20, width: 20, transform: 'translate(-50%, -50%)'}} />
292299
</UNSTABLE_ListBoxLoadingIndicator>
293300
);
294301
};
@@ -300,7 +307,7 @@ export const AsyncVirtualizedCollectionRenderCombobox = () => {
300307
cursor = cursor.replace(/^http:\/\//i, 'https://');
301308
}
302309

303-
await new Promise(resolve => setTimeout(resolve, 4000));
310+
await new Promise(resolve => setTimeout(resolve, 2000));
304311
let res = await fetch(cursor || `https://swapi.py4e.com/api/people/?search=${filterText}`, {signal});
305312
let json = await res.json();
306313

@@ -314,8 +321,9 @@ export const AsyncVirtualizedCollectionRenderCombobox = () => {
314321
return (
315322
<ComboBox inputValue={list.filterText} onInputChange={list.setFilterText} isLoading={list.isLoading} onLoadMore={list.loadMore}>
316323
<Label style={{display: 'block'}}>Async Virtualized Collection render ComboBox</Label>
317-
<div style={{display: 'flex'}}>
324+
<div style={{display: 'flex', position: 'relative'}}>
318325
<Input />
326+
{list.isLoading && <LoadingSpinner style={{left: '130px', top: '0px', height: 20, width: 20}} />}
319327
<Button>
320328
<span aria-hidden="true" style={{padding: '0 2px'}}></span>
321329
</Button>

packages/react-aria-components/stories/GridList.stories.tsx

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import React from 'react';
1616
import styles from '../example/index.css';
1717
import {UNSTABLE_GridListLoadingIndicator} from '../src/GridList';
1818
import {useAsyncList, useListData} from 'react-stately';
19+
import { LoadingSpinner } from './utils';
1920

2021
export default {
2122
title: 'React Aria Components'
@@ -171,6 +172,14 @@ export function VirtualizedGridListGrid() {
171172
);
172173
}
173174

175+
let renderEmptyState = ({isLoading}) => {
176+
return (
177+
<div style={{height: 30, width: '100%'}}>
178+
{isLoading ? <LoadingSpinner style={{height: 20, width: 20, transform: 'translate(-50%, -50%)'}} /> : 'No results'}
179+
</div>
180+
);
181+
};
182+
174183
interface Character {
175184
name: string,
176185
height: number,
@@ -180,10 +189,8 @@ interface Character {
180189

181190
const MyGridListLoaderIndicator = () => {
182191
return (
183-
<UNSTABLE_GridListLoadingIndicator>
184-
<span>
185-
Load more spinner
186-
</span>
192+
<UNSTABLE_GridListLoadingIndicator style={{height: 30, width: '100%'}}>
193+
<LoadingSpinner style={{height: 20, width: 20, transform: 'translate(-50%, -50%)'}} />
187194
</UNSTABLE_GridListLoadingIndicator>
188195
);
189196
};
@@ -195,7 +202,7 @@ export const AsyncGridList = () => {
195202
cursor = cursor.replace(/^http:\/\//i, 'https://');
196203
}
197204

198-
await new Promise(resolve => setTimeout(resolve, 4000));
205+
await new Promise(resolve => setTimeout(resolve, 2000));
199206
let res = await fetch(cursor || `https://swapi.py4e.com/api/people/?search=${filterText}`, {signal});
200207
let json = await res.json();
201208

@@ -214,7 +221,7 @@ export const AsyncGridList = () => {
214221
aria-label="async gridlist"
215222
isLoading={list.isLoading}
216223
onLoadMore={list.loadMore}
217-
renderEmptyState={() => list.isLoading ? 'Loading spinner' : 'No results found'}>
224+
renderEmptyState={({isLoading}) => renderEmptyState({isLoading})}>
218225
{item => <MyGridListItem id={item.name}>{item.name}</MyGridListItem>}
219226
</GridList>
220227
);
@@ -227,7 +234,7 @@ export const AsyncGridListVirtualized = () => {
227234
cursor = cursor.replace(/^http:\/\//i, 'https://');
228235
}
229236

230-
await new Promise(resolve => setTimeout(resolve, 4000));
237+
await new Promise(resolve => setTimeout(resolve, 2000));
231238
let res = await fetch(cursor || `https://swapi.py4e.com/api/people/?search=${filterText}`, {signal});
232239
let json = await res.json();
233240
return {
@@ -249,7 +256,7 @@ export const AsyncGridListVirtualized = () => {
249256
aria-label="async virtualized gridlist"
250257
isLoading={list.isLoading}
251258
onLoadMore={list.loadMore}
252-
renderEmptyState={() => list.isLoading ? 'Loading spinner' : 'No results found'}>
259+
renderEmptyState={({isLoading}) => renderEmptyState({isLoading})}>
253260
<Collection items={list.items}>
254261
{item => <MyGridListItem id={item.name}>{item.name}</MyGridListItem>}
255262
</Collection>

packages/react-aria-components/stories/ListBox.stories.tsx

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import {action} from '@storybook/addon-actions';
1414
import {Collection, DropIndicator, GridLayout, Header, ListBox, ListBoxItem, ListBoxProps, ListBoxSection, ListLayout, Separator, Text, useDragAndDrop, Virtualizer, WaterfallLayout} from 'react-aria-components';
1515
import {Key, useAsyncList, useListData} from 'react-stately';
1616
import {Layout, LayoutInfo, Rect, Size} from '@react-stately/virtualizer';
17-
import {MyListBoxItem} from './utils';
17+
import {LoadingSpinner, MyListBoxItem} from './utils';
1818
import React, {useMemo} from 'react';
1919
import styles from '../example/index.css';
2020
import {UNSTABLE_ListBoxLoadingIndicator} from '../src/ListBox';
@@ -431,6 +431,14 @@ export function VirtualizedListBoxWaterfall({minSize = 80, maxSize = 100}) {
431431
);
432432
}
433433

434+
let renderEmptyState = ({isLoading}) => {
435+
return (
436+
<div style={{height: 30, width: '100%'}}>
437+
{isLoading ? <LoadingSpinner style={{height: 20, width: 20, transform: 'translate(-50%, -50%)'}} /> : 'No results'}
438+
</div>
439+
);
440+
};
441+
434442
interface Character {
435443
name: string,
436444
height: number,
@@ -440,10 +448,8 @@ interface Character {
440448

441449
const MyListBoxLoaderIndicator = () => {
442450
return (
443-
<UNSTABLE_ListBoxLoadingIndicator>
444-
<span>
445-
Load more spinner
446-
</span>
451+
<UNSTABLE_ListBoxLoadingIndicator style={{height: 30, width: '100%'}}>
452+
<LoadingSpinner style={{height: 20, width: 20, transform: 'translate(-50%, -50%)'}} />
447453
</UNSTABLE_ListBoxLoadingIndicator>
448454
);
449455
};
@@ -457,7 +463,7 @@ export const AsyncListBox = (args) => {
457463
cursor = cursor.replace(/^http:\/\//i, 'https://');
458464
}
459465

460-
await new Promise(resolve => setTimeout(resolve, 4000));
466+
await new Promise(resolve => setTimeout(resolve, 2000));
461467
let res = await fetch(cursor || `https://swapi.py4e.com/api/people/?search=${filterText}`, {signal});
462468
let json = await res.json();
463469

@@ -480,7 +486,7 @@ export const AsyncListBox = (args) => {
480486
aria-label="async listbox"
481487
isLoading={list.isLoading}
482488
onLoadMore={list.loadMore}
483-
renderEmptyState={() => list.isLoading ? 'Loading spinner' : 'No results found'}>
489+
renderEmptyState={({isLoading}) => renderEmptyState({isLoading})}>
484490
{(item: Character) => (
485491
<MyListBoxItem
486492
style={{
@@ -573,7 +579,7 @@ export const AsyncListBoxVirtualized = (args) => {
573579
cursor = cursor.replace(/^http:\/\//i, 'https://');
574580
}
575581

576-
await new Promise(resolve => setTimeout(resolve, 4000));
582+
await new Promise(resolve => setTimeout(resolve, 2000));
577583
let res = await fetch(cursor || `https://swapi.py4e.com/api/people/?search=${filterText}`, {signal});
578584
let json = await res.json();
579585

@@ -604,7 +610,7 @@ export const AsyncListBoxVirtualized = (args) => {
604610
aria-label="async virtualized listbox"
605611
isLoading={list.isLoading}
606612
onLoadMore={list.loadMore}
607-
renderEmptyState={() => list.isLoading ? 'Loading spinner' : 'No results found'}>
613+
renderEmptyState={({isLoading}) => renderEmptyState({isLoading})}>
608614
<Collection items={list.items}>
609615
{(item: Character) => (
610616
<MyListBoxItem

packages/react-aria-components/stories/Select.stories.tsx

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

1313
import {Button, Collection, Label, ListBox, ListLayout, OverlayArrow, Popover, Select, SelectValue, Virtualizer} from 'react-aria-components';
14-
import {MyListBoxItem} from './utils';
14+
import {LoadingSpinner, MyListBoxItem} from './utils';
1515
import React from 'react';
1616
import styles from '../example/index.css';
1717
import {UNSTABLE_ListBoxLoadingIndicator} from '../src/ListBox';
@@ -122,7 +122,7 @@ export const AsyncVirtualizedDynamicSelect = () => {
122122
}
123123

124124
// Slow down load so progress circle can appear
125-
await new Promise(resolve => setTimeout(resolve, 4000));
125+
await new Promise(resolve => setTimeout(resolve, 2000));
126126
let res = await fetch(cursor || 'https://swapi.py4e.com/api/people/?search=', {signal});
127127
let json = await res.json();
128128
return {
@@ -135,9 +135,10 @@ export const AsyncVirtualizedDynamicSelect = () => {
135135
return (
136136
<Select isLoading={list.isLoading} onLoadMore={list.loadMore}>
137137
<Label style={{display: 'block'}}>Async Virtualized Dynamic Select</Label>
138-
<Button>
138+
<Button style={{position: 'relative'}}>
139139
<SelectValue />
140-
<span aria-hidden="true" style={{paddingLeft: 5}}></span>
140+
{list.isLoading && <LoadingSpinner style={{right: '20px', left: 'unset', top: '0px', height: '100%', width: 20}} />}
141+
<span aria-hidden="true" style={{paddingLeft: 25}}></span>
141142
</Button>
142143
<Popover>
143144
<OverlayArrow>
@@ -155,10 +156,8 @@ export const AsyncVirtualizedDynamicSelect = () => {
155156

156157
const MyListBoxLoaderIndicator = () => {
157158
return (
158-
<UNSTABLE_ListBoxLoadingIndicator>
159-
<span>
160-
Load more spinner
161-
</span>
159+
<UNSTABLE_ListBoxLoadingIndicator style={{height: 30, width: '100%'}}>
160+
<LoadingSpinner style={{height: 20, width: 20, transform: 'translate(-50%, -50%)'}} />
162161
</UNSTABLE_ListBoxLoadingIndicator>
163162
);
164163
};
@@ -171,7 +170,7 @@ export const AsyncVirtualizedCollectionRenderSelect = () => {
171170
}
172171

173172
// Slow down load so progress circle can appear
174-
await new Promise(resolve => setTimeout(resolve, 4000));
173+
await new Promise(resolve => setTimeout(resolve, 2000));
175174
let res = await fetch(cursor || 'https://swapi.py4e.com/api/people/?search=', {signal});
176175
let json = await res.json();
177176
return {
@@ -184,9 +183,10 @@ export const AsyncVirtualizedCollectionRenderSelect = () => {
184183
return (
185184
<Select isLoading={list.isLoading} onLoadMore={list.loadMore}>
186185
<Label style={{display: 'block'}}>Async Virtualized Collection render Select</Label>
187-
<Button>
186+
<Button style={{position: 'relative'}}>
188187
<SelectValue />
189-
<span aria-hidden="true" style={{paddingLeft: 5}}></span>
188+
{list.isLoading && <LoadingSpinner style={{right: '20px', left: 'unset', top: '0px', height: '100%', width: 20}} />}
189+
<span aria-hidden="true" style={{paddingLeft: 25}}></span>
190190
</Button>
191191
<Popover>
192192
<OverlayArrow>

packages/react-aria-components/stories/Table.stories.tsx

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@
1313
import {action} from '@storybook/addon-actions';
1414
import {Button, Cell, Checkbox, CheckboxProps, Collection, Column, ColumnProps, ColumnResizer, Dialog, DialogTrigger, DropIndicator, Heading, Menu, MenuTrigger, Modal, ModalOverlay, Popover, ResizableTableContainer, Row, Table, TableBody, TableHeader, TableLayout, useDragAndDrop, Virtualizer} from 'react-aria-components';
1515
import {isTextDropItem} from 'react-aria';
16-
import {MyMenuItem} from './utils';
17-
import React, {Suspense, useMemo, useRef, useState} from 'react';
16+
import {LoadingSpinner, MyMenuItem} from './utils';
17+
import React, {Suspense, useState} from 'react';
1818
import styles from '../example/index.css';
1919
import {UNSTABLE_TableLoadingIndicator} from '../src/Table';
2020
import {useAsyncList, useListData} from 'react-stately';
@@ -512,10 +512,8 @@ const MyTableLoadingIndicator = ({tableWidth = 400}) => {
512512
// These styles will make the load more spinner sticky. A user would know if their table is virtualized and thus could control this styling if they wanted to
513513
// TODO: this doesn't work because the virtualizer wrapper around the table body has overflow: hidden. Perhaps could change this by extending the table layout and
514514
// making the layoutInfo for the table body have allowOverflow
515-
<UNSTABLE_TableLoadingIndicator style={{height: 'inherit', position: 'sticky', top: 0, left: 0, width: tableWidth}}>
516-
<span>
517-
Load more spinner
518-
</span>
515+
<UNSTABLE_TableLoadingIndicator style={{height: 30, position: 'sticky', top: 0, left: 0, width: tableWidth}}>
516+
<LoadingSpinner style={{height: 20, width: 20, transform: 'translate(-50%, -50%)'}} />
519517
</UNSTABLE_TableLoadingIndicator>
520518
);
521519
};
@@ -602,8 +600,8 @@ export const TableLoadingRowRenderWrapperStory = {
602600

603601

604602
function renderEmptyLoader({isLoading, tableWidth = 400}) {
605-
let contents = isLoading ? 'Loading spinner' : 'No results found';
606-
return <div style={{height: 'inherit', position: 'sticky', top: 0, left: 0, width: tableWidth}}>{contents}</div>;
603+
let contents = isLoading ? <LoadingSpinner style={{height: 20, width: 20, transform: 'translate(-50%, -50%)'}} /> : 'No results found';
604+
return <div style={{height: 30, position: 'sticky', top: 0, left: 0, width: tableWidth}}>{contents}</div>;
607605
}
608606

609607
const RenderEmptyState = (args: {isLoading: boolean}) => {
@@ -657,7 +655,7 @@ const OnLoadMoreTable = () => {
657655
}
658656

659657
// Slow down load so progress circle can appear
660-
await new Promise(resolve => setTimeout(resolve, 4000));
658+
await new Promise(resolve => setTimeout(resolve, 2000));
661659
let res = await fetch(cursor || 'https://swapi.py4e.com/api/people/?search=', {signal});
662660
let json = await res.json();
663661
return {
@@ -850,7 +848,7 @@ const OnLoadMoreTableVirtualized = () => {
850848
}
851849

852850
// Slow down load so progress circle can appear
853-
await new Promise(resolve => setTimeout(resolve, 4000));
851+
await new Promise(resolve => setTimeout(resolve, 2000));
854852
let res = await fetch(cursor || 'https://swapi.py4e.com/api/people/?search=', {signal});
855853
let json = await res.json();
856854
return {
@@ -906,7 +904,7 @@ const OnLoadMoreTableVirtualizedResizeWrapper = () => {
906904
}
907905

908906
// Slow down load so progress circle can appear
909-
await new Promise(resolve => setTimeout(resolve, 4000));
907+
await new Promise(resolve => setTimeout(resolve, 2000));
910908
let res = await fetch(cursor || 'https://swapi.py4e.com/api/people/?search=', {signal});
911909
let json = await res.json();
912910
return {
@@ -953,7 +951,12 @@ const OnLoadMoreTableVirtualizedResizeWrapper = () => {
953951

954952
export const OnLoadMoreTableVirtualizedResizeWrapperStory = {
955953
render: OnLoadMoreTableVirtualizedResizeWrapper,
956-
name: 'Virtualized Table with async loading, resizable table container wrapper'
954+
name: 'Virtualized Table with async loading, with wrapper around Virtualizer',
955+
parameters: {
956+
description: {
957+
data: 'This table has a ResizableTableContainer wrapper around the Virtualizer. The table itself doesnt have any resizablity, this is simply to test that it still loads/scrolls in this configuration.'
958+
}
959+
}
957960
};
958961

959962
interface Launch {

0 commit comments

Comments
 (0)