From dd0d246a773cadeaeec7fbc897fb084786b0f47b Mon Sep 17 00:00:00 2001 From: xCyanGrizzly Date: Tue, 24 Mar 2026 16:22:50 +0100 Subject: [PATCH] feat: highlight matching files in package drawer when opened from search Co-Authored-By: Claude Opus 4.6 (1M context) --- .../stls/_components/package-files-drawer.tsx | 96 +++++++++++++------ src/app/(app)/stls/_components/stl-table.tsx | 1 + 2 files changed, 69 insertions(+), 28 deletions(-) diff --git a/src/app/(app)/stls/_components/package-files-drawer.tsx b/src/app/(app)/stls/_components/package-files-drawer.tsx index d29226c..cf434a9 100644 --- a/src/app/(app)/stls/_components/package-files-drawer.tsx +++ b/src/app/(app)/stls/_components/package-files-drawer.tsx @@ -52,6 +52,7 @@ interface PackageFilesDrawerProps { pkg: PackageRow | null; open: boolean; onOpenChange: (open: boolean) => void; + highlightTerm?: string; } function formatBytes(bytesStr: string): string { @@ -81,6 +82,15 @@ function getExtBadgeClass(ext: string | null): string { return EXTENSION_COLORS[ext.toLowerCase()] ?? "bg-zinc-500/15 text-zinc-400 border-zinc-500/30"; } +function fileMatchesHighlight(file: FileItem, term: string): boolean { + if (!term) return false; + const lower = term.toLowerCase(); + return ( + file.fileName.toLowerCase().includes(lower) || + file.path.toLowerCase().includes(lower) + ); +} + /** * Build a tree structure from flat file paths. */ @@ -120,11 +130,13 @@ function TreeNodeView({ depth, search, defaultOpen, + highlightTerm, }: { node: TreeNode; depth: number; search: string; defaultOpen: boolean; + highlightTerm?: string; }) { const [open, setOpen] = useState(defaultOpen); @@ -137,10 +149,22 @@ function TreeNodeView({ }); }, [node.children]); - // If searching, force all open + const hasHighlightedDescendant = useMemo(() => { + if (!highlightTerm) return false; + function check(n: TreeNode): boolean { + if (n.file && fileMatchesHighlight(n.file, highlightTerm!)) return true; + for (const child of n.children.values()) { + if (check(child)) return true; + } + return false; + } + return check(node); + }, [node, highlightTerm]); + + // If searching or has highlighted descendants, force all open useEffect(() => { - if (search) setOpen(true); - }, [search]); + if (search || hasHighlightedDescendant) setOpen(true); + }, [search, hasHighlightedDescendant]); if (node.isFolder && node.children.size > 0) { return ( @@ -177,6 +201,7 @@ function TreeNodeView({ depth={depth + 1} search={search} defaultOpen={depth < 1} // Auto-expand first 2 levels + highlightTerm={highlightTerm} /> ))} @@ -185,9 +210,15 @@ function TreeNodeView({ // File node if (node.file) { + const isHighlighted = highlightTerm ? fileMatchesHighlight(node.file, highlightTerm) : false; return (
@@ -223,7 +254,7 @@ function countFiles(node: TreeNode): number { const PAGE_SIZE = 100; -export function PackageFilesDrawer({ pkg, open, onOpenChange }: PackageFilesDrawerProps) { +export function PackageFilesDrawer({ pkg, open, onOpenChange, highlightTerm }: PackageFilesDrawerProps) { const [files, setFiles] = useState([]); const [total, setTotal] = useState(0); const [loading, setLoading] = useState(false); @@ -471,36 +502,45 @@ export function PackageFilesDrawer({ pkg, open, onOpenChange }: PackageFilesDraw depth={0} search={search} defaultOpen={true} + highlightTerm={highlightTerm} /> ))} ) : ( <> {/* Flat list for archives without folders */} - {filtered.map((file) => ( -
- -
-

- {file.fileName} -

+ {filtered.map((file) => { + const isHighlighted = highlightTerm ? fileMatchesHighlight(file, highlightTerm) : false; + return ( +
+ +
+

+ {file.fileName} +

+
+ {file.extension && ( + + .{file.extension} + + )} + + {formatBytes(file.uncompressedSize)} +
- {file.extension && ( - - .{file.extension} - - )} - - {formatBytes(file.uncompressedSize)} - -
- ))} + ); + })} )} diff --git a/src/app/(app)/stls/_components/stl-table.tsx b/src/app/(app)/stls/_components/stl-table.tsx index 980ed7c..f903380 100644 --- a/src/app/(app)/stls/_components/stl-table.tsx +++ b/src/app/(app)/stls/_components/stl-table.tsx @@ -165,6 +165,7 @@ export function StlTable({ onOpenChange={(open) => { if (!open) setViewPkg(null); }} + highlightTerm={searchTerm} />
);