create-aron-app 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +5 -2
- package/templates/_base/.cursor/agents/skills/clerk/SKILL.md +89 -0
- package/templates/_base/.cursor/agents/skills/clerk/clerk-backend-api/SKILL.md +142 -0
- package/templates/_base/.cursor/agents/skills/clerk/clerk-backend-api/scripts/api-specs-context.sh +30 -0
- package/templates/_base/.cursor/agents/skills/clerk/clerk-backend-api/scripts/execute-request.sh +88 -0
- package/templates/_base/.cursor/agents/skills/clerk/clerk-backend-api/scripts/extract-endpoint-detail.sh +165 -0
- package/templates/_base/.cursor/agents/skills/clerk/clerk-backend-api/scripts/extract-tag-endpoints.sh +208 -0
- package/templates/_base/.cursor/agents/skills/clerk/clerk-backend-api/scripts/extract-tags.js +14 -0
- package/templates/_base/.cursor/agents/skills/clerk/clerk-custom-ui/SKILL.md +157 -0
- package/templates/_base/.cursor/agents/skills/clerk/clerk-custom-ui/core-2/custom-sign-in.md +224 -0
- package/templates/_base/.cursor/agents/skills/clerk/clerk-custom-ui/core-2/custom-sign-up.md +190 -0
- package/templates/_base/.cursor/agents/skills/clerk/clerk-custom-ui/core-3/custom-sign-in.md +314 -0
- package/templates/_base/.cursor/agents/skills/clerk/clerk-custom-ui/core-3/custom-sign-up.md +259 -0
- package/templates/_base/.cursor/agents/skills/clerk/clerk-custom-ui/core-3/show-component.md +125 -0
- package/templates/_base/.cursor/agents/skills/clerk/clerk-nextjs-patterns/SKILL.md +94 -0
- package/templates/_base/.cursor/agents/skills/clerk/clerk-nextjs-patterns/references/api-routes.md +50 -0
- package/templates/_base/.cursor/agents/skills/clerk/clerk-nextjs-patterns/references/caching-auth.md +56 -0
- package/templates/_base/.cursor/agents/skills/clerk/clerk-nextjs-patterns/references/middleware-strategies.md +68 -0
- package/templates/_base/.cursor/agents/skills/clerk/clerk-nextjs-patterns/references/server-actions.md +56 -0
- package/templates/_base/.cursor/agents/skills/clerk/clerk-nextjs-patterns/references/server-vs-client.md +104 -0
- package/templates/_base/.cursor/agents/skills/clerk/clerk-webhooks/SKILL.md +131 -0
- package/templates/_base/.cursor/agents/skills/shadcn/SKILL.md +241 -0
- package/templates/_base/.cursor/agents/skills/shadcn/agents/openai.yml +5 -0
- package/templates/_base/.cursor/agents/skills/shadcn/assets/shadcn-small.png +0 -0
- package/templates/_base/.cursor/agents/skills/shadcn/assets/shadcn.png +0 -0
- package/templates/_base/.cursor/agents/skills/shadcn/cli.md +257 -0
- package/templates/_base/.cursor/agents/skills/shadcn/customization.md +202 -0
- package/templates/_base/.cursor/agents/skills/shadcn/evals/evals.json +47 -0
- package/templates/_base/.cursor/agents/skills/shadcn/mcp.md +94 -0
- package/templates/_base/.cursor/agents/skills/shadcn/rules/base-vs-radix.md +306 -0
- package/templates/_base/.cursor/agents/skills/shadcn/rules/composition.md +195 -0
- package/templates/_base/.cursor/agents/skills/shadcn/rules/forms.md +192 -0
- package/templates/_base/.cursor/agents/skills/shadcn/rules/icons.md +101 -0
- package/templates/_base/.cursor/agents/skills/shadcn/rules/styling.md +162 -0
- package/templates/_base/.cursor/commands/builder.md +0 -0
- package/templates/_base/.cursor/commands/pr.md +7 -0
- package/templates/_base/.cursor/rules/api_architecture.mdc +268 -0
- package/templates/_base/.cursor/rules/coding_standards.mdc +64 -0
- package/templates/_base/.cursor/rules/convex_rules.mdc +675 -0
- package/templates/_base/.cursor/rules/frontend_rules.mdc +268 -0
- package/templates/_base/.env.convex.example +3 -0
- package/templates/_base/.github/workflows/ci.yml +29 -0
- package/templates/_base/.nvmrc +1 -0
- package/templates/_base/.vscode/settings.json +9 -0
- package/templates/_base/apps/api/auth.config.ts +18 -0
- package/templates/_base/apps/api/functions.ts +99 -0
- package/templates/_base/apps/api/project.json +22 -0
- package/templates/_base/apps/api/schema.ts +11 -0
- package/templates/_base/apps/api/todos/crud.ts +81 -0
- package/templates/_base/apps/api/todos/schema.ts +11 -0
- package/templates/_base/apps/api/todos/types.ts +22 -0
- package/templates/_base/apps/api/tsconfig.json +23 -0
- package/templates/_base/apps/api/types.ts +16 -0
- package/templates/_base/biome.json +114 -0
- package/templates/_base/convex.json +4 -0
- package/templates/_base/emails/project.json +16 -0
- package/templates/_base/emails/tsconfig.json +5 -0
- package/templates/_base/emails/welcome_email.tsx +53 -0
- package/templates/_base/nx.json +29 -0
- package/templates/_base/package.json +73 -0
- package/templates/_base/scripts/sync_convex_env.ts +63 -0
- package/templates/_base/shared/assets/image.d.ts +4 -0
- package/templates/_base/shared/assets/src/styles/global.css +73 -0
- package/templates/_base/shared/assets/tsconfig.json +5 -0
- package/templates/_base/shared/ui/src/base/alert_dialog.tsx +139 -0
- package/templates/_base/shared/ui/src/base/badge.tsx +33 -0
- package/templates/_base/shared/ui/src/base/basic_data_table.tsx +61 -0
- package/templates/_base/shared/ui/src/base/button.tsx +69 -0
- package/templates/_base/shared/ui/src/base/button_group.tsx +82 -0
- package/templates/_base/shared/ui/src/base/card.tsx +79 -0
- package/templates/_base/shared/ui/src/base/checkbox.tsx +26 -0
- package/templates/_base/shared/ui/src/base/command.tsx +165 -0
- package/templates/_base/shared/ui/src/base/dialog.tsx +129 -0
- package/templates/_base/shared/ui/src/base/dropdown_menu.tsx +232 -0
- package/templates/_base/shared/ui/src/base/form.tsx +161 -0
- package/templates/_base/shared/ui/src/base/input.tsx +129 -0
- package/templates/_base/shared/ui/src/base/label.tsx +19 -0
- package/templates/_base/shared/ui/src/base/popover.tsx +46 -0
- package/templates/_base/shared/ui/src/base/radio_group.tsx +49 -0
- package/templates/_base/shared/ui/src/base/resizable.tsx +55 -0
- package/templates/_base/shared/ui/src/base/scroll_area.tsx +44 -0
- package/templates/_base/shared/ui/src/base/select.tsx +151 -0
- package/templates/_base/shared/ui/src/base/separator.tsx +32 -0
- package/templates/_base/shared/ui/src/base/sheet.tsx +130 -0
- package/templates/_base/shared/ui/src/base/side_bar.tsx +688 -0
- package/templates/_base/shared/ui/src/base/skeleton.tsx +7 -0
- package/templates/_base/shared/ui/src/base/spinner.tsx +20 -0
- package/templates/_base/shared/ui/src/base/switch.tsx +27 -0
- package/templates/_base/shared/ui/src/base/table.tsx +91 -0
- package/templates/_base/shared/ui/src/base/text_area.tsx +21 -0
- package/templates/_base/shared/ui/src/base/tooltip.tsx +31 -0
- package/templates/_base/shared/ui/src/base/utils.ts +17 -0
- package/templates/_base/shared/ui/src/hooks/use_keyboard_press.tsx +48 -0
- package/templates/_base/shared/ui/src/hooks/use_keyboard_release.tsx +48 -0
- package/templates/_base/shared/ui/src/hooks/use_mobile.tsx +25 -0
- package/templates/_base/shared/ui/src/hooks/use_mouse_click.tsx +44 -0
- package/templates/_base/shared/ui/src/hooks/use_mouse_location.tsx +55 -0
- package/templates/_base/shared/ui/src/hooks/use_outside_click.tsx +29 -0
- package/templates/_base/shared/ui/src/hooks/use_query_params.tsx +33 -0
- package/templates/_base/shared/ui/tsconfig.json +8 -0
- package/templates/_base/shared/utils/src/convex.ts +3 -0
- package/templates/_base/shared/utils/src/time.ts +12 -0
- package/templates/_base/shared/utils/tsconfig.json +5 -0
- package/templates/_base/skills-lock.json +35 -0
- package/templates/_base/tsconfig.base.json +34 -0
- package/templates/nextjs/.env.example +8 -0
- package/templates/nextjs/index.d.ts +6 -0
- package/templates/nextjs/next-env.d.ts +5 -0
- package/templates/nextjs/next.config.js +22 -0
- package/templates/nextjs/postcss.config.js +17 -0
- package/templates/nextjs/project.json +22 -0
- package/templates/nextjs/src/app/(auth)/layout.tsx +21 -0
- package/templates/nextjs/src/app/(auth)/not-allowed/page.tsx +22 -0
- package/templates/nextjs/src/app/(auth)/sign-in/[[...sign-in]]/page.tsx +15 -0
- package/templates/nextjs/src/app/(dashboard)/layout.tsx +27 -0
- package/templates/nextjs/src/app/(dashboard)/page.tsx +5 -0
- package/templates/nextjs/src/app/(dashboard)/todos/[id]/page.tsx +23 -0
- package/templates/nextjs/src/app/(dashboard)/todos/page.tsx +16 -0
- package/templates/nextjs/src/app/app.css +3 -0
- package/templates/nextjs/src/app/layout.tsx +26 -0
- package/templates/nextjs/src/convex.ts +11 -0
- package/templates/nextjs/src/middleware.ts +18 -0
- package/templates/nextjs/src/providers/convex_provider.tsx +44 -0
- package/templates/nextjs/src/surfaces/home_surface.tsx +22 -0
- package/templates/nextjs/src/surfaces/todos/all_todos_surface.tsx +97 -0
- package/templates/nextjs/src/surfaces/todos/create_todo_sheet.tsx +107 -0
- package/templates/nextjs/src/surfaces/todos/single_todo_surface.tsx +90 -0
- package/templates/nextjs/src/ui/sidebar/nav_link.tsx +36 -0
- package/templates/nextjs/src/ui/sidebar/sidebar.tsx +125 -0
- package/templates/nextjs/src/utils/font.ts +9 -0
- package/templates/nextjs/tsconfig.json +42 -0
- package/templates/react-router/.env.example +8 -0
- package/templates/react-router/postcss.config.js +15 -0
- package/templates/react-router/project.json +23 -0
- package/templates/react-router/public/favicon.ico +0 -0
- package/templates/react-router/react-router.config.ts +9 -0
- package/templates/react-router/src/app.css +3 -0
- package/templates/react-router/src/components/error_boundary.tsx +33 -0
- package/templates/react-router/src/layouts/sidebar/sidebar_aside/sidebar_aside.tsx +76 -0
- package/templates/react-router/src/layouts/sidebar/sidebar_aside/user_menu.tsx +36 -0
- package/templates/react-router/src/layouts/sidebar/sidebar_layout.tsx +22 -0
- package/templates/react-router/src/providers/api_auth_provider.tsx +38 -0
- package/templates/react-router/src/root.tsx +37 -0
- package/templates/react-router/src/routes/auth/layout.tsx +13 -0
- package/templates/react-router/src/routes/auth/sign-in.tsx +13 -0
- package/templates/react-router/src/routes/index.tsx +9 -0
- package/templates/react-router/src/routes/layout.tsx +26 -0
- package/templates/react-router/src/routes/todos/[id].tsx +22 -0
- package/templates/react-router/src/routes/todos/index.tsx +13 -0
- package/templates/react-router/src/routes.ts +12 -0
- package/templates/react-router/src/surfaces/home_surface.tsx +20 -0
- package/templates/react-router/src/surfaces/todos/all_todos_surface.tsx +87 -0
- package/templates/react-router/src/surfaces/todos/create_todo_sheet.tsx +102 -0
- package/templates/react-router/src/surfaces/todos/single_todo_surface.tsx +81 -0
- package/templates/react-router/tsconfig.json +20 -0
- package/templates/react-router/vite.config.ts +40 -0
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) Aron Weston 2025.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
"use client";
|
|
6
|
+
|
|
7
|
+
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
|
|
8
|
+
import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react";
|
|
9
|
+
import type * as React from "react";
|
|
10
|
+
|
|
11
|
+
import { cn } from "@/ui/base/utils";
|
|
12
|
+
|
|
13
|
+
function DropdownMenu({ ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.Root>) {
|
|
14
|
+
return <DropdownMenuPrimitive.Root data-slot="dropdown-menu" {...props} />;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function DropdownMenuPortal({
|
|
18
|
+
...props
|
|
19
|
+
}: React.ComponentProps<typeof DropdownMenuPrimitive.Portal>) {
|
|
20
|
+
return <DropdownMenuPrimitive.Portal data-slot="dropdown-menu-portal" {...props} />;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function DropdownMenuTrigger({
|
|
24
|
+
...props
|
|
25
|
+
}: React.ComponentProps<typeof DropdownMenuPrimitive.Trigger>) {
|
|
26
|
+
return <DropdownMenuPrimitive.Trigger data-slot="dropdown-menu-trigger" {...props} />;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function DropdownMenuContent({
|
|
30
|
+
className,
|
|
31
|
+
sideOffset = 4,
|
|
32
|
+
...props
|
|
33
|
+
}: React.ComponentProps<typeof DropdownMenuPrimitive.Content>) {
|
|
34
|
+
return (
|
|
35
|
+
<DropdownMenuPrimitive.Portal>
|
|
36
|
+
<DropdownMenuPrimitive.Content
|
|
37
|
+
data-slot="dropdown-menu-content"
|
|
38
|
+
sideOffset={sideOffset}
|
|
39
|
+
className={cn(
|
|
40
|
+
"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",
|
|
41
|
+
className,
|
|
42
|
+
)}
|
|
43
|
+
{...props}
|
|
44
|
+
/>
|
|
45
|
+
</DropdownMenuPrimitive.Portal>
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function DropdownMenuGroup({ ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.Group>) {
|
|
50
|
+
return <DropdownMenuPrimitive.Group data-slot="dropdown-menu-group" {...props} />;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function DropdownMenuItem({
|
|
54
|
+
className,
|
|
55
|
+
inset,
|
|
56
|
+
variant = "default",
|
|
57
|
+
...props
|
|
58
|
+
}: React.ComponentProps<typeof DropdownMenuPrimitive.Item> & {
|
|
59
|
+
inset?: boolean;
|
|
60
|
+
variant?: "default" | "destructive";
|
|
61
|
+
}) {
|
|
62
|
+
return (
|
|
63
|
+
<DropdownMenuPrimitive.Item
|
|
64
|
+
data-slot="dropdown-menu-item"
|
|
65
|
+
data-inset={inset}
|
|
66
|
+
data-variant={variant}
|
|
67
|
+
className={cn(
|
|
68
|
+
"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",
|
|
69
|
+
className,
|
|
70
|
+
)}
|
|
71
|
+
{...props}
|
|
72
|
+
/>
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function DropdownMenuCheckboxItem({
|
|
77
|
+
className,
|
|
78
|
+
children,
|
|
79
|
+
checked,
|
|
80
|
+
...props
|
|
81
|
+
}: React.ComponentProps<typeof DropdownMenuPrimitive.CheckboxItem>) {
|
|
82
|
+
return (
|
|
83
|
+
<DropdownMenuPrimitive.CheckboxItem
|
|
84
|
+
data-slot="dropdown-menu-checkbox-item"
|
|
85
|
+
className={cn(
|
|
86
|
+
"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",
|
|
87
|
+
className,
|
|
88
|
+
)}
|
|
89
|
+
checked={checked}
|
|
90
|
+
{...props}
|
|
91
|
+
>
|
|
92
|
+
<span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
|
|
93
|
+
<DropdownMenuPrimitive.ItemIndicator>
|
|
94
|
+
<CheckIcon className="size-4" />
|
|
95
|
+
</DropdownMenuPrimitive.ItemIndicator>
|
|
96
|
+
</span>
|
|
97
|
+
{children}
|
|
98
|
+
</DropdownMenuPrimitive.CheckboxItem>
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function DropdownMenuRadioGroup({
|
|
103
|
+
...props
|
|
104
|
+
}: React.ComponentProps<typeof DropdownMenuPrimitive.RadioGroup>) {
|
|
105
|
+
return <DropdownMenuPrimitive.RadioGroup data-slot="dropdown-menu-radio-group" {...props} />;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function DropdownMenuRadioItem({
|
|
109
|
+
className,
|
|
110
|
+
children,
|
|
111
|
+
...props
|
|
112
|
+
}: React.ComponentProps<typeof DropdownMenuPrimitive.RadioItem>) {
|
|
113
|
+
return (
|
|
114
|
+
<DropdownMenuPrimitive.RadioItem
|
|
115
|
+
data-slot="dropdown-menu-radio-item"
|
|
116
|
+
className={cn(
|
|
117
|
+
"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",
|
|
118
|
+
className,
|
|
119
|
+
)}
|
|
120
|
+
{...props}
|
|
121
|
+
>
|
|
122
|
+
<span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
|
|
123
|
+
<DropdownMenuPrimitive.ItemIndicator>
|
|
124
|
+
<CircleIcon className="size-2 fill-current" />
|
|
125
|
+
</DropdownMenuPrimitive.ItemIndicator>
|
|
126
|
+
</span>
|
|
127
|
+
{children}
|
|
128
|
+
</DropdownMenuPrimitive.RadioItem>
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function DropdownMenuLabel({
|
|
133
|
+
className,
|
|
134
|
+
inset,
|
|
135
|
+
...props
|
|
136
|
+
}: React.ComponentProps<typeof DropdownMenuPrimitive.Label> & {
|
|
137
|
+
inset?: boolean;
|
|
138
|
+
}) {
|
|
139
|
+
return (
|
|
140
|
+
<DropdownMenuPrimitive.Label
|
|
141
|
+
data-slot="dropdown-menu-label"
|
|
142
|
+
data-inset={inset}
|
|
143
|
+
className={cn("px-2 py-1.5 text-sm font-medium data-[inset]:pl-8", className)}
|
|
144
|
+
{...props}
|
|
145
|
+
/>
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function DropdownMenuSeparator({
|
|
150
|
+
className,
|
|
151
|
+
...props
|
|
152
|
+
}: React.ComponentProps<typeof DropdownMenuPrimitive.Separator>) {
|
|
153
|
+
return (
|
|
154
|
+
<DropdownMenuPrimitive.Separator
|
|
155
|
+
data-slot="dropdown-menu-separator"
|
|
156
|
+
className={cn("bg-border -mx-1 my-1 h-px", className)}
|
|
157
|
+
{...props}
|
|
158
|
+
/>
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function DropdownMenuShortcut({ className, ...props }: React.ComponentProps<"span">) {
|
|
163
|
+
return (
|
|
164
|
+
<span
|
|
165
|
+
data-slot="dropdown-menu-shortcut"
|
|
166
|
+
className={cn("text-muted-foreground ml-auto text-xs tracking-widest", className)}
|
|
167
|
+
{...props}
|
|
168
|
+
/>
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function DropdownMenuSub({ ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.Sub>) {
|
|
173
|
+
return <DropdownMenuPrimitive.Sub data-slot="dropdown-menu-sub" {...props} />;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function DropdownMenuSubTrigger({
|
|
177
|
+
className,
|
|
178
|
+
inset,
|
|
179
|
+
children,
|
|
180
|
+
...props
|
|
181
|
+
}: React.ComponentProps<typeof DropdownMenuPrimitive.SubTrigger> & {
|
|
182
|
+
inset?: boolean;
|
|
183
|
+
}) {
|
|
184
|
+
return (
|
|
185
|
+
<DropdownMenuPrimitive.SubTrigger
|
|
186
|
+
data-slot="dropdown-menu-sub-trigger"
|
|
187
|
+
data-inset={inset}
|
|
188
|
+
className={cn(
|
|
189
|
+
"focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
190
|
+
className,
|
|
191
|
+
)}
|
|
192
|
+
{...props}
|
|
193
|
+
>
|
|
194
|
+
{children}
|
|
195
|
+
<ChevronRightIcon className="ml-auto size-4" />
|
|
196
|
+
</DropdownMenuPrimitive.SubTrigger>
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function DropdownMenuSubContent({
|
|
201
|
+
className,
|
|
202
|
+
...props
|
|
203
|
+
}: React.ComponentProps<typeof DropdownMenuPrimitive.SubContent>) {
|
|
204
|
+
return (
|
|
205
|
+
<DropdownMenuPrimitive.SubContent
|
|
206
|
+
data-slot="dropdown-menu-sub-content"
|
|
207
|
+
className={cn(
|
|
208
|
+
"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",
|
|
209
|
+
className,
|
|
210
|
+
)}
|
|
211
|
+
{...props}
|
|
212
|
+
/>
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
export {
|
|
217
|
+
DropdownMenu,
|
|
218
|
+
DropdownMenuPortal,
|
|
219
|
+
DropdownMenuTrigger,
|
|
220
|
+
DropdownMenuContent,
|
|
221
|
+
DropdownMenuGroup,
|
|
222
|
+
DropdownMenuLabel,
|
|
223
|
+
DropdownMenuItem,
|
|
224
|
+
DropdownMenuCheckboxItem,
|
|
225
|
+
DropdownMenuRadioGroup,
|
|
226
|
+
DropdownMenuRadioItem,
|
|
227
|
+
DropdownMenuSeparator,
|
|
228
|
+
DropdownMenuShortcut,
|
|
229
|
+
DropdownMenuSub,
|
|
230
|
+
DropdownMenuSubTrigger,
|
|
231
|
+
DropdownMenuSubContent,
|
|
232
|
+
};
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import type * as LabelPrimitive from "@radix-ui/react-label";
|
|
2
|
+
import { Slot } from "@radix-ui/react-slot";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import type { ControllerProps, FieldPath, FieldValues } from "react-hook-form";
|
|
5
|
+
import { Controller, FormProvider, useFormContext } from "react-hook-form";
|
|
6
|
+
|
|
7
|
+
import { Label } from "@/ui/base/label";
|
|
8
|
+
import { cn } from "@/ui/base/utils";
|
|
9
|
+
|
|
10
|
+
const Form = FormProvider;
|
|
11
|
+
|
|
12
|
+
type FormFieldContextValue<
|
|
13
|
+
TFieldValues extends FieldValues = FieldValues,
|
|
14
|
+
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
|
15
|
+
> = {
|
|
16
|
+
name: TName;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const FormFieldContext = React.createContext<FormFieldContextValue>({} as FormFieldContextValue);
|
|
20
|
+
|
|
21
|
+
const FormField = <
|
|
22
|
+
TFieldValues extends FieldValues = FieldValues,
|
|
23
|
+
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
|
24
|
+
>({
|
|
25
|
+
...props
|
|
26
|
+
}: ControllerProps<TFieldValues, TName>) => {
|
|
27
|
+
return (
|
|
28
|
+
<FormFieldContext.Provider value={{ name: props.name }}>
|
|
29
|
+
<Controller {...props} />
|
|
30
|
+
</FormFieldContext.Provider>
|
|
31
|
+
);
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const useFormField = () => {
|
|
35
|
+
const fieldContext = React.useContext(FormFieldContext);
|
|
36
|
+
const itemContext = React.useContext(FormItemContext);
|
|
37
|
+
const { getFieldState, formState } = useFormContext();
|
|
38
|
+
|
|
39
|
+
const fieldState = getFieldState(fieldContext.name, formState);
|
|
40
|
+
|
|
41
|
+
if (!fieldContext) {
|
|
42
|
+
throw new Error("useFormField should be used within <FormField>");
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const { id } = itemContext;
|
|
46
|
+
|
|
47
|
+
return {
|
|
48
|
+
id,
|
|
49
|
+
name: fieldContext.name,
|
|
50
|
+
formItemId: `${id}-form-item`,
|
|
51
|
+
formDescriptionId: `${id}-form-item-description`,
|
|
52
|
+
formMessageId: `${id}-form-item-message`,
|
|
53
|
+
...fieldState,
|
|
54
|
+
};
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
type FormItemContextValue = {
|
|
58
|
+
id: string;
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const FormItemContext = React.createContext<FormItemContextValue>({} as FormItemContextValue);
|
|
62
|
+
|
|
63
|
+
const FormItem = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
|
|
64
|
+
({ className, ...props }, ref) => {
|
|
65
|
+
const id = React.useId();
|
|
66
|
+
|
|
67
|
+
return (
|
|
68
|
+
<FormItemContext.Provider value={{ id }}>
|
|
69
|
+
<div ref={ref} className={cn("space-y-2", className)} {...props} />
|
|
70
|
+
</FormItemContext.Provider>
|
|
71
|
+
);
|
|
72
|
+
},
|
|
73
|
+
);
|
|
74
|
+
FormItem.displayName = "FormItem";
|
|
75
|
+
|
|
76
|
+
const FormLabel = React.forwardRef<
|
|
77
|
+
React.ElementRef<typeof LabelPrimitive.Root>,
|
|
78
|
+
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root>
|
|
79
|
+
>(({ className, ...props }, ref) => {
|
|
80
|
+
const { error, formItemId } = useFormField();
|
|
81
|
+
|
|
82
|
+
return (
|
|
83
|
+
<Label
|
|
84
|
+
ref={ref}
|
|
85
|
+
className={cn(error && "text-destructive cursor-pointer", className)}
|
|
86
|
+
htmlFor={formItemId}
|
|
87
|
+
{...props}
|
|
88
|
+
/>
|
|
89
|
+
);
|
|
90
|
+
});
|
|
91
|
+
FormLabel.displayName = "FormLabel";
|
|
92
|
+
|
|
93
|
+
const FormControl = React.forwardRef<
|
|
94
|
+
React.ElementRef<typeof Slot>,
|
|
95
|
+
React.ComponentPropsWithoutRef<typeof Slot>
|
|
96
|
+
>(({ ...props }, ref) => {
|
|
97
|
+
const { error, formItemId, formDescriptionId, formMessageId } = useFormField();
|
|
98
|
+
|
|
99
|
+
return (
|
|
100
|
+
<Slot
|
|
101
|
+
ref={ref}
|
|
102
|
+
id={formItemId}
|
|
103
|
+
aria-describedby={!error ? `${formDescriptionId}` : `${formDescriptionId} ${formMessageId}`}
|
|
104
|
+
aria-invalid={!!error}
|
|
105
|
+
{...props}
|
|
106
|
+
/>
|
|
107
|
+
);
|
|
108
|
+
});
|
|
109
|
+
FormControl.displayName = "FormControl";
|
|
110
|
+
|
|
111
|
+
const FormDescription = React.forwardRef<
|
|
112
|
+
HTMLParagraphElement,
|
|
113
|
+
React.HTMLAttributes<HTMLParagraphElement>
|
|
114
|
+
>(({ className, ...props }, ref) => {
|
|
115
|
+
const { formDescriptionId } = useFormField();
|
|
116
|
+
|
|
117
|
+
return (
|
|
118
|
+
<p
|
|
119
|
+
ref={ref}
|
|
120
|
+
id={formDescriptionId}
|
|
121
|
+
className={cn("text-xs text-muted-foreground", className)}
|
|
122
|
+
{...props}
|
|
123
|
+
/>
|
|
124
|
+
);
|
|
125
|
+
});
|
|
126
|
+
FormDescription.displayName = "FormDescription";
|
|
127
|
+
|
|
128
|
+
const FormMessage = React.forwardRef<
|
|
129
|
+
HTMLParagraphElement,
|
|
130
|
+
React.HTMLAttributes<HTMLParagraphElement>
|
|
131
|
+
>(({ className, children, ...props }, ref) => {
|
|
132
|
+
const { error, formMessageId } = useFormField();
|
|
133
|
+
const body = error ? String(error?.message) : children;
|
|
134
|
+
|
|
135
|
+
if (!body) {
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return (
|
|
140
|
+
<p
|
|
141
|
+
ref={ref}
|
|
142
|
+
id={formMessageId}
|
|
143
|
+
className={cn("text-[0.8rem] font-medium text-destructive", className)}
|
|
144
|
+
{...props}
|
|
145
|
+
>
|
|
146
|
+
{body}
|
|
147
|
+
</p>
|
|
148
|
+
);
|
|
149
|
+
});
|
|
150
|
+
FormMessage.displayName = "FormMessage";
|
|
151
|
+
|
|
152
|
+
export {
|
|
153
|
+
Form,
|
|
154
|
+
FormControl,
|
|
155
|
+
FormDescription,
|
|
156
|
+
FormField,
|
|
157
|
+
FormItem,
|
|
158
|
+
FormLabel,
|
|
159
|
+
FormMessage,
|
|
160
|
+
useFormField,
|
|
161
|
+
};
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import type { LucideIcon, LucideProps } from "lucide-react";
|
|
2
|
+
import { Eye, EyeOff, Loader2, Lock, XIcon } from "lucide-react";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
|
|
5
|
+
import { Button } from "@/ui/base/button";
|
|
6
|
+
import { cn } from "@/ui/base/utils";
|
|
7
|
+
|
|
8
|
+
export interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
|
|
9
|
+
inputClassName?: string;
|
|
10
|
+
startIcon?: LucideIcon;
|
|
11
|
+
endIcon?: LucideIcon;
|
|
12
|
+
iconProps?: LucideProps;
|
|
13
|
+
isSearching?: boolean;
|
|
14
|
+
onClear?: () => void;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
|
18
|
+
(
|
|
19
|
+
{
|
|
20
|
+
className,
|
|
21
|
+
inputClassName,
|
|
22
|
+
type,
|
|
23
|
+
isSearching,
|
|
24
|
+
startIcon,
|
|
25
|
+
endIcon,
|
|
26
|
+
onClear,
|
|
27
|
+
iconProps = {},
|
|
28
|
+
...props
|
|
29
|
+
},
|
|
30
|
+
ref,
|
|
31
|
+
) => {
|
|
32
|
+
const [show, setShow] = React.useState(false);
|
|
33
|
+
const StartIcon = startIcon;
|
|
34
|
+
const EndIcon = endIcon;
|
|
35
|
+
const { className: iconClassName, ...iconRest } = iconProps;
|
|
36
|
+
|
|
37
|
+
if (type === "password") {
|
|
38
|
+
return (
|
|
39
|
+
<div className={cn("w-full relative", className)}>
|
|
40
|
+
<div className="absolute left-1.5 top-1/2 transform -translate-y-1/2">
|
|
41
|
+
<Lock size={18} className={cn("text-muted-foreground", iconClassName)} {...iconRest} />
|
|
42
|
+
</div>
|
|
43
|
+
<input
|
|
44
|
+
autoComplete="off"
|
|
45
|
+
type={!show ? type : "text"}
|
|
46
|
+
className={cn(
|
|
47
|
+
"flex h-10 w-full rounded-md border border-input bg-background py-2 px-8 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring focus-visible:ring-offset-0 disabled:cursor-not-allowed disabled:opacity-50",
|
|
48
|
+
inputClassName,
|
|
49
|
+
)}
|
|
50
|
+
ref={ref}
|
|
51
|
+
{...props}
|
|
52
|
+
/>
|
|
53
|
+
<button
|
|
54
|
+
onClick={() => setShow((prev) => !prev)}
|
|
55
|
+
className="absolute right-3 top-1/2 transform -translate-y-1/2"
|
|
56
|
+
type="button"
|
|
57
|
+
>
|
|
58
|
+
{show ? (
|
|
59
|
+
<Eye className="stroke-slate-700/70" size={18} />
|
|
60
|
+
) : (
|
|
61
|
+
<EyeOff className="stroke-slate-700/70" size={18} />
|
|
62
|
+
)}
|
|
63
|
+
</button>
|
|
64
|
+
</div>
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return (
|
|
69
|
+
<div className={cn("w-full relative", className)}>
|
|
70
|
+
{StartIcon && (
|
|
71
|
+
<div className="absolute left-1.5 top-1/2 transform -translate-y-1/2">
|
|
72
|
+
{isSearching ? (
|
|
73
|
+
<Loader2
|
|
74
|
+
size={18}
|
|
75
|
+
className={cn("text-muted-foreground animate-spin", iconClassName)}
|
|
76
|
+
/>
|
|
77
|
+
) : (
|
|
78
|
+
<StartIcon
|
|
79
|
+
size={18}
|
|
80
|
+
className={cn("text-muted-foreground", iconClassName)}
|
|
81
|
+
{...iconRest}
|
|
82
|
+
/>
|
|
83
|
+
)}
|
|
84
|
+
</div>
|
|
85
|
+
)}
|
|
86
|
+
|
|
87
|
+
<input
|
|
88
|
+
type={type}
|
|
89
|
+
className={cn(
|
|
90
|
+
"flex h-10 w-full rounded-md border border-input bg-background py-2 px-4 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring focus-visible:ring-offset-0 disabled:cursor-not-allowed disabled:opacity-50",
|
|
91
|
+
startIcon ? "pl-9 -ml-1" : "",
|
|
92
|
+
endIcon ? "pr-9 -mr-1" : "",
|
|
93
|
+
inputClassName,
|
|
94
|
+
)}
|
|
95
|
+
ref={ref}
|
|
96
|
+
{...props}
|
|
97
|
+
/>
|
|
98
|
+
|
|
99
|
+
{onClear && props.value && (
|
|
100
|
+
<div className="absolute right-3 top-1/2 transform -translate-y-1/2">
|
|
101
|
+
<Button
|
|
102
|
+
onClick={onClear}
|
|
103
|
+
variant={"outline"}
|
|
104
|
+
size="icon"
|
|
105
|
+
type="button"
|
|
106
|
+
className="p-1 h-auto w-auto rounded-lg"
|
|
107
|
+
>
|
|
108
|
+
<XIcon className={cn("text-muted-foreground", iconClassName)} size={18} />
|
|
109
|
+
</Button>
|
|
110
|
+
</div>
|
|
111
|
+
)}
|
|
112
|
+
|
|
113
|
+
{EndIcon && (
|
|
114
|
+
<div className="absolute right-3 top-1/2 transform -translate-y-1/2">
|
|
115
|
+
<EndIcon
|
|
116
|
+
className={cn("text-muted-foreground", iconClassName)}
|
|
117
|
+
{...iconRest}
|
|
118
|
+
size={18}
|
|
119
|
+
/>
|
|
120
|
+
</div>
|
|
121
|
+
)}
|
|
122
|
+
</div>
|
|
123
|
+
);
|
|
124
|
+
},
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
Input.displayName = "Input";
|
|
128
|
+
|
|
129
|
+
export { Input };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import * as LabelPrimitive from "@radix-ui/react-label";
|
|
2
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
|
|
5
|
+
import { cn } from "@/ui/base/utils";
|
|
6
|
+
|
|
7
|
+
const labelVariants = cva(
|
|
8
|
+
"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70",
|
|
9
|
+
);
|
|
10
|
+
|
|
11
|
+
const Label = React.forwardRef<
|
|
12
|
+
React.ElementRef<typeof LabelPrimitive.Root>,
|
|
13
|
+
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> & VariantProps<typeof labelVariants>
|
|
14
|
+
>(({ className, ...props }, ref) => (
|
|
15
|
+
<LabelPrimitive.Root ref={ref} className={cn(labelVariants(), className)} {...props} />
|
|
16
|
+
));
|
|
17
|
+
Label.displayName = LabelPrimitive.Root.displayName;
|
|
18
|
+
|
|
19
|
+
export { Label };
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) Aron Weston 2025.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
"use client";
|
|
6
|
+
|
|
7
|
+
import * as PopoverPrimitive from "@radix-ui/react-popover";
|
|
8
|
+
import type * as React from "react";
|
|
9
|
+
|
|
10
|
+
import { cn } from "@/ui/base/utils";
|
|
11
|
+
|
|
12
|
+
function Popover({ ...props }: React.ComponentProps<typeof PopoverPrimitive.Root>) {
|
|
13
|
+
return <PopoverPrimitive.Root data-slot="popover" {...props} />;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function PopoverTrigger({ ...props }: React.ComponentProps<typeof PopoverPrimitive.Trigger>) {
|
|
17
|
+
return <PopoverPrimitive.Trigger data-slot="popover-trigger" {...props} />;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function PopoverContent({
|
|
21
|
+
className,
|
|
22
|
+
align = "center",
|
|
23
|
+
sideOffset = 4,
|
|
24
|
+
...props
|
|
25
|
+
}: React.ComponentProps<typeof PopoverPrimitive.Content>) {
|
|
26
|
+
return (
|
|
27
|
+
<PopoverPrimitive.Portal>
|
|
28
|
+
<PopoverPrimitive.Content
|
|
29
|
+
data-slot="popover-content"
|
|
30
|
+
align={align}
|
|
31
|
+
sideOffset={sideOffset}
|
|
32
|
+
className={cn(
|
|
33
|
+
"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 w-72 origin-(--radix-popover-content-transform-origin) rounded-md border p-4 shadow-md outline-hidden",
|
|
34
|
+
className,
|
|
35
|
+
)}
|
|
36
|
+
{...props}
|
|
37
|
+
/>
|
|
38
|
+
</PopoverPrimitive.Portal>
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function PopoverAnchor({ ...props }: React.ComponentProps<typeof PopoverPrimitive.Anchor>) {
|
|
43
|
+
return <PopoverPrimitive.Anchor data-slot="popover-anchor" {...props} />;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor };
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) Aron Weston 2025.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
"use client";
|
|
6
|
+
|
|
7
|
+
import * as RadioGroupPrimitive from "@radix-ui/react-radio-group";
|
|
8
|
+
import { CircleIcon } from "lucide-react";
|
|
9
|
+
import type * as React from "react";
|
|
10
|
+
|
|
11
|
+
import { cn } from "@/ui/base/utils";
|
|
12
|
+
|
|
13
|
+
function RadioGroup({
|
|
14
|
+
className,
|
|
15
|
+
...props
|
|
16
|
+
}: React.ComponentProps<typeof RadioGroupPrimitive.Root>) {
|
|
17
|
+
return (
|
|
18
|
+
<RadioGroupPrimitive.Root
|
|
19
|
+
data-slot="radio-group"
|
|
20
|
+
className={cn("grid gap-3", className)}
|
|
21
|
+
{...props}
|
|
22
|
+
/>
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function RadioGroupItem({
|
|
27
|
+
className,
|
|
28
|
+
...props
|
|
29
|
+
}: React.ComponentProps<typeof RadioGroupPrimitive.Item>) {
|
|
30
|
+
return (
|
|
31
|
+
<RadioGroupPrimitive.Item
|
|
32
|
+
data-slot="radio-group-item"
|
|
33
|
+
className={cn(
|
|
34
|
+
"border-input text-primary focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 aspect-square size-4 shrink-0 rounded-full border shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50",
|
|
35
|
+
className,
|
|
36
|
+
)}
|
|
37
|
+
{...props}
|
|
38
|
+
>
|
|
39
|
+
<RadioGroupPrimitive.Indicator
|
|
40
|
+
data-slot="radio-group-indicator"
|
|
41
|
+
className="relative flex items-center justify-center"
|
|
42
|
+
>
|
|
43
|
+
<CircleIcon className="fill-primary absolute top-1/2 left-1/2 size-2 -translate-x-1/2 -translate-y-1/2" />
|
|
44
|
+
</RadioGroupPrimitive.Indicator>
|
|
45
|
+
</RadioGroupPrimitive.Item>
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export { RadioGroup, RadioGroupItem };
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) Aron Weston 2025.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
"use client";
|
|
6
|
+
|
|
7
|
+
import { GripVerticalIcon } from "lucide-react";
|
|
8
|
+
import type * as React from "react";
|
|
9
|
+
import * as ResizablePrimitive from "react-resizable-panels";
|
|
10
|
+
|
|
11
|
+
import { cn } from "@/ui/base/utils";
|
|
12
|
+
|
|
13
|
+
function ResizablePanelGroup({
|
|
14
|
+
className,
|
|
15
|
+
...props
|
|
16
|
+
}: React.ComponentProps<typeof ResizablePrimitive.PanelGroup>) {
|
|
17
|
+
return (
|
|
18
|
+
<ResizablePrimitive.PanelGroup
|
|
19
|
+
data-slot="resizable-panel-group"
|
|
20
|
+
className={cn("flex h-full w-full data-[panel-group-direction=vertical]:flex-col", className)}
|
|
21
|
+
{...props}
|
|
22
|
+
/>
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function ResizablePanel({ ...props }: React.ComponentProps<typeof ResizablePrimitive.Panel>) {
|
|
27
|
+
return <ResizablePrimitive.Panel data-slot="resizable-panel" {...props} />;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function ResizableHandle({
|
|
31
|
+
withHandle,
|
|
32
|
+
className,
|
|
33
|
+
...props
|
|
34
|
+
}: React.ComponentProps<typeof ResizablePrimitive.PanelResizeHandle> & {
|
|
35
|
+
withHandle?: boolean;
|
|
36
|
+
}) {
|
|
37
|
+
return (
|
|
38
|
+
<ResizablePrimitive.PanelResizeHandle
|
|
39
|
+
data-slot="resizable-handle"
|
|
40
|
+
className={cn(
|
|
41
|
+
"bg-border focus-visible:ring-ring relative flex w-px items-center justify-center after:absolute after:inset-y-0 after:left-1/2 after:w-1 after:-translate-x-1/2 focus-visible:ring-1 focus-visible:ring-offset-1 focus-visible:outline-hidden data-[panel-group-direction=vertical]:h-px data-[panel-group-direction=vertical]:w-full data-[panel-group-direction=vertical]:after:left-0 data-[panel-group-direction=vertical]:after:h-1 data-[panel-group-direction=vertical]:after:w-full data-[panel-group-direction=vertical]:after:translate-x-0 data-[panel-group-direction=vertical]:after:-translate-y-1/2 [&[data-panel-group-direction=vertical]>div]:rotate-90",
|
|
42
|
+
className,
|
|
43
|
+
)}
|
|
44
|
+
{...props}
|
|
45
|
+
>
|
|
46
|
+
{withHandle && (
|
|
47
|
+
<div className="bg-border z-10 flex h-4 w-3 items-center justify-center rounded-xs border">
|
|
48
|
+
<GripVerticalIcon className="size-2.5" />
|
|
49
|
+
</div>
|
|
50
|
+
)}
|
|
51
|
+
</ResizablePrimitive.PanelResizeHandle>
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export { ResizablePanelGroup, ResizablePanel, ResizableHandle };
|