Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

docs: Add Virtualizer docs #7700

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
Open

docs: Add Virtualizer docs #7700

wants to merge 10 commits into from

Conversation

devongovett
Copy link
Member

@devongovett devongovett commented Feb 1, 2025

Closes #7361, closes #6751, closes #5707, closes #6981

Adds docs for Virtualizer with examples of list, grid, and table layouts, along with basic docs for custom collection renderers. Decided to only put these docs on a standalone Virtualizer page and not add examples to each individual component for now to keep the size of component pages down. We'll see how well people fined it.

Added padding and gap options to ListLayout while I was at it since I needed it for the examples.

Also removes UNSTABLE prefix from virtualizer exports. Did not yet remove it from collection renderer and collection builder stuff. Are we ready to do that? We will need to update the hook docs to use new collections at some point as well.

@rspbot
Copy link

rspbot commented Feb 1, 2025

@rspbot
Copy link

rspbot commented Feb 1, 2025

Copy link
Member

@reidbarber reidbarber left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These look great!

packages/react-aria-components/src/Virtualizer.tsx Outdated Show resolved Hide resolved
Co-authored-by: Reid Barber <reid@reidbarber.com>
@rspbot
Copy link

rspbot commented Feb 4, 2025

@rspbot
Copy link

rspbot commented Feb 4, 2025

## API Changes

react-aria-components

/react-aria-components:UNSTABLE_TableLayout

-UNSTABLE_TableLayout <T> {
-  constructor: (ListLayoutOptions) => void
-  getContentSize: () => void
-  getDropTargetFromPoint: (number, number, (DropTarget) => boolean) => DropTarget | null
-  getDropTargetLayoutInfo: (ItemDropTarget) => LayoutInfo
-  getItemRect: (Key) => Rect | null
-  getLayoutInfo: (Key) => void
-  getVisibleLayoutInfos: (Rect) => void
-  getVisibleRect: () => Rect
-  shouldInvalidate: (Rect, Rect) => boolean
-  update: (InvalidationContext<TableLayoutProps>) => void
-  updateItemSize: (Key, Size) => void
-  useLayoutOptions: () => void
-  virtualizer: Virtualizer<{}, any> | null
-}

/react-aria-components:UNSTABLE_Virtualizer

-UNSTABLE_Virtualizer <O> {
-  children: ReactNode
-  layout: ILayout<O>
-  layoutOptions?: O
-}

/react-aria-components:UNSTABLE_ListLayout

-UNSTABLE_ListLayout <O = any, T> {
-  constructor: (ListLayoutOptions) => void
-  getContentSize: () => void
-  getDropTargetFromPoint: (number, number, (DropTarget) => boolean) => DropTarget | null
-  getDropTargetLayoutInfo: (ItemDropTarget) => LayoutInfo
-  getItemRect: (Key) => Rect | null
-  getLayoutInfo: (Key) => void
-  getVisibleLayoutInfos: (Rect) => void
-  getVisibleRect: () => Rect
-  shouldInvalidate: (Rect, Rect) => boolean
-  update: (InvalidationContext<O>) => void
-  updateItemSize: (Key, Size) => void
-  virtualizer: Virtualizer<{}, any> | null
-}

/react-aria-components:UNSTABLE_GridLayout

-UNSTABLE_GridLayout <O = any, T> {
-  constructor: (GridLayoutOptions) => void
-  getContentSize: () => Size
-  getDropTargetFromPoint: (number, number, (DropTarget) => boolean) => DropTarget
-  getDropTargetLayoutInfo: (ItemDropTarget) => LayoutInfo
-  getItemRect: (Key) => Rect | null
-  getLayoutInfo: (Key) => LayoutInfo | null
-  getVisibleLayoutInfos: (Rect) => Array<LayoutInfo>
-  getVisibleRect: () => Rect
-  shouldInvalidate: (Rect, Rect) => boolean
-  update: () => void
-  updateItemSize: (Key, Size) => boolean
-  virtualizer: Virtualizer<{}, any> | null
-}

/react-aria-components:ListLayoutOptions

 ListLayoutOptions {
-  dropIndicatorThickness?: number
+  dropIndicatorThickness?: number = 2
   estimatedHeadingHeight?: number
   estimatedRowHeight?: number
-  headingHeight?: number
-  loaderHeight?: number
-  rowHeight?: number
+  gap?: number = 0
+  headingHeight?: number = 48
+  loaderHeight?: number = 48
+  padding?: number = 0
+  rowHeight?: number = 48
 }

/react-aria-components:TableLayout

+TableLayout <T> {
+  constructor: (ListLayoutOptions) => void
+  getContentSize: () => void
+  getDropTargetFromPoint: (number, number, (DropTarget) => boolean) => DropTarget | null
+  getDropTargetLayoutInfo: (ItemDropTarget) => LayoutInfo
+  getLayoutInfo: (Key) => void
+  getVisibleLayoutInfos: (Rect) => void
+  shouldInvalidate: (Rect, Rect) => boolean
+  update: (InvalidationContext<TableLayoutProps>) => void
+  updateItemSize: (Key, Size) => void
+  useLayoutOptions: () => void
+  virtualizer: Virtualizer<{}, any> | null
+}

/react-aria-components:Virtualizer

+Virtualizer <O> {
+  children: ReactNode
+  layout: ILayout<O>
+  layoutOptions?: O
+}

/react-aria-components:ListLayout

+ListLayout <O = any, T> {
+  constructor: (ListLayoutOptions) => void
+  getContentSize: () => void
+  getDropTargetFromPoint: (number, number, (DropTarget) => boolean) => DropTarget | null
+  getDropTargetLayoutInfo: (ItemDropTarget) => LayoutInfo
+  getLayoutInfo: (Key) => void
+  getVisibleLayoutInfos: (Rect) => void
+  shouldInvalidate: (Rect, Rect) => boolean
+  update: (InvalidationContext<O>) => void
+  updateItemSize: (Key, Size) => void
+  virtualizer: Virtualizer<{}, any> | null
+}

/react-aria-components:GridLayout

+GridLayout <O = any, T> {
+  constructor: (GridLayoutOptions) => void
+  getContentSize: () => Size
+  getDropTargetFromPoint: (number, number, (DropTarget) => boolean) => DropTarget
+  getDropTargetLayoutInfo: (ItemDropTarget) => LayoutInfo
+  getLayoutInfo: (Key) => LayoutInfo | null
+  getVisibleLayoutInfos: (Rect) => Array<LayoutInfo>
+  shouldInvalidate: (Rect, Rect) => boolean
+  update: () => void
+  updateItemSize: (Key, Size) => boolean
+  virtualizer: Virtualizer<{}, any> | null
+}

/react-aria-components:Layout

+Layout <O = any, T extends {} = Node<any>> {
+  getContentSize: () => Size
+  getDropTargetLayoutInfo: (ItemDropTarget) => LayoutInfo
+  getLayoutInfo: (Key) => LayoutInfo | null
+  getVisibleLayoutInfos: (Rect) => Array<LayoutInfo>
+  shouldInvalidate: (Rect, Rect) => boolean
+  update: (InvalidationContext<O>) => void
+  updateItemSize: (Key, Size) => boolean
+  virtualizer: Virtualizer<{}, any> | null
+}

/react-aria-components:LayoutInfo

+LayoutInfo {
+  allowOverflow: boolean = false
+  constructor: (string, Key, Rect) => void
+  content: any | null
+  copy: () => LayoutInfo
+  estimatedSize: boolean = false
+  isSticky: boolean = false
+  key: Key
+  opacity: number = 1
+  parentKey: Key | null
+  rect: Rect
+  transform: string | null
+  type: string
+  zIndex: number
+}

/react-aria-components:Size

+Size {
+  area: any
+  constructor: (any, any) => void
+  copy: () => Size
+  equals: (Size) => boolean
+  height: number
+  width: number
+}

/react-aria-components:Rect

+Rect {
+  area: number
+  bottomLeft: Point
+  bottomRight: Point
+  constructor: (any, any, any, any) => void
+  containsPoint: (Point) => boolean
+  containsRect: (Rect) => boolean
+  copy: () => Rect
+  equals: (Rect) => void
+  getCornerInRect: (Rect) => RectCorner | null
+  height: number
+  intersection: (Rect) => Rect
+  intersects: (Rect) => boolean
+  maxX: number
+  maxY: number
+  pointEquals: (Point | Rect) => void
+  sizeEquals: (Size | Rect) => void
+  topLeft: Point
+  topRight: Point
+  union: (Rect) => void
+  width: number
+  x: number
+  y: number
+}

/react-aria-components:Point

+Point {
+  constructor: (any, any) => void
+  copy: () => Point
+  equals: (Point) => boolean
+  isOrigin: () => boolean
+  x: number
+  y: number
+}

@react-spectrum/card

/@react-spectrum/card:GalleryLayout

 GalleryLayout <T> {
   _distributeWidths: (any) => void
   _findClosest: (Rect, Rect) => void
   _findClosestLayoutInfo: (Rect, Rect) => void
   buildCollection: () => void
   collection: GridCollection<T>
   constructor: (GalleryLayoutOptions) => void
   direction: Direction
   disabledKeys: Set<Key>
   getContentSize: () => void
   getDropTargetLayoutInfo: (ItemDropTarget) => LayoutInfo
   getFirstKey: () => void
-  getItemRect: (Key) => Rect | null
   getKeyAbove: (Key) => void
   getKeyBelow: (Key) => void
   getKeyForSearch: (string, Key) => void
   getKeyLeftOf: (Key) => void
   getKeyPageAbove: (Key) => void
   getKeyPageBelow: (Key) => void
   getKeyRightOf: (Key) => void
   getLastKey: () => void
   getLayoutInfo: (Key) => void
   getVisibleLayoutInfos: (Rect, any) => void
-  getVisibleRect: () => Rect
   isLoading: boolean
   isVisible: (LayoutInfo, Rect, boolean) => void
   itemPadding: number
   layoutType: any
   scale: Scale
   shouldInvalidate: (Rect, Rect) => boolean
   update: (InvalidationContext<CardViewLayoutOptions>) => void
   updateItemSize: (Key, Size) => boolean
   virtualizer: Virtualizer<{}, any> | null
 }

/@react-spectrum/card:GridLayout

 GridLayout <T> {
   _findClosest: (Rect, Rect) => void
   _findClosestLayoutInfo: (Rect, Rect) => void
   buildChild: (Node<T>, number, number) => LayoutInfo
   buildCollection: () => void
   cardOrientation: Orientation
   collection: GridCollection<T>
   constructor: (GridLayoutOptions) => void
   direction: Direction
   disabledKeys: Set<Key>
   getContentSize: () => void
   getDropTargetLayoutInfo: (ItemDropTarget) => LayoutInfo
   getFirstKey: () => void
   getIndexAtPoint: (any, any, any) => void
-  getItemRect: (Key) => Rect | null
   getKeyAbove: (Key) => void
   getKeyBelow: (Key) => void
   getKeyForSearch: (string, Key) => void
   getKeyLeftOf: (Key) => void
   getKeyPageAbove: (Key) => void
   getKeyPageBelow: (Key) => void
   getKeyRightOf: (Key) => void
   getLastKey: () => void
   getLayoutInfo: (Key) => void
   getVisibleLayoutInfos: (Rect, any) => void
-  getVisibleRect: () => Rect
   isLoading: boolean
   isVisible: (LayoutInfo, Rect, boolean) => void
   itemPadding: number
   layoutType: any
   scale: Scale
   shouldInvalidate: (Rect, Rect) => boolean
   update: (InvalidationContext<CardViewLayoutOptions>) => void
   updateItemSize: (Key, Size) => boolean
   virtualizer: Virtualizer<{}, any> | null
 }

/@react-spectrum/card:WaterfallLayout

 WaterfallLayout <T> {
   _findClosest: (Rect, Rect) => void
   _findClosestLayoutInfo: (Rect, Rect) => void
   buildCollection: (InvalidationContext) => void
   collection: GridCollection<T>
   constructor: (WaterfallLayoutOptions) => void
   direction: Direction
   disabledKeys: Set<Key>
   getClosestLeft: (Key) => void
   getClosestRight: (Key) => void
   getContentSize: () => void
   getDropTargetLayoutInfo: (ItemDropTarget) => LayoutInfo
   getFirstKey: () => void
-  getItemRect: (Key) => Rect | null
   getKeyAbove: (Key) => void
   getKeyBelow: (Key) => void
   getKeyForSearch: (string, Key) => void
   getKeyLeftOf: (Key) => void
   getKeyPageAbove: (Key) => void
   getKeyPageBelow: (Key) => void
   getKeyRightOf: (Key) => void
   getLastKey: () => void
   getLayoutInfo: (Key) => void
   getNextColumnIndex: (any) => void
   getVisibleLayoutInfos: (Rect, any) => void
-  getVisibleRect: () => Rect
   isLoading: boolean
   isVisible: (LayoutInfo, Rect, boolean) => void
   layoutType: any
   margin: number
   shouldInvalidate: (Rect, Rect) => boolean
   update: (InvalidationContext<CardViewLayoutOptions>) => void
   updateItemSize: (Key, Size) => void
   virtualizer: Virtualizer<{}, any> | null
 }

@react-spectrum/s2

/@react-spectrum/s2:ImageProps

 ImageProps {
   UNSAFE_className?: string
   UNSAFE_style?: CSSProperties
   alt?: string
   crossOrigin?: 'anonymous' | 'use-credentials'
   decoding?: 'async' | 'auto' | 'sync'
-  fetchPriority?: 'high' | 'low' | 'auto'
   group?: ImageGroup
   loading?: 'eager' | 'lazy'
   referrerPolicy?: HTMLAttributeReferrerPolicy
   renderError?: () => ReactNode
   src?: string
   styles?: StyleString
 }

@react-stately/layout

/@react-stately/layout:ListLayoutOptions

 ListLayoutOptions {
-  dropIndicatorThickness?: number
+  dropIndicatorThickness?: number = 2
   estimatedHeadingHeight?: number
   estimatedRowHeight?: number
-  headingHeight?: number
-  loaderHeight?: number
-  rowHeight?: number
+  gap?: number = 0
+  headingHeight?: number = 48
+  loaderHeight?: number = 48
+  padding?: number = 0
+  rowHeight?: number = 48
 }

/@react-stately/layout:GridLayout

 GridLayout <O = any, T> {
   constructor: (GridLayoutOptions) => void
   getContentSize: () => Size
   getDropTargetFromPoint: (number, number, (DropTarget) => boolean) => DropTarget
   getDropTargetLayoutInfo: (ItemDropTarget) => LayoutInfo
-  getItemRect: (Key) => Rect | null
   getLayoutInfo: (Key) => LayoutInfo | null
   getVisibleLayoutInfos: (Rect) => Array<LayoutInfo>
-  getVisibleRect: () => Rect
   shouldInvalidate: (Rect, Rect) => boolean
   update: () => void
   updateItemSize: (Key, Size) => boolean
   virtualizer: Virtualizer<{}, any> | null

/@react-stately/layout:ListLayout

 ListLayout <O = any, T> {
   constructor: (ListLayoutOptions) => void
   getContentSize: () => void
   getDropTargetFromPoint: (number, number, (DropTarget) => boolean) => DropTarget | null
   getDropTargetLayoutInfo: (ItemDropTarget) => LayoutInfo
-  getItemRect: (Key) => Rect | null
   getLayoutInfo: (Key) => void
   getVisibleLayoutInfos: (Rect) => void
-  getVisibleRect: () => Rect
   shouldInvalidate: (Rect, Rect) => boolean
   update: (InvalidationContext<O>) => void
   updateItemSize: (Key, Size) => void
   virtualizer: Virtualizer<{}, any> | null

/@react-stately/layout:TableLayout

 TableLayout <O extends TableLayoutProps = TableLayoutProps, T> {
   constructor: (ListLayoutOptions) => void
   getContentSize: () => void
   getDropTargetFromPoint: (number, number, (DropTarget) => boolean) => DropTarget | null
   getDropTargetLayoutInfo: (ItemDropTarget) => LayoutInfo
-  getItemRect: (Key) => Rect | null
   getLayoutInfo: (Key) => void
   getVisibleLayoutInfos: (Rect) => void
-  getVisibleRect: () => Rect
   shouldInvalidate: (Rect, Rect) => boolean
   update: (InvalidationContext<TableLayoutProps>) => void
   updateItemSize: (Key, Size) => void
   virtualizer: Virtualizer<{}, any> | null

@react-stately/virtualizer

/@react-stately/virtualizer:Layout

-Layout <O = any, T extends {}> {
+Layout <O = any, T extends {} = Node<any>> {
   getContentSize: () => Size
   getDropTargetLayoutInfo: (ItemDropTarget) => LayoutInfo
-  getItemRect: (Key) => Rect | null
   getLayoutInfo: (Key) => LayoutInfo | null
   getVisibleLayoutInfos: (Rect) => Array<LayoutInfo>
-  getVisibleRect: () => Rect
   shouldInvalidate: (Rect, Rect) => boolean
   update: (InvalidationContext<O>) => void
   updateItemSize: (Key, Size) => boolean
   virtualizer: Virtualizer<{}, any> | null

/@react-stately/virtualizer:LayoutInfo

 LayoutInfo {
   allowOverflow: boolean = false
   constructor: (string, Key, Rect) => void
   content: any | null
   copy: () => LayoutInfo
-  estimatedSize: boolean
-  isSticky: boolean
+  estimatedSize: boolean = false
+  isSticky: boolean = false
   key: Key
-  opacity: number
+  opacity: number = 1
   parentKey: Key | null
   rect: Rect
   transform: string | null
   type: string
 }

Comment on lines +111 to 112
return new ListLayout({
rowHeight: isDetached ? 42 : 40
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return new ListLayout({
rowHeight: isDetached ? 42 : 40
return new ListLayout({
rowHeight: 40,
gap: isDetached ? 2 : 0

now that there is a gap property

* The gap between items.
* @default 0
*/
gap?: number,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe an odd question, where does the drop indicator render in relation to the gap? top? bottom? middle?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

drop indicator should be centered between the items, regardless of the gap


```tsx
import type {CollectionRenderer} from 'react-aria-components';
import {UNSTABLE_CollectionRendererContext as CollectionRendererContext} from 'react-aria-components';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if we're keeping this as unstable, can we put a pre-release badge on the title of the section?


**Note**: ensure that the value passed to `CollectionRendererContext` is memoized so React does not unmount and remount `CollectionRoot` and `CollectionBranch` unnecessarily.

You also need to ensure that any additional DOM elements you add, such as wrappers around the items, are valid within the ARIA pattern that the collection component follows. Use `role="presentation"` for elements added only for styling purposes.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

put in an important requirement?


## Layouts

Virtualizer uses <TypeLink links={docs.links} type={docs.exports.Layout} /> objects to determine the position and size of each item, and provide the list of currently visible items. When using a Virtualizer, all items are positioned by the `Layout`, and CSS layout properties such as flexbox and grid do not apply.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Virtualizer uses <TypeLink links={docs.links} type={docs.exports.Layout} /> objects to determine the position and size of each item, and provide the list of currently visible items. When using a Virtualizer, all items are positioned by the `Layout`, and CSS layout properties such as flexbox and grid do not apply.
Virtualizer uses <TypeLink links={docs.links} type={docs.exports.Layout} /> objects to determine the position and size of each item, and provides the list of currently visible items. When using a Virtualizer, all items are positioned by the `Layout`, and CSS layout properties such as flexbox and grid do not apply.

`GridLayout` supports layout of items in an equal size grid. The items are sized between a minimum and maximum size depending on the width of the container. It supports the following options:

<TypeContext.Provider value={docs.links}>
<InterfaceType {...docs.exports.GridLayoutOptions} />
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for some reason, some sizes render as 200 x 200, but others are just Infinity, with no cross dimension

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

where?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

items.push({id: i, name: `Item ${i}`});
}

return (
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Screenshot 2025-02-05 at 8 09 52 am
Last example needs to have less height on the items so it doesn't vertically scroll

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lol, stupid scrollbars 😂

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
4 participants