implemented 'create new list' button + dialog

This commit is contained in:
2024-11-22 23:27:39 -06:00
parent e55b080544
commit 7a6d9bed4d
5 changed files with 265 additions and 8 deletions

View File

@@ -0,0 +1,250 @@
"use client";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
useDialogControls,
} from "~/app/_components/ui/dialog";
import { Button } from "~/app/_components/ui/button";
import { Input } from "~/app/_components/ui/input";
import {
Form,
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "~/app/_components/ui/form";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { api } from "~/trpc/react";
import { toast } from "sonner";
import {
type ListCreationSchema,
listCreationFormSchema,
} from "~/lib/schemas/list-creation-form";
import { RadioGroup, RadioGroupItem } from "~/app/_components/ui/radio-group";
import { VARIANTS } from "~/lib/data/list-variants";
import { LABELS } from "~/lib/data/task-labels";
import { Checkbox } from "~/app/_components/ui/checkbox";
import { Separator } from "~/app/_components/ui/separator";
export const CreateNewButton = () => {
const [isOpen, onOpenChange] = useDialogControls();
const handleOpenChange = (open: boolean) => {
form.reset();
onOpenChange(open);
};
const utils = api.useUtils();
const { mutate } = api.list.create.useMutation({
onSuccess: (_, variables) => {
toast.success(`Created new list: ${variables.name}`);
onOpenChange(false);
form.reset();
void utils.list.getAll.invalidate();
},
});
const form = useForm<ListCreationSchema>({
resolver: zodResolver(listCreationFormSchema),
defaultValues: {
name: "",
variant: "",
labels: [],
id: false,
idPrefix: "",
},
});
const onSubmit = (values: ListCreationSchema) => {
console.log("SUBMIT", values);
mutate(values);
};
return (
<Dialog open={isOpen} onOpenChange={handleOpenChange}>
<DialogTrigger asChild>
<Button>create a new list</Button>
</DialogTrigger>
<DialogContent className="sm:max-w-[425px]">
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
<DialogHeader>
<DialogTitle>create new list</DialogTitle>
<DialogDescription>
customize the list to your needs.
</DialogDescription>
</DialogHeader>
<Separator />
<div className="space-y-4">
{/* Name */}
<FormField
control={form.control}
name="name"
render={({ field }) => (
<FormItem>
<div className="mb-2">
<FormLabel>title</FormLabel>
<FormDescription>
this is the name of your list.
</FormDescription>
</div>
<FormControl>
<Input placeholder="to-do" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
{/* Variants */}
<FormField
control={form.control}
name="variant"
render={({ field }) => (
<FormItem className="space-y-3">
<div className="mb-2">
<FormLabel>variant</FormLabel>
<FormDescription>
select the type of list you want to use.
</FormDescription>
</div>
<FormControl>
<RadioGroup
onValueChange={field.onChange}
defaultValue={field.value}
className="flex flex-col space-y-1"
>
{VARIANTS.map((v) => (
<FormItem
key={v}
className="flex items-center space-x-3 space-y-0"
>
<FormControl>
<RadioGroupItem value={v} />
</FormControl>
<FormLabel className="font-normal">{v}</FormLabel>
</FormItem>
))}
</RadioGroup>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<Separator />
{/* ID */}
<FormField
control={form.control}
name="id"
render={({ field }) => (
<FormItem className="flex flex-row items-start space-x-3 space-y-0">
<FormControl>
<Checkbox
checked={field.value}
onCheckedChange={field.onChange}
/>
</FormControl>
<div className="grid gap-1.5 leading-none">
<FormLabel>enable task id</FormLabel>
<FormDescription>
assign an id number to each task.
</FormDescription>
</div>
</FormItem>
)}
/>
{/* ID Prefix */}
<FormField
control={form.control}
name="idPrefix"
disabled={!form.getValues("id")}
render={({ field }) => (
<FormItem>
<div className="mb-2">
<FormLabel>id prefix</FormLabel>
<FormDescription>
(optional) add a prefix to your id. ex:
&quot;ls-53&quot;
</FormDescription>
</div>
<FormControl>
<Input placeholder="ls-" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
{/* Labels */}
<FormField
control={form.control}
name="labels"
render={() => (
<FormItem>
<div className="mb-2">
<FormLabel>labels</FormLabel>
<FormDescription>
(optional) select the task labels you want to be able to
use.
</FormDescription>
</div>
{LABELS.map((label) => (
<FormField
key={label}
control={form.control}
name="labels"
render={({ field }) => {
return (
<FormItem
key={label}
className="flex flex-row items-start space-x-3 space-y-0"
>
<FormControl>
<Checkbox
checked={field.value?.includes(label)}
onCheckedChange={(checked) => {
return checked
? field.onChange([...field.value, label])
: field.onChange(
field.value?.filter(
(value) => value !== label,
),
);
}}
/>
</FormControl>
<FormLabel className="text-sm font-normal">
{label}
</FormLabel>
</FormItem>
);
}}
/>
))}
<FormMessage />
</FormItem>
)}
/>
</div>
<DialogFooter>
<Button type="submit">Save changes</Button>
</DialogFooter>
</form>
</Form>
</DialogContent>
</Dialog>
);
};

View File

@@ -1,8 +1,8 @@
import { auth } from "~/server/auth"; import { auth } from "~/server/auth";
import { UserNav } from "~/app/_components/user-nav"; import { UserNav } from "~/app/_components/macro/user-nav";
import { Button } from "~/app/_components/ui/button";
import { api } from "~/trpc/server"; import { api } from "~/trpc/server";
import { ListButton } from "~/app/(home)/_components/list-button"; import { ListButton } from "~/app/(home)/_components/list-button";
import { CreateNewButton } from "~/app/(home)/_components/create-new-button";
export default async function Home() { export default async function Home() {
const session = await auth(); const session = await auth();
@@ -23,7 +23,7 @@ export default async function Home() {
<div className="mx-auto max-w-6xl px-6"> <div className="mx-auto max-w-6xl px-6">
<div className="flex h-full flex-1 flex-col space-y-8 p-8"> <div className="flex h-full flex-1 flex-col space-y-8 p-8">
<div className="flex items-center justify-between space-y-2"> <div className="flex items-center justify-between space-y-2">
<Button>create a new list</Button> <CreateNewButton />
<UserNav /> <UserNav />
</div> </div>

View File

@@ -21,7 +21,7 @@ const DialogOverlay = React.forwardRef<
<DialogPrimitive.Overlay <DialogPrimitive.Overlay
ref={ref} ref={ref}
className={cn( className={cn(
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/80", "fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
className, className,
)} )}
{...props} {...props}
@@ -38,7 +38,7 @@ const DialogContent = React.forwardRef<
<DialogPrimitive.Content <DialogPrimitive.Content
ref={ref} ref={ref}
className={cn( className={cn(
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border border-neutral-200 bg-white p-6 shadow-lg duration-200 sm:rounded-lg dark:border-neutral-800 dark:bg-neutral-950", "fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border border-neutral-200 bg-white p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] dark:border-neutral-800 dark:bg-neutral-950 sm:rounded-lg",
className, className,
)} )}
{...props} {...props}
@@ -120,3 +120,10 @@ export {
DialogTitle, DialogTitle,
DialogDescription, DialogDescription,
}; };
export const useDialogControls = () => {
const [isOpen, setIsOpen] = React.useState(false);
const onOpenChange = (open: boolean) => setIsOpen(open);
return [isOpen, onOpenChange] as const;
};

View File

@@ -165,7 +165,7 @@ const FormMessage = React.forwardRef<
ref={ref} ref={ref}
id={formMessageId} id={formMessageId}
className={cn( className={cn(
"text-gruv-red-bg dark:text-gruv-red-fg text-[0.8rem] font-medium", "text-[0.8rem] font-medium text-gruv-red-bg dark:text-gruv-red-fg",
className, className,
)} )}
{...props} {...props}

View File

@@ -28,13 +28,13 @@ const RadioGroupItem = React.forwardRef<
<RadioGroupPrimitive.Item <RadioGroupPrimitive.Item
ref={ref} ref={ref}
className={cn( className={cn(
"aspect-square h-4 w-4 rounded-full border border-neutral-200 border-neutral-900 text-neutral-900 shadow focus:outline-none focus-visible:ring-1 focus-visible:ring-neutral-950 disabled:cursor-not-allowed disabled:opacity-50 dark:border-neutral-50 dark:border-neutral-800 dark:text-neutral-50 dark:focus-visible:ring-neutral-300", "aspect-square h-4 w-4 rounded-full border border-neutral-900 text-neutral-900 shadow focus:outline-none focus-visible:ring-1 focus-visible:ring-neutral-950 disabled:cursor-not-allowed disabled:opacity-50 dark:border-neutral-50 dark:text-neutral-50 dark:focus-visible:ring-neutral-300",
className, className,
)} )}
{...props} {...props}
> >
<RadioGroupPrimitive.Indicator className="flex items-center justify-center"> <RadioGroupPrimitive.Indicator className="flex items-center justify-center">
<Circle className="fill-primary h-3.5 w-3.5" /> <Circle className="h-3.5 w-3.5 fill-inherit" />
</RadioGroupPrimitive.Indicator> </RadioGroupPrimitive.Indicator>
</RadioGroupPrimitive.Item> </RadioGroupPrimitive.Item>
); );