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: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# Changelog

## 0.2.0

- dropped `minVisibleItems` prop in favor of simplicity (same effect can achieved with reversed flex direction and reversing the items array)
- the component is now ssr-safe(tested adjusted on ssr environment).

## 0.1.0

- Initial release
Expand Down
3 changes: 2 additions & 1 deletion demo/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + React + TS</title>
<meta name="description" content="Responsive React component that shows only items that fit and groups the rest into a customizable overflow element. Recalculates on resize." />
<title>React Responsive Overflow List - Demo</title>
</head>
<body>
<div id="root"></div>
Expand Down
20 changes: 14 additions & 6 deletions demo/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,32 @@
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"dev": "pnpm dev:spa",
"dev:local": "pnpm install --link-workspace-packages && pnpm dev",
"dev:published": "pnpm install && pnpm dev",
"build": "tsc -b && vite build",
"dev:published": "pnpm install --no-link-workspace-packages && pnpm dev",
"build": "tsc -b && pnpm build:ssr",
"lint": "eslint .",
"preview": "vite preview"
"dev:ssr": "VITE_APP_MODE=ssr vike dev",
"dev:spa": "VITE_APP_MODE=spa vike dev",
"build:ssr": "VITE_APP_MODE=ssr vike build",
"build:spa": "VITE_APP_MODE=spa vike build",
"preview": "vike preview"
},
"dependencies": {
"@radix-ui/react-dropdown-menu": "^2.1.16",
"@radix-ui/themes": "^3.2.1",
"@tanstack/react-virtual": "^3.13.12",
"@types/react-syntax-highlighter": "^15.5.13",
"express": "^4.18.2",
"lucide-react": "^0.544.0",
"react": "^19.1.1",
"react-copy-to-clipboard": "^5.1.0",
"react-dom": "^19.1.1",
"react-responsive-overflow-list": "latest",
"react-syntax-highlighter": "^15.6.6"
"react-responsive-overflow-list": "workspace:*",
"react-syntax-highlighter": "^15.6.6",
"vike": "^0.4.240",
"vike-react": "^0.6.6",
"vite-plugin-ssr": "^0.4.142"
},
"devDependencies": {
"@eslint/js": "^9.33.0",
Expand Down
9 changes: 9 additions & 0 deletions demo/pages/index/+Page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@

import App from '../../src/App.tsx'
export { Page }

function Page() {
return <>
<App />
</>
}
10 changes: 10 additions & 0 deletions demo/renderer/+config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import type { Config } from "vike/types";
import vikeReact from "vike-react/config";

const isSpa = (process.env.VITE_APP_MODE ?? "ssr") === "spa";

export default {
prerender: true,
extends: [vikeReact],
ssr: !isSpa,
} satisfies Config;
5 changes: 3 additions & 2 deletions demo/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import "@radix-ui/themes/styles.css";
import { Theme } from "@radix-ui/themes";
import { CustomOverflowExample } from "./examples/CustomOverflowExample";
import { BasicExample } from "./examples/BasicExample";
Expand All @@ -7,7 +8,7 @@ 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 { ReverseOrderExample } from "./examples/ReverseOrderExample";
import { Github } from "lucide-react";
import "./App.css";

Expand Down Expand Up @@ -48,7 +49,7 @@ function App() {
<RadixVirtualizationExample />
<FlushImmediatelyExample />
<OneItemWiderExample />
<MaxRowsOverflowExample />
<ReverseOrderExample />
</main>

<footer>
Expand Down
28 changes: 24 additions & 4 deletions demo/src/components/CollapsibleCodePreview.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,30 @@
import { useState } from "react";
import { useState, useEffect } from "react";
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
import { tomorrow } from "react-syntax-highlighter/dist/esm/styles/prism";
import { Copy, ChevronDown, ChevronRight, Check } from "lucide-react";
import { CopyToClipboard } from "react-copy-to-clipboard";
import { IconButton } from "@radix-ui/themes";

// SSR-safe copy functionality
function CopyToClipboardWrapper({ children, text, onCopy }: any) {
const [CopyToClipboard, setCopyToClipboard] = useState<any>(null);

useEffect(() => {
// Dynamically import on client-side only
if (typeof window !== 'undefined') {
import('react-copy-to-clipboard').then((module) => {
setCopyToClipboard(() => module.default);
Copy link

Choose a reason for hiding this comment

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

Bug: Dynamic Import State Error

The CopyToClipboardWrapper component's dynamic import has two issues: it sets state to a function that returns module.default instead of the component itself, and it uses module.default when CopyToClipboard was a named export. This causes a runtime error when React tries to render a function as a component.

Fix in Cursor Fix in Web

});
}
}, []);

if (!CopyToClipboard) {
// Fallback during SSR or before component loads
return children;
}

return <CopyToClipboard text={text} onCopy={onCopy}>{children}</CopyToClipboard>;
}

interface CollapsibleCodePreviewProps {
code: string;
language?: string;
Expand Down Expand Up @@ -43,11 +63,11 @@ export function CollapsibleCodePreview({
<span className="code-title">{title}</span>
</button>

<CopyToClipboard text={combinedCode} onCopy={handleCopy}>
<CopyToClipboardWrapper text={combinedCode} onCopy={handleCopy}>
<IconButton className="copy-button" aria-label="Copy code">
{copied ? <Check size={16} className="copy-success" /> : <Copy size={16} />}
</IconButton>
</CopyToClipboard>
</CopyToClipboardWrapper>
</div>

{!isCollapsed && (
Expand Down
4 changes: 2 additions & 2 deletions demo/src/examples/CustomOverflowExample.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ import { OverflowList, type OverflowElementProps } from "react-responsive-overfl
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
import { tomorrow } from "react-syntax-highlighter/dist/esm/styles/prism";
import { CollapsibleCodePreview } from "../components/CollapsibleCodePreview";
import { VirtualizedRadixOverflowMenu } from "./VirtualizedRadixOverflowMenu";
import { RadixOverflowMenu } from "./RadixOverflowMenu";
import virtualizedCodeExample from "./VirtualizedRadixOverflowMenu?raw";
import radixCodeExample from "./RadixOverflowMenu?raw";
import { VirtualizedRadixOverflowMenu } from "./VirtualizedRadixOverflowMenu";
import virtualizedCodeExample from "./VirtualizedRadixOverflowMenu?raw";

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

Expand Down
38 changes: 0 additions & 38 deletions demo/src/examples/MaxRowsOverflowExample.tsx

This file was deleted.

77 changes: 77 additions & 0 deletions demo/src/examples/ReverseOrderExample.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
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";

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

export function ReverseOrderExample() {
return (
<section className="demo">
<h2 id="reverse-order-example">Reverse Order Example</h2>
<p>Compare normal overflow (shrinks from end) vs reverse overflow (shrinks from start)</p>
<div className="code-preview">
<SyntaxHighlighter language="tsx" style={tomorrow}>
{`// Normal overflow - shrinks from end
<OverflowList
items={tags}
renderItem={(item, index) => (
<span key={index} className="tag-item">
{item}
</span>
)}
style={{ gap: "8px" }}
/>

// Reverse overflow - shrinks from start
<OverflowList
items={[...tags].reverse()}
renderItem={(item, index) => (
<span key={index} className="tag-item">
{item}
</span>
)}
style={{
gap: "8px",
flexDirection: "row-reverse",
justifyContent: "flex-end"
}}
/>`}
</SyntaxHighlighter>
</div>

<div className="demo-section">
<h3>Normal Overflow (shrinks from end)</h3>
<div className="demo-container">
<OverflowList
items={tags}
renderItem={(item, index) => (
<span key={index} className="tag-item">
{item}
</span>
)}
style={{ gap: "8px" }}
/>
</div>
</div>

<div className="demo-section">
<h3>Reverse Overflow (shrinks from start)</h3>
<div className="demo-container">
<OverflowList
items={[...tags].reverse()}
renderItem={(item, index) => (
<span key={index} className="tag-item">
{item}
</span>
)}
style={{
gap: "8px",
flexDirection: "row-reverse",
justifyContent: "flex-end"
}}
/>
</div>
</div>
</section>
);
}
1 change: 0 additions & 1 deletion demo/src/main.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import "@radix-ui/themes/styles.css";
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import App from "./App.tsx";
Expand Down
8 changes: 0 additions & 8 deletions demo/src/prerender.tsx

This file was deleted.

36 changes: 20 additions & 16 deletions demo/vite.config.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,26 @@
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import { fileURLToPath, URL } from "node:url";
import { vitePrerenderPlugin } from "vite-prerender-plugin";
import path from "node:path";
import vike from "vike/plugin";

export default defineConfig({
plugins: [
react(),
vitePrerenderPlugin({
renderTarget: "#root",
prerenderScript: path.resolve(__dirname, "src/prerender.tsx"),
}),
],
base: process.env.NODE_ENV === "production" ? "/react-responsive-overflow-list/" : "/",
resolve: {
// trick to avoid needing to build-watch the library when developing the demo
alias: {
"react-responsive-overflow-list": fileURLToPath(new URL("../src/index.ts", import.meta.url)),
export default defineConfig(() => {
const appMode = process.env.VITE_APP_MODE || ("spa" as "spa" | "ssr");
console.log(`VITE_APP_MODE is "${appMode}"`);

return {
plugins: [react(), vike()],
ssr: {
// Don't externalize React for SSR
noExternal: ["react-responsive-overflow-list", "react-syntax-highlighter", "@types/react-syntax-highlighter"],
// Handle CommonJS modules
// external: ["react-copy-to-clipboard"],
},
base: process.env.NODE_ENV === "production" ? "/react-responsive-overflow-list/" : "/",
resolve: {
// trick to avoid needing to build-watch the library when developing the demo
alias: {
"react-responsive-overflow-list": fileURLToPath(new URL("../src/index.ts", import.meta.url)),
},
},
},
};
});
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "react-responsive-overflow-list",
"version": "0.1.2",
"version": "0.2.0",
"description": "A responsive React component that shows as many items as can fit within constraints, hiding overflow items behind a configurable overflow renderer",
"main": "dist/index.js",
"module": "dist/index.mjs",
Expand Down
Loading