diff --git a/AGENTS.md b/AGENTS.md index a12c97841..f339c0756 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -100,7 +100,8 @@ Concise but detailed reference for contributors working across the `moeru-ai/air - Prefer Vue v-bind class arrays for readability when working with UnoCSS & tailwindcss: do `:class="['px-2 py-1','flex items-center','bg-white/50 dark:bg-black/50']"`, don't do `class="px-2 py-1 flex items-center bg-white/50 dark:bg-black/50"`, don't do `px="2" py="1" flex="~ items-center" bg="white/50 dark:black/50"`; avoid long inline `class=""`. Refactor legacy when you touch it. - Use/extend UnoCSS shortcuts/rules in `uno.config.ts`; add new shortcuts/rules/plugins there when standardizing styles. Prefer UnoCSS over Tailwind. - Check `apps/stage-web/src/styles` for existing animations; reuse or extend before adding new ones. If you need config references, see `apps/stage-web/tsconfig.json` and `uno.config.ts`. -- Build primitives on `@proj-airi/ui` (reka-ui) instead of raw DOM; see `packages/ui/src/components/Form` for patterns. +- Build primitives on `@proj-airi/ui` (reka-ui) instead of raw DOM; see [`docs/ai/context/ui-components.md`](docs/ai/context/ui-components.md) for the full component API reference and `packages/ui/src/components/Form` for implementation patterns. +- **When adding or updating components in `packages/ui`**, update [`docs/ai/context/ui-components.md`](docs/ai/context/ui-components.md) to reflect the change (props, slots, emits, description). - Use Iconify icon sets; avoid bespoke SVGs. - Animations: keep intuitive, lively, and readable. - `useDark` (VueUse): set `disableTransition: false` or use existing composables in `packages/ui`. diff --git a/docs/ai/context/ui-components.md b/docs/ai/context/ui-components.md new file mode 100644 index 000000000..1e28f627f --- /dev/null +++ b/docs/ai/context/ui-components.md @@ -0,0 +1,551 @@ +# `@proj-airi/ui` Component Reference + +> **Auto-maintained**: When adding or updating components in `packages/ui`, update this document accordingly. + +Standardized primitives built on [reka-ui](https://reka-ui.com/). Minimal business logic — use these instead of raw DOM elements. + +Source: `packages/ui/src/components/` + +--- + +## Animations + +### TransitionBidirectional + +Bidirectional Vue `` wrapper with customizable CSS classes. + +| Prop | Type | Default | Description | +|------|------|---------|-------------| +| `fromClass` | `string?` | — | CSS class for initial state | +| `activeClass` | `string?` | — | CSS class during transition | +| `toClass` | `string?` | — | CSS class for final state | + +**Slots**: `default` + +### TransitionHorizontal + +Horizontal slide/fade transition (0.5s hardcoded). + +**Props**: None | **Slots**: `default` + +### TransitionVertical + +Smooth vertical expand/collapse with height animation and opacity control. + +| Prop | Type | Default | Description | +|------|------|---------|-------------| +| `duration` | `number?` | `250` | Animation duration (ms) | +| `easingEnter` | `string?` | `'ease-in-out'` | Enter easing function | +| `easingLeave` | `string?` | `'ease-in-out'` | Leave easing function | +| `opacityClosed` | `number?` | `0` | Opacity when closed | +| `opacityOpened` | `number?` | `1` | Opacity when opened | + +**Slots**: `default` + +--- + +## Layout + +### Collapsible + +Expandable/collapsible container with trigger button and vertical animation. + +| Prop | Type | Default | Description | +|------|------|---------|-------------| +| `default` | `boolean?` | — | Initial visibility | +| `label` | `string?` | — | Trigger button label | + +**v-model**: `visible: boolean` +**Slots**: `trigger({ visible, setVisible })`, `default({ visible, setVisible })` + +### Screen + +Responsive screen component that calculates canvas dimensions based on breakpoints. + +**Props**: None | **Slots**: `default({ width, height })` + +### Skeleton + +Loading placeholder with animation. + +| Prop | Type | Default | Description | +|------|------|---------|-------------| +| `animation` | `'pulse' \| 'wave' \| 'none'` | `'pulse'` | Animation style | + +**Slots**: `default` + +--- + +## Misc + +### Button + +Versatile button with variants, sizes, and states. + +| Prop | Type | Default | Description | +|------|------|---------|-------------| +| `toggled` | `boolean?` | `false` | Toggle state | +| `icon` | `string?` | — | UnoCSS/Iconify icon class | +| `label` | `string?` | — | Button text | +| `disabled` | `boolean?` | `false` | Disabled state | +| `loading` | `boolean?` | `false` | Loading state | +| `variant` | `'primary' \| 'secondary' \| 'secondary-muted' \| 'danger' \| 'caution' \| 'pure' \| 'ghost'` | `'primary'` | Visual variant | +| `size` | `'sm' \| 'md' \| 'lg'` | `'md'` | Size | +| `shape` | `'rounded' \| 'pill' \| 'square'` | `'pill'` | Shape | +| `block` | `boolean?` | `false` | Full width | + +**Slots**: `default` (fallback when no `label`) + +### Callout + +Alert/callout box with themed accent bar. + +| Prop | Type | Default | Description | +|------|------|---------|-------------| +| `theme` | `'primary' \| 'violet' \| 'lime' \| 'orange'` | `'primary'` | Color theme | +| `label` | `string?` | — | Title | + +**Slots**: `label`, `default` + +### ContainerError + +Error display with copy/feedback buttons and scrollable stack trace. + +| Prop | Type | Default | Description | +|------|------|---------|-------------| +| `error` | `unknown?` | — | Error object | +| `message` | `string?` | — | Custom message | +| `stack` | `string?` | — | Stack trace | +| `includeStack` | `boolean?` | `true` | Show stack | +| `showCopyButton` | `boolean?` | `true` | Show copy button | +| `showFeedbackButton` | `boolean?` | `true` | Show feedback button | +| `copyButtonLabel` | `string?` | `'Copy'` | Copy button text | +| `copiedButtonLabel` | `string?` | `'Copied'` | Copied state text | +| `feedbackButtonLabel` | `string?` | `'Feedback'` | Feedback button text | +| `heightPreset` | `'sm' \| 'md' \| 'lg' \| 'xl' \| 'auto'` | `'md'` | Container height | + +**Emits**: `copy(content: string)`, `feedback()` + +### DoubleCheckButton + +Two-stage confirmation button — click once to reveal confirm/cancel. + +| Prop | Type | Default | Description | +|------|------|---------|-------------| +| `variant` | `ButtonVariant` | `'danger'` | Confirm button variant | +| `cancelVariant` | `ButtonVariant` | `'secondary'` | Cancel button variant | +| `size` | `'sm' \| 'md' \| 'lg'` | `'md'` | Size | +| `block` | `boolean?` | `false` | Full width | +| `disabled` | `boolean?` | `false` | Disabled | +| `loading` | `boolean?` | `false` | Loading | + +**Emits**: `confirm()`, `cancel()` +**Slots**: `default` (initial text), `confirm` (confirm text), `cancel` (cancel text) + +### Progress + +Linear progress bar with animated shine. + +| Prop | Type | Default | Description | +|------|------|---------|-------------| +| `progress` | `number` | *(required)* | Percentage 0–100 | +| `barClass` | `string?` | — | Custom bar color class | + +--- + +## Form — Input + +### Input + +Basic text/number input. + +| Prop | Type | Default | Description | +|------|------|---------|-------------| +| `type` | `InputType?` | — | HTML input type | +| `variant` | `'primary' \| 'secondary' \| 'primary-dimmed'` | `'primary'` | Visual variant | +| `size` | `'sm' \| 'md' \| 'lg'` | `'md'` | Size | + +**v-model**: `modelValue: string | number` + +### BasicInputFile + +Low-level file input with drag-drop support. + +| Prop | Type | Default | Description | +|------|------|---------|-------------| +| `accept` | `string?` | — | Accepted MIME types | +| `multiple` | `boolean?` | — | Allow multiple files | +| `isDraggingClasses` | `string \| string[]?` | — | Classes when dragging | +| `isNotDraggingClasses` | `string \| string[]?` | — | Classes when not dragging | + +**v-model**: `modelValue: File[]` +**Slots**: `default({ isDragging, firstFile, files })` + +### InputFile + +File input with preview and drag-drop UI. + +| Prop | Type | Default | Description | +|------|------|---------|-------------| +| `accept` | `string?` | — | Accepted file types | +| `multiple` | `boolean?` | — | Allow multiple | +| `placeholder` | `string?` | `'Choose file'` | Placeholder text | + +**v-model**: `modelValue: File[] | undefined` + +### InputFileCard + +Styled file upload card with drag-and-drop zone. + +| Prop | Type | Default | Description | +|------|------|---------|-------------| +| `accept` | `string?` | — | Accepted file types | +| `multiple` | `boolean?` | — | Allow multiple | + +**v-model**: inherits from `BasicInputFile` +**Slots**: `default` (custom upload UI) + +### InputKeyValue + +Two-column input for key-value pairs. + +| Prop | Type | Default | Description | +|------|------|---------|-------------| +| `name` | `string?` | — | Input name attribute | +| `keyPlaceholder` | `string?` | — | Key placeholder | +| `valuePlaceholder` | `string?` | — | Value placeholder | + +**v-model**: `propertyKey: string`, `propertyValue: string` + +--- + +## Form — Textarea + +### BasicTextarea + +Auto-resizing textarea with submit and paste-file events. + +| Prop | Type | Default | Description | +|------|------|---------|-------------| +| `defaultHeight` | `string?` | — | Initial height when empty | +| `submitOnEnter` | `boolean?` | `true` | Submit on Enter (Shift+Enter for newline) | + +**v-model**: `input: string` +**Emits**: `submit(message: string)`, `pasteFile(files: File[])` + +### Textarea + +Styled textarea wrapping `BasicTextarea`. + +**v-model**: `modelValue: string` + +--- + +## Form — Checkbox / Radio + +### Checkbox + +Toggle switch using reka-ui `SwitchRoot`. + +| Prop | Type | Default | Description | +|------|------|---------|-------------| +| `disabled` | `boolean?` | — | Disabled state | + +**v-model**: `modelValue: boolean` + +### Radio + +Single radio button for radio groups. + +| Prop | Type | Default | Description | +|------|------|---------|-------------| +| `id` | `string` | *(required)* | Unique ID | +| `name` | `string` | *(required)* | Radio group name | +| `value` | `string` | *(required)* | Option value | +| `title` | `string` | *(required)* | Display label | +| `deprecated` | `boolean?` | `false` | Deprecation indicator | + +**v-model**: `modelValue: string` + +--- + +## Form — Range + +### Range + +Horizontal slider with progress visualization. + +| Prop | Type | Default | Description | +|------|------|---------|-------------| +| `min` | `number?` | `0` | Minimum value | +| `max` | `number?` | `100` | Maximum value | +| `step` | `number?` | `1` | Step increment | +| `disabled` | `boolean?` | `false` | Disabled | +| `thumbColor` | `string?` | `'#9090906e'` | Thumb color | +| `trackColor` | `string?` | `'gray'` | Track color | +| `trackValueColor` | `string?` | `'red'` | Filled track color | + +**v-model**: `modelValue: number` + +### ColorHueRange + +HSL hue selector (0–360) with rainbow gradient. + +| Prop | Type | Default | Description | +|------|------|---------|-------------| +| `disabled` | `boolean?` | — | Disabled | + +**v-model**: `modelValue: number` + +### RoundRange + +Rounded-style slider. + +| Prop | Type | Default | Description | +|------|------|---------|-------------| +| `min` | `number?` | `0` | Minimum | +| `max` | `number?` | `100` | Maximum | +| `step` | `number?` | `1` | Step | +| `disabled` | `boolean?` | `false` | Disabled | + +**v-model**: `modelValue: number` + +--- + +## Form — Select + +### Select + +Dropdown select using reka-ui with grouping and custom rendering. + +| Prop | Type | Default | Description | +|------|------|---------|-------------| +| `options` | `SelectOptionItem[] \| SelectOptionGroupItem[]` | *(required)* | Options | +| `placeholder` | `string?` | `'Select an option'` | Placeholder | +| `disabled` | `boolean?` | `false` | Disabled | +| `by` | `string \| ((a: T, b: T) => boolean)?` | — | Custom comparison | +| `contentMinWidth` | `string \| number?` | `160` | Dropdown min width | +| `contentWidth` | `string \| number?` | — | Dropdown width | +| `shape` | `'rounded' \| 'default'` | `'default'` | Shape | +| `variant` | `'blurry' \| 'default'` | `'default'` | Variant | + +**v-model**: `modelValue: T` +**Slots**: `value({ option, value, placeholder })`, `option({ option })` + +### SelectOption + +Individual option item within `Select`. + +| Prop | Type | Default | Description | +|------|------|---------|-------------| +| `option` | `SelectOptionItem` | *(required)* | Option data | + +**Slots**: `default` + +--- + +## Form — Combobox + +### Combobox + +Searchable dropdown/autocomplete using reka-ui with grouping. + +| Prop | Type | Default | Description | +|------|------|---------|-------------| +| `options` | `ComboboxOptionItem[] \| ComboboxOptionGroupItem[]` | *(required)* | Options | +| `placeholder` | `string?` | — | Placeholder | +| `disabled` | `boolean?` | `false` | Disabled | +| `contentMinWidth` | `string \| number?` | — | Dropdown min width | +| `contentWidth` | `string \| number?` | — | Dropdown width | + +**v-model**: `modelValue: T` +**Slots**: `option({ option })`, `empty` + +### ComboboxSelect + +Simplified Combobox wrapper for string/number options. + +| Prop | Type | Default | Description | +|------|------|---------|-------------| +| `options` | `{ label, value, description?, disabled?, icon? }[]?` | — | Options | +| `placeholder` | `string?` | — | Placeholder | +| `disabled` | `boolean?` | `false` | Disabled | +| `title` | `string?` | — | Title | +| `layout` | `'horizontal' \| 'vertical'?` | — | Layout direction | +| `contentMinWidth` | `string \| number?` | — | Dropdown min width | +| `contentWidth` | `string \| number?` | — | Dropdown width | + +**v-model**: `modelValue: string | number` +**Slots**: `option({ option })`, `empty` + +### ComboboxOption + +Option item within combobox (uses provide/inject). + +| Prop | Type | Default | Description | +|------|------|---------|-------------| +| `value` | `string \| number` | *(required)* | Value | +| `label` | `string?` | — | Display text | +| `active` | `boolean?` | — | Active state | + +**Slots**: `default` + +--- + +## Form — SelectTab + +### SelectTab + +Tab-like selection using radio buttons with animated indicator. + +| Prop | Type | Default | Description | +|------|------|---------|-------------| +| `options` | `SelectTabOption[]` | *(required)* | Tab options `{ label, value, description?, icon? }` | +| `disabled` | `boolean?` | `false` | Disabled | +| `readonly` | `boolean?` | `false` | Read-only | +| `size` | `'sm' \| 'md'` | `'md'` | Size | + +**v-model**: `modelValue: T` + +--- + +## Form — Field (Labeled wrappers) + +All Field components wrap a base input with `label`, `description`, and consistent layout. Common slots: `label`, `description`. + +### FieldInput + +| Prop | Type | Default | Description | +|------|------|---------|-------------| +| `label` | `string?` | — | Label | +| `description` | `string?` | — | Helper text | +| `placeholder` | `string?` | — | Placeholder | +| `required` | `boolean?` | — | Required indicator | +| `type` | `InputType?` | — | Input type | +| `inputClass` | `string?` | — | Custom input class | +| `singleLine` | `boolean?` | `true` | `true` = input, `false` = textarea | + +**v-model**: `modelValue: T` + +### FieldCheckbox + +| Prop | Type | Default | Description | +|------|------|---------|-------------| +| `label` | `string?` | — | Label | +| `description` | `string?` | — | Helper text | +| `disabled` | `boolean?` | — | Disabled | +| `placement` | `'left' \| 'right'` | `'right'` | Switch position | + +**v-model**: `modelValue: boolean` + +### FieldTextArea + +| Prop | Type | Default | Description | +|------|------|---------|-------------| +| `label` | `string?` | — | Label | +| `description` | `string?` | — | Helper text | +| `placeholder` | `string?` | — | Placeholder | +| `required` | `boolean?` | — | Required indicator | +| `textareaClass` | `string?` | — | Custom textarea class | +| `rows` | `number?` | `6` | Rows | + +**v-model**: `modelValue: string` + +### FieldRange + +| Prop | Type | Default | Description | +|------|------|---------|-------------| +| `min` | `number?` | — | Min | +| `max` | `number?` | — | Max | +| `step` | `number?` | — | Step | +| `label` | `string?` | — | Label | +| `description` | `string?` | — | Helper text | +| `formatValue` | `(value: number) => string?` | — | Value formatter | +| `as` | `'label' \| 'div'` | `'label'` | Wrapper element | + +**v-model**: `modelValue: number` + +### FieldInputFile + +| Prop | Type | Default | Description | +|------|------|---------|-------------| +| `label` | `string?` | — | Label | +| `description` | `string?` | — | Helper text | +| `accept` | `string?` | — | Accepted types | +| `multiple` | `boolean?` | — | Multiple | +| `placeholder` | `string?` | — | Placeholder | + +**v-model**: `modelValue: File[] | undefined` + +### FieldSelect + +| Prop | Type | Default | Description | +|------|------|---------|-------------| +| `label` | `string` | *(required)* | Label | +| `description` | `string?` | — | Helper text | +| `options` | `SelectOptionItem[] \| SelectOptionGroupItem[]?` | — | Options | +| `placeholder` | `string?` | — | Placeholder | +| `disabled` | `boolean?` | — | Disabled | +| `layout` | `'horizontal' \| 'vertical'` | `'horizontal'` | Layout | +| `by` | `string \| ((a, b) => boolean)?` | — | Comparison | +| `shape` | `'rounded' \| 'default'?` | — | Shape | +| `variant` | `'blurry' \| 'default'?` | — | Variant | + +**v-model**: `modelValue: T` +**Slots**: `label`, `description`, `value`, `option` + +### FieldCombobox + +| Prop | Type | Default | Description | +|------|------|---------|-------------| +| `label` | `string` | *(required)* | Label | +| `description` | `string?` | — | Helper text | +| `options` | `{ label, value, description?, disabled?, icon? }[]?` | — | Options | +| `placeholder` | `string?` | — | Placeholder | +| `disabled` | `boolean?` | `false` | Disabled | +| `layout` | `'horizontal' \| 'vertical'` | `'horizontal'` | Layout | + +**v-model**: `modelValue: string` +**Slots**: `label`, `description`, `option`, `empty` + +### FieldKeyValues + +Dynamic key-value pair list with add/remove. + +| Prop | Type | Default | Description | +|------|------|---------|-------------| +| `label` | `string?` | — | Label | +| `description` | `string?` | — | Helper text | +| `name` | `string?` | — | Input name | +| `keyPlaceholder` | `string?` | — | Key placeholder | +| `valuePlaceholder` | `string?` | — | Value placeholder | +| `required` | `boolean?` | — | Required | +| `inputClass` | `string?` | — | Custom input class | + +**v-model**: `keyValues: { key: string, value: string }[]` +**Emits**: `remove(index: number)`, `add(key: string, value: string)` + +### FieldValues + +Dynamic string list with add/remove. + +| Prop | Type | Default | Description | +|------|------|---------|-------------| +| `label` | `string?` | — | Label | +| `description` | `string?` | — | Helper text | +| `name` | `string?` | — | Input name | +| `valuePlaceholder` | `string?` | — | Value placeholder | +| `required` | `boolean?` | — | Required | +| `inputClass` | `string?` | — | Custom input class | + +**v-model**: `items: string[]` +**Emits**: `remove(index: number)`, `add()` + +--- + +## Composables + +Exported from `packages/ui/src/composables/`: + +- **`useDeferredMount()`** — Defers component mounting (useful for heavy components). +- **`useTheme()`** — Theme management composable.