feat: add profile section in forms example
This commit is contained in:
parent
686ac00cab
commit
b12bff136e
|
|
@ -30,7 +30,7 @@ const examples = [
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Forms',
|
name: 'Forms',
|
||||||
href: '/examples/forms',
|
href: '/examples/forms/forms',
|
||||||
code: 'https://github.com/radix-vue/shadcn-vue/tree/dev/apps/www/src/examples/forms',
|
code: 'https://github.com/radix-vue/shadcn-vue/tree/dev/apps/www/src/examples/forms',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
||||||
0
apps/www/src/content/examples/forms/account.md
Normal file
0
apps/www/src/content/examples/forms/account.md
Normal file
0
apps/www/src/content/examples/forms/appearence.md
Normal file
0
apps/www/src/content/examples/forms/appearence.md
Normal file
5
apps/www/src/content/examples/forms/forms.md
Normal file
5
apps/www/src/content/examples/forms/forms.md
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
<script setup>
|
||||||
|
import FormsExample from "@/examples/forms/Example.vue"
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<FormsExample />
|
||||||
174
apps/www/src/examples/forms/Example.vue
Normal file
174
apps/www/src/examples/forms/Example.vue
Normal file
|
|
@ -0,0 +1,174 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from 'vue'
|
||||||
|
import * as z from 'zod'
|
||||||
|
import SidebarNav from './components/SidebarNav.vue'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
|
import { Input } from '@/lib/registry/new-york/ui/input'
|
||||||
|
import { Label } from '@/lib/registry/new-york/ui/label'
|
||||||
|
import { Separator } from '@/lib/registry/new-york/ui/separator'
|
||||||
|
import { Textarea } from '@/lib/registry/new-york/ui/textarea'
|
||||||
|
import {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectGroup,
|
||||||
|
SelectItem,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue,
|
||||||
|
} from '@/lib/registry/default/ui/select'
|
||||||
|
import { Button } from '@/lib/registry/default/ui/button'
|
||||||
|
|
||||||
|
const verifiedEmails = ref(['m@example.com', 'm@google.com', 'm@support.com'])
|
||||||
|
|
||||||
|
const profileForm = ref({
|
||||||
|
username: '',
|
||||||
|
email: '',
|
||||||
|
bio: 'I own a computer.',
|
||||||
|
urls: [
|
||||||
|
{ value: 'https://shadcn.com' },
|
||||||
|
{ value: 'http://twitter.com/shadcn' },
|
||||||
|
],
|
||||||
|
})
|
||||||
|
|
||||||
|
const profileFormSchema = z.object({
|
||||||
|
username: z
|
||||||
|
.string()
|
||||||
|
.min(2, {
|
||||||
|
message: 'Username must be at least 2 characters.',
|
||||||
|
})
|
||||||
|
.max(30, {
|
||||||
|
message: 'Username must not be longer than 30 characters.',
|
||||||
|
}),
|
||||||
|
email: z
|
||||||
|
.string({
|
||||||
|
required_error: 'Please select an email to display.',
|
||||||
|
})
|
||||||
|
.email(),
|
||||||
|
bio: z.string().max(160, { message: 'Bio must not be longer than 160 characters.' }).min(4, { message: 'Bio must be at least 2 characters.' }),
|
||||||
|
urls: z
|
||||||
|
.array(
|
||||||
|
z.object({
|
||||||
|
value: z.string().url({ message: 'Please enter a valid URL.' }),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.optional(),
|
||||||
|
})
|
||||||
|
|
||||||
|
type ProfileFormValues = z.infer<typeof profileFormSchema>
|
||||||
|
const errors = ref<z.ZodFormattedError<ProfileFormValues> | null>(null)
|
||||||
|
|
||||||
|
async function handleSubmit() {
|
||||||
|
const result = profileFormSchema.safeParse(profileForm.value)
|
||||||
|
if (!result.success) {
|
||||||
|
errors.value = result.error.format()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
errors.value = null
|
||||||
|
console.log('Form submitted!')
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="md:hidden" />
|
||||||
|
<div class="hidden space-y-6 p-10 pb-16 md:block">
|
||||||
|
<div class="space-y-0.5">
|
||||||
|
<h2 class="text-2xl font-bold tracking-tight">
|
||||||
|
Settings
|
||||||
|
</h2>
|
||||||
|
<p class="text-muted-foreground">
|
||||||
|
Manage your account settings and set e-mail preferences.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<Separator class="my-6" />
|
||||||
|
<div class="flex flex-col space-y-8 lg:flex-row lg:space-x-12 lg:space-y-0">
|
||||||
|
<aside class="-mx-4 lg:w-1/5">
|
||||||
|
<SidebarNav />
|
||||||
|
</aside>
|
||||||
|
<div class="flex-1 lg:max-w-2xl">
|
||||||
|
<div class="space-y-6">
|
||||||
|
<div>
|
||||||
|
<h3 class="text-lg font-medium">
|
||||||
|
Profile
|
||||||
|
</h3>
|
||||||
|
<p class="text-sm text-muted-foreground">
|
||||||
|
This is how others will see you on the site.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<Separator />
|
||||||
|
<form class="space-y-8" @submit.prevent="handleSubmit">
|
||||||
|
<div class="grid gap-2">
|
||||||
|
<Label for="username" :class="cn('text-sm', errors?.username && 'text-destructive')">
|
||||||
|
Username
|
||||||
|
</Label>
|
||||||
|
<Input id="username" v-model="profileForm.username" placeholder="shadcn" />
|
||||||
|
<span class="text-muted-foreground text-sm">
|
||||||
|
This is your public display name. It can be your real name or a pseudonym. You can only change this once every 30 days.
|
||||||
|
</span>
|
||||||
|
<div v-if="errors?.username" class="text-sm text-destructive">
|
||||||
|
<span v-for="error in errors.username._errors" :key="error">{{ error }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="grid gap-2">
|
||||||
|
<Label for="email" :class="cn('text-sm', errors?.email && 'text-destructive')">
|
||||||
|
Email
|
||||||
|
</Label>
|
||||||
|
<Select id="email" v-model="profileForm.email">
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="Select an email" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectGroup>
|
||||||
|
<SelectItem v-for="email in verifiedEmails" :key="email" :value="email">
|
||||||
|
{{ email }}
|
||||||
|
</SelectItem>
|
||||||
|
</SelectGroup>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
<span class="text-muted-foreground text-sm">
|
||||||
|
You can manage verified email addresses in your email settings.
|
||||||
|
</span>
|
||||||
|
<div v-if="errors?.email" class="text-sm text-destructive">
|
||||||
|
<span v-for="error in errors.email._errors" :key="error">{{ error }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="grid gap-2">
|
||||||
|
<Label for="bio" :class="cn('text-sm', errors?.bio && 'text-destructive')">
|
||||||
|
Bio
|
||||||
|
</Label>
|
||||||
|
<Textarea id="bio" v-model="profileForm.bio" placeholder="Tell us about yourself." />
|
||||||
|
<span class="text-muted-foreground text-sm">
|
||||||
|
You can @mention other users and organizations to link to them.
|
||||||
|
</span>
|
||||||
|
<div v-if="errors?.bio" class="text-sm text-destructive">
|
||||||
|
<span v-for="error in errors.bio._errors" :key="error">{{ error }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="grid gap-2">
|
||||||
|
<Label for="urls" :class="cn('text-sm', errors?.urls && 'text-destructive')">
|
||||||
|
URLs
|
||||||
|
</Label>
|
||||||
|
<Input v-for="(url, index) in profileForm.urls" id="urls" :key="index" v-model="url.value" />
|
||||||
|
<div v-if="errors?.urls" class="text-sm text-destructive">
|
||||||
|
<span v-for="error in errors.urls._errors" :key="error">{{ error }}</span>
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
class="text-xs w-20 mt-2"
|
||||||
|
@click="profileForm.urls?.push({ value: '' })"
|
||||||
|
>
|
||||||
|
Add URL
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-start">
|
||||||
|
<Button type="submit">
|
||||||
|
Update profile
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
53
apps/www/src/examples/forms/components/SidebarNav.vue
Normal file
53
apps/www/src/examples/forms/components/SidebarNav.vue
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useRoute } from 'vitepress'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
import { Button } from '@/lib/registry/new-york/ui/button'
|
||||||
|
|
||||||
|
interface Item {
|
||||||
|
title: string
|
||||||
|
href: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const $route = useRoute()
|
||||||
|
|
||||||
|
const sidebarNavItems: Item[] = [
|
||||||
|
{
|
||||||
|
title: 'Profile',
|
||||||
|
href: '/examples/forms/forms',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Account',
|
||||||
|
href: '/examples/forms/account',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Appearance',
|
||||||
|
href: '/examples/forms/appearance',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Notifications',
|
||||||
|
href: '/examples/forms/notifications',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Display',
|
||||||
|
href: '/examples/forms/display',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<nav class="flex space-x-2 lg:flex-col lg:space-x-0 lg:space-y-1">
|
||||||
|
<Button
|
||||||
|
v-for="item in sidebarNavItems"
|
||||||
|
:key="item.title"
|
||||||
|
as="a"
|
||||||
|
:href="item.href"
|
||||||
|
variant="ghost"
|
||||||
|
:class="cn(
|
||||||
|
'w-full text-left justify-start',
|
||||||
|
$route.path === `${item.href}.html` && 'bg-muted hover:bg-muted',
|
||||||
|
)"
|
||||||
|
>
|
||||||
|
{{ item.title }}
|
||||||
|
</Button>
|
||||||
|
</nav>
|
||||||
|
</template>
|
||||||
Loading…
Reference in New Issue
Block a user