Skip to content

Commit 2fd3c81

Browse files
LFDanLuyihuiliao
andauthored
docs: Add docs for horizontal orientation (adobe#9843)
* prevent orientation from being exposed in S2 for gridlist * add docs for gridlist orientation * update ListLayoutOptions to clearer names and document horizontal orientation * make it a bit smaller * update virtualizer list example per review * update gridlist for horizontal * forgot to remove * rename rowHeight and other height related properties * simplify and reduce image size * Apply suggestion from @yihuiliao Co-authored-by: Yihui Liao <44729383+yihuiliao@users.noreply.github.com> --------- Co-authored-by: Yihui Liao <44729383+yihuiliao@users.noreply.github.com>
1 parent 9fe4ec1 commit 2fd3c81

File tree

9 files changed

+493
-59
lines changed

9 files changed

+493
-59
lines changed

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

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ import {useScale} from './utils';
5151
import {useSpectrumContextProps} from './useSpectrumContextProps';
5252
import {Virtualizer} from 'react-aria-components/Virtualizer';
5353

54-
export interface ListViewProps<T> extends Omit<GridListProps<T>, 'className' | 'style' | 'children' | 'selectionBehavior' | 'dragAndDropHooks' | 'layout' | 'render' | 'keyboardNavigationBehavior' | keyof GlobalDOMAttributes>, DOMProps, UnsafeStyles, ListViewStylesProps, SlotProps {
54+
export interface ListViewProps<T> extends Omit<GridListProps<T>, 'className' | 'style' | 'children' | 'selectionBehavior' | 'dragAndDropHooks' | 'layout' | 'render' | 'keyboardNavigationBehavior' | 'orientation' | keyof GlobalDOMAttributes>, DOMProps, UnsafeStyles, ListViewStylesProps, SlotProps {
5555
/** Spectrum-defined styles, returned by the `style()` macro. */
5656
styles?: StylesPropWithHeight,
5757
/** The current loading state of the ListView. */
@@ -864,4 +864,3 @@ export function ListViewItem(props: ListViewItemProps): ReactNode {
864864
</GridListItem>
865865
);
866866
}
867-

packages/dev/s2-docs/pages/react-aria/GridList.mdx

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -672,6 +672,43 @@ function Example(props) {
672672
}
673673
```
674674

675+
## Layouts
676+
677+
Use the `layout` and `orientation` props to arrange items in horizontal and vertical stacks and grids. This affects keyboard navigation and drag and drop behavior.
678+
679+
```tsx render docs={docs.exports.GridList} links={docs.links} props={['layout', 'orientation', 'keyboardNavigationBehavior']} initialProps={{layout: 'grid', orientation: 'horizontal', keyboardNavigationBehavior: 'tab'}} wide
680+
"use client";
681+
import {Text} from 'react-aria-components';
682+
import {GridList, GridListItem} from 'vanilla-starter/GridList';
683+
684+
///- begin collapse -///
685+
let photos = [
686+
{id: 1, title: 'Desert Sunset', description: 'PNG • 2/3/2024', src: 'https://images.unsplash.com/photo-1705034598432-1694e203cdf3?q=80&w=600&auto=format&fit=crop'},
687+
{id: 2, title: 'Hiking Trail', description: 'JPEG • 1/10/2022', src: 'https://images.unsplash.com/photo-1722233987129-61dc344db8b6?q=80&w=600&auto=format&fit=crop'},
688+
{id: 3, title: 'Lion', description: 'JPEG • 8/28/2021', src: 'https://images.unsplash.com/photo-1629812456605-4a044aa38fbc?q=80&w=600&auto=format&fit=crop'},
689+
{id: 4, title: 'Mountain Sunrise', description: 'PNG • 3/15/2015', src: 'https://images.unsplash.com/photo-1722172118908-1a97c312ce8c?q=80&w=600&auto=format&fit=crop'},
690+
{id: 5, title: 'Giraffe tongue', description: 'PNG • 11/27/2019', src: 'https://images.unsplash.com/photo-1574870111867-089730e5a72b?q=80&w=600&auto=format&fit=crop'},
691+
{id: 6, title: 'Golden Hour', description: 'WEBP • 7/24/2024', src: 'https://images.unsplash.com/photo-1718378037953-ab21bf2cf771?q=80&w=600&auto=format&fit=crop'},
692+
];
693+
///- end collapse -///
694+
695+
<GridList
696+
/*- begin highlight -*/
697+
/* PROPS */
698+
/*- end highlight -*/
699+
aria-label="Photos"
700+
items={photos}
701+
selectionMode="multiple">
702+
{item => (
703+
<GridListItem textValue={item.title}>
704+
<img src={item.src} alt="" />
705+
<Text>{item.title}</Text>
706+
<Text slot="description">{item.description}</Text>
707+
</GridListItem>
708+
)}
709+
</GridList>
710+
```
711+
675712
## Drag and drop
676713

677714
GridList supports drag and drop interactions when the `dragAndDropHooks` prop is provided using the <TypeLink links={docs.links} type={docs.exports.useDragAndDrop} /> hook. Users can drop data on the list as a whole, on individual items, insert new items between existing ones, or reorder items. React Aria supports drag and drop via mouse, touch, keyboard, and screen reader interactions. See the [drag and drop guide](dnd?component=GridList) to learn more.

packages/dev/s2-docs/pages/react-aria/Virtualizer.mdx

Lines changed: 255 additions & 5 deletions
Large diffs are not rendered by default.

packages/react-aria-components/exports/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -203,5 +203,5 @@ export type {ListOptions as ListDataOptions, ListData} from 'react-stately/useLi
203203
export type {TreeOptions as TreeDataOptions, TreeData} from 'react-stately/useTreeData';
204204
export type {AsyncListOptions, AsyncListData, AsyncListLoadFunction, AsyncListLoadOptions, AsyncListStateUpdate} from 'react-stately/useAsyncList';
205205
export type {AutocompleteState} from 'react-stately/private/autocomplete/useAutocompleteState';
206-
export type {ListLayoutOptions, GridLayoutOptions, WaterfallLayoutOptions} from 'react-stately/useVirtualizerState';
206+
export type {ListLayoutOptions, GridLayoutOptions, TableLayoutProps, WaterfallLayoutOptions} from 'react-stately/useVirtualizerState';
207207
export type {RangeValue, ValidationResult, RouterConfig} from '@react-types/shared';

packages/react-stately/src/layout/ListLayout.ts

Lines changed: 97 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -25,25 +25,25 @@ export interface ListLayoutOptions {
2525
*/
2626
orientation?: Orientation,
2727
/**
28-
* The fixed height of a row in px.
28+
* The fixed size of a row in px with respect to the applied orientation.
2929
* @default 48
3030
*/
31-
rowHeight?: number,
32-
/** The estimated height of a row, when row heights are variable. */
33-
estimatedRowHeight?: number,
31+
rowSize?: number,
32+
/** The estimated size of a row in px with respect to the applied orientation, when row sizes are variable. */
33+
estimatedRowSize?: number,
3434
/**
35-
* The fixed height of a section header in px.
35+
* The fixed size of a section header in px with respect to the applied orientation.
3636
* @default 48
3737
*/
38-
headingHeight?: number,
39-
/** The estimated height of a section header, when the height is variable. */
40-
estimatedHeadingHeight?: number,
38+
headingSize?: number,
39+
/** The estimated size of a section header in px with respect to the applied orientation, when heading sizes are variable. */
40+
estimatedHeadingSize?: number,
4141
/**
42-
* The fixed height of a loader element in px. This loader is specifically for
42+
* The fixed size of a loader element in px with respect to the applied orientation. This loader is specifically for
4343
* "load more" elements rendered when loading more rows at the root level or inside nested row/sections.
4444
* @default 48
4545
*/
46-
loaderHeight?: number,
46+
loaderSize?: number,
4747
/**
4848
* The thickness of the drop indicator.
4949
* @default 2
@@ -58,7 +58,34 @@ export interface ListLayoutOptions {
5858
* The padding around the list.
5959
* @default 0
6060
*/
61-
padding?: number
61+
padding?: number,
62+
/**
63+
* The fixed height of a row in px.
64+
* @default 48
65+
* @deprecated Use `rowSize` instead.
66+
*/
67+
rowHeight?: number,
68+
/** The estimated height of a row, when row heights are variable.
69+
* @deprecated Use `estimatedRowSize` instead.
70+
*/
71+
estimatedRowHeight?: number,
72+
/**
73+
* The fixed height of a section header in px.
74+
* @default 48
75+
* @deprecated Use `headingSize` instead.
76+
*/
77+
headingHeight?: number,
78+
/** The estimated height of a section header, when the height is variable.
79+
* @deprecated Use `estimatedHeadingSize` instead.
80+
*/
81+
estimatedHeadingHeight?: number,
82+
/**
83+
* The fixed height of a loader element in px. This loader is specifically for
84+
* "load more" elements rendered when loading more rows at the root level or inside nested row/sections.
85+
* @default 48
86+
* @deprecated Use `loaderSize` instead.
87+
*/
88+
loaderHeight?: number
6289
}
6390

6491
// A wrapper around LayoutInfo that supports hierarchy
@@ -74,16 +101,16 @@ const DEFAULT_HEIGHT = 48;
74101

75102
/**
76103
* ListLayout is a virtualizer Layout implementation
77-
* that arranges its items in a vertical stack. It supports both fixed
78-
* and variable height items.
104+
* that arranges its items in a stack along its applied orientation.
105+
* It supports both fixed and variable size items.
79106
*/
80107
export class ListLayout<T, O extends ListLayoutOptions = ListLayoutOptions> extends Layout<Node<T>, O> implements DropTargetDelegate {
81-
protected rowHeight: number | null;
108+
protected rowSize: number | null;
82109
protected orientation: Orientation;
83-
protected estimatedRowHeight: number | null;
84-
protected headingHeight: number | null;
85-
protected estimatedHeadingHeight: number | null;
86-
protected loaderHeight: number | null;
110+
protected estimatedRowSize: number | null;
111+
protected headingSize: number | null;
112+
protected estimatedHeadingSize: number | null;
113+
protected loaderSize: number | null;
87114
protected dropIndicatorThickness: number;
88115
protected gap: number;
89116
protected padding: number;
@@ -103,12 +130,12 @@ export class ListLayout<T, O extends ListLayoutOptions = ListLayoutOptions> exte
103130
*/
104131
constructor(options: ListLayoutOptions = {}) {
105132
super();
106-
this.rowHeight = options.rowHeight ?? null;
133+
this.rowSize = options?.rowSize ?? options?.rowHeight ?? null;
107134
this.orientation = options.orientation ?? 'vertical';
108-
this.estimatedRowHeight = options.estimatedRowHeight ?? null;
109-
this.headingHeight = options.headingHeight ?? null;
110-
this.estimatedHeadingHeight = options.estimatedHeadingHeight ?? null;
111-
this.loaderHeight = options.loaderHeight ?? null;
135+
this.estimatedRowSize = options?.estimatedRowSize ?? options?.estimatedRowHeight ?? null;
136+
this.headingSize = options?.headingSize ?? options?.headingHeight ?? null;
137+
this.estimatedHeadingSize = options?.estimatedHeadingSize ?? options?.estimatedHeadingHeight ?? null;
138+
this.loaderSize = options?.loaderSize ?? options?.loaderHeight ?? null;
112139
this.dropIndicatorThickness = options.dropIndicatorThickness || 2;
113140
this.gap = options.gap || 0;
114141
this.padding = options.padding || 0;
@@ -126,6 +153,31 @@ export class ListLayout<T, O extends ListLayoutOptions = ListLayoutOptions> exte
126153
return this.virtualizer!.collection;
127154
}
128155

156+
/** @deprecated Use `rowSize` instead. */
157+
protected get rowHeight(): number | null {
158+
return this.rowSize;
159+
}
160+
161+
/** @deprecated Use `estimatedRowSize` instead. */
162+
protected get estimatedRowHeight(): number | null {
163+
return this.estimatedRowSize;
164+
}
165+
166+
/** @deprecated Use `headingSize` instead. */
167+
protected get headingHeight(): number | null {
168+
return this.headingSize;
169+
170+
}
171+
/** @deprecated Use `estimatedHeadingSize` instead. */
172+
protected get estimatedHeadingHeight(): number | null {
173+
return this.estimatedHeadingSize;
174+
}
175+
176+
/** @deprecated Use `loaderSize` instead. */
177+
protected get loaderHeight(): number | null {
178+
return this.loaderSize;
179+
}
180+
129181
getLayoutInfo(key: Key): LayoutInfo | null {
130182
this.ensureLayoutInfo(key);
131183
return this.layoutNodes.get(key)?.layoutInfo || null;
@@ -139,7 +191,7 @@ export class ListLayout<T, O extends ListLayoutOptions = ListLayoutOptions> exte
139191
// Adjust rect to keep number of visible rows consistent.
140192
// (only if height > 1 or width > 1 for getDropTargetFromPoint)
141193
if (visibleRect[heightProperty] > 1) {
142-
let rowHeight = (this.rowHeight ?? this.estimatedRowHeight ?? DEFAULT_HEIGHT) + this.gap;
194+
let rowHeight = (this.rowSize ?? this.estimatedRowSize ?? DEFAULT_HEIGHT) + this.gap;
143195
visibleRect[offsetProperty] = Math.floor(visibleRect[offsetProperty] / rowHeight) * rowHeight;
144196
visibleRect[heightProperty] = Math.ceil(visibleRect[heightProperty] / rowHeight) * rowHeight;
145197
}
@@ -208,21 +260,21 @@ export class ListLayout<T, O extends ListLayoutOptions = ListLayoutOptions> exte
208260
// Also invalidate if fixed sizes/gaps change.
209261
let options = invalidationContext.layoutOptions;
210262
return invalidationContext.sizeChanged
211-
|| this.rowHeight !== (options?.rowHeight ?? this.rowHeight)
263+
|| this.rowSize !== (options?.rowSize ?? options?.rowHeight ?? this.rowSize)
212264
|| this.orientation !== (options?.orientation ?? this.orientation)
213-
|| this.headingHeight !== (options?.headingHeight ?? this.headingHeight)
214-
|| this.loaderHeight !== (options?.loaderHeight ?? this.loaderHeight)
265+
|| this.headingSize !== (options?.headingSize ?? options?.headingHeight ?? this.headingSize)
266+
|| this.loaderSize !== (options?.loaderSize ?? options?.loaderHeight ?? this.loaderSize)
215267
|| this.gap !== (options?.gap ?? this.gap)
216268
|| this.padding !== (options?.padding ?? this.padding);
217269
}
218270

219271
shouldInvalidateLayoutOptions(newOptions: O, oldOptions: O): boolean {
220-
return newOptions.rowHeight !== oldOptions.rowHeight
272+
return (newOptions?.rowSize ?? newOptions?.rowHeight) !== (oldOptions?.rowSize ?? oldOptions?.rowHeight)
221273
|| newOptions.orientation !== oldOptions.orientation
222-
|| newOptions.estimatedRowHeight !== oldOptions.estimatedRowHeight
223-
|| newOptions.headingHeight !== oldOptions.headingHeight
224-
|| newOptions.estimatedHeadingHeight !== oldOptions.estimatedHeadingHeight
225-
|| newOptions.loaderHeight !== oldOptions.loaderHeight
274+
|| (newOptions?.estimatedRowSize ?? newOptions?.estimatedRowHeight) !== (oldOptions?.estimatedRowSize ?? oldOptions?.estimatedRowHeight)
275+
|| (newOptions?.headingSize ?? newOptions?.headingHeight) !== (oldOptions?.headingSize ?? oldOptions?.headingHeight)
276+
|| (newOptions?.estimatedHeadingSize ?? newOptions?.estimatedHeadingHeight) !== (oldOptions?.estimatedHeadingSize ?? oldOptions?.estimatedHeadingHeight)
277+
|| (newOptions?.loaderSize ?? newOptions?.loaderHeight) !== (oldOptions?.loaderSize ?? oldOptions?.loaderHeight)
226278
|| newOptions.dropIndicatorThickness !== oldOptions.dropIndicatorThickness
227279
|| newOptions.gap !== oldOptions.gap
228280
|| newOptions.padding !== oldOptions.padding;
@@ -240,12 +292,12 @@ export class ListLayout<T, O extends ListLayoutOptions = ListLayoutOptions> exte
240292
}
241293

242294
let options = invalidationContext.layoutOptions;
243-
this.rowHeight = options?.rowHeight ?? this.rowHeight;
295+
this.rowSize = options?.rowSize ?? options?.rowHeight ?? this.rowSize;
244296
this.orientation = options?.orientation ?? this.orientation;
245-
this.estimatedRowHeight = options?.estimatedRowHeight ?? this.estimatedRowHeight;
246-
this.headingHeight = options?.headingHeight ?? this.headingHeight;
247-
this.estimatedHeadingHeight = options?.estimatedHeadingHeight ?? this.estimatedHeadingHeight;
248-
this.loaderHeight = options?.loaderHeight ?? this.loaderHeight;
297+
this.estimatedRowSize = options?.estimatedRowSize ?? options?.estimatedRowHeight ?? this.estimatedRowSize;
298+
this.headingSize = options?.headingSize ?? options?.headingHeight ?? this.headingSize;
299+
this.estimatedHeadingSize = options?.estimatedHeadingSize ?? options?.estimatedHeadingHeight ?? this.estimatedHeadingSize;
300+
this.loaderSize = options?.loaderSize ?? options?.loaderHeight ?? this.loaderSize;
249301
this.dropIndicatorThickness = options?.dropIndicatorThickness ?? this.dropIndicatorThickness;
250302
this.gap = options?.gap ?? this.gap;
251303
this.padding = options?.padding ?? this.padding;
@@ -284,7 +336,7 @@ export class ListLayout<T, O extends ListLayoutOptions = ListLayoutOptions> exte
284336
for (let node of collectionNodes) {
285337
let offsetProperty = this.orientation === 'horizontal' ? 'x' : 'y';
286338
let maxOffsetProperty = this.orientation === 'horizontal' ? 'maxX' : 'maxY';
287-
let rowHeight = (this.rowHeight ?? this.estimatedRowHeight ?? DEFAULT_HEIGHT) + this.gap;
339+
let rowHeight = (this.rowSize ?? this.estimatedRowSize ?? DEFAULT_HEIGHT) + this.gap;
288340
// Skip rows before the valid rectangle unless they are already cached.
289341
if (node.type === 'item' && offset + rowHeight < this.requestedRect[offsetProperty] && !this.isValid(node, offset)) {
290342
offset += rowHeight;
@@ -377,10 +429,10 @@ export class ListLayout<T, O extends ListLayoutOptions = ListLayoutOptions> exte
377429
// room for the loader alongside rendering the emptyState
378430
if (this.orientation === 'horizontal') {
379431
rect.height = this.virtualizer!.contentSize.height - this.padding - y;
380-
rect.width = node.props.isLoading ? this.loaderHeight ?? this.rowHeight ?? this.estimatedRowHeight ?? DEFAULT_HEIGHT : 0;
432+
rect.width = node.props.isLoading ? this.loaderSize ?? this.rowSize ?? this.estimatedRowSize ?? DEFAULT_HEIGHT : 0;
381433
} else {
382434
rect.width = this.virtualizer!.contentSize.width - this.padding - x;
383-
rect.height = node.props.isLoading ? this.loaderHeight ?? this.rowHeight ?? this.estimatedRowHeight ?? DEFAULT_HEIGHT : 0;
435+
rect.height = node.props.isLoading ? this.loaderSize ?? this.rowSize ?? this.estimatedRowSize ?? DEFAULT_HEIGHT : 0;
384436
}
385437

386438
return {
@@ -409,7 +461,7 @@ export class ListLayout<T, O extends ListLayoutOptions = ListLayoutOptions> exte
409461
continue;
410462
}
411463

412-
let rowHeight = (this.rowHeight ?? this.estimatedRowHeight ?? DEFAULT_HEIGHT) + this.gap;
464+
let rowHeight = (this.rowSize ?? this.estimatedRowSize ?? DEFAULT_HEIGHT) + this.gap;
413465

414466
// Skip rows before the valid rectangle unless they are already cached.
415467
if (offset + rowHeight < this.requestedRect[offsetProperty] && !this.isValid(node, offset)) {
@@ -444,7 +496,7 @@ export class ListLayout<T, O extends ListLayoutOptions = ListLayoutOptions> exte
444496
let widthProperty = this.orientation === 'horizontal' ? 'height' : 'width';
445497
let heightProperty = this.orientation === 'horizontal' ? 'width' : 'height';
446498
let width = this.virtualizer!.visibleRect[widthProperty] - this.padding - (this.orientation === 'horizontal' ? y : x);
447-
let rectHeight = this.headingHeight;
499+
let rectHeight = this.headingSize;
448500
let isEstimated = false;
449501

450502
// If no explicit height is available, use an estimated height.
@@ -460,7 +512,7 @@ export class ListLayout<T, O extends ListLayoutOptions = ListLayoutOptions> exte
460512
rectHeight = previousLayoutNode!.layoutInfo.rect[heightProperty];
461513
isEstimated = width !== previousLayoutInfo.rect[widthProperty] || curNode !== lastNode || previousLayoutInfo.estimatedSize;
462514
} else {
463-
rectHeight = (node.rendered ? this.estimatedHeadingHeight : 0);
515+
rectHeight = (node.rendered ? this.estimatedHeadingSize : 0);
464516
isEstimated = true;
465517
}
466518
}
@@ -485,7 +537,7 @@ export class ListLayout<T, O extends ListLayoutOptions = ListLayoutOptions> exte
485537
let heightProperty = this.orientation === 'horizontal' ? 'width' : 'height';
486538

487539
let width = this.virtualizer!.visibleRect[widthProperty] - this.padding - (this.orientation === 'horizontal' ? y : x);
488-
let rectHeight = this.rowHeight;
540+
let rectHeight = this.rowSize;
489541
let isEstimated = false;
490542

491543
// If no explicit height is available, use an estimated height.
@@ -498,7 +550,7 @@ export class ListLayout<T, O extends ListLayoutOptions = ListLayoutOptions> exte
498550
rectHeight = previousLayoutNode.layoutInfo.rect[heightProperty];
499551
isEstimated = width !== previousLayoutNode.layoutInfo.rect[widthProperty] || node !== previousLayoutNode.node || previousLayoutNode.layoutInfo.estimatedSize;
500552
} else {
501-
rectHeight = this.estimatedRowHeight;
553+
rectHeight = this.estimatedRowSize;
502554
isEstimated = true;
503555
}
504556
}

0 commit comments

Comments
 (0)