Upload beautify
This commit is contained in:
101
docs/ui/tag-input.md
Normal file
101
docs/ui/tag-input.md
Normal file
@@ -0,0 +1,101 @@
|
||||
# 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 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=<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
|
||||
|
||||
```jsx
|
||||
<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.
|
||||
Reference in New Issue
Block a user