Skip to content

Commit a7ad78f

Browse files
committed
feat(ui): add form, select, dropdown-menu and textarea components
- Implement reusable form components with validation support - Add select component with customizable options and styling - Create dropdown-menu component with various menu items and submenus - Introduce textarea component with responsive styling - Update schema with zod validation schemas for video operations
1 parent af6eeca commit a7ad78f

File tree

9 files changed

+880
-115
lines changed

9 files changed

+880
-115
lines changed

.vscode/mcp.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"servers":{"MCP_DOCKER":{"command":"docker","args":["mcp","gateway","run"],"env":{"LOCALAPPDATA":"C:\\Users\\Epsaind\\AppData\\Local","ProgramData":"C:\\ProgramData","ProgramFiles":"C:\\Program Files"},"type":"stdio"}}}

bun.lock

Lines changed: 177 additions & 95 deletions
Large diffs are not rendered by default.

package.json

Lines changed: 21 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,11 @@
1111
"build": "next build",
1212
"start": "next start",
1313
"lint": "next lint",
14-
"seed": "bun ./src/scripts/seed-categories.ts"
14+
"seed": "bun ./src/scripts/seed-categories.ts"
1515
},
1616
"dependencies": {
17-
"@clerk/nextjs": "^6.23.0",
18-
"@hookform/resolvers": "^4.1.3",
17+
"@clerk/nextjs": "^6.24.0",
18+
"@hookform/resolvers": "^5.1.1",
1919
"@mux/mux-node": "^11.1.0",
2020
"@mux/mux-uploader-react": "^1.2.0",
2121
"@neondatabase/serverless": "^0.10.4",
@@ -46,54 +46,56 @@
4646
"@radix-ui/react-toggle": "^1.1.9",
4747
"@radix-ui/react-toggle-group": "^1.1.10",
4848
"@radix-ui/react-tooltip": "^1.2.7",
49-
"@tanstack/react-query": "^5.81.2",
50-
"@trpc/client": "^11.4.2",
51-
"@trpc/react-query": "^11.4.2",
52-
"@trpc/server": "^11.4.2",
53-
"@trpc/tanstack-react-query": "^11.4.2",
49+
"@tanstack/react-query": "^5.82.0",
50+
"@trpc/client": "^11.4.3",
51+
"@trpc/react-query": "^11.4.3",
52+
"@trpc/server": "^11.4.3",
53+
"@trpc/tanstack-react-query": "^11.4.3",
5454
"@upstash/ratelimit": "^2.0.5",
55-
"@upstash/redis": "^1.35.0",
55+
"@upstash/redis": "^1.35.1",
5656
"@vercel/speed-insights": "^1.2.0",
5757
"class-variance-authority": "^0.7.1",
5858
"client-only": "^0.0.1",
5959
"clsx": "^2.1.1",
6060
"cmdk": "^1.1.1",
6161
"date-fns": "^4.1.0",
62-
"dotenv": "^16.5.0",
62+
"dotenv": "^16.6.1",
6363
"drizzle-orm": "^0.40.1",
64+
"drizzle-zod": "^0.8.2",
6465
"embla-carousel-react": "^8.6.0",
6566
"input-otp": "^1.4.2",
6667
"lucide-react": "^0.477.0",
6768
"next": "15.2.0",
6869
"next-themes": "^0.4.6",
70+
"ngrok": "^5.0.0-beta.2",
6971
"react": "^19.1.0",
70-
"react-day-picker": "^9.7.0",
72+
"react-day-picker": "^9.8.0",
7173
"react-dom": "^19.1.0",
7274
"react-error-boundary": "^5.0.0",
73-
"react-hook-form": "^7.58.1",
75+
"react-hook-form": "^7.60.0",
7476
"react-resizable-panels": "^2.1.9",
7577
"recharts": "^2.15.4",
7678
"server-only": "^0.0.1",
77-
"sonner": "^2.0.5",
79+
"sonner": "^2.0.6",
7880
"superjson": "^2.2.2",
79-
"svix": "^1.68.0",
81+
"svix": "^1.69.0",
8082
"tailwind-merge": "^3.3.1",
8183
"tailwindcss-animate": "^1.0.7",
8284
"vaul": "^1.1.2",
83-
"zod": "^3.25.67"
85+
"zod": "^4.0.5"
8486
},
8587
"devDependencies": {
8688
"@eslint/eslintrc": "^3.3.1",
87-
"@tailwindcss/postcss": "^4.1.10",
88-
"@types/node": "^22.15.33",
89+
"@tailwindcss/postcss": "^4.1.11",
90+
"@types/node": "^22.16.3",
8991
"@types/react": "^19.1.8",
9092
"@types/react-dom": "^19.1.6",
9193
"concurrently": "^9.2.0",
9294
"drizzle-kit": "^0.30.6",
93-
"eslint": "^9.29.0",
95+
"eslint": "^9.30.1",
9496
"eslint-config-next": "15.2.0",
9597
"postcss": "^8.5.6",
96-
"tailwindcss": "^4.1.10",
98+
"tailwindcss": "^4.1.11",
9799
"tsx": "^4.20.3",
98100
"typescript": "^5.8.3"
99101
}

src/components/ui/dropdown-menu.tsx

Lines changed: 257 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,257 @@
1+
"use client"
2+
3+
import * as React from "react"
4+
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
5+
import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react"
6+
7+
import { cn } from "@/lib/utils"
8+
9+
function DropdownMenu({
10+
...props
11+
}: React.ComponentProps<typeof DropdownMenuPrimitive.Root>) {
12+
return <DropdownMenuPrimitive.Root data-slot="dropdown-menu" {...props} />
13+
}
14+
15+
function DropdownMenuPortal({
16+
...props
17+
}: React.ComponentProps<typeof DropdownMenuPrimitive.Portal>) {
18+
return (
19+
<DropdownMenuPrimitive.Portal data-slot="dropdown-menu-portal" {...props} />
20+
)
21+
}
22+
23+
function DropdownMenuTrigger({
24+
...props
25+
}: React.ComponentProps<typeof DropdownMenuPrimitive.Trigger>) {
26+
return (
27+
<DropdownMenuPrimitive.Trigger
28+
data-slot="dropdown-menu-trigger"
29+
{...props}
30+
/>
31+
)
32+
}
33+
34+
function DropdownMenuContent({
35+
className,
36+
sideOffset = 4,
37+
...props
38+
}: React.ComponentProps<typeof DropdownMenuPrimitive.Content>) {
39+
return (
40+
<DropdownMenuPrimitive.Portal>
41+
<DropdownMenuPrimitive.Content
42+
data-slot="dropdown-menu-content"
43+
sideOffset={sideOffset}
44+
className={cn(
45+
"bg-popover text-popover-foreground 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-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 max-h-(--radix-dropdown-menu-content-available-height) min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border p-1 shadow-md",
46+
className
47+
)}
48+
{...props}
49+
/>
50+
</DropdownMenuPrimitive.Portal>
51+
)
52+
}
53+
54+
function DropdownMenuGroup({
55+
...props
56+
}: React.ComponentProps<typeof DropdownMenuPrimitive.Group>) {
57+
return (
58+
<DropdownMenuPrimitive.Group data-slot="dropdown-menu-group" {...props} />
59+
)
60+
}
61+
62+
function DropdownMenuItem({
63+
className,
64+
inset,
65+
variant = "default",
66+
...props
67+
}: React.ComponentProps<typeof DropdownMenuPrimitive.Item> & {
68+
inset?: boolean
69+
variant?: "default" | "destructive"
70+
}) {
71+
return (
72+
<DropdownMenuPrimitive.Item
73+
data-slot="dropdown-menu-item"
74+
data-inset={inset}
75+
data-variant={variant}
76+
className={cn(
77+
"focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[svg]:!text-destructive [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
78+
className
79+
)}
80+
{...props}
81+
/>
82+
)
83+
}
84+
85+
function DropdownMenuCheckboxItem({
86+
className,
87+
children,
88+
checked,
89+
...props
90+
}: React.ComponentProps<typeof DropdownMenuPrimitive.CheckboxItem>) {
91+
return (
92+
<DropdownMenuPrimitive.CheckboxItem
93+
data-slot="dropdown-menu-checkbox-item"
94+
className={cn(
95+
"focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
96+
className
97+
)}
98+
checked={checked}
99+
{...props}
100+
>
101+
<span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
102+
<DropdownMenuPrimitive.ItemIndicator>
103+
<CheckIcon className="size-4" />
104+
</DropdownMenuPrimitive.ItemIndicator>
105+
</span>
106+
{children}
107+
</DropdownMenuPrimitive.CheckboxItem>
108+
)
109+
}
110+
111+
function DropdownMenuRadioGroup({
112+
...props
113+
}: React.ComponentProps<typeof DropdownMenuPrimitive.RadioGroup>) {
114+
return (
115+
<DropdownMenuPrimitive.RadioGroup
116+
data-slot="dropdown-menu-radio-group"
117+
{...props}
118+
/>
119+
)
120+
}
121+
122+
function DropdownMenuRadioItem({
123+
className,
124+
children,
125+
...props
126+
}: React.ComponentProps<typeof DropdownMenuPrimitive.RadioItem>) {
127+
return (
128+
<DropdownMenuPrimitive.RadioItem
129+
data-slot="dropdown-menu-radio-item"
130+
className={cn(
131+
"focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
132+
className
133+
)}
134+
{...props}
135+
>
136+
<span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
137+
<DropdownMenuPrimitive.ItemIndicator>
138+
<CircleIcon className="size-2 fill-current" />
139+
</DropdownMenuPrimitive.ItemIndicator>
140+
</span>
141+
{children}
142+
</DropdownMenuPrimitive.RadioItem>
143+
)
144+
}
145+
146+
function DropdownMenuLabel({
147+
className,
148+
inset,
149+
...props
150+
}: React.ComponentProps<typeof DropdownMenuPrimitive.Label> & {
151+
inset?: boolean
152+
}) {
153+
return (
154+
<DropdownMenuPrimitive.Label
155+
data-slot="dropdown-menu-label"
156+
data-inset={inset}
157+
className={cn(
158+
"px-2 py-1.5 text-sm font-medium data-[inset]:pl-8",
159+
className
160+
)}
161+
{...props}
162+
/>
163+
)
164+
}
165+
166+
function DropdownMenuSeparator({
167+
className,
168+
...props
169+
}: React.ComponentProps<typeof DropdownMenuPrimitive.Separator>) {
170+
return (
171+
<DropdownMenuPrimitive.Separator
172+
data-slot="dropdown-menu-separator"
173+
className={cn("bg-border -mx-1 my-1 h-px", className)}
174+
{...props}
175+
/>
176+
)
177+
}
178+
179+
function DropdownMenuShortcut({
180+
className,
181+
...props
182+
}: React.ComponentProps<"span">) {
183+
return (
184+
<span
185+
data-slot="dropdown-menu-shortcut"
186+
className={cn(
187+
"text-muted-foreground ml-auto text-xs tracking-widest",
188+
className
189+
)}
190+
{...props}
191+
/>
192+
)
193+
}
194+
195+
function DropdownMenuSub({
196+
...props
197+
}: React.ComponentProps<typeof DropdownMenuPrimitive.Sub>) {
198+
return <DropdownMenuPrimitive.Sub data-slot="dropdown-menu-sub" {...props} />
199+
}
200+
201+
function DropdownMenuSubTrigger({
202+
className,
203+
inset,
204+
children,
205+
...props
206+
}: React.ComponentProps<typeof DropdownMenuPrimitive.SubTrigger> & {
207+
inset?: boolean
208+
}) {
209+
return (
210+
<DropdownMenuPrimitive.SubTrigger
211+
data-slot="dropdown-menu-sub-trigger"
212+
data-inset={inset}
213+
className={cn(
214+
"focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground flex cursor-default items-center rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[inset]:pl-8",
215+
className
216+
)}
217+
{...props}
218+
>
219+
{children}
220+
<ChevronRightIcon className="ml-auto size-4" />
221+
</DropdownMenuPrimitive.SubTrigger>
222+
)
223+
}
224+
225+
function DropdownMenuSubContent({
226+
className,
227+
...props
228+
}: React.ComponentProps<typeof DropdownMenuPrimitive.SubContent>) {
229+
return (
230+
<DropdownMenuPrimitive.SubContent
231+
data-slot="dropdown-menu-sub-content"
232+
className={cn(
233+
"bg-popover text-popover-foreground 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-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-hidden rounded-md border p-1 shadow-lg",
234+
className
235+
)}
236+
{...props}
237+
/>
238+
)
239+
}
240+
241+
export {
242+
DropdownMenu,
243+
DropdownMenuPortal,
244+
DropdownMenuTrigger,
245+
DropdownMenuContent,
246+
DropdownMenuGroup,
247+
DropdownMenuLabel,
248+
DropdownMenuItem,
249+
DropdownMenuCheckboxItem,
250+
DropdownMenuRadioGroup,
251+
DropdownMenuRadioItem,
252+
DropdownMenuSeparator,
253+
DropdownMenuShortcut,
254+
DropdownMenuSub,
255+
DropdownMenuSubTrigger,
256+
DropdownMenuSubContent,
257+
}

0 commit comments

Comments
 (0)