Skip to content

feat: add Combobox component - #26

Open
patrickkuei wants to merge 1 commit into
masterfrom
feat/combobox
Open

feat: add Combobox component#26
patrickkuei wants to merge 1 commit into
masterfrom
feat/combobox

Conversation

@patrickkuei

Copy link
Copy Markdown
Owner

Closes #14

Summary

  • Adds a Combobox component: a cyberpunk-styled searchable dropdown for long option lists (country pickers, tag search, corporation lookup), extending Select's controlled-value pattern with Input's focus-glow styling.
  • API decisions: follows the WAI-ARIA combobox pattern (role="combobox" on the input + a listbox popup with aria-activedescendant) so arrow keys move a highlight without moving DOM focus off the text input — same approach used for RadioGroup/Accordion's roving-tabindex widgets, adapted for a single focusable input. variant and responsive size reuse Input/Select's exact vocabulary and shadow/glow classes rather than inventing new styling. The full option list stays visible immediately on focus (an isFiltering flag distinguishes "just opened, showing the selected label" from "actively typing") so opening a combobox that already has a value doesn't appear to filter itself down to one match. allowCustomValue lets typed text that doesn't match any option commit as-is on Enter/blur — text that exactly matches an option's label (case-insensitive) still resolves to that option instead of a raw string commit.
  • No scope was deferred — the issue's full ask (options, value/onValueChange, text input filtering as you type, keyboard navigation with arrow keys/Enter/Escape, optional allowCustomValue, cyberpunk styling matching Select/Input) is covered in this PR.

Changes

  • Added Combobox (src/components/Combobox.tsx) — options, value/defaultValue/onValueChange, label, placeholder, variant, responsive size, disabled, helperText, error, allowCustomValue, noOptionsText, ariaLabel, className, id, name. No new runtime dependencies — filtering is a plain case-insensitive substring match.
  • Exported Combobox/ComboboxProps/ComboboxOption from src/components/index.ts.
  • Stories (Combobox.stories.tsx): Default, Secondary/Danger/Ghost variants, sizes (Small/Medium/Large), WithError, Disabled, AllowCustomValue, Controlled, and an AllVariants render story (cyberpunk copy throughout, e.g. "Target Sector", "Purge Target", "Ghost Protocol").
  • Tests (Combobox.test.tsx, 19 cases): renders without crashing, role="combobox"/aria-expanded/aria-controls/aria-autocomplete wiring, opens showing all options on focus, filters as you type, no-matches message, option selection via click, disabled options blocked, keyboard navigation (ArrowDown/ArrowUp/Home/End) skipping disabled options with correct aria-activedescendant, Escape reverting unmatched text, blur reverting (default) vs. committing (allowCustomValue) unmatched text, exact-label-match resolving to that option instead of a raw commit, controlled value without self-managing state, aria-selected wiring, error state + aria-invalid, disabled state, responsive size.
  • Wired Combobox into scripts/generate-manifest.js (CATEGORY_MAP → Forms, alongside Select/RadioGroup; DOC_SUMMARIES) and regenerated docs via npm run docs:generate — diff reviewed, only Combobox-related lines changed.
  • Added a CHANGELOG entry under [Unreleased].

Test plan

  • npm run lint / type-check / test -- --project=unit all pass (314 tests, including 19 new for Combobox)
  • Storybook: Default + variants + sizes + disabled + AllVariants render correctly
  • Keyboard nav + screen reader smoke test

Generated by Claude Code

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.

New component: Combobox / Autocomplete

2 participants