2.8 KiB
2.8 KiB
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<string | object> |
[] |
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 tagComma→ add tagTab→ accept highlighted suggestionBackspace(empty input) → remove last tagEscape→ 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=<query>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
<TagInput
value={state.metadata.tags}
onChange={(nextTags) => {
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.