Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,6 @@ See the **Flush Immediately** example in the live demo.
| `as` | `React.ElementType` | `"div"` | Polymorphic root element. |
| `maxRows` | `number` | `1` | Visible rows before overflow. |
| `maxVisibleItems` | `number` | `100` | Hard cap on visible items. |
| `minVisibleItems` | `number` | `0` | Keep at least N visible. |
| `renderOverflow` | `(hidden: T[]) => ReactNode` | default chip | Custom overflow UI. |
| `renderOverflowItem` | `(item: T, i: number) => ReactNode` | `renderItem` | For expanded lists/menus. |
| `renderOverflowProps` | `Partial<OverflowElementProps<T>>` | — | Props for default overflow. |
Expand All @@ -138,7 +137,7 @@ It’s **expected** you’ll wrap `OverflowList` for product needs (design syste
- **Radix UI + Virtualization wrapper** (search, large datasets, a11y, perf):

- **Demo:** see [Radix UI + Virtualization](https://eliav2.github.io/react-responsive-overflow-list/#radix-ui-virtualization-example) in the live site
- [**Source**](demo/src/examples/RadixVirtualizedOverflowList.tsx)
- [**Source**](demo/src/components/RadixVirtualizedOverflowList.tsx)
- Uses `@tanstack/react-virtual` and the helper `createLimitedRangeExtractor(...)`.

---
Expand All @@ -155,7 +154,7 @@ It’s **expected** you’ll wrap `OverflowList` for product needs (design syste
### Edge cases handled

- Single wide item exceeding container width
- `minVisibleItems` / `maxVisibleItems` respected
- `maxRows` / `maxVisibleItems` respected
- Varying item widths, responsive content
- Multi-row overflow detection

Expand Down
2 changes: 1 addition & 1 deletion demo/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "demo",
"private": true,
"version": "0.1.0",
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
Expand Down
313 changes: 15 additions & 298 deletions demo/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,126 +1,16 @@
import { useState } from "react";
import { OverflowList } from "react-responsive-overflow-list";
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
import { tomorrow } from "react-syntax-highlighter/dist/esm/styles/prism";
import { Theme, Switch } from "@radix-ui/themes";
import { Theme } from "@radix-ui/themes";
import { CustomOverflowExample } from "./examples/CustomOverflowExample";
import { RadixVirtualizedOverflowList } from "./examples/RadixVirtualizedOverflowList";
import { BasicExample } from "./examples/BasicExample";
import { ChildrenPatternExample } from "./examples/ChildrenPatternExample";
import { MultiRowExample } from "./examples/MultiRowExample";
import { CustomHostElementExample } from "./examples/CustomHostElementExample";
import { RadixVirtualizationExample } from "./examples/RadixVirtualizationExample";
import { FlushImmediatelyExample } from "./examples/FlushImmediatelyExample";
import { OneItemWiderExample } from "./examples/OneItemWiderExample";
import { MaxRowsOverflowExample } from "./examples/MaxRowsOverflowExample";
import { Github } from "lucide-react";
import "./App.css";

const fruits = ["Apple", "Banana", "Cherry", "Date", "Elderberry", "Fig", "Grape", "Honeydew", "Kiwi", "Lemon"];

const tags = ["React", "TypeScript", "CSS", "HTML", "JavaScript", "Node.js", "Express", "MongoDB", "Vite", "ESLint"];

const menuItems = ["Home", "About", "Services", "Portfolio", "Blog", "Contact", "Careers", "Support"];

function MultiRowExample() {
const [maxRows, setMaxRows] = useState(2);

return (
<section className="demo">
<h2 id="multi-row-example">Multi-row Example</h2>
<p>Allow up to {maxRows} rows before overflow</p>
<div className="code-preview">
<SyntaxHighlighter language="tsx" style={tomorrow}>
{`<OverflowList
items={fruits.concat(tags).concat(menuItems)}
renderItem={(item) => <span className="multi-item">{item}</span>}
maxRows={${maxRows}}
style={{ gap: "4px" }}
/>`}
</SyntaxHighlighter>
</div>
<div className="controls">
<label htmlFor="maxRows">Max Rows:</label>
<input
id="maxRows"
type="number"
min="1"
max="10"
value={maxRows}
onChange={(e) => setMaxRows(parseInt(e.target.value) || 1)}
className="max-rows-input"
/>
</div>

<div className="demo-container">
<OverflowList
items={fruits.concat(tags).concat(menuItems)}
renderItem={(item) => <span className="multi-item">{item}</span>}
maxRows={maxRows}
style={{ gap: "4px" }}
/>
</div>
</section>
);
}

function FlushImmediatelyExample() {
const [flushImmediately, setFlushImmediately] = useState(false);

return (
<section className="demo">
<h2 id="flush-immediately-example">Flush Immediately Example</h2>
<p>
Control how updates are applied when the container resizes.
<strong>flushImmediately={flushImmediately ? "true" : "false"}</strong>
(default: true)
</p>
<div className="code-preview">
<SyntaxHighlighter language="tsx" style={tomorrow}>
{`<OverflowList
items={fruits.concat(tags)}
renderItem={(item) => <span className="multi-item">{item}</span>}
flushImmediately={${flushImmediately}}
style={{ gap: "4px" }}
/>`}
</SyntaxHighlighter>
</div>
<div className="controls">
<div style={{ display: "flex", alignItems: "center", gap: "8px" }}>
<label htmlFor="flush-toggle">Flush Immediately:</label>
<Switch id="flush-toggle" checked={flushImmediately} onCheckedChange={setFlushImmediately} />
<span style={{ fontSize: "14px", color: "#666" }}>
{flushImmediately ? "Enabled (No flickering)" : "Disabled (better performance)"}
</span>
</div>
</div>

<div style={{ marginBottom: "16px", padding: "12px", backgroundColor: "#f5f5f5", borderRadius: "4px" }}>
<h4 style={{ margin: "0 0 8px 0", fontSize: "14px" }}>Trade-offs:</h4>
<ul style={{ margin: 0, paddingLeft: "20px", fontSize: "14px" }}>
<li>
<strong>flushImmediately=true:</strong> Updates are applied immediately using flushSync, avoiding flickering
but may impact performance
</li>
<li>
<strong>flushImmediately=false:</strong> Updates are applied in the requestAnimationFrame callback, avoiding
forced reflow and improving performance but may cause slight flickering
</li>
<li>
<strong>Default behavior:</strong> flushImmediately is true by default to prioritize smooth visual
experience
</li>
</ul>

<div style={{ marginTop: "12px", fontStyle: "italic", color: "#888", fontSize: "14px" }}>
Resize quickly below to observe the difference!
</div>
</div>

<div className="demo-container">
<OverflowList
items={fruits}
renderItem={(item) => <span className="multi-item">{item}</span>}
flushImmediately={flushImmediately}
style={{ gap: "4px" }}
/>
</div>
</section>
);
}

function App() {
return (
<Theme>
Expand Down Expand Up @@ -150,188 +40,15 @@ function App() {
</header>

<main>
<section className="demo">
<h2 id="basic-example">Basic Example</h2>
<p>Simple list with default overflow element</p>
<div className="code-preview">
<SyntaxHighlighter language="tsx" style={tomorrow}>
{`<OverflowList
items={fruits}
renderItem={(item, index) => (
<span key={index} className="fruit-item">
{item}
</span>
)}
style={{ gap: "8px" }}
/>`}
</SyntaxHighlighter>
</div>
<div className="demo-container">
<OverflowList
items={fruits}
renderItem={(item, index) => (
<span key={index} className="fruit-item">
{item}
</span>
)}
style={{ gap: "8px" }}
/>
</div>
</section>

<section className="demo">
<h2 id="children-pattern">Children Pattern</h2>
<p>Using children instead of items array</p>
<div className="code-preview">
<SyntaxHighlighter language="tsx" style={tomorrow}>
{`<OverflowList>
<button>Action 1</button>
<button>Action 2</button>
...
</OverflowList>`}
</SyntaxHighlighter>
</div>
<div className="demo-container">
<OverflowList style={{ gap: "8px" }}>
<button className="action-button">Action 1</button>
<button className="action-button">Action 2</button>
<button className="action-button">Action 3</button>
<button className="action-button">Action 4</button>
<button className="action-button">Action 5</button>
<button className="action-button">Action 6</button>
</OverflowList>
</div>
</section>

<BasicExample />
<ChildrenPatternExample />
<MultiRowExample />

<CustomOverflowExample />

<section className="demo">
<h2 id="custom-host-element">Custom Host Element</h2>
<p>Using the 'as' prop to render as different HTML elements</p>
<div className="code-preview">
<SyntaxHighlighter language="tsx" style={tomorrow}>
{`<OverflowList as="nav" style={{ gap: "8px" }}>
<a href="#home">Home</a>
<a href="#about">About</a>
<a href="#contact">Contact</a>
</OverflowList>`}
</SyntaxHighlighter>
</div>
<div className="demo-container">
<OverflowList as="nav" style={{ gap: "8px" }}>
<a href="#home" className="demo-item demo-item--primary">
Home
</a>
<a href="#about" className="demo-item demo-item--primary">
About
</a>
<a href="#contact" className="demo-item demo-item--primary">
Contact
</a>
<a href="#services" className="demo-item demo-item--primary">
Services
</a>
<a href="#portfolio" className="demo-item demo-item--primary">
Portfolio
</a>
</OverflowList>
</div>
</section>

<section className="demo">
<h2 id="radix-ui-virtualization-example">Radix UI + Virtualization Example</h2>
<p>
This is an EXAMPLE implementation showing how to wrap OverflowList with Radix UI dropdown and
virtualization. In real-world applications, it's expected that you'll wrap OverflowList with your own
components tailored to your specific needs, design system, and UI framework.
</p>
<div className="code-preview">
<SyntaxHighlighter language="tsx" style={tomorrow}>
{`import { RadixVirtualizedOverflowList } from "./examples/RadixVirtualizedOverflowList";

// Small dataset - uses simple dropdown
<RadixVirtualizedOverflowList
items={tags}
renderItem={(tag) => <span className="tag">#{tag}</span>}
style={{ gap: "6px" }}
/>

// Large dataset - automatically uses virtualization
<RadixVirtualizedOverflowList
items={Array.from({ length: 1000 }, (_, i) => \`Item \${i + 1}\`)}
renderItem={(item) => <span className="tag">#{item}</span>}
virtualizationThreshold={50}
enableSearch={true}
style={{ gap: "6px" }}
/>`}
</SyntaxHighlighter>
</div>

<div className="demo-container">
<h4 style={{ margin: "0 0 12px 0", fontSize: "16px" }}>Small Dataset (Simple Dropdown)</h4>
<RadixVirtualizedOverflowList
items={tags}
renderItem={(tag) => <span className="tag">#{tag}</span>}
style={{ gap: "6px" }}
/>
</div>

<div className="demo-container" style={{ marginTop: "24px" }}>
<h4 style={{ margin: "0 0 12px 0", fontSize: "16px" }}>
Large Dataset (Virtualized Dropdown with Search)
</h4>
<RadixVirtualizedOverflowList
items={Array.from({ length: 1000 }, (_, i) => `Item ${i + 1}`)}
renderItem={(item) => <span className="tag">#{item}</span>}
virtualizationThreshold={50}
enableSearch={true}
searchPlaceholder="Search items..."
style={{ gap: "6px" }}
/>
</div>

<div className="demo-note">
<strong>This example demonstrates:</strong>
<ul style={{ margin: "8px 0", paddingLeft: "20px" }}>
<li>
<strong>Automatic virtualization:</strong> Switches to virtualized dropdown when item count exceeds
threshold
</li>
<li>
<strong>Search functionality:</strong> Built-in search/filter for large datasets
</li>
<li>
<strong>Radix UI integration:</strong> Full accessibility and keyboard navigation support
</li>
<li>
<strong>Customizable:</strong> Configurable thresholds, styling, and behavior
</li>
<li>
<strong>Performance optimized:</strong> Efficient rendering for thousands of items
</li>
</ul>
<p style={{ margin: "12px 0 0 0", fontStyle: "italic", color: "#666" }}>
<strong>Note:</strong> This is just an example implementation. In real-world applications, it's expected
that you'll wrap OverflowList with your own components tailored to your specific needs and design
system.
<br />
<strong>Source:</strong>{" "}
<a
href="https://github.com/eliav2/react-responsive-overflow-list/blob/main/demo/src/examples/RadixVirtualizedOverflowList.tsx"
target="_blank"
rel="noopener noreferrer"
>
View implementation on GitHub
</a>
</p>
</div>
</section>

<CustomHostElementExample />
<RadixVirtualizationExample />
<FlushImmediatelyExample />

{/* <div className="demo-container"></div> */}
<OneItemWiderExample />
<MaxRowsOverflowExample />
</main>

<footer>
Expand Down
Loading