Skip to content

ui: card layout for shared components with collapsible kind sections#85

Merged
TerrifiedBug merged 1 commit intomainfrom
library-ui-3e3
Mar 10, 2026
Merged

ui: card layout for shared components with collapsible kind sections#85
TerrifiedBug merged 1 commit intomainfrom
library-ui-3e3

Conversation

@TerrifiedBug
Copy link
Owner

Summary

  • Replace table-row layout with card grid on the shared components list page, matching the card style used in the rest of the library
  • Group the component catalog on the "New Shared Component" page into collapsible Source / Transform / Sink sections with count badges, making the ~117 item list much easier to navigate
  • Both pages now share consistent visual structure: collapsible kind headers with colored labels + responsive card grids

Test plan

  • Open /library/shared-components — existing components render as cards in a grid within collapsible kind sections
  • Click a component card — navigates to detail page
  • Collapse/expand kind sections — chevron rotates, content toggles
  • Open /library/shared-components/new — catalog grouped by Sources, Transforms, Sinks with collapsible headers
  • Collapse a section — cards hide, count badge still visible
  • Search filters across all sections, hiding empty groups
  • Click a catalog card — proceeds to configure step as before
  • Verify responsive layout at mobile / tablet / desktop widths

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.
@greptile-apps
Copy link
Contributor

greptile-apps bot commented Mar 10, 2026

Greptile Summary

This PR refactors two library pages to use a card-grid layout inside collapsible kind sections, replacing the previous table-row layout in shared-components/page.tsx and the flat card grid in shared-components/new/page.tsx. The change is purely presentational and the data-fetching/mutation logic is untouched.

  • Both pages now share the same Collapsible + card-grid pattern, with colored kind labels and count badges.
  • groupedCatalog memo is correctly derived from filteredCatalog so search correctly hides empty groups.
  • Keyboard accessibility regression in page.tsx: the native <button> element used for each shared-component row was replaced by a <Card> (<div>), removing Tab-focusability and Enter/Space activation. The focus-visible:ring styles were also dropped in the process.

Confidence Score: 4/5

  • Safe to merge with one minor keyboard accessibility regression to address.
  • The changes are purely UI/presentational with no mutations, auth, or data-access logic touched. The one real regression is that <button><Card> in page.tsx removes keyboard focusability and activation for component rows, which is a correctness issue but not a data-loss or security risk.
  • src/app/(dashboard)/library/shared-components/page.tsx — the Card elements replacing button rows need tabIndex/role/onKeyDown restored for keyboard accessibility.

Important Files Changed

Filename Overview
src/app/(dashboard)/library/shared-components/page.tsx Replaces table-row layout with card grid inside collapsible kind sections. The <button> element used for each row was replaced with a <Card> (<div>), removing native keyboard focus and activation.
src/app/(dashboard)/library/shared-components/new/page.tsx Adds groupedCatalog memo derived from filteredCatalog, extracts a new CatalogKindSection collapsible component, and reorganises the catalog grid into per-kind sections. Logic is correct; no pre-existing accessibility issues were worsened here since the cards were already <div>-based.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[SharedComponentsPage] --> B{selectedEnvironmentId?}
    B -- No --> C[Prompt to select environment]
    B -- Yes --> D[useQuery sharedComponent.list]
    D --> E{filtered.length == 0?}
    E -- Yes --> F[Empty state]
    E -- No --> G[grouped by KIND_ORDER]
    G --> H[KindSection SOURCE]
    G --> I[KindSection TRANSFORM]
    G --> J[KindSection SINK]
    H & I & J --> K[Collapsible header + Card grid]
    K --> L[onClick → router.push detail page]

    M[NewSharedComponentPage] --> N{step}
    N -- select --> O[filteredCatalog via useMemo]
    O --> P{filteredCatalog.length == 0?}
    P -- Yes --> Q[Empty search state]
    P -- No --> R[groupedCatalog via useMemo]
    R --> S[CatalogKindSection × 3]
    S --> T[onClick → handleSelectComponent]
    T --> U[step = configure]
    N -- configure --> V[SchemaForm + Details card]
    V --> W[createMutation.mutate]
Loading

Last reviewed commit: 99df614

Comment on lines 226 to 249
{items.map((sc) => (
<button
<Card
key={sc.id}
className="cursor-pointer transition-colors hover:bg-accent/50"
onClick={() => onItemClick(sc.id)}
className="flex w-full cursor-pointer items-center gap-4 px-4 py-3 text-left transition-colors hover:bg-muted/50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-inset"
>
<Link2 className="h-4 w-4 shrink-0 text-muted-foreground" />
<div className="min-w-0 flex-1">
<p className="truncate text-sm font-medium">{sc.name}</p>
<CardHeader className="pb-2">
<div className="flex items-start justify-between gap-2">
<CardTitle className="text-sm">{sc.name}</CardTitle>
<Badge variant="outline" className={cn("text-xs", badgeClass)}>
v{sc.version}
</Badge>
</div>
</CardHeader>
<CardContent>
<p className="text-xs text-muted-foreground">{sc.componentType}</p>
</div>
<div className="flex items-center gap-3 shrink-0 text-xs text-muted-foreground">
<span>{sc.linkedPipelineCount} linked</span>
<Badge variant="outline" className={cn("text-xs", badgeClass)}>
v{sc.version}
</Badge>
<span className="w-16 text-right">{formatRelativeTime(sc.updatedAt)}</span>
</div>
</button>
<div className="mt-2 flex items-center gap-2 text-xs text-muted-foreground">
<Link2 className="h-3 w-3" />
<span>{sc.linkedPipelineCount} linked</span>
<span className="ml-auto">{formatRelativeTime(sc.updatedAt)}</span>
</div>
</CardContent>
</Card>
))}
Copy link
Contributor

Choose a reason for hiding this comment

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

Keyboard accessibility regression

The previous implementation used a native <button> element (see the diff), which is natively keyboard-focusable (Tab) and activatable (Enter/Space). The replacement <Card> renders as a <div>, which receives no keyboard focus by default, and the focus-visible:ring styles were removed along with it.

Users who navigate with a keyboard or assistive technology can no longer reach or activate individual component cards. To restore the original behaviour, add tabIndex={0}, role="button", an onKeyDown handler, and the focus-visible ring classes back to the <Card>.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/app/(dashboard)/library/shared-components/page.tsx
Line: 226-249

Comment:
**Keyboard accessibility regression**

The previous implementation used a native `<button>` element (see the diff), which is natively keyboard-focusable (Tab) and activatable (Enter/Space). The replacement `<Card>` renders as a `<div>`, which receives no keyboard focus by default, and the `focus-visible:ring` styles were removed along with it.

Users who navigate with a keyboard or assistive technology can no longer reach or activate individual component cards. To restore the original behaviour, add `tabIndex={0}`, `role="button"`, an `onKeyDown` handler, and the focus-visible ring classes back to the `<Card>`.

How can I resolve this? If you propose a fix, please make it concise.

@TerrifiedBug TerrifiedBug merged commit 14f606f into main Mar 10, 2026
12 checks passed
@TerrifiedBug TerrifiedBug deleted the library-ui-3e3 branch March 10, 2026 14:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant