Skip to content

Commit 0b06824

Browse files
committed
fix FF load more resizable wrapping container table, make s2 picker load more like v3
1 parent 3fe09f6 commit 0b06824

File tree

5 files changed

+87
-66
lines changed

5 files changed

+87
-66
lines changed

packages/@react-aria/utils/src/useLoadMore.ts

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -49,12 +49,15 @@ export function useLoadMore(props: LoadMoreProps, ref: RefObject<HTMLElement | n
4949
let sentinelObserver = useRef<IntersectionObserver>(null);
5050

5151
let triggerLoadMore = useEffectEvent((entries: IntersectionObserverEntry[]) => {
52-
// Only one entry should exist so this should be ok. Also use "isIntersecting" over an equality check of 0 since it seems like there is cases where
52+
// Use "isIntersecting" over an equality check of 0 since it seems like there is cases where
5353
// a intersection ratio of 0 can be reported when isIntersecting is actually true
54-
if (entries[0].isIntersecting && !isLoading && !(collection && collectionAwaitingUpdate.current) && onLoadMore) {
55-
onLoadMore();
56-
if (collection !== null && lastCollection.current !== null) {
57-
collectionAwaitingUpdate.current = true;
54+
// TODO: firefox seems to gather multiple entries, will need to reproduce in a base repro
55+
for (let entry of entries) {
56+
if (entry.isIntersecting && !isLoading && !(collection && collectionAwaitingUpdate.current) && onLoadMore) {
57+
onLoadMore();
58+
if (collection !== null && lastCollection.current !== null) {
59+
collectionAwaitingUpdate.current = true;
60+
}
5861
}
5962
}
6063
});
@@ -72,7 +75,6 @@ export function useLoadMore(props: LoadMoreProps, ref: RefObject<HTMLElement | n
7275
if (collection !== lastCollection.current && !isLoading) {
7376
collectionAwaitingUpdate.current = false;
7477
}
75-
7678
sentinelObserver.current = new IntersectionObserver(triggerLoadMore, {root: ref.current, rootMargin: `0px ${100 * scrollOffset}% ${100 * scrollOffset}% ${100 * scrollOffset}%`});
7779
if (sentinelRef?.current) {
7880
sentinelObserver.current.observe(sentinelRef.current);

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

Lines changed: 58 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import {
2626
ListBoxProps,
2727
Provider,
2828
SectionProps,
29+
SelectRenderProps,
2930
SelectStateContext,
3031
SelectValue,
3132
UNSTABLE_ListBoxLoadingIndicator
@@ -393,46 +394,15 @@ export const Picker = /*#__PURE__*/ (forwardRef as forwardRefType)(function Pick
393394
isQuiet
394395
})}>
395396
{(renderProps) => (
396-
<>
397-
<SelectValue className={valueStyles({isQuiet}) + ' ' + raw('&> * {display: none;}')}>
398-
{({defaultChildren}) => {
399-
return (
400-
<Provider
401-
values={[
402-
[IconContext, {
403-
slots: {
404-
icon: {
405-
render: centerBaseline({slot: 'icon', styles: iconCenterWrapper}),
406-
styles: icon
407-
}
408-
}
409-
}],
410-
[TextContext, {
411-
slots: {
412-
description: {},
413-
label: {styles: style({
414-
display: 'block',
415-
flexGrow: 1,
416-
truncate: true
417-
})}
418-
}
419-
}],
420-
[InsideSelectValueContext, true]
421-
]}>
422-
{defaultChildren}
423-
</Provider>
424-
);
425-
}}
426-
</SelectValue>
427-
{isInvalid && <FieldErrorIcon isDisabled={isDisabled} />}
428-
{isLoading && loadingCircle}
429-
<Chevron size={size} isLoading={isLoading} />
430-
{isFocusVisible && isQuiet && <span className={quietFocusLine} /> }
431-
{isInvalid && !isDisabled && !isQuiet &&
432-
// @ts-ignore known limitation detecting functions from the theme
433-
<div className={invalidBorder({...renderProps, size})} />
434-
}
435-
</>
397+
<PickerButtonInner
398+
{...renderProps}
399+
isFocusVisible={isFocusVisible}
400+
size={size}
401+
isLoading={isLoading}
402+
isQuiet={isQuiet}
403+
isInvalid={isInvalid}
404+
isOpen={isOpen}
405+
loadingCircle={loadingCircle} />
436406
)}
437407
</Button>
438408
</PressResponder>
@@ -562,16 +532,58 @@ export function PickerSection<T extends object>(props: PickerSectionProps<T>): R
562532
);
563533
}
564534

565-
interface ChevronProps<T extends object> extends Pick<PickerProps<T>, 'size' | 'isLoading'> {}
535+
interface PickerButtonInnerProps<T extends object> extends Pick<PickerProps<T>, 'size' | 'isLoading' | 'isQuiet'>, Pick<SelectRenderProps, 'isOpen' | 'isInvalid'>, ButtonRenderProps {
536+
loadingCircle: ReactNode
537+
}
566538

567-
function Chevron<T extends object>(props: ChevronProps<T>) {
568-
let {size, isLoading} = props;
539+
function PickerButtonInner<T extends object>(props: PickerButtonInnerProps<T>) {
540+
let {size, isLoading, isQuiet, isInvalid, isDisabled, isFocusVisible, isOpen, loadingCircle} = props;
569541
let state = useContext(SelectStateContext);
570542
// If it is the initial load, the collection either hasn't been formed or only has the loader so apply the disabled style
571543
let isInitialLoad = (state?.collection.size == null || state?.collection.size <= 1) && isLoading;
544+
572545
return (
573-
<ChevronIcon
574-
size={size}
575-
className={iconStyles({isInitialLoad})} />
546+
<>
547+
<SelectValue className={valueStyles({isQuiet}) + ' ' + raw('&> * {display: none;}')}>
548+
{({defaultChildren}) => {
549+
return (
550+
<Provider
551+
values={[
552+
[IconContext, {
553+
slots: {
554+
icon: {
555+
render: centerBaseline({slot: 'icon', styles: iconCenterWrapper}),
556+
styles: icon
557+
}
558+
}
559+
}],
560+
[TextContext, {
561+
slots: {
562+
description: {},
563+
label: {styles: style({
564+
display: 'block',
565+
flexGrow: 1,
566+
truncate: true
567+
})}
568+
}
569+
}],
570+
[InsideSelectValueContext, true]
571+
]}>
572+
{defaultChildren}
573+
</Provider>
574+
);
575+
}}
576+
</SelectValue>
577+
{isInvalid && <FieldErrorIcon isDisabled={isDisabled} />}
578+
{isInitialLoad && !isOpen && loadingCircle}
579+
<ChevronIcon
580+
size={size}
581+
className={iconStyles({isInitialLoad})} />
582+
{isFocusVisible && isQuiet && <span className={quietFocusLine} /> }
583+
{isInvalid && !isDisabled && !isQuiet &&
584+
// @ts-ignore known limitation detecting functions from the theme
585+
<div className={invalidBorder({...props, size})} />
586+
}
587+
</>
576588
);
577589
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,11 @@
1212

1313
import {Button, Checkbox, CheckboxProps, Collection, DropIndicator, GridLayout, GridList, GridListItem, GridListItemProps, ListLayout, Size, Tag, TagGroup, TagList, useDragAndDrop, Virtualizer} from 'react-aria-components';
1414
import {classNames} from '@react-spectrum/utils';
15+
import {LoadingSpinner} from './utils';
1516
import React from 'react';
1617
import styles from '../example/index.css';
1718
import {UNSTABLE_GridListLoadingIndicator} from '../src/GridList';
1819
import {useAsyncList, useListData} from 'react-stately';
19-
import { LoadingSpinner } from './utils';
2020

2121
export default {
2222
title: 'React Aria Components'

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

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -249,11 +249,10 @@ export function VirtualizedListBox({variableHeight}) {
249249

250250
return (
251251
<Virtualizer
252-
layout={ListLayout}
253-
layoutOptions={{
254-
[variableHeight ? 'estimatedRowHeight' : 'rowHeight']: 25,
252+
layout={new ListLayout({
253+
estimatedRowHeight: 25,
255254
estimatedHeadingHeight: 26
256-
}}>
255+
})}>
257256
<ListBox className={styles.menu} style={{height: 400}} aria-label="virtualized listbox" items={sections}>
258257
{section => (
259258
<ListBoxSection className={styles.group}>
@@ -478,7 +477,7 @@ export const AsyncListBox = (args) => {
478477
<ListBox
479478
{...args}
480479
style={{
481-
height: args.orientation === 'horizontal' ? 100 : 400,
480+
height: args.orientation === 'horizontal' ? 'fit-content' : 400,
482481
width: args.orientation === 'horizontal' ? 400 : 200,
483482
overflow: 'auto'
484483
}}
@@ -599,7 +598,7 @@ export const AsyncListBoxVirtualized = (args) => {
599598
<ListBox
600599
{...args}
601600
style={{
602-
height: args.orientation === 'horizontal' ? 100 : 400,
601+
height: args.orientation === 'horizontal' ? 'fit-content' : 400,
603602
width: args.orientation === 'horizontal' ? 400 : 100,
604603
border: '1px solid gray',
605604
background: 'lightgray',

packages/react-aria-components/test/Table.test.js

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1621,18 +1621,21 @@ describe('Table', () => {
16211621
let rows = getAllByRole('row');
16221622
expect(rows).toHaveLength(6);
16231623
let loader = rows[5];
1624-
expect(loader).toHaveTextContent('Load more spinner');
16251624

16261625
let cell = within(loader).getByRole('rowheader');
16271626
expect(cell).toHaveAttribute('colspan', '3');
1627+
1628+
let spinner = within(cell).getByRole('progressbar');
1629+
expect(spinner).toHaveAttribute('aria-label', 'loading');
16281630
});
16291631

16301632
it('should not focus the load more row when using ArrowDown', async () => {
16311633
let {getAllByRole} = render(<LoadingMoreTable isLoadingMore />);
16321634

16331635
let rows = getAllByRole('row');
16341636
let loader = rows[5];
1635-
expect(loader).toHaveTextContent('Load more spinner');
1637+
let spinner = within(loader).getByRole('progressbar');
1638+
expect(spinner).toHaveAttribute('aria-label', 'loading');
16361639

16371640
await user.tab();
16381641
expect(document.activeElement).toBe(rows[1]);
@@ -1654,7 +1657,8 @@ describe('Table', () => {
16541657

16551658
let rows = getAllByRole('row');
16561659
let loader = rows[5];
1657-
expect(loader).toHaveTextContent('Load more spinner');
1660+
let spinner = within(loader).getByRole('progressbar');
1661+
expect(spinner).toHaveAttribute('aria-label', 'loading');
16581662

16591663
await user.tab();
16601664
expect(document.activeElement).toBe(rows[1]);
@@ -1671,7 +1675,8 @@ describe('Table', () => {
16711675

16721676
let rows = getAllByRole('row');
16731677
let loader = rows[5];
1674-
expect(loader).toHaveTextContent('Load more spinner');
1678+
let spinner = within(loader).getByRole('progressbar');
1679+
expect(spinner).toHaveAttribute('aria-label', 'loading');
16751680

16761681
await user.tab();
16771682
expect(document.activeElement).toBe(rows[1]);
@@ -1710,7 +1715,8 @@ describe('Table', () => {
17101715

17111716
expect(rows).toHaveLength(2);
17121717
expect(body).toHaveAttribute('data-empty', 'true');
1713-
expect(loader).toHaveTextContent('Loading spinner');
1718+
let spinner = within(loader).getByRole('progressbar');
1719+
expect(spinner).toHaveAttribute('aria-label', 'loading');
17141720

17151721
rerender(<EmptyLoadingTable />);
17161722

@@ -1727,7 +1733,8 @@ describe('Table', () => {
17271733
let rows = getAllByRole('row');
17281734
expect(rows).toHaveLength(4);
17291735
let loader = rows[3];
1730-
expect(loader).toHaveTextContent('Load more spinner');
1736+
let spinner = within(loader).getByRole('progressbar');
1737+
expect(spinner).toHaveAttribute('aria-label', 'loading');
17311738

17321739
let selectAll = getAllByRole('checkbox')[0];
17331740
expect(selectAll).toHaveAttribute('aria-label', 'Select All');
@@ -1747,7 +1754,8 @@ describe('Table', () => {
17471754
expect(rows).toHaveLength(4);
17481755
expect(rows[1]).toHaveTextContent('Adobe Photoshop');
17491756
let loader = rows[3];
1750-
expect(loader).toHaveTextContent('Load more spinner');
1757+
let spinner = within(loader).getByRole('progressbar');
1758+
expect(spinner).toHaveAttribute('aria-label', 'loading');
17511759

17521760
let dragButton = getAllByRole('button')[0];
17531761
expect(dragButton).toHaveAttribute('aria-label', 'Drag Adobe Photoshop');

0 commit comments

Comments
 (0)