feat(web): CategoryPicker multi-column selector component #13

Closed
opened 2026-02-07 02:19:52 +00:00 by forbes · 0 comments
Owner

Context

The CreateItemPane currently uses a flat <select> dropdown to pick a category code from the schema's enum values. The redesign calls for a multi-column hierarchical picker (like macOS Finder column view) that makes it easier to browse and select categories when there are many options.

Current State

The schema kindred-rd has a category segment of type enum with values like A001, P001, etc. These are flat key-value pairs (code → description). There is no built-in hierarchy.

Approach: Schema-Derived Grouping

Rather than requiring a backend categories table immediately, the picker can group existing enum values by prefix pattern or by a configurable mapping. For the initial implementation:

  1. Flat mode (works today): Render all category values in a single scrollable column with search filtering. This is already an improvement over the <select> dropdown.
  2. Grouped mode (future): If/when the backend adds a categories table or hierarchical schema metadata, the picker can render multiple columns.

Component: CategoryPicker

File: web/src/components/items/CategoryPicker.tsx

Props

interface CategoryPickerProps {
  value: string;                          // selected category code, e.g. 'A001'
  onChange: (code: string) => void;
  categories: Record<string, string>;     // code → description from schema
}

Flat Mode Layout (Phase 1)

┌─────────────────────────────────────────┐
│ [Search categories...              ]     │
├─────────────────────────────────────────┤
│ A001  Electronic Assembly            ▸  │
│ A002  Mechanical Assembly            ▸  │
│ F001  Fastener - Bolt                   │
│ P001  Resistor                          │
│ P002  Capacitor                         │
│ ...                                     │
└─────────────────────────────────────────┘
  Selected: A001 — Electronic Assembly
  • Container: --ctp-base bg, 1px solid --ctp-surface1 border, 0.4rem radius
  • Search input at top (filters by code and description)
  • Scrollable list, max-height 200px
  • Each row 28px, 0.85rem font
  • Hover: --ctp-surface0 bg
  • Selected: translucent mauve bg (rgba(203,166,247,0.12)), --ctp-mauve text, weight 600
  • Code in monospace, description after dash
  • Breadcrumb below showing selected value

Multi-Column Mode (Phase 2 — future, requires #backend hierarchy)

Three side-by-side columns with sticky headers, column 1 shows groups, column 2 shows subgroups, column 3 shows leaf values. Each column scrolls independently at max 180px height.

Hook: useCategories

File: web/src/hooks/useCategories.ts

function useCategories(): {
  categories: Record<string, string>;  // flat code → description
  loading: boolean;
  error: string | null;
}

Fetches GET /api/schemas/kindred-rd on mount, extracts the category segment's values map. Caches in a module-level variable to avoid refetching.

No Backend Changes Required (Phase 1)

Uses existing GET /api/schemas/{name} endpoint. The category enum values already contain the data needed for flat mode.

Acceptance Criteria

  • CategoryPicker renders scrollable list of category values
  • Search input filters categories by code and description
  • Click selects a category, highlights it
  • Selected value shown as breadcrumb below picker
  • Replaces the <select> in CreateItemPane
  • useCategories hook fetches and caches schema data
  • TypeScript compiles clean
## Context The CreateItemPane currently uses a flat `<select>` dropdown to pick a category code from the schema's enum values. The redesign calls for a multi-column hierarchical picker (like macOS Finder column view) that makes it easier to browse and select categories when there are many options. ## Current State The schema `kindred-rd` has a `category` segment of type `enum` with values like `A001`, `P001`, etc. These are flat key-value pairs (`code → description`). There is no built-in hierarchy. ## Approach: Schema-Derived Grouping Rather than requiring a backend `categories` table immediately, the picker can group existing enum values by prefix pattern or by a configurable mapping. For the initial implementation: 1. **Flat mode** (works today): Render all category values in a single scrollable column with search filtering. This is already an improvement over the `<select>` dropdown. 2. **Grouped mode** (future): If/when the backend adds a `categories` table or hierarchical schema metadata, the picker can render multiple columns. ## Component: `CategoryPicker` **File**: `web/src/components/items/CategoryPicker.tsx` ### Props ```typescript interface CategoryPickerProps { value: string; // selected category code, e.g. 'A001' onChange: (code: string) => void; categories: Record<string, string>; // code → description from schema } ``` ### Flat Mode Layout (Phase 1) ``` ┌─────────────────────────────────────────┐ │ [Search categories... ] │ ├─────────────────────────────────────────┤ │ A001 Electronic Assembly ▸ │ │ A002 Mechanical Assembly ▸ │ │ F001 Fastener - Bolt │ │ P001 Resistor │ │ P002 Capacitor │ │ ... │ └─────────────────────────────────────────┘ Selected: A001 — Electronic Assembly ``` - Container: `--ctp-base` bg, `1px solid --ctp-surface1` border, `0.4rem` radius - Search input at top (filters by code and description) - Scrollable list, max-height 200px - Each row 28px, `0.85rem` font - Hover: `--ctp-surface0` bg - Selected: translucent mauve bg (`rgba(203,166,247,0.12)`), `--ctp-mauve` text, weight 600 - Code in monospace, description after dash - Breadcrumb below showing selected value ### Multi-Column Mode (Phase 2 — future, requires #backend hierarchy) Three side-by-side columns with sticky headers, column 1 shows groups, column 2 shows subgroups, column 3 shows leaf values. Each column scrolls independently at max 180px height. ## Hook: `useCategories` **File**: `web/src/hooks/useCategories.ts` ```typescript function useCategories(): { categories: Record<string, string>; // flat code → description loading: boolean; error: string | null; } ``` Fetches `GET /api/schemas/kindred-rd` on mount, extracts the `category` segment's `values` map. Caches in a module-level variable to avoid refetching. ## No Backend Changes Required (Phase 1) Uses existing `GET /api/schemas/{name}` endpoint. The category enum values already contain the data needed for flat mode. ## Acceptance Criteria - [ ] CategoryPicker renders scrollable list of category values - [ ] Search input filters categories by code and description - [ ] Click selects a category, highlights it - [ ] Selected value shown as breadcrumb below picker - [ ] Replaces the `<select>` in CreateItemPane - [ ] useCategories hook fetches and caches schema data - [ ] TypeScript compiles clean
Sign in to join this conversation.
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: kindred/silo#13