-
-
Notifications
You must be signed in to change notification settings - Fork 2.6k
Room list: add a grouped virtualized list to shared components #32566
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
Merged
Merged
Changes from 10 commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
e3aa500
refactor: extract most of the logic from the virtualized list
florianduros 5c692b1
refactor: use `FlatVirtualizedList` instead of `VirtualizedList`
florianduros 892ae33
feat: add grouped virtualized list to shared components
florianduros db77594
feat: add accessiblity helps for virtualized list
florianduros a1a682c
test: use one test suite for the two virtualized lists
florianduros a4046a6
test: update storybook screenshots
florianduros 4c2d4ee
feat: add keyboard navigation on header
florianduros 64a2aad
test: make a11y test pass
florianduros b73cd33
chore: delete old screenshot
florianduros a18553a
doc: a11y docs to list stories
florianduros 640fdf2
chore: fix copyright
florianduros fd68dc9
Merge branch 'develop' into florianduros/grouped-virtuallist
florianduros File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
File renamed without changes
Binary file added
BIN
+22.9 KB
...List/GroupedVirtualizedList/GroupedVirtualizedList.stories.tsx/default-auto.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
113 changes: 113 additions & 0 deletions
113
...-components/src/utils/VirtualizedList/FlatVirtualizedList/FlatVirtualizedList.stories.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,113 @@ | ||
| /* | ||
| Copyright 2026 New Vector Ltd. | ||
|
|
||
| SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial | ||
| Please see LICENSE files in the repository root for full details. | ||
| */ | ||
|
|
||
| import React from "react"; | ||
|
|
||
| import type { Meta, StoryObj } from "@storybook/react-vite"; | ||
| import { FlatVirtualizedList, type FlatVirtualizedListProps } from "./FlatVirtualizedList"; | ||
| import { type VirtualizedListContext } from "../virtualized-list"; | ||
| import { items, SimpleItemComponent } from "../story-mock"; | ||
| import { getContainerAccessibleProps, getItemAccessibleProps } from "../accessbility"; | ||
|
|
||
| const meta = { | ||
| title: "Utils/VirtualizedList/FlatVirtualizedList", | ||
| component: FlatVirtualizedList<SimpleItemComponent, undefined>, | ||
| parameters: { | ||
| docs: { | ||
| description: { | ||
| component: ` | ||
| A flat virtualized list that renders large datasets efficiently using | ||
| [react-virtuoso](https://virtuoso.dev/), while exposing full keyboard navigation. | ||
|
|
||
| ## Accessibility with **\`listbox\`** ARIA pattern | ||
|
|
||
| This example uses the **\`listbox\`** ARIA pattern, which maps naturally to a | ||
| flat list of selectable options. | ||
|
|
||
| ### Container props — \`getContainerAccessibleProps("listbox")\` | ||
|
|
||
| Spread the result of \`getContainerAccessibleProps("listbox")\` directly onto the | ||
| \`FlatVirtualizedList\` component to mark the scrollable container as a \`listbox\`: | ||
|
|
||
| | Prop | Value | Purpose | | ||
| |------|-------|---------| | ||
| | \`role\` | \`"listbox"\` | Identifies the container as a listbox widget to assistive technologies. | | ||
|
|
||
| \`\`\`tsx | ||
| <FlatVirtualizedList | ||
| {...getContainerAccessibleProps("listbox")} | ||
| aria-label="My list" | ||
| {/* other props */} | ||
| /> | ||
| \`\`\` | ||
|
|
||
| ### Item props — \`getItemAccessibleProps("listbox", index, listSize)\` | ||
|
|
||
| Spread the result of \`getItemAccessibleProps("listbox", index, listSize)\` onto each rendered | ||
| item element so that screen readers can announce position and total count even when most DOM | ||
| nodes are not mounted (virtualized): | ||
|
|
||
| | Prop | Value | Purpose | | ||
| |------|-------|---------| | ||
| | \`role\` | \`"option"\` | Identifies the element as a selectable option within the listbox. | | ||
| | \`aria-posinset\` | \`index + 1\` | 1-based position of this option within the full set. | | ||
| | \`aria-setsize\` | \`listSize\` | Total number of options in the list. | | ||
|
|
||
| The list uses a [roving tabindex](https://www.w3.org/WAI/ARIA/apg/practices/keyboard-interface/#kbd_roving_tabindex) | ||
| pattern: \`context.tabIndexKey\` holds the key of the item that currently owns focus. Set | ||
| \`tabIndex={0}\` on the matching item and \`tabIndex={-1}\` on every other to keep the list | ||
| to a single tab stop while arrow-key navigation moves focus between items. | ||
|
|
||
| \`\`\`tsx | ||
| getItemComponent={(index, item, context, onFocus) => { | ||
| const selected = context.tabIndexKey === item.id; | ||
|
|
||
| return ( | ||
| <button | ||
| type="button" | ||
| tabIndex={selected ? 0 : -1} | ||
| {...getItemAccessibleProps("listbox", index, items.length)} | ||
| onFocus={(e) => onFocus(item, e)} | ||
| onClick={() => console.log("Clicked item")}} | ||
| > | ||
| {item.label} | ||
| </button> | ||
| ); | ||
| }} | ||
| \`\`\` | ||
| `, | ||
| }, | ||
| }, | ||
| }, | ||
| args: { | ||
| items, | ||
| "getItemComponent": ( | ||
| index: number, | ||
| item: SimpleItemComponent, | ||
| context: VirtualizedListContext<undefined>, | ||
| onFocus: (item: SimpleItemComponent, e: React.FocusEvent) => void, | ||
| ) => ( | ||
| <SimpleItemComponent | ||
| key={item.id} | ||
| item={item} | ||
| context={context} | ||
| onFocus={onFocus} | ||
| {...getItemAccessibleProps("listbox", index, items.length)} | ||
| /> | ||
| ), | ||
| "isItemFocusable": () => true, | ||
| "getItemKey": (item) => item.id, | ||
| "style": { height: "400px" }, | ||
| "aria-label": "Flat virtualized list", | ||
| ...getContainerAccessibleProps("listbox"), | ||
| }, | ||
| } satisfies Meta<FlatVirtualizedListProps<SimpleItemComponent, undefined>>; | ||
|
|
||
| export default meta; | ||
| type Story = StoryObj<typeof meta>; | ||
|
|
||
| export const Default: Story = {}; | ||
58 changes: 58 additions & 0 deletions
58
...s/shared-components/src/utils/VirtualizedList/FlatVirtualizedList/FlatVirtualizedList.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,58 @@ | ||
| /* | ||
| * Copyright 2026 Element Creations Ltd. | ||
| * | ||
| * SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial | ||
| * Please see LICENSE files in the repository root for full details. | ||
| */ | ||
|
|
||
| import React, { type JSX, useCallback } from "react"; | ||
| import { Virtuoso } from "react-virtuoso"; | ||
|
|
||
| import { useVirtualizedList, type VirtualizedListContext, type VirtualizedListProps } from "../virtualized-list"; | ||
|
|
||
| export interface FlatVirtualizedListProps<Item, Context> extends VirtualizedListProps<Item, Context> { | ||
| /** | ||
| * Function that renders each list item as a JSX element. | ||
| * @param index - The index of the item in the list | ||
| * @param item - The data item to render | ||
| * @param context - The context object containing the focused key and any additional data | ||
| * @param onFocus - A callback that is required to be called when the item component receives focus | ||
| * @returns JSX element representing the rendered item | ||
| */ | ||
| getItemComponent: ( | ||
| index: number, | ||
| item: Item, | ||
| context: VirtualizedListContext<Context>, | ||
| onFocus: (item: Item, e: React.FocusEvent) => void, | ||
| ) => JSX.Element; | ||
| } | ||
|
|
||
| /** | ||
| * A generic virtualized list component built on top of react-virtuoso. | ||
| * Provides keyboard navigation and virtualized rendering for performance with large lists. | ||
| * | ||
| * @template Item - The type of data items in the list | ||
| * @template Context - The type of additional context data passed to items | ||
| */ | ||
| export function FlatVirtualizedList<Item, Context>(props: FlatVirtualizedListProps<Item, Context>): React.ReactElement { | ||
|
Check warning on line 37 in packages/shared-components/src/utils/VirtualizedList/FlatVirtualizedList/FlatVirtualizedList.tsx
|
||
| const { getItemComponent, ...restProps } = props; | ||
| const { onFocusForGetItemComponent, ...virtuosoProps } = useVirtualizedList<Item, Context>(restProps); | ||
|
|
||
| const getItemComponentInternal = useCallback( | ||
| (index: number, item: Item, context: VirtualizedListContext<Context>): JSX.Element => | ||
| getItemComponent(index, item, context, onFocusForGetItemComponent), | ||
| [getItemComponent, onFocusForGetItemComponent], | ||
| ); | ||
|
|
||
| return ( | ||
| <Virtuoso | ||
| // note that either the container of direct children must be focusable to be axe | ||
| // compliant, so we leave tabIndex as the default so the container can be focused | ||
| // (virtuoso wraps the children inside another couple of elements so setting it | ||
| // on those doesn't seem to work, unfortunately) | ||
| itemContent={getItemComponentInternal} | ||
| data={props.items} | ||
| {...virtuosoProps} | ||
| /> | ||
| ); | ||
| } | ||
9 changes: 9 additions & 0 deletions
9
packages/shared-components/src/utils/VirtualizedList/FlatVirtualizedList/index.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| /* | ||
| * Copyright 2026 Element Creations Ltd. | ||
| * | ||
| * SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial | ||
| * Please see LICENSE files in the repository root for full details. | ||
| */ | ||
|
|
||
| export { FlatVirtualizedList } from "./FlatVirtualizedList"; | ||
| export type { FlatVirtualizedListProps } from "./FlatVirtualizedList"; |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.