Skip to content

Commit 43209b4

Browse files
zernoniaclaude
andauthored
docs: use componentField in vee-validate examples (#1909)
* docs: use componentField in vee-validate examples shadcn-vue form controls are Vue components with a modelValue prop bound internally via v-model. Binding `field` passes `value`, which falls through as a plain attribute and is overwritten by the component's own v-model, so initialValues never render. Typing still works, which hid the bug. Switch Input, Textarea, InputGroupInput, InputGroupTextarea, Select, Checkbox, RadioGroup and Switch to `v-bind="componentField"`. Checkbox arrays keep manual binding via the `value` and `handleChange` slot props, since one field owns the whole array. Also: - Add Component/Native tabs to Anatomy showing when each slot prop applies - Fix invalid `(checked | 'indeterminate')` handler signature - Add missing `type="checkbox"` to the Switch example - Move `:aria-invalid` from RadioGroupItem to RadioGroup - Drop `@blur` from Select; SelectRoot sets inheritAttrs: false and discards it - Realign stale showLineNumbers ranges Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * docs: migrate from form to field components --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
1 parent cc9f4ec commit 43209b4

13 files changed

Lines changed: 291 additions & 202 deletions
Lines changed: 31 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,17 @@
11
<script setup lang="ts">
22
import { toTypedSchema } from '@vee-validate/zod'
3-
import { useForm } from 'vee-validate'
3+
import { useForm, Field as VeeField } from 'vee-validate'
44
import { toast } from 'vue-sonner'
55
66
import { z } from 'zod'
77
import { Button } from '@/registry/new-york-v4/ui/button'
88
import {
9-
FormControl,
10-
FormDescription,
11-
FormField,
12-
FormItem,
13-
FormLabel,
14-
FormMessage,
15-
} from '@/registry/new-york-v4/ui/form'
9+
Field,
10+
FieldDescription,
11+
FieldError,
12+
FieldGroup,
13+
FieldLabel,
14+
} from '@/registry/new-york-v4/ui/field'
1615
import {
1716
NativeSelect,
1817
NativeSelectOption,
@@ -37,12 +36,18 @@ const onSubmit = form.handleSubmit((values) => {
3736
</script>
3837

3938
<template>
40-
<form class="w-full max-w-sm space-y-6" @submit="onSubmit">
41-
<FormField v-slot="{ componentField }" name="country">
42-
<FormItem>
43-
<FormLabel>Country</FormLabel>
44-
<FormControl>
45-
<NativeSelect v-bind="(componentField as any)">
39+
<form class="w-full max-w-sm" @submit="onSubmit">
40+
<FieldGroup>
41+
<VeeField v-slot="{ componentField, errors }" name="country">
42+
<Field :data-invalid="!!errors.length">
43+
<FieldLabel for="form-native-select-country">
44+
Country
45+
</FieldLabel>
46+
<NativeSelect
47+
id="form-native-select-country"
48+
v-bind="(componentField as any)"
49+
:aria-invalid="!!errors.length"
50+
>
4651
<NativeSelectOption value="">
4752
Select a country
4853
</NativeSelectOption>
@@ -56,16 +61,18 @@ const onSubmit = form.handleSubmit((values) => {
5661
Canada
5762
</NativeSelectOption>
5863
</NativeSelect>
59-
</FormControl>
60-
<FormDescription>
61-
Select a country
62-
</FormDescription>
63-
<FormMessage />
64-
</FormItem>
65-
</FormField>
64+
<FieldDescription>
65+
Select a country
66+
</FieldDescription>
67+
<FieldError v-if="errors.length" :errors="errors" />
68+
</Field>
69+
</VeeField>
6670

67-
<Button type="submit" class="w-full">
68-
Submit
69-
</Button>
71+
<Field>
72+
<Button type="submit" class="w-full">
73+
Submit
74+
</Button>
75+
</Field>
76+
</FieldGroup>
7077
</form>
7178
</template>

apps/v4/components/demo/StepperForm.vue

Lines changed: 82 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,17 @@
11
<script setup lang="ts">
22
import { Check, Circle, Dot } from '@lucide/vue'
33
import { toTypedSchema } from '@vee-validate/zod'
4+
import { Field as VeeField, Form as VeeForm } from 'vee-validate'
45
import { h, ref } from 'vue'
56
import { toast } from 'vue-sonner'
67
import * as z from 'zod'
78
import { Button } from '@/registry/new-york-v4/ui/button'
8-
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@/registry/new-york-v4/ui/form'
9+
import {
10+
Field,
11+
FieldError,
12+
FieldGroup,
13+
FieldLabel,
14+
} from '@/registry/new-york-v4/ui/field'
915
import { Input } from '@/registry/new-york-v4/ui/input'
1016
import {
1117
Select,
@@ -66,7 +72,7 @@ function onSubmit(values: any) {
6672
</script>
6773

6874
<template>
69-
<Form
75+
<VeeForm
7076
v-slot="{ meta, values, validate }"
7177
as="" keep-values :validation-schema="toTypedSchema(formSchema[stepIndex - 1]!)"
7278
>
@@ -125,62 +131,85 @@ function onSubmit(values: any) {
125131
</StepperItem>
126132
</div>
127133

128-
<div class="flex flex-col gap-4 mt-4">
134+
<FieldGroup class="mt-4">
129135
<template v-if="stepIndex === 1">
130-
<FormField v-slot="{ componentField }" name="fullName">
131-
<FormItem>
132-
<FormLabel>Full Name</FormLabel>
133-
<FormControl>
134-
<Input type="text" v-bind="componentField" />
135-
</FormControl>
136-
<FormMessage />
137-
</FormItem>
138-
</FormField>
136+
<VeeField v-slot="{ componentField, errors }" name="fullName">
137+
<Field :data-invalid="!!errors.length">
138+
<FieldLabel for="form-stepper-fullName">
139+
Full Name
140+
</FieldLabel>
141+
<Input
142+
id="form-stepper-fullName"
143+
v-bind="componentField"
144+
type="text"
145+
:aria-invalid="!!errors.length"
146+
/>
147+
<FieldError v-if="errors.length" :errors="errors" />
148+
</Field>
149+
</VeeField>
139150

140-
<FormField v-slot="{ componentField }" name="email">
141-
<FormItem>
142-
<FormLabel>Email</FormLabel>
143-
<FormControl>
144-
<Input type="email " v-bind="componentField" />
145-
</FormControl>
146-
<FormMessage />
147-
</FormItem>
148-
</FormField>
151+
<VeeField v-slot="{ componentField, errors }" name="email">
152+
<Field :data-invalid="!!errors.length">
153+
<FieldLabel for="form-stepper-email">
154+
Email
155+
</FieldLabel>
156+
<Input
157+
id="form-stepper-email"
158+
v-bind="componentField"
159+
type="email"
160+
:aria-invalid="!!errors.length"
161+
/>
162+
<FieldError v-if="errors.length" :errors="errors" />
163+
</Field>
164+
</VeeField>
149165
</template>
150166

151167
<template v-if="stepIndex === 2">
152-
<FormField v-slot="{ componentField }" name="password">
153-
<FormItem>
154-
<FormLabel>Password</FormLabel>
155-
<FormControl>
156-
<Input type="password" v-bind="componentField" />
157-
</FormControl>
158-
<FormMessage />
159-
</FormItem>
160-
</FormField>
168+
<VeeField v-slot="{ componentField, errors }" name="password">
169+
<Field :data-invalid="!!errors.length">
170+
<FieldLabel for="form-stepper-password">
171+
Password
172+
</FieldLabel>
173+
<Input
174+
id="form-stepper-password"
175+
v-bind="componentField"
176+
type="password"
177+
:aria-invalid="!!errors.length"
178+
/>
179+
<FieldError v-if="errors.length" :errors="errors" />
180+
</Field>
181+
</VeeField>
161182

162-
<FormField v-slot="{ componentField }" name="confirmPassword">
163-
<FormItem>
164-
<FormLabel>Confirm Password</FormLabel>
165-
<FormControl>
166-
<Input type="password" v-bind="componentField" />
167-
</FormControl>
168-
<FormMessage />
169-
</FormItem>
170-
</FormField>
183+
<VeeField v-slot="{ componentField, errors }" name="confirmPassword">
184+
<Field :data-invalid="!!errors.length">
185+
<FieldLabel for="form-stepper-confirmPassword">
186+
Confirm Password
187+
</FieldLabel>
188+
<Input
189+
id="form-stepper-confirmPassword"
190+
v-bind="componentField"
191+
type="password"
192+
:aria-invalid="!!errors.length"
193+
/>
194+
<FieldError v-if="errors.length" :errors="errors" />
195+
</Field>
196+
</VeeField>
171197
</template>
172198

173199
<template v-if="stepIndex === 3">
174-
<FormField v-slot="{ componentField }" name="favoriteDrink">
175-
<FormItem>
176-
<FormLabel>Drink</FormLabel>
177-
200+
<VeeField v-slot="{ componentField, errors }" name="favoriteDrink">
201+
<Field :data-invalid="!!errors.length">
202+
<FieldLabel for="form-stepper-favoriteDrink">
203+
Drink
204+
</FieldLabel>
178205
<Select v-bind="componentField">
179-
<FormControl>
180-
<SelectTrigger class="w-full!">
181-
<SelectValue placeholder="Select a drink" />
182-
</SelectTrigger>
183-
</FormControl>
206+
<SelectTrigger
207+
id="form-stepper-favoriteDrink"
208+
class="w-full!"
209+
:aria-invalid="!!errors.length"
210+
>
211+
<SelectValue placeholder="Select a drink" />
212+
</SelectTrigger>
184213
<SelectContent>
185214
<SelectGroup>
186215
<SelectItem value="coffee">
@@ -195,11 +224,11 @@ function onSubmit(values: any) {
195224
</SelectGroup>
196225
</SelectContent>
197226
</Select>
198-
<FormMessage />
199-
</FormItem>
200-
</FormField>
227+
<FieldError v-if="errors.length" :errors="errors" />
228+
</Field>
229+
</VeeField>
201230
</template>
202-
</div>
231+
</FieldGroup>
203232

204233
<div class="flex items-center justify-between mt-4">
205234
<Button :disabled="isPrevDisabled" variant="outline" size="sm" @click="prevStep()">
@@ -218,5 +247,5 @@ function onSubmit(values: any) {
218247
</div>
219248
</form>
220249
</Stepper>
221-
</Form>
250+
</VeeForm>
222251
</template>

apps/v4/components/demo/VeeValidateArrayDemo.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ const onSubmit = handleSubmit((data) => {
8787
<VeeField
8888
v-for="(field, index) in fields"
8989
:key="field.key"
90-
v-slot="{ field: fieldProps, errors: fieldErrors }"
90+
v-slot="{ componentField: fieldProps, errors: fieldErrors }"
9191
:name="`emails[${index}].address`"
9292
>
9393
<Field

apps/v4/components/demo/VeeValidateCheckboxDemo.vue

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ const onSubmit = handleSubmit((data) => {
8080
<CardContent>
8181
<form id="form-vee-checkbox" @submit="onSubmit">
8282
<FieldGroup>
83-
<VeeField v-slot="{ field, errors }" name="responses" type="checkbox">
83+
<VeeField v-slot="{ componentField, errors }" name="responses" type="checkbox">
8484
<FieldSet :data-invalid="!!errors.length">
8585
<FieldLegend variant="label">
8686
Responses
@@ -93,10 +93,8 @@ const onSubmit = handleSubmit((data) => {
9393
<Field orientation="horizontal">
9494
<Checkbox
9595
id="form-vee-checkbox-responses"
96-
:name="field.name"
97-
:model-value="field.value"
96+
v-bind="componentField"
9897
disabled
99-
@update:model-value="field.onChange"
10098
/>
10199
<FieldLabel
102100
for="form-vee-checkbox-responses"
@@ -110,7 +108,7 @@ const onSubmit = handleSubmit((data) => {
110108
</FieldSet>
111109
</VeeField>
112110
<FieldSeparator />
113-
<VeeField v-slot="{ field, errors }" name="tasks">
111+
<VeeField v-slot="{ value, handleChange, errors }" name="tasks">
114112
<FieldSet :data-invalid="!!errors.length">
115113
<FieldLegend variant="label">
116114
Tasks
@@ -127,17 +125,15 @@ const onSubmit = handleSubmit((data) => {
127125
>
128126
<Checkbox
129127
:id="`form-vee-checkbox-${task.id}`"
130-
:name="field.name"
131128
:aria-invalid="!!errors.length"
132-
:model-value="field.value?.includes(task.id)"
129+
:model-value="value?.includes(task.id)"
133130
@update:model-value="
134131
(checked: boolean | 'indeterminate') => {
135-
const newValue = checked
136-
? [...(field.value || []), task.id]
137-
: (field.value || []).filter(
138-
(value: string) => value !== task.id,
139-
);
140-
field.onChange(newValue);
132+
handleChange(
133+
checked
134+
? [...(value || []), task.id]
135+
: (value || []).filter((id: string) => id !== task.id),
136+
);
141137
}
142138
"
143139
/>

0 commit comments

Comments
 (0)