Skip to content

Commit 963c16d

Browse files
author
belledw
committed
added 'add task' menu
1 parent 9fc278d commit 963c16d

9 files changed

Lines changed: 1249 additions & 50 deletions

File tree

client/package-lock.json

Lines changed: 768 additions & 47 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

client/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
"@radix-ui/react-avatar": "^1.1.11",
1919
"@radix-ui/react-checkbox": "^1.3.3",
2020
"@radix-ui/react-label": "^2.1.8",
21+
"@radix-ui/react-popover": "^1.1.15",
22+
"@radix-ui/react-separator": "^1.1.8",
2123
"@radix-ui/react-slot": "^1.2.4",
2224
"@tanstack/react-query": "^5.80.7",
2325
"@tanstack/react-query-devtools": "^5.80.7",
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
"use client";
2+
3+
import { Check, X } from "lucide-react";
4+
5+
import { Button } from "@/components/ui/button";
6+
import { DateTimePicker } from "@/components/ui/date-time-picker";
7+
import { Field, FieldGroup, FieldLabel } from "@/components/ui/field";
8+
import { Input } from "@/components/ui/input";
9+
import { TextTitle } from "@/components/ui/text-styles";
10+
11+
interface AddTaskProps {
12+
className?: string;
13+
onClickClose: () => void;
14+
}
15+
16+
export function AddTask(props: AddTaskProps) {
17+
return (
18+
<div className="flex flex-col gap-5">
19+
<TextTitle className="flex gap-2 text-3xl">Add Task</TextTitle>
20+
<FieldGroup>
21+
<Field>
22+
<FieldLabel htmlFor="checkout-7j9-card-name-43j">
23+
Task Title
24+
</FieldLabel>
25+
<Input
26+
id="checkout-7j9-card-name-43j"
27+
placeholder="Feed Spaghetti"
28+
required
29+
/>
30+
</Field>
31+
<DateTimePicker />
32+
<div className="flex gap-5 lg:w-1/2">
33+
<Field>
34+
<FieldLabel htmlFor="checkout-7j9-card-name">For Pet</FieldLabel>
35+
<Input
36+
id="checkout-7j9-card-name"
37+
placeholder="Spaghetti"
38+
required
39+
/>
40+
</Field>
41+
<Field>
42+
<FieldLabel htmlFor="checkout-7j9-card-name-4">
43+
User Assignee
44+
</FieldLabel>
45+
<Input
46+
id="checkout-7j9-card-name-4"
47+
placeholder="@username"
48+
required
49+
/>
50+
</Field>
51+
</div>
52+
<Field>
53+
<FieldLabel htmlFor="checkout-7j9-card-name-">
54+
Description (optional)
55+
</FieldLabel>
56+
<Input
57+
id="checkout-7j9-card-name-"
58+
placeholder="50.0 g x Premium Cat Biscuits"
59+
/>
60+
</Field>
61+
</FieldGroup>
62+
<div className="mt-5 flex items-center gap-5">
63+
<Button
64+
className="gray-600 hover:gray-400 rounded-full"
65+
onClick={props.onClickClose}
66+
>
67+
<Check className="mr-1" /> Save Task
68+
</Button>
69+
<Button
70+
className="rounded-full"
71+
variant="outline"
72+
onClick={props.onClickClose}
73+
>
74+
<X className="mr-1" /> Discard and close
75+
</Button>
76+
</div>
77+
<hr className="my-5"></hr>
78+
</div>
79+
);
80+
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
"use client";
2+
import { ChevronDownIcon } from "lucide-react";
3+
import * as React from "react";
4+
5+
import { Button } from "@/components/ui/button";
6+
import { Calendar } from "@/components/ui/calendar";
7+
import { Input } from "@/components/ui/input";
8+
import { Label } from "@/components/ui/label";
9+
import {
10+
Popover,
11+
PopoverContent,
12+
PopoverTrigger,
13+
} from "@/components/ui/popover";
14+
import { dm_sans } from "@/lib/fonts";
15+
16+
export function DateTimePicker() {
17+
const [open, setOpen] = React.useState(false);
18+
const [date, setDate] = React.useState<Date | undefined>(undefined);
19+
return (
20+
<div className="flex gap-4">
21+
<div className="flex flex-col gap-3">
22+
<Label htmlFor="date-picker" className="px-1">
23+
Date
24+
</Label>
25+
<Popover open={open} onOpenChange={setOpen}>
26+
<PopoverTrigger asChild>
27+
<Button
28+
variant="outline"
29+
id="date-picker"
30+
className="w-32 justify-between font-normal"
31+
>
32+
{date ? date.toLocaleDateString() : "Select date"}
33+
<ChevronDownIcon />
34+
</Button>
35+
</PopoverTrigger>
36+
<PopoverContent className="w-auto overflow-hidden p-0" align="start">
37+
<Calendar
38+
className={`${dm_sans.className}`}
39+
mode="single"
40+
selected={date}
41+
captionLayout="dropdown"
42+
onSelect={(date) => {
43+
setDate(date);
44+
setOpen(false);
45+
}}
46+
/>
47+
</PopoverContent>
48+
</Popover>
49+
</div>
50+
<div className="flex flex-col gap-3">
51+
<Label htmlFor="time-picker" className="px-1">
52+
Time
53+
</Label>
54+
<Input
55+
type="time"
56+
id="time-picker"
57+
step="1"
58+
defaultValue="10:30:00"
59+
className="appearance-none bg-background [&::-webkit-calendar-picker-indicator]:hidden [&::-webkit-calendar-picker-indicator]:appearance-none"
60+
/>
61+
</div>
62+
</div>
63+
);
64+
}

client/src/components/ui/field.tsx

Lines changed: 242 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,242 @@
1+
import { cva, type VariantProps } from "class-variance-authority";
2+
import { useMemo } from "react";
3+
4+
import { Label } from "@/components/ui/label";
5+
import { Separator } from "@/components/ui/separator";
6+
import { cn } from "@/lib/utils";
7+
8+
function FieldSet({ className, ...props }: React.ComponentProps<"fieldset">) {
9+
return (
10+
<fieldset
11+
data-slot="field-set"
12+
className={cn(
13+
"flex flex-col gap-6",
14+
"has-[>[data-slot=checkbox-group]]:gap-3 has-[>[data-slot=radio-group]]:gap-3",
15+
className,
16+
)}
17+
{...props}
18+
/>
19+
);
20+
}
21+
22+
function FieldLegend({
23+
className,
24+
variant = "legend",
25+
...props
26+
}: React.ComponentProps<"legend"> & { variant?: "legend" | "label" }) {
27+
return (
28+
<legend
29+
data-slot="field-legend"
30+
data-variant={variant}
31+
className={cn(
32+
"mb-3 font-medium",
33+
"data-[variant=legend]:text-base",
34+
"data-[variant=label]:text-sm",
35+
className,
36+
)}
37+
{...props}
38+
/>
39+
);
40+
}
41+
42+
function FieldGroup({ className, ...props }: React.ComponentProps<"div">) {
43+
return (
44+
<div
45+
data-slot="field-group"
46+
className={cn(
47+
"group/field-group @container/field-group flex w-full flex-col gap-7 data-[slot=checkbox-group]:gap-3 [&>[data-slot=field-group]]:gap-4",
48+
className,
49+
)}
50+
{...props}
51+
/>
52+
);
53+
}
54+
55+
const fieldVariants = cva(
56+
"group/field data-[invalid=true]:text-destructive flex w-full gap-3",
57+
{
58+
variants: {
59+
orientation: {
60+
vertical: ["flex-col [&>*]:w-full [&>.sr-only]:w-auto"],
61+
horizontal: [
62+
"flex-row items-center",
63+
"[&>[data-slot=field-label]]:flex-auto",
64+
"has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px has-[>[data-slot=field-content]]:items-start",
65+
],
66+
responsive: [
67+
"@md/field-group:flex-row @md/field-group:items-center @md/field-group:[&>*]:w-auto flex-col [&>*]:w-full [&>.sr-only]:w-auto",
68+
"@md/field-group:[&>[data-slot=field-label]]:flex-auto",
69+
"@md/field-group:has-[>[data-slot=field-content]]:items-start @md/field-group:has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px",
70+
],
71+
},
72+
},
73+
defaultVariants: {
74+
orientation: "vertical",
75+
},
76+
},
77+
);
78+
79+
function Field({
80+
className,
81+
orientation = "vertical",
82+
...props
83+
}: React.ComponentProps<"div"> & VariantProps<typeof fieldVariants>) {
84+
return (
85+
<div
86+
role="group"
87+
data-slot="field"
88+
data-orientation={orientation}
89+
className={cn(fieldVariants({ orientation }), className)}
90+
{...props}
91+
/>
92+
);
93+
}
94+
95+
function FieldContent({ className, ...props }: React.ComponentProps<"div">) {
96+
return (
97+
<div
98+
data-slot="field-content"
99+
className={cn(
100+
"group/field-content flex flex-1 flex-col gap-1.5 leading-snug",
101+
className,
102+
)}
103+
{...props}
104+
/>
105+
);
106+
}
107+
108+
function FieldLabel({
109+
className,
110+
...props
111+
}: React.ComponentProps<typeof Label>) {
112+
return (
113+
<Label
114+
data-slot="field-label"
115+
className={cn(
116+
"group/field-label peer/field-label flex w-fit gap-2 leading-snug group-data-[disabled=true]/field:opacity-50",
117+
"has-[>[data-slot=field]]:w-full has-[>[data-slot=field]]:flex-col has-[>[data-slot=field]]:rounded-md has-[>[data-slot=field]]:border [&>[data-slot=field]]:p-4",
118+
"has-data-[state=checked]:bg-primary/5 has-data-[state=checked]:border-primary dark:has-data-[state=checked]:bg-primary/10",
119+
className,
120+
)}
121+
{...props}
122+
/>
123+
);
124+
}
125+
126+
function FieldTitle({ className, ...props }: React.ComponentProps<"div">) {
127+
return (
128+
<div
129+
data-slot="field-label"
130+
className={cn(
131+
"flex w-fit items-center gap-2 text-sm font-medium leading-snug group-data-[disabled=true]/field:opacity-50",
132+
className,
133+
)}
134+
{...props}
135+
/>
136+
);
137+
}
138+
139+
function FieldDescription({ className, ...props }: React.ComponentProps<"p">) {
140+
return (
141+
<p
142+
data-slot="field-description"
143+
className={cn(
144+
"text-sm font-normal leading-normal text-muted-foreground group-has-[[data-orientation=horizontal]]/field:text-balance",
145+
"nth-last-2:-mt-1 last:mt-0 [[data-variant=legend]+&]:-mt-1.5",
146+
"[&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4",
147+
className,
148+
)}
149+
{...props}
150+
/>
151+
);
152+
}
153+
154+
function FieldSeparator({
155+
children,
156+
className,
157+
...props
158+
}: React.ComponentProps<"div"> & {
159+
children?: React.ReactNode;
160+
}) {
161+
return (
162+
<div
163+
data-slot="field-separator"
164+
data-content={!!children}
165+
className={cn(
166+
"relative -my-2 h-5 text-sm group-data-[variant=outline]/field-group:-mb-2",
167+
className,
168+
)}
169+
{...props}
170+
>
171+
<Separator className="absolute inset-0 top-1/2" />
172+
{children && (
173+
<span
174+
className="relative mx-auto block w-fit bg-background px-2 text-muted-foreground"
175+
data-slot="field-separator-content"
176+
>
177+
{children}
178+
</span>
179+
)}
180+
</div>
181+
);
182+
}
183+
184+
function FieldError({
185+
className,
186+
children,
187+
errors,
188+
...props
189+
}: React.ComponentProps<"div"> & {
190+
errors?: Array<{ message?: string } | undefined>;
191+
}) {
192+
const content = useMemo(() => {
193+
if (children) {
194+
return children;
195+
}
196+
197+
if (!errors) {
198+
return null;
199+
}
200+
201+
if (errors?.length === 1 && errors[0]?.message) {
202+
return errors[0].message;
203+
}
204+
205+
return (
206+
<ul className="ml-4 flex list-disc flex-col gap-1">
207+
{errors.map(
208+
(error, index) =>
209+
error?.message && <li key={index}>{error.message}</li>,
210+
)}
211+
</ul>
212+
);
213+
}, [children, errors]);
214+
215+
if (!content) {
216+
return null;
217+
}
218+
219+
return (
220+
<div
221+
role="alert"
222+
data-slot="field-error"
223+
className={cn("text-sm font-normal text-destructive", className)}
224+
{...props}
225+
>
226+
{content}
227+
</div>
228+
);
229+
}
230+
231+
export {
232+
Field,
233+
FieldContent,
234+
FieldDescription,
235+
FieldError,
236+
FieldGroup,
237+
FieldLabel,
238+
FieldLegend,
239+
FieldSeparator,
240+
FieldSet,
241+
FieldTitle,
242+
};

0 commit comments

Comments
 (0)