Files
HaimKortovich a2eb1f3789 Add nuxt-skills and update auto quotes to use new policy API structure
- Add nuxt-skills (vue, nuxt, nuxt-ui) to .claude/skills/
- Create useCustomerSelection() composable for managing insured/buyer selection
- Create usePolicyApi() composable for policy API operations
- Update auto quote components to use insured/buyer instead of client
- Update vehicle fields: remove valorVehiculo, add market_value, requested_value, rc_limits
- Make chassis_number and engine_number optional
- Update auto quote types and composables to match new API structure
- Update auto quote page to submit to policy API with new structure
2026-04-27 14:56:53 -05:00

329 lines
8.6 KiB
Markdown

# Forms
## Form Components
| Component | Purpose | Key Props | Version |
| ---------------- | ------------------- | --------------------------------------------- | ------- |
| `UInput` | Text input | `type`, `placeholder`, `icon`, `loading` | |
| `UTextarea` | Multi-line text | `rows`, `autoresize`, `maxrows` | |
| `USelect` | Native select | `options`, `placeholder` | |
| `USelectMenu` | Custom select | `options`, `searchable`, `multiple` | |
| `UInputMenu` | Autocomplete | `options`, `searchable` | |
| `UCheckbox` | Single checkbox | `label`, `description` | |
| `UCheckboxGroup` | Multiple checkboxes | `items`, `orientation` | |
| `URadioGroup` | Radio options | `items`, `orientation` | |
| `USwitch` | Toggle switch | `label`, `description`, `on-icon`, `off-icon` | |
| `USlider` | Range slider | `min`, `max`, `step` | |
| `UPinInput` | Code/OTP input | `length`, `type`, `mask` | |
| `UInputNumber` | Number input | `min`, `max`, `step`, `format-options` | |
| `UInputDate` | Date picker | `mode`, `range`, `locale` | v4.2+ |
| `UInputTime` | Time picker | `hour-cycle`, `minute-step` | v4.2+ |
| `UInputTags` | Tag input | `max`, `placeholder` | |
| `UColorPicker` | Color selection | `format`, `swatches` | |
| `UFileUpload` | File upload | `accept`, `multiple`, `max-files` | |
## Basic Input Examples
```vue
<script setup>
const email = ref('')
const bio = ref('')
const country = ref('')
</script>
<template>
<!-- Text input -->
<UInput v-model="email" type="email" placeholder="Email" icon="i-heroicons-envelope" />
<!-- With validation state -->
<UInput v-model="email" :color="emailError ? 'error' : undefined" />
<!-- Textarea -->
<UTextarea v-model="bio" placeholder="Bio" :rows="3" autoresize />
<!-- Select -->
<USelect v-model="country" :options="['USA', 'Canada', 'Mexico']" placeholder="Country" />
</template>
```
## SelectMenu (Custom Dropdown)
```vue
<script setup>
const selected = ref()
const options = [
{ label: 'John', value: 'john', avatar: { src: '/john.png' } },
{ label: 'Jane', value: 'jane', avatar: { src: '/jane.png' } }
]
</script>
<template>
<USelectMenu
v-model="selected"
:options="options"
searchable
clear
placeholder="Select user"
>
<template #option="{ option }">
<UAvatar v-bind="option.avatar" size="xs" />
<span>{{ option.label }}</span>
</template>
</USelectMenu>
</template>
```
### SelectMenu/InputMenu Props (v4.4+)
```vue
<USelectMenu
v-model="selected"
:options="options"
clear <!-- Add clear button (v4.4+) -->
:viewport-ref="ref" <!-- For infinite scroll (v4.4+) -->
/>
```
## Checkbox & Radio
```vue
<script setup>
const agreed = ref(false)
const plan = ref('free')
const features = ref([])
</script>
<template>
<!-- Single checkbox -->
<UCheckbox v-model="agreed" label="I agree to terms" description="Required" />
<!-- Radio group -->
<URadioGroup
v-model="plan"
:items="[
{ label: 'Free', value: 'free', description: '$0/mo' },
{ label: 'Pro', value: 'pro', description: '$10/mo' }
]"
/>
<!-- Checkbox group -->
<UCheckboxGroup
v-model="features"
:items="[
{ label: 'Dark mode', value: 'dark' },
{ label: 'Notifications', value: 'notifications' }
]"
/>
</template>
```
## Form Validation
Uses Standard Schema (Zod, Valibot, Yup, Joi, etc.)
### With Zod
```vue
<script setup lang="ts">
import { z } from 'zod'
const schema = z.object({
email: z.string().email('Invalid email'),
password: z.string().min(8, 'Min 8 characters')
})
type Schema = z.output<typeof schema>
const state = reactive<Partial<Schema>>({
email: '',
password: ''
})
const form = ref()
async function onSubmit() {
await form.value.validate()
// Submit logic
}
</script>
<template>
<UForm ref="form" :schema="schema" :state="state" @submit="onSubmit">
<UFormField name="email" label="Email">
<UInput v-model="state.email" type="email" />
</UFormField>
<UFormField name="password" label="Password">
<UInput v-model="state.password" type="password" />
</UFormField>
<UButton type="submit">Submit</UButton>
</UForm>
</template>
```
### With Valibot
```vue
<script setup lang="ts">
import * as v from 'valibot'
const schema = v.object({
email: v.pipe(v.string(), v.email('Invalid email')),
password: v.pipe(v.string(), v.minLength(8, 'Min 8 characters'))
})
type Schema = v.InferOutput<typeof schema>
const state = reactive<Partial<Schema>>({
email: '',
password: ''
})
</script>
<template>
<UForm :schema="schema" :state="state" @submit="onSubmit">
<!-- Same as above -->
</UForm>
</template>
```
## UFormField Props
```vue
<UFormField
name="email" <!-- Field name (matches state key) -->
label="Email" <!-- Label text -->
description="Your email" <!-- Help text -->
hint="Optional" <!-- Right-aligned hint -->
required <!-- Shows asterisk -->
:help="error?.message" <!-- Error message -->
>
<UInput v-model="state.email" />
</UFormField>
```
## UFieldGroup (Group Fields)
```vue
<UFieldGroup label="Name">
<UInput v-model="firstName" placeholder="First" />
<UInput v-model="lastName" placeholder="Last" />
</UFieldGroup>
```
## Input States
```vue
<!-- Disabled -->
<UInput disabled placeholder="Disabled" />
<!-- Loading -->
<UInput :loading="true" placeholder="Loading..." />
<!-- With icon -->
<UInput icon="i-heroicons-magnifying-glass" placeholder="Search" />
<!-- Trailing icon -->
<UInput trailing-icon="i-heroicons-x-mark" placeholder="Clearable" />
```
## File Upload
```vue
<script setup>
const { files, open, reset } = useFileUpload()
</script>
<template>
<UFileUpload v-model="files" accept="image/*" multiple :max-files="5">
<UButton @click="open">Upload</UButton>
</UFileUpload>
</template>
```
**Note (v4.4):** FileUpload now emits `null` when clearing files (previously emitted empty array).
## Date & Time Pickers (v4.2+)
### Date Picker
```vue
<script setup>
const date = ref(new Date())
const range = ref({ start: new Date(), end: new Date() })
</script>
<template>
<!-- Single date -->
<UInputDate v-model="date" />
<!-- Date range -->
<UInputDate v-model="range" mode="range" />
<!-- With locale -->
<UInputDate v-model="date" locale="es" />
</template>
```
### Time Picker
```vue
<script setup>
import { Time } from '@internationalized/date'
const time = ref(new Time(12, 0))
</script>
<template>
<!-- Basic time picker -->
<UInputTime v-model="time" />
<!-- 24-hour format -->
<UInputTime v-model="time" hour-cycle="24" />
<!-- Custom step (minutes) -->
<UInputTime v-model="time" :minute-step="15" />
</template>
```
## Common Patterns
### Login Form
```vue
<UForm :schema="loginSchema" :state="state" @submit="login">
<UFormField name="email" label="Email" required>
<UInput v-model="state.email" type="email" icon="i-heroicons-envelope" />
</UFormField>
<UFormField name="password" label="Password" required>
<UInput v-model="state.password" type="password" icon="i-heroicons-lock-closed" />
</UFormField>
<UButton type="submit" block :loading="loading">Sign in</UButton>
</UForm>
```
### Settings Form
```vue
<UForm :state="settings" @submit="save">
<UFormField name="notifications" label="Notifications">
<USwitch v-model="settings.notifications" label="Enable notifications" />
</UFormField>
<UFormField name="theme" label="Theme">
<URadioGroup v-model="settings.theme" :items="themeOptions" />
</UFormField>
<UButton type="submit">Save</UButton>
</UForm>
```
## Best Practices
| Do | Don't |
| --------------------------------- | -------------------------- |
| Use UForm + UFormField | Build custom form wrappers |
| Use Standard Schema (Zod/Valibot) | Write custom validation |
| Use v-model on form inputs | Manually sync form state |
| Use `required` prop for asterisks | Add asterisks manually |
| Use `description` for help text | Use separate help elements |