Skip to content

Commit 5627d88

Browse files
authored
feat: Support async loading in S2 TreeView (#8528)
* add async loading support to S2 treeview * fix chevrons appearing on all tree items * fix treeview story docs * fix lint * readding isEmphasized and isDetached
1 parent b1e8e3e commit 5627d88

File tree

4 files changed

+440
-23
lines changed

4 files changed

+440
-23
lines changed

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

Lines changed: 47 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -23,28 +23,36 @@ import {
2323
TreeItem,
2424
TreeItemContent,
2525
TreeItemContentProps,
26+
TreeLoadMoreItem,
27+
TreeLoadMoreItemProps,
2628
useContextProps,
2729
Virtualizer
2830
} from 'react-aria-components';
2931
import {centerBaseline} from './CenterBaseline';
3032
import {Checkbox} from './Checkbox';
3133
import Chevron from '../ui-icons/Chevron';
3234
import {colorMix, focusRing, fontRelative, lightDark, style} from '../style' with {type: 'macro'};
33-
import {DOMRef, DOMRefValue, forwardRefType, GlobalDOMAttributes, Key} from '@react-types/shared';
35+
import {DOMRef, forwardRefType, GlobalDOMAttributes, Key, LoadingState} from '@react-types/shared';
3436
import {getAllowedOverrides, StylesPropWithHeight, UnsafeStyles} from './style-utils' with {type: 'macro'};
3537
import {IconContext} from './Icon';
38+
// @ts-ignore
39+
import intlMessages from '../intl/*.json';
40+
import {ProgressCircle} from './ProgressCircle';
3641
import {raw} from '../style/style-macro' with {type: 'macro'};
3742
import React, {createContext, forwardRef, JSXElementConstructor, ReactElement, ReactNode, useContext, useRef} from 'react';
3843
import {TextContext} from './Content';
3944
import {useDOMRef} from '@react-spectrum/utils';
40-
import {useLocale} from 'react-aria';
45+
import {useLocale, useLocalizedStringFormatter} from 'react-aria';
4146
import {useScale} from './utils';
4247

4348
interface S2TreeProps {
4449
// Only detatched is supported right now with the current styles from Spectrum
50+
// See https://github.com/adobe/react-spectrum/pull/7343 for what remaining combinations are left
51+
/** Whether the tree should be displayed with a [detached style](https://spectrum.adobe.com/page/tree-view/#Detached). */
4552
isDetached?: boolean,
53+
/** Handler that is called when a user performs an action on a row. */
4654
onAction?: (key: Key) => void,
47-
// not fully supported yet
55+
/** Whether the tree should be displayed with a [emphasized style](https://spectrum.adobe.com/page/tree-view/#Emphasis). */
4856
isEmphasized?: boolean
4957
}
5058

@@ -58,6 +66,11 @@ export interface TreeViewItemProps extends Omit<RACTreeItemProps, 'className' |
5866
hasChildItems?: boolean
5967
}
6068

69+
export interface TreeViewLoadMoreItemProps extends Pick<TreeLoadMoreItemProps, 'onLoadMore'> {
70+
/** The current loading state of the TreeView or TreeView row. */
71+
loadingState?: LoadingState
72+
}
73+
6174
interface TreeRendererContextValue {
6275
renderer?: (item) => ReactElement<any, string | JSXElementConstructor<any>>
6376
}
@@ -91,7 +104,10 @@ const tree = style({
91104
}
92105
}, getAllowedOverrides({height: true}));
93106

94-
const TreeView = /*#__PURE__*/ (forwardRef as forwardRefType)(function TreeView<T extends object>(props: TreeViewProps<T>, ref: DOMRef<HTMLDivElement>) {
107+
/**
108+
* A tree view provides users with a way to navigate nested hierarchical information.
109+
*/
110+
export const TreeView = /*#__PURE__*/ (forwardRef as forwardRefType)(function TreeView<T extends object>(props: TreeViewProps<T>, ref: DOMRef<HTMLDivElement>) {
95111
let {children, isDetached, isEmphasized, UNSAFE_className, UNSAFE_style} = props;
96112
let scale = useScale();
97113

@@ -180,7 +196,6 @@ const treeRow = style({
180196
}
181197
});
182198

183-
184199
const treeCellGrid = style({
185200
display: 'grid',
186201
width: 'full',
@@ -346,7 +361,6 @@ export const TreeViewItemContent = (props: TreeViewItemContentProps): ReactNode
346361
gridArea: 'level-padding',
347362
width: 'calc(calc(var(--tree-item-level, 0) - 1) * var(--indent))'
348363
})} />
349-
{/* TODO: revisit when we do async loading, at the moment hasChildItems will only cause the chevron to be rendered, no aria/data attributes indicating the row's expandability are added */}
350364
<ExpandableRowChevron isDisabled={isDisabled} isExpanded={isExpanded} scale={scale} isHidden={!(hasChildItems)} />
351365
<Provider
352366
values={[
@@ -368,6 +382,33 @@ export const TreeViewItemContent = (props: TreeViewItemContentProps): ReactNode
368382
);
369383
};
370384

385+
const centeredWrapper = style({
386+
display: 'flex',
387+
alignItems: 'center',
388+
justifyContent: 'center',
389+
width: 'full',
390+
height: 'full'
391+
});
392+
393+
export const TreeViewLoadMoreItem = (props: TreeViewLoadMoreItemProps): ReactNode => {
394+
let {loadingState, onLoadMore} = props;
395+
let stringFormatter = useLocalizedStringFormatter(intlMessages, '@react-spectrum/s2');
396+
let isLoading = loadingState === 'loading' || loadingState === 'loadingMore';
397+
return (
398+
<TreeLoadMoreItem isLoading={isLoading} onLoadMore={onLoadMore} className={style({width: 'full', marginY: 4})}>
399+
{() => {
400+
return (
401+
<div className={centeredWrapper}>
402+
<ProgressCircle
403+
isIndeterminate
404+
aria-label={stringFormatter.format('table.loadingMore')} />
405+
</div>
406+
);
407+
}}
408+
</TreeLoadMoreItem>
409+
);
410+
};
411+
371412
interface ExpandableRowChevronProps {
372413
isExpanded?: boolean,
373414
isDisabled?: boolean,
@@ -437,9 +478,3 @@ function ExpandableRowChevron(props: ExpandableRowChevronProps) {
437478
</Button>
438479
);
439480
}
440-
441-
/**
442-
* A tree view provides users with a way to navigate nested hierarchical information.
443-
*/
444-
const _TreeView: <T extends object>(props: TreeViewProps<T> & React.RefAttributes<DOMRefValue<HTMLDivElement>>) => ReactElement | null = TreeView;
445-
export {_TreeView as TreeView};

packages/@react-spectrum/s2/src/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ export {ToastContainer as UNSTABLE_ToastContainer, ToastQueue as UNSTABLE_ToastQ
8686
export {ToggleButton, ToggleButtonContext} from './ToggleButton';
8787
export {ToggleButtonGroup, ToggleButtonGroupContext} from './ToggleButtonGroup';
8888
export {Tooltip, TooltipTrigger} from './Tooltip';
89-
export {TreeView, TreeViewItem, TreeViewItemContent} from './TreeView';
89+
export {TreeView, TreeViewItem, TreeViewItemContent, TreeViewLoadMoreItem} from './TreeView';
9090

9191
export {pressScale} from './pressScale';
9292

@@ -164,5 +164,5 @@ export type {ToastOptions, ToastContainerProps} from './Toast';
164164
export type {ToggleButtonProps} from './ToggleButton';
165165
export type {ToggleButtonGroupProps} from './ToggleButtonGroup';
166166
export type {TooltipProps} from './Tooltip';
167-
export type {TreeViewProps, TreeViewItemProps, TreeViewItemContentProps} from './TreeView';
167+
export type {TreeViewProps, TreeViewItemProps, TreeViewItemContentProps, TreeViewLoadMoreItemProps} from './TreeView';
168168
export type {AutocompleteProps, FileTriggerProps, TooltipTriggerComponentProps as TooltipTriggerProps, SortDescriptor} from 'react-aria-components';

0 commit comments

Comments
 (0)