feat(web): TagInput component for project multi-select #11

Closed
opened 2026-02-07 02:18:55 +00:00 by forbes · 0 comments
Owner

Context

The CreateItemPane currently uses toggle buttons to select projects. This needs to be replaced with a searchable multi-select tag input that scales better when there are many projects.

This component will also be reusable for other tag-like fields across the app.

Component: TagInput

File: web/src/components/TagInput.tsx

Props

interface TagInputProps {
  value: string[];
  onChange: (ids: string[]) => void;
  placeholder?: string;
  searchFn: (query: string) => Promise<{ id: string; label: string }[]>;
}

Behavior

  • Container styled like a form input (--ctp-base bg, --ctp-surface1 border)
  • Selected tags as inline pills (rgba(203,166,247,0.15) bg, --ctp-mauve text) with x remove button
  • Bare input that grows to fill remaining width
  • On typing, debounce 200ms, call searchFn(query)
  • Dropdown below input with matching results, 28px rows, max-height 160px
  • Already-selected items excluded from results
  • Click or Enter selects, Escape closes dropdown

Usage for Projects

searchFn calls GET /api/projects and filters client-side, mapping to { id: project.code, label: project.code + ' - ' + project.name }

No Backend Changes Required

Uses existing GET /api/projects endpoint.

Acceptance Criteria

  • TagInput component renders with pills for selected values
  • Search input filters results with debounce
  • Dropdown shows matches, excludes already-selected
  • Add/remove tags works
  • TypeScript compiles clean
## Context The CreateItemPane currently uses toggle buttons to select projects. This needs to be replaced with a searchable multi-select tag input that scales better when there are many projects. This component will also be reusable for other tag-like fields across the app. ## Component: `TagInput` **File**: `web/src/components/TagInput.tsx` ### Props ```typescript interface TagInputProps { value: string[]; onChange: (ids: string[]) => void; placeholder?: string; searchFn: (query: string) => Promise<{ id: string; label: string }[]>; } ``` ### Behavior - Container styled like a form input (`--ctp-base` bg, `--ctp-surface1` border) - Selected tags as inline pills (`rgba(203,166,247,0.15)` bg, `--ctp-mauve` text) with x remove button - Bare input that grows to fill remaining width - On typing, debounce 200ms, call searchFn(query) - Dropdown below input with matching results, 28px rows, max-height 160px - Already-selected items excluded from results - Click or Enter selects, Escape closes dropdown ### Usage for Projects `searchFn` calls `GET /api/projects` and filters client-side, mapping to `{ id: project.code, label: project.code + ' - ' + project.name }` ### No Backend Changes Required Uses existing `GET /api/projects` endpoint. ## Acceptance Criteria - [ ] TagInput component renders with pills for selected values - [ ] Search input filters results with debounce - [ ] Dropdown shows matches, excludes already-selected - [ ] Add/remove tags works - [ ] 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#11