shadcn-vue/apps/www/src/examples/forms/components/AccountForm.vue

209 lines
6.8 KiB
Vue

<script setup lang="ts">
import { h, ref } from 'vue'
import * as z from 'zod'
import { toDate } from 'radix-vue/date'
import { toTypedSchema } from '@vee-validate/zod'
import { Check, ChevronsUpDown } from 'lucide-vue-next'
import { CalendarDate, DateFormatter, getLocalTimeZone, today } from '@internationalized/date'
import { cn } from '@/lib/utils'
import RadixIconsCalendar from '~icons/radix-icons/calendar'
import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from '@/lib/registry/new-york/ui/form'
import { Input } from '@/lib/registry/new-york/ui/input'
import { Separator } from '@/lib/registry/new-york/ui/separator'
import {
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
} from '@/lib/registry/new-york/ui/command'
import { Button } from '@/lib/registry/new-york/ui/button'
import {
Popover,
PopoverContent,
PopoverTrigger,
} from '@/lib/registry/new-york/ui/popover'
import { Calendar } from '@/lib/registry/new-york/ui/calendar'
import { toast } from '@/lib/registry/new-york/ui/toast'
const open = ref(false)
const dateValue = ref()
const placeholder = ref()
const languages = [
{ label: 'English', value: 'en' },
{ label: 'French', value: 'fr' },
{ label: 'German', value: 'de' },
{ label: 'Spanish', value: 'es' },
{ label: 'Portuguese', value: 'pt' },
{ label: 'Russian', value: 'ru' },
{ label: 'Japanese', value: 'ja' },
{ label: 'Korean', value: 'ko' },
{ label: 'Chinese', value: 'zh' },
] as const
const df = new DateFormatter('en-US', {
dateStyle: 'long',
})
const accountFormSchema = toTypedSchema(z.object({
name: z
.string({
required_error: 'Required.',
})
.min(2, {
message: 'Name must be at least 2 characters.',
})
.max(30, {
message: 'Name must not be longer than 30 characters.',
}),
dob: z.string().datetime().optional().refine(date => date !== undefined, 'Please select a valid date.'),
language: z.string().min(1, 'Please select a language.'),
}))
// https://github.com/logaretm/vee-validate/issues/3521
// https://github.com/logaretm/vee-validate/discussions/3571
async function onSubmit(values: any) {
toast({
title: 'You submitted the following values:',
description: h('pre', { class: 'mt-2 w-[340px] rounded-md bg-slate-950 p-4' }, h('code', { class: 'text-white' }, JSON.stringify(values, null, 2))),
})
}
</script>
<template>
<div>
<h3 class="text-lg font-medium">
Account
</h3>
<p class="text-sm text-muted-foreground">
Update your account settings. Set your preferred language and timezone.
</p>
</div>
<Separator />
<Form v-slot="{ setFieldValue }" :validation-schema="accountFormSchema" class="space-y-8" @submit="onSubmit">
<FormField v-slot="{ componentField }" name="name">
<FormItem>
<FormLabel>Name</FormLabel>
<FormControl>
<Input type="text" placeholder="Your name" v-bind="componentField" />
</FormControl>
<FormDescription>
This is the name that will be displayed on your profile and in emails.
</FormDescription>
<FormMessage />
</FormItem>
</FormField>
<FormField v-slot="{ field, value }" name="dob">
<FormItem class="flex flex-col">
<FormLabel>Date of birth</FormLabel>
<Popover>
<PopoverTrigger as-child>
<FormControl>
<Button
variant="outline" :class="cn(
'w-[240px] justify-start text-left font-normal',
!value && 'text-muted-foreground',
)"
>
<RadixIconsCalendar class="mr-2 h-4 w-4 opacity-50" />
<span>{{ value ? df.format(toDate(dateValue, getLocalTimeZone())) : "Pick a date" }}</span>
</Button>
</FormControl>
</PopoverTrigger>
<PopoverContent class="p-0">
<Calendar
v-model:placeholder="placeholder"
v-model="dateValue"
calendar-label="Date of birth"
initial-focus
:min-value="new CalendarDate(1900, 1, 1)"
:max-value="today(getLocalTimeZone())"
@update:model-value="(v) => {
if (v) {
dateValue = v
setFieldValue('dob', toDate(v).toISOString())
}
else {
dateValue = undefined
setFieldValue('dob', undefined)
}
}"
/>
</PopoverContent>
</Popover>
<FormDescription>
Your date of birth is used to calculate your age.
</FormDescription>
<FormMessage />
</FormItem>
<input type="hidden" v-bind="field">
</FormField>
<FormField v-slot="{ value }" name="language">
<FormItem class="flex flex-col">
<FormLabel>Language</FormLabel>
<Popover v-model:open="open">
<PopoverTrigger as-child>
<FormControl>
<Button
variant="outline" role="combobox" :aria-expanded="open" :class="cn(
'w-[200px] justify-between',
!value && 'text-muted-foreground',
)"
>
{{ value ? languages.find(
(language) => language.value === value,
)?.label : 'Select language...' }}
<ChevronsUpDown class="ml-2 h-4 w-4 shrink-0 opacity-50" />
</Button>
</FormControl>
</PopoverTrigger>
<PopoverContent class="w-[200px] p-0">
<Command>
<CommandInput placeholder="Search language..." />
<CommandEmpty>No language found.</CommandEmpty>
<CommandList>
<CommandGroup>
<CommandItem
v-for="language in languages" :key="language.value" :value="language.label"
@select="() => {
setFieldValue('language', language.value)
open = false
}"
>
<Check
:class="cn(
'mr-2 h-4 w-4',
value === language.value ? 'opacity-100' : 'opacity-0',
)"
/>
{{ language.label }}
</CommandItem>
</CommandGroup>
</CommandList>
</Command>
</PopoverContent>
</Popover>
<FormDescription>
This is the language that will be used in the dashboard.
</FormDescription>
<FormMessage />
</FormItem>
</FormField>
<div class="flex justify-start">
<Button type="submit">
Update account
</Button>
</div>
</Form>
</template>