Skip to content

Commit

Permalink
feat: virtualization feature, docs for listbox
Browse files Browse the repository at this point in the history
  • Loading branch information
vinroger committed Dec 1, 2024
1 parent e6f64f3 commit 4acc232
Show file tree
Hide file tree
Showing 8 changed files with 269 additions and 1 deletion.
4 changes: 4 additions & 0 deletions apps/docs/content/components/listbox/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -22,4 +24,6 @@ export const listboxContent = {
sections,
customStyles,
topContent,
virtualization,
virtualizationTenThousand,
};
Original file line number Diff line number Diff line change
@@ -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 (
<div className="flex w-full flex-wrap md:flex-nowrap gap-4">
<Listbox
isVirtualized
label={"Select from 10000 items"}
placeholder="Select..."
virtualization={{
maxListboxHeight: 400,
itemHeight: 40,
}}
>
{items.map((item, index) => (
<ListboxItem key={index} value={item.value}>
{item.label}
</ListboxItem>
))}
</Listbox>
</div>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import App from "./virtualization-ten-thousand.raw.jsx?raw";

const react = {
"/App.jsx": App,
};

export default {
...react,
};
56 changes: 56 additions & 0 deletions apps/docs/content/components/listbox/virtualization.raw.jsx
Original file line number Diff line number Diff line change
@@ -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 (
<div className="flex w-full flex-wrap md:flex-nowrap gap-4">
<Listbox
isVirtualized
label={"Select from 1000 items"}
placeholder="Select..."
virtualization={{
maxListboxHeight: 400,
itemHeight: 40,
}}
>
{items.map((item, index) => (
<ListboxItem key={index} value={item.value}>
{item.label}
</ListboxItem>
))}
</Listbox>
</div>
);
}
9 changes: 9 additions & 0 deletions apps/docs/content/components/listbox/virtualization.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import App from "./virtualization.raw.jsx?raw";

const react = {
"/App.jsx": App,
};

export default {
...react,
};
28 changes: 28 additions & 0 deletions apps/docs/content/docs/components/listbox.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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`.

<CodeDemo
title="Virtualization"
files={listboxContent.virtualization}
/>

> **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.

<CodeDemo title="Ten Thousand Items" files={listboxContent.virtualizationTenThousand} />

## Slots

Listbox has 3 components with slots the base one `Listbox`, `ListboxItem` and `ListboxSection` components.
Expand Down Expand Up @@ -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",
Expand Down
105 changes: 105 additions & 0 deletions packages/components/listbox/stories/listbox.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
<div className="flex w-full max-w-full py-20 px-20">
<Listbox label={`Select from ${args.numItems} items`} {...args}>
{largeDataset.map((item, index) => (
<ListboxItem key={index} value={item.value}>
{item.label}
</ListboxItem>
))}
</Listbox>
</div>
);
};

export const Default = {
render: Template,
args: {
Expand Down Expand Up @@ -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,
},
},
};
2 changes: 1 addition & 1 deletion pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 4acc232

Please sign in to comment.