From 4acc2328e967c4c44aa9031231924b51d9a66341 Mon Sep 17 00:00:00 2001 From: Vincentius Roger Kuswara Date: Mon, 2 Dec 2024 00:51:49 +0800 Subject: [PATCH] feat: virtualization feature, docs for listbox --- apps/docs/content/components/listbox/index.ts | 4 + .../virtualization-ten-thousand.raw.jsx | 57 ++++++++++ .../listbox/virtualization-ten-thousand.ts | 9 ++ .../components/listbox/virtualization.raw.jsx | 56 ++++++++++ .../components/listbox/virtualization.ts | 9 ++ apps/docs/content/docs/components/listbox.mdx | 28 +++++ .../listbox/stories/listbox.stories.tsx | 105 ++++++++++++++++++ pnpm-lock.yaml | 2 +- 8 files changed, 269 insertions(+), 1 deletion(-) create mode 100644 apps/docs/content/components/listbox/virtualization-ten-thousand.raw.jsx create mode 100644 apps/docs/content/components/listbox/virtualization-ten-thousand.ts create mode 100644 apps/docs/content/components/listbox/virtualization.raw.jsx create mode 100644 apps/docs/content/components/listbox/virtualization.ts diff --git a/apps/docs/content/components/listbox/index.ts b/apps/docs/content/components/listbox/index.ts index 35c8dcd9c4..2f83ec0bd9 100644 --- a/apps/docs/content/components/listbox/index.ts +++ b/apps/docs/content/components/listbox/index.ts @@ -9,6 +9,8 @@ import description from "./description"; import sections from "./sections"; import customStyles from "./custom-styles"; import topContent from "./top-content"; +import virtualization from "./virtualization"; +import virtualizationTenThousand from "./virtualization-ten-thousand"; export const listboxContent = { usage, @@ -22,4 +24,6 @@ export const listboxContent = { sections, customStyles, topContent, + virtualization, + virtualizationTenThousand, }; diff --git a/apps/docs/content/components/listbox/virtualization-ten-thousand.raw.jsx b/apps/docs/content/components/listbox/virtualization-ten-thousand.raw.jsx new file mode 100644 index 0000000000..f569bea6cc --- /dev/null +++ b/apps/docs/content/components/listbox/virtualization-ten-thousand.raw.jsx @@ -0,0 +1,57 @@ +import {Listbox, ListboxItem} from "@nextui-org/react"; + +const generateItems = (n) => { + const items = [ + "Cat", + "Dog", + "Elephant", + "Lion", + "Tiger", + "Giraffe", + "Dolphin", + "Penguin", + "Zebra", + "Shark", + "Whale", + "Otter", + "Crocodile", + ]; + + const dataset = []; + + for (let i = 0; i < n; i++) { + const item = items[i % items.length]; + + dataset.push({ + label: `${item}${i}`, + value: `${item.toLowerCase()}${i}`, + description: "Sample description", + }); + } + + return dataset; +}; + +export default function App() { + const items = generateItems(1000); + + return ( +
+ + {items.map((item, index) => ( + + {item.label} + + ))} + +
+ ); +} diff --git a/apps/docs/content/components/listbox/virtualization-ten-thousand.ts b/apps/docs/content/components/listbox/virtualization-ten-thousand.ts new file mode 100644 index 0000000000..1b8e486cb5 --- /dev/null +++ b/apps/docs/content/components/listbox/virtualization-ten-thousand.ts @@ -0,0 +1,9 @@ +import App from "./virtualization-ten-thousand.raw.jsx?raw"; + +const react = { + "/App.jsx": App, +}; + +export default { + ...react, +}; diff --git a/apps/docs/content/components/listbox/virtualization.raw.jsx b/apps/docs/content/components/listbox/virtualization.raw.jsx new file mode 100644 index 0000000000..96ec2011b8 --- /dev/null +++ b/apps/docs/content/components/listbox/virtualization.raw.jsx @@ -0,0 +1,56 @@ +import {Listbox, ListboxItem} from "@nextui-org/react"; +const generateItems = (n) => { + const items = [ + "Cat", + "Dog", + "Elephant", + "Lion", + "Tiger", + "Giraffe", + "Dolphin", + "Penguin", + "Zebra", + "Shark", + "Whale", + "Otter", + "Crocodile", + ]; + + const dataset = []; + + for (let i = 0; i < n; i++) { + const item = items[i % items.length]; + + dataset.push({ + label: `${item}${i}`, + value: `${item.toLowerCase()}${i}`, + description: "Sample description", + }); + } + + return dataset; +}; + +export default function App() { + const items = generateItems(1000); + + return ( +
+ + {items.map((item, index) => ( + + {item.label} + + ))} + +
+ ); +} diff --git a/apps/docs/content/components/listbox/virtualization.ts b/apps/docs/content/components/listbox/virtualization.ts new file mode 100644 index 0000000000..e40cbd641f --- /dev/null +++ b/apps/docs/content/components/listbox/virtualization.ts @@ -0,0 +1,9 @@ +import App from "./virtualization.raw.jsx?raw"; + +const react = { + "/App.jsx": App, +}; + +export default { + ...react, +}; diff --git a/apps/docs/content/docs/components/listbox.mdx b/apps/docs/content/docs/components/listbox.mdx index 2c8eb66fc1..52f9b92575 100644 --- a/apps/docs/content/docs/components/listbox.mdx +++ b/apps/docs/content/docs/components/listbox.mdx @@ -151,6 +151,22 @@ function App() { } ``` +### Virtualization + +Select supports virtualization, which allows efficient rendering of large lists by only rendering items that are visible in the viewport. You can enable virtualization by setting the `isVirtualized` prop to `true`. + + + +> **Note**: The virtualization strategy is based on the [@tanstack/react-virtual](https://tanstack.com/virtual/latest) package, which provides efficient rendering of large lists by only rendering items that are visible in the viewport. +#### Ten Thousand Items + +Here's an example of using virtualization with 10,000 items. + + + ## Slots Listbox has 3 components with slots the base one `Listbox`, `ListboxItem` and `ListboxSection` components. @@ -328,6 +344,18 @@ You can customize the `Listbox` items style by using the `itemClasses` prop and type: "boolean", description: "Whether keyboard navigation is circular.", default: "false" + }, + { + attribute: "isVirtualized", + type: "boolean", + description: "Whether to enable virtualization.", + default: "false" + }, + { + attribute: "virtualization", + type: "Record<\"maxListboxHeight\" & \"itemHeight\", number>", + description: "Configuration for virtualization, optimizing rendering for large datasets. Required if isVirtualized is set to true.", + default: "-", }, { attribute: "hideEmptyContent", diff --git a/packages/components/listbox/stories/listbox.stories.tsx b/packages/components/listbox/stories/listbox.stories.tsx index 9f24e947a6..c670e57621 100644 --- a/packages/components/listbox/stories/listbox.stories.tsx +++ b/packages/components/listbox/stories/listbox.stories.tsx @@ -679,6 +679,59 @@ const CustomWithClassNamesTemplate = ({color, variant, disableAnimation, ...args ); }; +interface LargeDatasetSchema { + label: string; + value: string; + description: string; +} + +function generateLargeDataset(n: number): LargeDatasetSchema[] { + const dataset: LargeDatasetSchema[] = []; + const items = [ + "Cat", + "Dog", + "Elephant", + "Lion", + "Tiger", + "Giraffe", + "Dolphin", + "Penguin", + "Zebra", + "Shark", + "Whale", + "Otter", + "Crocodile", + ]; + + for (let i = 0; i < n; i++) { + const item = items[i % items.length]; + + dataset.push({ + label: `${item}${i}`, + value: `${item.toLowerCase()}${i}`, + description: "Sample description", + }); + } + + return dataset; +} + +const LargeDatasetTemplate = (args: ListboxProps & {numItems: number}) => { + const largeDataset = generateLargeDataset(args.numItems); + + return ( +
+ + {largeDataset.map((item, index) => ( + + {item.label} + + ))} + +
+ ); +}; + export const Default = { render: Template, args: { @@ -782,3 +835,55 @@ export const CustomWithClassNames = { ...defaultProps, }, }; + +export const OneThousandList = { + render: LargeDatasetTemplate, + args: { + ...defaultProps, + numItems: 1000, + isVirtualized: true, + virtualization: { + maxListboxHeight: 400, + itemHeight: 20, + }, + }, +}; + +export const TenThousandList = { + render: LargeDatasetTemplate, + args: { + ...defaultProps, + numItems: 10000, + isVirtualized: true, + virtualization: { + maxListboxHeight: 400, + itemHeight: 20, + }, + }, +}; + +export const CustomMaxListboxHeight = { + render: LargeDatasetTemplate, + args: { + ...defaultProps, + numItems: 1000, + isVirtualized: true, + virtualization: { + maxListboxHeight: 600, + itemHeight: 20, + }, + }, +}; + +export const CustomItemHeight = { + render: LargeDatasetTemplate, + args: { + ...defaultProps, + numItems: 1000, + isVirtualized: true, + virtualization: { + itemHeight: 40, + maxListboxHeight: 600, + }, + }, +}; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c65136c311..781634e8c3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -22124,7 +22124,7 @@ snapshots: doctrine: 2.1.0 eslint: 7.32.0 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@5.62.0(eslint@7.32.0)(typescript@4.9.5))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@2.7.1(eslint-plugin-import@2.31.0)(eslint@7.32.0))(eslint@7.32.0) + eslint-module-utils: 2.12.0(@typescript-eslint/parser@5.62.0(eslint@7.32.0)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@5.62.0(eslint@7.32.0)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@7.32.0))(eslint@7.32.0) hasown: 2.0.2 is-core-module: 2.15.1 is-glob: 4.0.3