diff --git a/package-lock.json b/package-lock.json index 1ec27a1..45801af 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,6 +18,7 @@ "@radix-ui/react-slot": "^1.2.3", "@radix-ui/react-tabs": "^1.1.13", "@radix-ui/react-tooltip": "^1.2.8", + "@tanstack/react-virtual": "^3.13.23", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "cmdk": "^1.1.1", @@ -2464,6 +2465,33 @@ "tailwindcss": "4.2.2" } }, + "node_modules/@tanstack/react-virtual": { + "version": "3.13.23", + "resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.13.23.tgz", + "integrity": "sha512-XnMRnHQ23piOVj2bzJqHrRrLg4r+F86fuBcwteKfbIjJrtGxb4z7tIvPVAe4B+4UVwo9G4Giuz5fmapcrnZ0OQ==", + "license": "MIT", + "dependencies": { + "@tanstack/virtual-core": "3.13.23" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/@tanstack/virtual-core": { + "version": "3.13.23", + "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.13.23.tgz", + "integrity": "sha512-zSz2Z2HNyLjCplANTDyl3BcdQJc2k1+yyFoKhNRmCr7V7dY8o8q5m8uFTI1/Pg1kL+Hgrz6u3Xo6eFUB7l66cg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, "node_modules/@tybys/wasm-util": { "version": "0.10.1", "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", @@ -9145,6 +9173,19 @@ "tailwindcss": "4.2.2" } }, + "@tanstack/react-virtual": { + "version": "3.13.23", + "resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.13.23.tgz", + "integrity": "sha512-XnMRnHQ23piOVj2bzJqHrRrLg4r+F86fuBcwteKfbIjJrtGxb4z7tIvPVAe4B+4UVwo9G4Giuz5fmapcrnZ0OQ==", + "requires": { + "@tanstack/virtual-core": "3.13.23" + } + }, + "@tanstack/virtual-core": { + "version": "3.13.23", + "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.13.23.tgz", + "integrity": "sha512-zSz2Z2HNyLjCplANTDyl3BcdQJc2k1+yyFoKhNRmCr7V7dY8o8q5m8uFTI1/Pg1kL+Hgrz6u3Xo6eFUB7l66cg==" + }, "@tybys/wasm-util": { "version": "0.10.1", "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", diff --git a/package.json b/package.json index 3f88fde..0d39fe0 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "@radix-ui/react-slot": "^1.2.3", "@radix-ui/react-tabs": "^1.1.13", "@radix-ui/react-tooltip": "^1.2.8", + "@tanstack/react-virtual": "^3.13.23", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "cmdk": "^1.1.1", diff --git a/src/app/os/keys/page.tsx b/src/app/os/keys/page.tsx index 69c5777..d1ebfff 100644 --- a/src/app/os/keys/page.tsx +++ b/src/app/os/keys/page.tsx @@ -1,18 +1,13 @@ "use client"; -import { useState, useEffect, useMemo, useCallback } from "react"; +import { useState, useEffect, useMemo, useCallback, memo, useRef } from "react"; import { useSearchParams } from "next/navigation"; -import { useDebounce } from "use-debounce"; +import { useVirtualizer } from "@tanstack/react-virtual"; import Link from "next/link"; import { Search, X, ChevronRight, ChevronDown } from "lucide-react"; import { Input } from "@/components/ui/input"; import { Button } from "@/components/ui/button"; -import { - Collapsible, - CollapsibleContent, - CollapsibleTrigger, -} from "@/components/ui/collapsible"; import { createEngine } from "@/lib/engine"; @@ -25,7 +20,6 @@ function groupKeysByPrefix(keys: string[]): GroupedKeys { for (const key of keys) { const parts = key.split("."); - // Use first 3 segments for com.apple.*, otherwise use the whole key let prefix: string; if (parts.length >= 3 && parts[0] === "com" && parts[1] === "apple") { prefix = `${parts[0]}.${parts[1]}.${parts[2]}`; @@ -44,7 +38,7 @@ function groupKeysByPrefix(keys: string[]): GroupedKeys { return groups; } -function KeyBadge({ +const KeyBadge = memo(function KeyBadge({ keyName, prefix, os, @@ -77,63 +71,13 @@ function KeyBadge({ )} ); -} +}); -function KeyGroup({ - prefix, - keys, - os, - forceOpen, -}: { +interface RowItem { + type: "header" | "keys" | "single"; prefix: string; keys: string[]; - os: string; - forceOpen: boolean | null; -}) { - const [open, setOpen] = useState(forceOpen ?? keys.length <= 8); - - useEffect(() => { - if (forceOpen !== null) { - setOpen(forceOpen); - } - }, [forceOpen]); - - // Single key in group - just show it inline without collapsible wrapper - if (keys.length === 1) { - return ( -