From 99df61484b162ec627868eafc8c22a41fbecb7ca Mon Sep 17 00:00:00 2001 From: TerrifiedBug Date: Tue, 10 Mar 2026 14:08:40 +0000 Subject: [PATCH] ui: card layout for shared components with collapsible kind sections Replace table rows with card grid on the shared components list page to match the visual style used elsewhere in the library. Group the component catalog on the create page into collapsible Source/Transform/Sink sections so the long list is easier to navigate. --- .../library/shared-components/new/page.tsx | 156 ++++++++++++++---- .../library/shared-components/page.tsx | 40 +++-- 2 files changed, 148 insertions(+), 48 deletions(-) diff --git a/src/app/(dashboard)/library/shared-components/new/page.tsx b/src/app/(dashboard)/library/shared-components/new/page.tsx index c4ecfb5..7d0595a 100644 --- a/src/app/(dashboard)/library/shared-components/new/page.tsx +++ b/src/app/(dashboard)/library/shared-components/new/page.tsx @@ -8,7 +8,7 @@ import { useEnvironmentStore } from "@/stores/environment-store"; import { VECTOR_CATALOG } from "@/lib/vector/catalog"; import { toast } from "sonner"; import Link from "next/link"; -import { ArrowLeft, Loader2, Plus, Search } from "lucide-react"; +import { ArrowLeft, ChevronDown, Loader2, Plus, Search } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Badge } from "@/components/ui/badge"; @@ -22,12 +22,18 @@ import { CardHeader, CardTitle, } from "@/components/ui/card"; +import { + Collapsible, + CollapsibleContent, + CollapsibleTrigger, +} from "@/components/ui/collapsible"; import { SchemaForm } from "@/components/config-forms/schema-form"; +import { cn } from "@/lib/utils"; import type { VectorComponentDef } from "@/lib/vector/types"; /* ------------------------------------------------------------------ */ -/* Kind badge styling */ +/* Kind styling */ /* ------------------------------------------------------------------ */ const kindVariant: Record = { @@ -38,6 +44,23 @@ const kindVariant: Record = { sink: "bg-orange-100 text-orange-800 dark:bg-orange-900/40 dark:text-orange-300", }; +const kindSectionConfig: Record = { + source: { + label: "Sources", + accent: "text-emerald-600 dark:text-emerald-400", + }, + transform: { + label: "Transforms", + accent: "text-sky-600 dark:text-sky-400", + }, + sink: { + label: "Sinks", + accent: "text-orange-600 dark:text-orange-400", + }, +}; + +const KIND_ORDER = ["source", "transform", "sink"] as const; + /* ------------------------------------------------------------------ */ /* Page Component */ /* ------------------------------------------------------------------ */ @@ -71,6 +94,16 @@ export default function NewSharedComponentPage() { ); }, [search]); + const groupedCatalog = useMemo( + () => + KIND_ORDER.map((kind) => ({ + kind, + ...kindSectionConfig[kind], + items: filteredCatalog.filter((c) => c.kind === kind), + })), + [filteredCatalog], + ); + const createMutation = useMutation( trpc.sharedComponent.create.mutationOptions({ onSuccess: (sc) => { @@ -162,42 +195,30 @@ export default function NewSharedComponentPage() { /> - {/* Component grid */} -
- {filteredCatalog.map((comp) => ( - handleSelectComponent(comp)} - > - -
- - {comp.displayName} - - - {comp.kind} - -
-
- -

- {comp.description} -

-
-
- ))} -
- - {filteredCatalog.length === 0 && ( + {/* Component sections by kind */} + {filteredCatalog.length === 0 ? (

No components match your search.

+ ) : ( +
+ {groupedCatalog.map((group) => { + if (group.items.length === 0) return null; + return ( + + ); + })} +
)} )} @@ -289,3 +310,72 @@ export default function NewSharedComponentPage() { ); } + +/* ------------------------------------------------------------------ */ +/* Catalog Kind Section (collapsible) */ +/* ------------------------------------------------------------------ */ + +function CatalogKindSection({ + label, + accent, + badgeClass, + count, + items, + onSelect, +}: { + label: string; + accent: string; + badgeClass: string; + count: number; + items: VectorComponentDef[]; + onSelect: (comp: VectorComponentDef) => void; +}) { + const [open, setOpen] = useState(true); + + return ( + + + + {label} + + {count} + + + +
+ {items.map((comp) => ( + onSelect(comp)} + > + +
+ + {comp.displayName} + + + {comp.kind} + +
+
+ +

+ {comp.description} +

+
+
+ ))} +
+
+
+ ); +} diff --git a/src/app/(dashboard)/library/shared-components/page.tsx b/src/app/(dashboard)/library/shared-components/page.tsx index 4e509df..3f8ec66 100644 --- a/src/app/(dashboard)/library/shared-components/page.tsx +++ b/src/app/(dashboard)/library/shared-components/page.tsx @@ -12,6 +12,12 @@ import { Badge } from "@/components/ui/badge"; import { Input } from "@/components/ui/input"; import { Skeleton } from "@/components/ui/skeleton"; import { PageHeader } from "@/components/page-header"; +import { + Card, + CardContent, + CardHeader, + CardTitle, +} from "@/components/ui/card"; import { Collapsible, CollapsibleContent, @@ -216,26 +222,30 @@ function KindSection({ -
+
{items.map((sc) => ( - +
+ + {sc.linkedPipelineCount} linked + {formatRelativeTime(sc.updatedAt)} +
+ + ))}