# TagInput UI Component ## Overview `TagInput` is the reusable tag entry component for Skinbase artwork flows. It is designed for: - Upload page - Artwork edit page - Admin moderation screens The component encapsulates all tag UX behavior (chips, search, keyboard flow, AI suggestions, status hints) so pages stay thin. ## File Location - `resources/js/components/tags/TagInput.jsx` ## Props | Prop | Type | Default | Description | | --- | --- | --- | --- | | `value` | `string \| string[]` | required | Controlled selected tags. Can be CSV string or array. | | `onChange` | `(tags: string[]) => void` | required | Called whenever selected tags change. | | `suggestedTags` | `Array` | `[]` | AI-suggested tags shown as clickable pills. | | `disabled` | `boolean` | `false` | Disables input and interactions. | | `maxTags` | `number` | `15` | Maximum number of selected tags. | | `minLength` | `number` | `2` | Minimum normalized tag length. | | `maxLength` | `number` | `32` | Maximum normalized tag length. | | `placeholder` | `string` | `Type tags…` | Input placeholder text. | | `searchEndpoint` | `string` | `/api/tags/search` | Search API endpoint. | | `popularEndpoint` | `string` | `/api/tags/popular` | Popular tags endpoint when input is empty. | ## Normalization Tags are normalized client-side before being added: - lowercase - trim - spaces → `-` - remove unsupported characters - collapse repeated separators - max length = 32 Server-side normalization/validation still applies and remains authoritative. ## Keyboard & Interaction - `Enter` → add tag - `Comma` → add tag - `Tab` → accept highlighted suggestion - `Backspace` (empty input) → remove last tag - `Escape` → close suggestion dropdown - Paste CSV (`a, b, c`) → split and add valid tags ## Accessibility - Suggestion dropdown uses `role="listbox"` - Suggestions use `role="option"` - Active item uses `aria-selected` - Input uses `aria-expanded`, `aria-controls`, `aria-autocomplete` ## API Usage The component performs debounced search (300ms): - `GET /api/tags/search?q=` - `GET /api/tags/popular` (empty query) Behavior: - caches recent query results - aborts outdated requests - max 8 suggestions - excludes already-selected tags - shows non-blocking message when search fails ## Upload Integration Example ```jsx { dispatch({ type: 'SET_METADATA', payload: { tags: nextTags.join(', ') } }) }} suggestedTags={props.suggested_tags || []} maxTags={15} minLength={2} maxLength={32} /> ``` ## Events & Save Strategy `TagInput` itself does not persist to backend on keystrokes. Persistence is done on save/publish boundary by page logic, e.g.: - `PUT /api/artworks/{id}/tags` This keeps UI responsive and avoids unnecessary API writes.