diff --git a/apps/www/app/examples/dashboard/components/team-switcher.tsx b/apps/www/app/examples/dashboard/components/team-switcher.tsx index c4496ee802..2f9b58e0cb 100644 --- a/apps/www/app/examples/dashboard/components/team-switcher.tsx +++ b/apps/www/app/examples/dashboard/components/team-switcher.tsx @@ -82,7 +82,7 @@ export default function TeamSwitcher({ className }: TeamSwitcherProps) { + + + + + date > new Date() || date < new Date("1900-01-01") + } + initialFocus + /> + + + + Your date of birth is used to calculate your age. + + + + )} + /> + ( + + Language + + + + + + + + + + No framework found. + + {languages.map((language) => ( + { + form.setValue("language", value) + }} + > + + {language.label} + + ))} + + + + + + This is the language that will be used in the dashboard. + + + + )} + /> + + + + ) +} diff --git a/apps/www/app/examples/forms/account/page.tsx b/apps/www/app/examples/forms/account/page.tsx new file mode 100644 index 0000000000..f48c4e1d3a --- /dev/null +++ b/apps/www/app/examples/forms/account/page.tsx @@ -0,0 +1,18 @@ +import { Separator } from "@/components/ui/separator" +import { AccountForm } from "@/app/examples/forms/account/account-form" + +export default function SettingsAccountPage() { + return ( +
+
+

Account

+

+ Update your account settings. Set your preferred language and + timezone. +

+
+ + +
+ ) +} diff --git a/apps/www/app/examples/forms/appearance/appearance-form.tsx b/apps/www/app/examples/forms/appearance/appearance-form.tsx new file mode 100644 index 0000000000..21229028f8 --- /dev/null +++ b/apps/www/app/examples/forms/appearance/appearance-form.tsx @@ -0,0 +1,164 @@ +"use client" + +import { zodResolver } from "@hookform/resolvers/zod" +import { ChevronDown } from "lucide-react" +import { useForm } from "react-hook-form" +import * as z from "zod" + +import { cn } from "@/lib/utils" +import { Button, buttonVariants } from "@/components/ui/button" +import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group" +import { toast } from "@/components/ui/use-toast" +import { + Form, + FormControl, + FormDescription, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@/components/react-hook-form/form" + +const appearanceFormSchema = z.object({ + theme: z.enum(["light", "dark"], { + required_error: "Please select a theme.", + }), + font: z.enum(["inter", "manrope", "system"], { + invalid_type_error: "Select a font", + required_error: "Please select a font.", + }), +}) + +type AppearanceFormValues = z.infer + +// This can come from your database or API. +const defaultValues: Partial = { + theme: "light", +} + +export function AppearanceForm() { + const form = useForm({ + resolver: zodResolver(appearanceFormSchema), + defaultValues, + }) + + function onSubmit(data: AppearanceFormValues) { + toast({ + title: "You submitted the following values:", + description: ( +
+          {JSON.stringify(data, null, 2)}
+        
+ ), + }) + } + + return ( +
+ + ( + + Font +
+ + + + +
+ + Set the font you want to use in the dashboard. + + +
+ )} + /> + ( + + Theme + + Select the theme for the dashboard. + + + + + + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + Light + + + + + + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + Dark + + + + + + )} + /> + + + + + ) +} diff --git a/apps/www/app/examples/forms/appearance/page.tsx b/apps/www/app/examples/forms/appearance/page.tsx new file mode 100644 index 0000000000..929253454b --- /dev/null +++ b/apps/www/app/examples/forms/appearance/page.tsx @@ -0,0 +1,18 @@ +import { Separator } from "@/components/ui/separator" +import { AppearanceForm } from "@/app/examples/forms/appearance/appearance-form" + +export default function SettingsAppearancePage() { + return ( +
+
+

Appearance

+

+ Customize the appearance of the app. Automatically switch between day + and night themes. +

+
+ + +
+ ) +} diff --git a/apps/www/app/examples/forms/components/sidebar-nav.tsx b/apps/www/app/examples/forms/components/sidebar-nav.tsx new file mode 100644 index 0000000000..addcfefdc5 --- /dev/null +++ b/apps/www/app/examples/forms/components/sidebar-nav.tsx @@ -0,0 +1,44 @@ +"use client" + +import Link from "next/link" +import { usePathname } from "next/navigation" + +import { cn } from "@/lib/utils" +import { buttonVariants } from "@/components/ui/button" + +interface SidebarNavProps extends React.HTMLAttributes { + items: { + href: string + title: string + }[] +} + +export function SidebarNav({ className, items, ...props }: SidebarNavProps) { + const pathname = usePathname() + + return ( + + ) +} diff --git a/apps/www/app/examples/forms/display/display-form.tsx b/apps/www/app/examples/forms/display/display-form.tsx new file mode 100644 index 0000000000..09147907aa --- /dev/null +++ b/apps/www/app/examples/forms/display/display-form.tsx @@ -0,0 +1,132 @@ +"use client" + +import { zodResolver } from "@hookform/resolvers/zod" +import { useForm } from "react-hook-form" +import * as z from "zod" + +import { Button } from "@/components/ui/button" +import { Checkbox } from "@/components/ui/checkbox" +import { toast } from "@/components/ui/use-toast" +import { + Form, + FormControl, + FormDescription, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@/components/react-hook-form/form" + +const items = [ + { + id: "recents", + label: "Recents", + }, + { + id: "home", + label: "Home", + }, + { + id: "applications", + label: "Applications", + }, + { + id: "desktop", + label: "Desktop", + }, + { + id: "downloads", + label: "Downloads", + }, + { + id: "documents", + label: "Documents", + }, +] as const + +const displayFormSchema = z.object({ + items: z.array(z.string()).refine((value) => value.some((item) => item), { + message: "You have to select at least one item.", + }), +}) + +type DisplayFormValues = z.infer + +// This can come from your database or API. +const defaultValues: Partial = { + items: ["recents", "home"], +} + +export function DisplayForm() { + const form = useForm({ + resolver: zodResolver(displayFormSchema), + defaultValues, + }) + + function onSubmit(data: DisplayFormValues) { + toast({ + title: "You submitted the following values:", + description: ( +
+          {JSON.stringify(data, null, 2)}
+        
+ ), + }) + } + + return ( +
+ + ( + +
+ Sidebar + + Select the items you want to display in the sidebar. + +
+ {items.map((item) => ( + { + return ( + + + { + return checked + ? field.onChange([...field.value, item.id]) + : field.onChange( + field.value?.filter( + (value) => value !== item.id + ) + ) + }} + /> + + + {item.label} + + + ) + }} + /> + ))} + +
+ )} + /> + + + + ) +} diff --git a/apps/www/app/examples/forms/display/page.tsx b/apps/www/app/examples/forms/display/page.tsx new file mode 100644 index 0000000000..5d8942301d --- /dev/null +++ b/apps/www/app/examples/forms/display/page.tsx @@ -0,0 +1,17 @@ +import { Separator } from "@/components/ui/separator" +import { DisplayForm } from "@/app/examples/forms/display/display-form" + +export default function SettingsDisplayPage() { + return ( +
+
+

Display

+

+ Turn items on or off to control what's displayed in the app. +

+
+ + +
+ ) +} diff --git a/apps/www/app/examples/forms/layout.tsx b/apps/www/app/examples/forms/layout.tsx new file mode 100644 index 0000000000..36208d26b3 --- /dev/null +++ b/apps/www/app/examples/forms/layout.tsx @@ -0,0 +1,75 @@ +import { Metadata } from "next" +import Image from "next/image" + +import { Separator } from "@/components/ui/separator" +import { SidebarNav } from "@/app/examples/forms/components/sidebar-nav" + +export const metadata: Metadata = { + title: "Forms", + description: "Advanced form example using react-hook-form and Zod.", +} + +const sidebarNavItems = [ + { + title: "Profile", + href: "/examples/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", + }, +] + +interface SettingsLayoutProps { + children: React.ReactNode +} + +export default function SettingsLayout({ children }: SettingsLayoutProps) { + return ( + <> +
+ Forms + Forms +
+
+
+

Settings

+

+ Manage your account settings and set e-mail preferences. +

+
+ +
+ +
{children}
+
+
+ + ) +} diff --git a/apps/www/app/examples/forms/notifications/notifications-form.tsx b/apps/www/app/examples/forms/notifications/notifications-form.tsx new file mode 100644 index 0000000000..344719cf6b --- /dev/null +++ b/apps/www/app/examples/forms/notifications/notifications-form.tsx @@ -0,0 +1,222 @@ +"use client" + +import Link from "next/link" +import { zodResolver } from "@hookform/resolvers/zod" +import { useForm } from "react-hook-form" +import * as z from "zod" + +import { Button } from "@/components/ui/button" +import { Checkbox } from "@/components/ui/checkbox" +import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group" +import { Switch } from "@/components/ui/switch" +import { toast } from "@/components/ui/use-toast" +import { + Form, + FormControl, + FormDescription, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@/components/react-hook-form/form" + +const notificationsFormSchema = z.object({ + type: z.enum(["all", "mentions", "none"], { + required_error: "You need to select a notification type.", + }), + mobile: z.boolean().default(false).optional(), + communication_emails: z.boolean().default(false).optional(), + social_emails: z.boolean().default(false).optional(), + marketing_emails: z.boolean().default(false).optional(), + security_emails: z.boolean(), +}) + +type NotificationsFormValues = z.infer + +// This can come from your database or API. +const defaultValues: Partial = { + communication_emails: false, + marketing_emails: false, + social_emails: true, + security_emails: true, +} + +export function NotificationsForm() { + const form = useForm({ + resolver: zodResolver(notificationsFormSchema), + defaultValues, + }) + + function onSubmit(data: NotificationsFormValues) { + toast({ + title: "You submitted the following values:", + description: ( +
+          {JSON.stringify(data, null, 2)}
+        
+ ), + }) + } + + return ( +
+ + ( + + Notify me about... + + + + + + + + All new messages + + + + + + + + Direct messages and mentions + + + + + + + Nothing + + + + + + )} + /> +
+

Email Notifications

+
+ ( + +
+ + Communication emails + + + Receive emails about your account activity. + +
+ + + +
+ )} + /> + ( + +
+ + Marketing emails + + + Receive emails about new products, features, and more. + +
+ + + +
+ )} + /> + ( + +
+ Social emails + + Receive emails for friend requests, follows, and more. + +
+ + + +
+ )} + /> + ( + +
+ Security emails + + Receive emails about your account activity and security. + +
+ + + +
+ )} + /> +
+
+ ( + + + + +
+ + Use different settings for my mobile devices + + + You can manage your mobile notifications in the{" "} + mobile settings page. + +
+
+ )} + /> + + + + ) +} diff --git a/apps/www/app/examples/forms/notifications/page.tsx b/apps/www/app/examples/forms/notifications/page.tsx new file mode 100644 index 0000000000..659dd4b0a8 --- /dev/null +++ b/apps/www/app/examples/forms/notifications/page.tsx @@ -0,0 +1,18 @@ +import { Separator } from "@/components/ui/separator" +import { AccountForm } from "@/app/examples/forms/account/account-form" +import { NotificationsForm } from "@/app/examples/forms/notifications/notifications-form" + +export default function SettingsNotificationsPage() { + return ( +
+
+

Notifications

+

+ Configure how you receive notifications. +

+
+ + +
+ ) +} diff --git a/apps/www/app/examples/forms/page.tsx b/apps/www/app/examples/forms/page.tsx new file mode 100644 index 0000000000..3f5f336649 --- /dev/null +++ b/apps/www/app/examples/forms/page.tsx @@ -0,0 +1,17 @@ +import { Separator } from "@/components/ui/separator" +import { ProfileForm } from "@/app/examples/forms/profile-form" + +export default function SettingsProfilePage() { + return ( +
+
+

Profile

+

+ This is how others will see you on the site. +

+
+ + +
+ ) +} diff --git a/apps/www/app/examples/forms/profile-form.tsx b/apps/www/app/examples/forms/profile-form.tsx new file mode 100644 index 0000000000..ec94aa5251 --- /dev/null +++ b/apps/www/app/examples/forms/profile-form.tsx @@ -0,0 +1,191 @@ +"use client" + +import Link from "next/link" +import { zodResolver } from "@hookform/resolvers/zod" +import { useFieldArray, useForm } from "react-hook-form" +import * as z from "zod" + +import { cn } from "@/lib/utils" +import { Button } from "@/components/ui/button" +import { Input } from "@/components/ui/input" +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select" +import { Textarea } from "@/components/ui/textarea" +import { toast } from "@/components/ui/use-toast" +import { + Form, + FormControl, + FormDescription, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@/components/react-hook-form/form" + +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).min(4), + urls: z + .array( + z.object({ + value: z.string().url({ message: "Please enter a valid URL." }), + }) + ) + .optional(), +}) + +type ProfileFormValues = z.infer + +// This can come from your database or API. +const defaultValues: Partial = { + bio: "I own a computer.", + urls: [ + { value: "https://shadcn.com" }, + { value: "http://twitter.com/shadcn" }, + ], +} + +export function ProfileForm() { + const form = useForm({ + resolver: zodResolver(profileFormSchema), + defaultValues, + mode: "onChange", + }) + + const { fields, append } = useFieldArray({ + name: "urls", + control: form.control, + }) + + function onSubmit(data: ProfileFormValues) { + toast({ + title: "You submitted the following values:", + description: ( +
+          {JSON.stringify(data, null, 2)}
+        
+ ), + }) + } + + return ( +
+ + ( + + Username + + + + + This is your public display name. It can be your real name or a + pseudonym. You can only change this once every 30 days. + + + + )} + /> + ( + + Email + + + You can manage verified email addresses in your{" "} + email settings. + + + + )} + /> + ( + + Bio + +