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.
Files changed (156) hide show
  1. package/package.json +5 -2
  2. package/templates/_base/.cursor/agents/skills/clerk/SKILL.md +89 -0
  3. package/templates/_base/.cursor/agents/skills/clerk/clerk-backend-api/SKILL.md +142 -0
  4. package/templates/_base/.cursor/agents/skills/clerk/clerk-backend-api/scripts/api-specs-context.sh +30 -0
  5. package/templates/_base/.cursor/agents/skills/clerk/clerk-backend-api/scripts/execute-request.sh +88 -0
  6. package/templates/_base/.cursor/agents/skills/clerk/clerk-backend-api/scripts/extract-endpoint-detail.sh +165 -0
  7. package/templates/_base/.cursor/agents/skills/clerk/clerk-backend-api/scripts/extract-tag-endpoints.sh +208 -0
  8. package/templates/_base/.cursor/agents/skills/clerk/clerk-backend-api/scripts/extract-tags.js +14 -0
  9. package/templates/_base/.cursor/agents/skills/clerk/clerk-custom-ui/SKILL.md +157 -0
  10. package/templates/_base/.cursor/agents/skills/clerk/clerk-custom-ui/core-2/custom-sign-in.md +224 -0
  11. package/templates/_base/.cursor/agents/skills/clerk/clerk-custom-ui/core-2/custom-sign-up.md +190 -0
  12. package/templates/_base/.cursor/agents/skills/clerk/clerk-custom-ui/core-3/custom-sign-in.md +314 -0
  13. package/templates/_base/.cursor/agents/skills/clerk/clerk-custom-ui/core-3/custom-sign-up.md +259 -0
  14. package/templates/_base/.cursor/agents/skills/clerk/clerk-custom-ui/core-3/show-component.md +125 -0
  15. package/templates/_base/.cursor/agents/skills/clerk/clerk-nextjs-patterns/SKILL.md +94 -0
  16. package/templates/_base/.cursor/agents/skills/clerk/clerk-nextjs-patterns/references/api-routes.md +50 -0
  17. package/templates/_base/.cursor/agents/skills/clerk/clerk-nextjs-patterns/references/caching-auth.md +56 -0
  18. package/templates/_base/.cursor/agents/skills/clerk/clerk-nextjs-patterns/references/middleware-strategies.md +68 -0
  19. package/templates/_base/.cursor/agents/skills/clerk/clerk-nextjs-patterns/references/server-actions.md +56 -0
  20. package/templates/_base/.cursor/agents/skills/clerk/clerk-nextjs-patterns/references/server-vs-client.md +104 -0
  21. package/templates/_base/.cursor/agents/skills/clerk/clerk-webhooks/SKILL.md +131 -0
  22. package/templates/_base/.cursor/agents/skills/shadcn/SKILL.md +241 -0
  23. package/templates/_base/.cursor/agents/skills/shadcn/agents/openai.yml +5 -0
  24. package/templates/_base/.cursor/agents/skills/shadcn/assets/shadcn-small.png +0 -0
  25. package/templates/_base/.cursor/agents/skills/shadcn/assets/shadcn.png +0 -0
  26. package/templates/_base/.cursor/agents/skills/shadcn/cli.md +257 -0
  27. package/templates/_base/.cursor/agents/skills/shadcn/customization.md +202 -0
  28. package/templates/_base/.cursor/agents/skills/shadcn/evals/evals.json +47 -0
  29. package/templates/_base/.cursor/agents/skills/shadcn/mcp.md +94 -0
  30. package/templates/_base/.cursor/agents/skills/shadcn/rules/base-vs-radix.md +306 -0
  31. package/templates/_base/.cursor/agents/skills/shadcn/rules/composition.md +195 -0
  32. package/templates/_base/.cursor/agents/skills/shadcn/rules/forms.md +192 -0
  33. package/templates/_base/.cursor/agents/skills/shadcn/rules/icons.md +101 -0
  34. package/templates/_base/.cursor/agents/skills/shadcn/rules/styling.md +162 -0
  35. package/templates/_base/.cursor/commands/builder.md +0 -0
  36. package/templates/_base/.cursor/commands/pr.md +7 -0
  37. package/templates/_base/.cursor/rules/api_architecture.mdc +268 -0
  38. package/templates/_base/.cursor/rules/coding_standards.mdc +64 -0
  39. package/templates/_base/.cursor/rules/convex_rules.mdc +675 -0
  40. package/templates/_base/.cursor/rules/frontend_rules.mdc +268 -0
  41. package/templates/_base/.env.convex.example +3 -0
  42. package/templates/_base/.github/workflows/ci.yml +29 -0
  43. package/templates/_base/.nvmrc +1 -0
  44. package/templates/_base/.vscode/settings.json +9 -0
  45. package/templates/_base/apps/api/auth.config.ts +18 -0
  46. package/templates/_base/apps/api/functions.ts +99 -0
  47. package/templates/_base/apps/api/project.json +22 -0
  48. package/templates/_base/apps/api/schema.ts +11 -0
  49. package/templates/_base/apps/api/todos/crud.ts +81 -0
  50. package/templates/_base/apps/api/todos/schema.ts +11 -0
  51. package/templates/_base/apps/api/todos/types.ts +22 -0
  52. package/templates/_base/apps/api/tsconfig.json +23 -0
  53. package/templates/_base/apps/api/types.ts +16 -0
  54. package/templates/_base/biome.json +114 -0
  55. package/templates/_base/convex.json +4 -0
  56. package/templates/_base/emails/project.json +16 -0
  57. package/templates/_base/emails/tsconfig.json +5 -0
  58. package/templates/_base/emails/welcome_email.tsx +53 -0
  59. package/templates/_base/nx.json +29 -0
  60. package/templates/_base/package.json +73 -0
  61. package/templates/_base/scripts/sync_convex_env.ts +63 -0
  62. package/templates/_base/shared/assets/image.d.ts +4 -0
  63. package/templates/_base/shared/assets/src/styles/global.css +73 -0
  64. package/templates/_base/shared/assets/tsconfig.json +5 -0
  65. package/templates/_base/shared/ui/src/base/alert_dialog.tsx +139 -0
  66. package/templates/_base/shared/ui/src/base/badge.tsx +33 -0
  67. package/templates/_base/shared/ui/src/base/basic_data_table.tsx +61 -0
  68. package/templates/_base/shared/ui/src/base/button.tsx +69 -0
  69. package/templates/_base/shared/ui/src/base/button_group.tsx +82 -0
  70. package/templates/_base/shared/ui/src/base/card.tsx +79 -0
  71. package/templates/_base/shared/ui/src/base/checkbox.tsx +26 -0
  72. package/templates/_base/shared/ui/src/base/command.tsx +165 -0
  73. package/templates/_base/shared/ui/src/base/dialog.tsx +129 -0
  74. package/templates/_base/shared/ui/src/base/dropdown_menu.tsx +232 -0
  75. package/templates/_base/shared/ui/src/base/form.tsx +161 -0
  76. package/templates/_base/shared/ui/src/base/input.tsx +129 -0
  77. package/templates/_base/shared/ui/src/base/label.tsx +19 -0
  78. package/templates/_base/shared/ui/src/base/popover.tsx +46 -0
  79. package/templates/_base/shared/ui/src/base/radio_group.tsx +49 -0
  80. package/templates/_base/shared/ui/src/base/resizable.tsx +55 -0
  81. package/templates/_base/shared/ui/src/base/scroll_area.tsx +44 -0
  82. package/templates/_base/shared/ui/src/base/select.tsx +151 -0
  83. package/templates/_base/shared/ui/src/base/separator.tsx +32 -0
  84. package/templates/_base/shared/ui/src/base/sheet.tsx +130 -0
  85. package/templates/_base/shared/ui/src/base/side_bar.tsx +688 -0
  86. package/templates/_base/shared/ui/src/base/skeleton.tsx +7 -0
  87. package/templates/_base/shared/ui/src/base/spinner.tsx +20 -0
  88. package/templates/_base/shared/ui/src/base/switch.tsx +27 -0
  89. package/templates/_base/shared/ui/src/base/table.tsx +91 -0
  90. package/templates/_base/shared/ui/src/base/text_area.tsx +21 -0
  91. package/templates/_base/shared/ui/src/base/tooltip.tsx +31 -0
  92. package/templates/_base/shared/ui/src/base/utils.ts +17 -0
  93. package/templates/_base/shared/ui/src/hooks/use_keyboard_press.tsx +48 -0
  94. package/templates/_base/shared/ui/src/hooks/use_keyboard_release.tsx +48 -0
  95. package/templates/_base/shared/ui/src/hooks/use_mobile.tsx +25 -0
  96. package/templates/_base/shared/ui/src/hooks/use_mouse_click.tsx +44 -0
  97. package/templates/_base/shared/ui/src/hooks/use_mouse_location.tsx +55 -0
  98. package/templates/_base/shared/ui/src/hooks/use_outside_click.tsx +29 -0
  99. package/templates/_base/shared/ui/src/hooks/use_query_params.tsx +33 -0
  100. package/templates/_base/shared/ui/tsconfig.json +8 -0
  101. package/templates/_base/shared/utils/src/convex.ts +3 -0
  102. package/templates/_base/shared/utils/src/time.ts +12 -0
  103. package/templates/_base/shared/utils/tsconfig.json +5 -0
  104. package/templates/_base/skills-lock.json +35 -0
  105. package/templates/_base/tsconfig.base.json +34 -0
  106. package/templates/nextjs/.env.example +8 -0
  107. package/templates/nextjs/index.d.ts +6 -0
  108. package/templates/nextjs/next-env.d.ts +5 -0
  109. package/templates/nextjs/next.config.js +22 -0
  110. package/templates/nextjs/postcss.config.js +17 -0
  111. package/templates/nextjs/project.json +22 -0
  112. package/templates/nextjs/src/app/(auth)/layout.tsx +21 -0
  113. package/templates/nextjs/src/app/(auth)/not-allowed/page.tsx +22 -0
  114. package/templates/nextjs/src/app/(auth)/sign-in/[[...sign-in]]/page.tsx +15 -0
  115. package/templates/nextjs/src/app/(dashboard)/layout.tsx +27 -0
  116. package/templates/nextjs/src/app/(dashboard)/page.tsx +5 -0
  117. package/templates/nextjs/src/app/(dashboard)/todos/[id]/page.tsx +23 -0
  118. package/templates/nextjs/src/app/(dashboard)/todos/page.tsx +16 -0
  119. package/templates/nextjs/src/app/app.css +3 -0
  120. package/templates/nextjs/src/app/layout.tsx +26 -0
  121. package/templates/nextjs/src/convex.ts +11 -0
  122. package/templates/nextjs/src/middleware.ts +18 -0
  123. package/templates/nextjs/src/providers/convex_provider.tsx +44 -0
  124. package/templates/nextjs/src/surfaces/home_surface.tsx +22 -0
  125. package/templates/nextjs/src/surfaces/todos/all_todos_surface.tsx +97 -0
  126. package/templates/nextjs/src/surfaces/todos/create_todo_sheet.tsx +107 -0
  127. package/templates/nextjs/src/surfaces/todos/single_todo_surface.tsx +90 -0
  128. package/templates/nextjs/src/ui/sidebar/nav_link.tsx +36 -0
  129. package/templates/nextjs/src/ui/sidebar/sidebar.tsx +125 -0
  130. package/templates/nextjs/src/utils/font.ts +9 -0
  131. package/templates/nextjs/tsconfig.json +42 -0
  132. package/templates/react-router/.env.example +8 -0
  133. package/templates/react-router/postcss.config.js +15 -0
  134. package/templates/react-router/project.json +23 -0
  135. package/templates/react-router/public/favicon.ico +0 -0
  136. package/templates/react-router/react-router.config.ts +9 -0
  137. package/templates/react-router/src/app.css +3 -0
  138. package/templates/react-router/src/components/error_boundary.tsx +33 -0
  139. package/templates/react-router/src/layouts/sidebar/sidebar_aside/sidebar_aside.tsx +76 -0
  140. package/templates/react-router/src/layouts/sidebar/sidebar_aside/user_menu.tsx +36 -0
  141. package/templates/react-router/src/layouts/sidebar/sidebar_layout.tsx +22 -0
  142. package/templates/react-router/src/providers/api_auth_provider.tsx +38 -0
  143. package/templates/react-router/src/root.tsx +37 -0
  144. package/templates/react-router/src/routes/auth/layout.tsx +13 -0
  145. package/templates/react-router/src/routes/auth/sign-in.tsx +13 -0
  146. package/templates/react-router/src/routes/index.tsx +9 -0
  147. package/templates/react-router/src/routes/layout.tsx +26 -0
  148. package/templates/react-router/src/routes/todos/[id].tsx +22 -0
  149. package/templates/react-router/src/routes/todos/index.tsx +13 -0
  150. package/templates/react-router/src/routes.ts +12 -0
  151. package/templates/react-router/src/surfaces/home_surface.tsx +20 -0
  152. package/templates/react-router/src/surfaces/todos/all_todos_surface.tsx +87 -0
  153. package/templates/react-router/src/surfaces/todos/create_todo_sheet.tsx +102 -0
  154. package/templates/react-router/src/surfaces/todos/single_todo_surface.tsx +81 -0
  155. package/templates/react-router/tsconfig.json +20 -0
  156. 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 };