better-ts-stack 2.1.2 → 2.2.0

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 (117) hide show
  1. package/README.md +16 -8
  2. package/dist/args.js +2 -1
  3. package/dist/builder/buildPlanner.js +7 -1
  4. package/dist/builder/configGenerator.js +28 -3
  5. package/dist/builder/index.js +2 -1
  6. package/dist/builder/readmeGenerator.js +2 -2
  7. package/dist/output/nextSteps.js +2 -4
  8. package/dist/prompts/stack.js +3 -5
  9. package/dist/types/index.js +1 -1
  10. package/package.json +1 -1
  11. package/templates/backend/express/src/env.ts.hbs +13 -2
  12. package/templates/backend/express/src/index.ts.hbs +31 -2
  13. package/templates/backend/nestjs/src/app.module.ts.hbs +4 -1
  14. package/templates/backend/nestjs/src/env.ts.hbs +13 -2
  15. package/templates/backend/nestjs/src/health/health.controller.ts.hbs +7 -0
  16. package/templates/backend/nestjs/src/{main.ts → main.ts.hbs} +11 -0
  17. package/templates/frontend/nextjs/app/globals.css +119 -79
  18. package/templates/frontend/nextjs/app/layout.tsx +13 -11
  19. package/templates/frontend/nextjs/app/page.tsx.hbs +63 -43
  20. package/templates/frontend/nextjs/components/ui/alert.tsx +75 -0
  21. package/templates/frontend/nextjs/components/ui/badge.tsx +51 -0
  22. package/templates/frontend/nextjs/components/ui/button.tsx +33 -27
  23. package/templates/frontend/nextjs/components/ui/card.tsx +76 -34
  24. package/templates/frontend/nextjs/components/ui/field.tsx +203 -32
  25. package/templates/frontend/nextjs/components/ui/input.tsx +8 -7
  26. package/templates/frontend/nextjs/components/ui/label.tsx +19 -0
  27. package/templates/frontend/nextjs/components/ui/separator.tsx +24 -0
  28. package/templates/frontend/nextjs/components/ui/skeleton.tsx +13 -0
  29. package/templates/frontend/nextjs/components/ui/spinner.tsx +10 -0
  30. package/templates/frontend/nextjs/components.json +11 -5
  31. package/templates/frontend/nextjs/config.json +4 -3
  32. package/templates/frontend/nextjs/lib/utils.ts +1 -6
  33. package/templates/frontend/tanstack/components.json +12 -5
  34. package/templates/frontend/tanstack/config.json +5 -2
  35. package/templates/frontend/tanstack/src/components/ui/alert.tsx +75 -0
  36. package/templates/frontend/tanstack/src/components/ui/badge.tsx +51 -0
  37. package/templates/frontend/tanstack/src/components/ui/button.tsx +33 -27
  38. package/templates/frontend/tanstack/src/components/ui/card.tsx +76 -34
  39. package/templates/frontend/tanstack/src/components/ui/field.tsx +201 -32
  40. package/templates/frontend/tanstack/src/components/ui/input.tsx +8 -7
  41. package/templates/frontend/tanstack/src/components/ui/label.tsx +17 -0
  42. package/templates/frontend/tanstack/src/components/ui/separator.tsx +24 -0
  43. package/templates/frontend/tanstack/src/components/ui/skeleton.tsx +13 -0
  44. package/templates/frontend/tanstack/src/components/ui/spinner.tsx +10 -0
  45. package/templates/frontend/tanstack/src/lib/utils.ts +1 -6
  46. package/templates/frontend/tanstack/src/routes/index.tsx.hbs +64 -45
  47. package/templates/frontend/tanstack/src/styles/app.css +100 -55
  48. package/templates/modules/agents/AGENTS.md.hbs +29 -21
  49. package/templates/modules/auth/express/config.json +12 -11
  50. package/templates/modules/auth/express/src/lib/auth.ts.hbs +143 -0
  51. package/templates/modules/auth/express/src/lib/email.ts +37 -0
  52. package/templates/modules/auth/express/src/middleware/requireAuth.ts +29 -0
  53. package/templates/modules/auth/express/src/routes/me.ts +13 -0
  54. package/templates/modules/auth/nestjs/config.json +13 -11
  55. package/templates/modules/auth/nestjs/src/auth/auth.module.ts.hbs +64 -14
  56. package/templates/modules/auth/nestjs/src/auth/auth.ts.hbs +152 -0
  57. package/templates/modules/auth/nestjs/src/auth/email.ts +37 -0
  58. package/templates/modules/auth/nestjs/src/me/me.controller.ts +15 -0
  59. package/templates/modules/auth/nextjs/app/dashboard/page.tsx +101 -45
  60. package/templates/modules/auth/nextjs/app/forgot-password/page.tsx +11 -0
  61. package/templates/modules/auth/nextjs/app/reset-password/page.tsx +17 -0
  62. package/templates/modules/auth/nextjs/app/sign-in/page.tsx +23 -17
  63. package/templates/modules/auth/nextjs/app/sign-up/page.tsx +25 -19
  64. package/templates/modules/auth/nextjs/components/auth/change-password-form.tsx +125 -0
  65. package/templates/modules/auth/nextjs/components/auth/email-verification-card.tsx +91 -0
  66. package/templates/modules/auth/nextjs/components/auth/forgot-password-form.tsx +133 -0
  67. package/templates/modules/auth/nextjs/components/auth/reset-password-form.tsx +155 -0
  68. package/templates/modules/auth/nextjs/components/auth/sign-in-form.tsx +46 -32
  69. package/templates/modules/auth/nextjs/components/auth/sign-out-button.tsx +6 -5
  70. package/templates/modules/auth/nextjs/components/auth/sign-up-form.tsx +36 -30
  71. package/templates/modules/auth/nextjs/components/auth/social-buttons.tsx +73 -0
  72. package/templates/modules/auth/nextjs/config.json +8 -2
  73. package/templates/modules/auth/nextjs/lib/auth-schema.ts +27 -2
  74. package/templates/modules/auth/nextjs/lib/auth.ts.hbs +65 -0
  75. package/templates/modules/auth/nextjs/lib/email.ts +43 -0
  76. package/templates/modules/auth/nextjs/lib/session.ts +12 -0
  77. package/templates/modules/auth/tanstack/config.json +9 -3
  78. package/templates/modules/auth/tanstack/src/components/auth/change-password-form.tsx +123 -0
  79. package/templates/modules/auth/tanstack/src/components/auth/email-verification-card.tsx +89 -0
  80. package/templates/modules/auth/tanstack/src/components/auth/forgot-password-form.tsx +131 -0
  81. package/templates/modules/auth/tanstack/src/components/auth/reset-password-form.tsx +149 -0
  82. package/templates/modules/auth/tanstack/src/components/auth/sign-in-form.tsx +44 -35
  83. package/templates/modules/auth/tanstack/src/components/auth/sign-out-button.tsx +7 -6
  84. package/templates/modules/auth/tanstack/src/components/auth/sign-up-form.tsx +37 -34
  85. package/templates/modules/auth/tanstack/src/components/auth/social-buttons.tsx +71 -0
  86. package/templates/modules/auth/tanstack/src/lib/auth-functions.ts +7 -1
  87. package/templates/modules/auth/tanstack/src/lib/auth-schema.ts +27 -2
  88. package/templates/modules/auth/tanstack/src/lib/auth.ts.hbs +61 -1
  89. package/templates/modules/auth/tanstack/src/lib/email.ts +43 -0
  90. package/templates/modules/auth/tanstack/src/routeTree.gen.ts +61 -3
  91. package/templates/modules/auth/tanstack/src/routes/dashboard.tsx +40 -32
  92. package/templates/modules/auth/tanstack/src/routes/forgot-password.tsx +17 -0
  93. package/templates/modules/auth/tanstack/src/routes/reset-password.tsx +25 -0
  94. package/templates/modules/auth/tanstack/src/routes/sign-in.tsx +19 -12
  95. package/templates/modules/auth/tanstack/src/routes/sign-up.tsx +21 -14
  96. package/templates/modules/drizzle/express/src/lib/schema.ts.hbs +90 -0
  97. package/templates/modules/drizzle/nestjs/src/database/schema.ts.hbs +90 -0
  98. package/templates/modules/mongoose/nestjs/config.json +2 -1
  99. package/templates/modules/prisma/express/prisma/schema.prisma.hbs +92 -0
  100. package/templates/modules/prisma/nestjs/prisma/schema.prisma.hbs +91 -0
  101. package/templates/modules/rate-limit/express/src/middleware/rateLimit.ts +0 -8
  102. package/templates/modules/auth/express/src/controllers/authController.ts.hbs +0 -116
  103. package/templates/modules/auth/express/src/lib/jwt.ts.hbs +0 -27
  104. package/templates/modules/auth/express/src/middleware/requireAuth.ts.hbs +0 -25
  105. package/templates/modules/auth/express/src/routes/auth.ts.hbs +0 -27
  106. package/templates/modules/auth/express/src/services/userStore.ts.hbs +0 -117
  107. package/templates/modules/auth/nestjs/src/auth/auth.controller.ts.hbs +0 -61
  108. package/templates/modules/auth/nestjs/src/auth/auth.guard.ts +0 -36
  109. package/templates/modules/auth/nestjs/src/auth/auth.schemas.ts +0 -35
  110. package/templates/modules/auth/nestjs/src/auth/auth.service.ts +0 -70
  111. package/templates/modules/auth/nestjs/src/auth/jwt.ts +0 -27
  112. package/templates/modules/auth/nestjs/src/auth/users.service.ts.hbs +0 -144
  113. package/templates/modules/auth/nestjs/src/auth/zod-validation.pipe.ts +0 -16
  114. package/templates/modules/drizzle/express/src/lib/schema.ts +0 -12
  115. package/templates/modules/drizzle/nestjs/src/database/schema.ts +0 -12
  116. package/templates/modules/prisma/express/prisma/schema.prisma +0 -24
  117. package/templates/modules/prisma/nestjs/prisma/schema.prisma +0 -23
@@ -1,67 +1,238 @@
1
- import * as React from "react";
1
+ "use client"
2
2
 
3
- import { cn } from "@/lib/utils";
3
+ import { useMemo } from "react"
4
+ import { cva, type VariantProps } from "class-variance-authority"
5
+ import { cn } from "cn"
4
6
 
5
- function FieldGroup({
7
+ import { Label } from "@/components/ui/label"
8
+ import { Separator } from "@/components/ui/separator"
9
+
10
+ function FieldSet({ className, ...props }: React.ComponentProps<"fieldset">) {
11
+ return (
12
+ <fieldset
13
+ data-slot="field-set"
14
+ className={cn(
15
+ "flex flex-col gap-6 has-[>[data-slot=checkbox-group]]:gap-3 has-[>[data-slot=radio-group]]:gap-3",
16
+ className
17
+ )}
18
+ {...props}
19
+ />
20
+ )
21
+ }
22
+
23
+ function FieldLegend({
6
24
  className,
25
+ variant = "legend",
7
26
  ...props
8
- }: React.HTMLAttributes<HTMLDivElement>) {
9
- return <div className={cn("space-y-5", className)} {...props} />;
27
+ }: React.ComponentProps<"legend"> & { variant?: "legend" | "label" }) {
28
+ return (
29
+ <legend
30
+ data-slot="field-legend"
31
+ data-variant={variant}
32
+ className={cn(
33
+ "mb-3 font-medium data-[variant=label]:text-sm data-[variant=legend]:text-base",
34
+ className
35
+ )}
36
+ {...props}
37
+ />
38
+ )
39
+ }
40
+
41
+ function FieldGroup({ className, ...props }: React.ComponentProps<"div">) {
42
+ return (
43
+ <div
44
+ data-slot="field-group"
45
+ className={cn(
46
+ "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",
47
+ className
48
+ )}
49
+ {...props}
50
+ />
51
+ )
10
52
  }
11
53
 
12
- type FieldProps = React.HTMLAttributes<HTMLDivElement> & {
13
- "data-invalid"?: boolean;
14
- };
54
+ const fieldVariants = cva(
55
+ "group/field flex w-full gap-3 data-[invalid=true]:text-destructive",
56
+ {
57
+ variants: {
58
+ orientation: {
59
+ vertical: "flex-col *:w-full [&>.sr-only]:w-auto",
60
+ horizontal:
61
+ "flex-row items-center has-[>[data-slot=field-content]]:items-start *:data-[slot=field-label]:flex-auto has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px",
62
+ responsive:
63
+ "flex-col *:w-full @md/field-group:flex-row @md/field-group:items-center @md/field-group:*:w-auto @md/field-group:has-[>[data-slot=field-content]]:items-start @md/field-group:*:data-[slot=field-label]:flex-auto [&>.sr-only]:w-auto @md/field-group:has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px",
64
+ },
65
+ },
66
+ defaultVariants: {
67
+ orientation: "vertical",
68
+ },
69
+ }
70
+ )
15
71
 
16
72
  function Field({
17
73
  className,
18
- "data-invalid": dataInvalid,
74
+ orientation = "vertical",
19
75
  ...props
20
- }: FieldProps) {
76
+ }: React.ComponentProps<"div"> & VariantProps<typeof fieldVariants>) {
77
+ return (
78
+ <div
79
+ role="group"
80
+ data-slot="field"
81
+ data-orientation={orientation}
82
+ className={cn(fieldVariants({ orientation }), className)}
83
+ {...props}
84
+ />
85
+ )
86
+ }
87
+
88
+ function FieldContent({ className, ...props }: React.ComponentProps<"div">) {
21
89
  return (
22
90
  <div
23
- data-invalid={dataInvalid}
24
- className={cn("space-y-2", className)}
91
+ data-slot="field-content"
92
+ className={cn(
93
+ "group/field-content flex flex-1 flex-col gap-1 leading-snug",
94
+ className
95
+ )}
25
96
  {...props}
26
97
  />
27
- );
98
+ )
28
99
  }
29
100
 
30
101
  function FieldLabel({
31
102
  className,
32
103
  ...props
33
- }: React.LabelHTMLAttributes<HTMLLabelElement>) {
104
+ }: React.ComponentProps<typeof Label>) {
34
105
  return (
35
- <label
36
- className={cn("text-sm font-medium text-slate-900", className)}
106
+ <Label
107
+ data-slot="field-label"
108
+ className={cn(
109
+ "group/field-label peer/field-label flex w-fit gap-2 leading-snug group-data-[disabled=true]/field:opacity-50 has-data-checked:border-primary/30 has-data-checked:bg-primary/5 has-[>[data-slot=field]]:rounded-md has-[>[data-slot=field]]:border has-[>[data-slot=field]]:not-has-[:disabled,[data-disabled]]:hover:bg-muted/50 has-[>[data-slot=field]]:has-[:focus-visible]:border-ring has-[>[data-slot=field]]:has-[:focus-visible]:ring-3 has-[>[data-slot=field]]:has-[:focus-visible]:ring-ring/50 *:data-[slot=field]:p-3 dark:has-data-checked:border-primary/20 dark:has-data-checked:bg-primary/10",
110
+ "has-[>[data-slot=field]]:w-full has-[>[data-slot=field]]:flex-col",
111
+ className
112
+ )}
37
113
  {...props}
38
114
  />
39
- );
115
+ )
40
116
  }
41
117
 
42
- function FieldDescription({
118
+ function FieldTitle({ className, ...props }: React.ComponentProps<"div">) {
119
+ return (
120
+ <div
121
+ data-slot="field-label"
122
+ className={cn(
123
+ "flex w-fit items-center gap-2 text-sm font-medium group-data-[disabled=true]/field:opacity-50",
124
+ className
125
+ )}
126
+ {...props}
127
+ />
128
+ )
129
+ }
130
+
131
+ function FieldDescription({ className, ...props }: React.ComponentProps<"p">) {
132
+ return (
133
+ <p
134
+ data-slot="field-description"
135
+ className={cn(
136
+ "text-left text-sm leading-normal font-normal text-muted-foreground group-has-data-horizontal/field:text-balance [[data-variant=legend]+&]:-mt-1.5",
137
+ "last:mt-0 nth-last-2:-mt-1",
138
+ "[&>a]:underline [&>a]:underline-offset-4 [&>a:hover]:text-primary",
139
+ className
140
+ )}
141
+ {...props}
142
+ />
143
+ )
144
+ }
145
+
146
+ function FieldSeparator({
147
+ children,
43
148
  className,
44
149
  ...props
45
- }: React.HTMLAttributes<HTMLParagraphElement>) {
46
- return <p className={cn("text-sm text-slate-500", className)} {...props} />;
150
+ }: React.ComponentProps<"div"> & {
151
+ children?: React.ReactNode
152
+ }) {
153
+ return (
154
+ <div
155
+ data-slot="field-separator"
156
+ data-content={!!children}
157
+ className={cn(
158
+ "relative -my-2 h-5 text-sm group-data-[variant=outline]/field-group:-mb-2",
159
+ className
160
+ )}
161
+ {...props}
162
+ >
163
+ <Separator className="absolute inset-0 top-1/2" />
164
+ {children && (
165
+ <span
166
+ className="relative mx-auto block w-fit bg-background px-2 text-muted-foreground"
167
+ data-slot="field-separator-content"
168
+ >
169
+ {children}
170
+ </span>
171
+ )}
172
+ </div>
173
+ )
47
174
  }
48
175
 
49
- type FieldErrorProps = React.HTMLAttributes<HTMLParagraphElement> & {
50
- errors?: Array<{ message?: string } | undefined>;
51
- };
176
+ function FieldError({
177
+ className,
178
+ children,
179
+ errors,
180
+ ...props
181
+ }: React.ComponentProps<"div"> & {
182
+ errors?: Array<{ message?: string } | undefined>
183
+ }) {
184
+ const content = useMemo(() => {
185
+ if (children) {
186
+ return children
187
+ }
188
+
189
+ if (!errors?.length) {
190
+ return null
191
+ }
52
192
 
53
- function FieldError({ className, errors, ...props }: FieldErrorProps) {
54
- const message = errors?.find((error) => error?.message)?.message;
193
+ const uniqueErrors = [
194
+ ...new Map(errors.map((error) => [error?.message, error])).values(),
195
+ ]
55
196
 
56
- if (!message) {
57
- return null;
197
+ if (uniqueErrors?.length == 1) {
198
+ return uniqueErrors[0]?.message
199
+ }
200
+
201
+ return (
202
+ <ul className="ml-4 flex list-disc flex-col gap-1">
203
+ {uniqueErrors.map(
204
+ (error, index) =>
205
+ error?.message && <li key={index}>{error.message}</li>
206
+ )}
207
+ </ul>
208
+ )
209
+ }, [children, errors])
210
+
211
+ if (!content) {
212
+ return null
58
213
  }
59
214
 
60
215
  return (
61
- <p className={cn("text-sm font-medium text-rose-600", className)} {...props}>
62
- {message}
63
- </p>
64
- );
216
+ <div
217
+ role="alert"
218
+ data-slot="field-error"
219
+ className={cn("text-sm font-normal text-destructive", className)}
220
+ {...props}
221
+ >
222
+ {content}
223
+ </div>
224
+ )
65
225
  }
66
226
 
67
- export { Field, FieldDescription, FieldError, FieldGroup, FieldLabel };
227
+ export {
228
+ Field,
229
+ FieldLabel,
230
+ FieldDescription,
231
+ FieldError,
232
+ FieldGroup,
233
+ FieldLegend,
234
+ FieldSeparator,
235
+ FieldSet,
236
+ FieldContent,
237
+ FieldTitle,
238
+ }
@@ -1,18 +1,19 @@
1
- import * as React from "react";
2
-
3
- import { cn } from "@/lib/utils";
1
+ import * as React from "react"
2
+ import { Input as InputPrimitive } from "@base-ui/react/input"
3
+ import { cn } from "cn"
4
4
 
5
5
  function Input({ className, type, ...props }: React.ComponentProps<"input">) {
6
6
  return (
7
- <input
7
+ <InputPrimitive
8
8
  type={type}
9
+ data-slot="input"
9
10
  className={cn(
10
- "flex h-11 w-full rounded-2xl border border-slate-200 bg-white px-3 py-2 text-sm text-slate-950 shadow-sm transition-[color,box-shadow] outline-none placeholder:text-slate-400 focus-visible:border-sky-500 focus-visible:ring-[3px] focus-visible:ring-sky-500/20 disabled:cursor-not-allowed disabled:opacity-50",
11
+ "h-9 w-full min-w-0 rounded-md border border-input bg-transparent px-2.5 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40",
11
12
  className
12
13
  )}
13
14
  {...props}
14
15
  />
15
- );
16
+ )
16
17
  }
17
18
 
18
- export { Input };
19
+ export { Input }
@@ -0,0 +1,19 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { cn } from "cn"
5
+
6
+ function Label({ className, ...props }: React.ComponentProps<"label">) {
7
+ return (
8
+ <label
9
+ data-slot="label"
10
+ className={cn(
11
+ "flex items-center gap-2 text-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50",
12
+ className
13
+ )}
14
+ {...props}
15
+ />
16
+ )
17
+ }
18
+
19
+ export { Label }
@@ -0,0 +1,24 @@
1
+ "use client"
2
+
3
+ import { Separator as SeparatorPrimitive } from "@base-ui/react/separator"
4
+ import { cn } from "cn"
5
+
6
+ function Separator({
7
+ className,
8
+ orientation = "horizontal",
9
+ ...props
10
+ }: SeparatorPrimitive.Props) {
11
+ return (
12
+ <SeparatorPrimitive
13
+ data-slot="separator"
14
+ orientation={orientation}
15
+ className={cn(
16
+ "shrink-0 bg-border data-horizontal:h-px data-horizontal:w-full data-vertical:w-px data-vertical:self-stretch",
17
+ className
18
+ )}
19
+ {...props}
20
+ />
21
+ )
22
+ }
23
+
24
+ export { Separator }
@@ -0,0 +1,13 @@
1
+ import { cn } from "cn"
2
+
3
+ function Skeleton({ className, ...props }: React.ComponentProps<"div">) {
4
+ return (
5
+ <div
6
+ data-slot="skeleton"
7
+ className={cn("animate-pulse rounded-md bg-muted", className)}
8
+ {...props}
9
+ />
10
+ )
11
+ }
12
+
13
+ export { Skeleton }
@@ -0,0 +1,10 @@
1
+ import { cn } from "cn"
2
+ import { Loader2Icon } from "lucide-react"
3
+
4
+ function Spinner({ className, ...props }: React.ComponentProps<"svg">) {
5
+ return (
6
+ <Loader2Icon data-slot="spinner" role="status" aria-label="Loading" className={cn("size-4 animate-spin", className)} {...props} />
7
+ )
8
+ }
9
+
10
+ export { Spinner }
@@ -1,19 +1,25 @@
1
1
  {
2
2
  "$schema": "https://ui.shadcn.com/schema.json",
3
- "style": "new-york",
3
+ "style": "base-vega",
4
4
  "rsc": true,
5
5
  "tsx": true,
6
6
  "tailwind": {
7
7
  "config": "",
8
8
  "css": "app/globals.css",
9
9
  "baseColor": "neutral",
10
- "cssVariables": true
10
+ "cssVariables": true,
11
+ "prefix": ""
11
12
  },
12
13
  "iconLibrary": "lucide",
14
+ "rtl": false,
13
15
  "aliases": {
14
16
  "components": "@/components",
15
- "lib": "@/lib",
17
+ "utils": "@/lib/utils",
16
18
  "ui": "@/components/ui",
17
- "utils": "@/lib/utils"
18
- }
19
+ "lib": "@/lib",
20
+ "hooks": "@/hooks"
21
+ },
22
+ "menuColor": "default",
23
+ "menuAccent": "subtle",
24
+ "registries": {}
19
25
  }
@@ -11,14 +11,15 @@
11
11
  "type:check": "tsc --noEmit"
12
12
  },
13
13
  "dependencies": {
14
+ "@base-ui/react": "^1.9.0",
14
15
  "class-variance-authority": "^0.7.1",
15
- "clsx": "^2.1.1",
16
+ "cn": "^0.5.2",
16
17
  "lucide-react": "^1.66.0",
17
18
  "next": "^16.4.5",
18
19
  "react": "^19.3.1",
19
20
  "react-dom": "^19.3.1",
20
- "tailwind-merge": "^3.7.0",
21
- "@radix-ui/react-slot": "^1.5.0"
21
+ "shadcn": "^4.23.0",
22
+ "tw-animate-css": "^1.4.0"
22
23
  },
23
24
  "devDependencies": {
24
25
  "@tailwindcss/postcss": "^4.3.3",
@@ -1,6 +1 @@
1
- import { clsx, type ClassValue } from "clsx";
2
- import { twMerge } from "tailwind-merge";
3
-
4
- export function cn(...inputs: ClassValue[]) {
5
- return twMerge(clsx(inputs));
6
- }
1
+ export { cn } from "cn";
@@ -1,18 +1,25 @@
1
1
  {
2
2
  "$schema": "https://ui.shadcn.com/schema.json",
3
- "style": "new-york",
3
+ "style": "base-vega",
4
+ "rsc": false,
4
5
  "tsx": true,
5
6
  "tailwind": {
6
7
  "config": "",
7
8
  "css": "src/styles/app.css",
8
9
  "baseColor": "neutral",
9
- "cssVariables": true
10
+ "cssVariables": true,
11
+ "prefix": ""
10
12
  },
11
13
  "iconLibrary": "lucide",
14
+ "rtl": false,
12
15
  "aliases": {
13
16
  "components": "~/components",
14
- "lib": "~/lib",
17
+ "utils": "~/lib/utils",
15
18
  "ui": "~/components/ui",
16
- "utils": "~/lib/utils"
17
- }
19
+ "lib": "~/lib",
20
+ "hooks": "~/hooks"
21
+ },
22
+ "menuColor": "default",
23
+ "menuAccent": "subtle",
24
+ "registries": {}
18
25
  }
@@ -12,6 +12,8 @@
12
12
  "lint": "eslint src"
13
13
  },
14
14
  "dependencies": {
15
+ "@base-ui/react": "^1.9.0",
16
+ "@fontsource-variable/inter": "^5.3.0",
15
17
  "@tanstack/react-query": "^5.105.0",
16
18
  "@tanstack/react-query-devtools": "^5.105.0",
17
19
  "@tanstack/react-router": "^1.170.46",
@@ -19,11 +21,12 @@
19
21
  "@tanstack/react-router-ssr-query": "^1.167.3",
20
22
  "@tanstack/react-start": "^1.169.4",
21
23
  "class-variance-authority": "^0.7.1",
22
- "clsx": "^2.1.1",
24
+ "cn": "^0.5.2",
23
25
  "lucide-react": "^1.66.0",
24
26
  "react": "^19.3.1",
25
27
  "react-dom": "^19.3.1",
26
- "tailwind-merge": "^3.7.0"
28
+ "shadcn": "^4.23.0",
29
+ "tw-animate-css": "^1.4.0"
27
30
  },
28
31
  "devDependencies": {
29
32
  "@eslint/js": "^10.0.1",
@@ -0,0 +1,75 @@
1
+ import * as React from "react"
2
+ import { cva, type VariantProps } from "class-variance-authority"
3
+ import { cn } from "cn"
4
+
5
+ const alertVariants = cva(
6
+ "group/alert relative grid w-full gap-0.5 rounded-lg border px-4 py-3 text-left text-sm has-data-[slot=alert-action]:relative has-data-[slot=alert-action]:pr-18 has-[>svg]:grid-cols-[auto_1fr] has-[>svg]:gap-x-2.5 *:[svg]:row-span-2 *:[svg]:translate-y-0.5 *:[svg]:text-current *:[svg:not([class*='size-'])]:size-4",
7
+ {
8
+ variants: {
9
+ variant: {
10
+ default: "bg-card text-card-foreground",
11
+ destructive:
12
+ "bg-card text-destructive *:data-[slot=alert-description]:text-destructive/90 *:[svg]:text-current",
13
+ },
14
+ },
15
+ defaultVariants: {
16
+ variant: "default",
17
+ },
18
+ }
19
+ )
20
+
21
+ function Alert({
22
+ className,
23
+ variant,
24
+ ...props
25
+ }: React.ComponentProps<"div"> & VariantProps<typeof alertVariants>) {
26
+ return (
27
+ <div
28
+ data-slot="alert"
29
+ role="alert"
30
+ className={cn(alertVariants({ variant }), className)}
31
+ {...props}
32
+ />
33
+ )
34
+ }
35
+
36
+ function AlertTitle({ className, ...props }: React.ComponentProps<"div">) {
37
+ return (
38
+ <div
39
+ data-slot="alert-title"
40
+ className={cn(
41
+ "font-medium group-has-[>svg]/alert:col-start-2 [&_a]:underline [&_a]:underline-offset-3 [&_a]:hover:text-foreground",
42
+ className
43
+ )}
44
+ {...props}
45
+ />
46
+ )
47
+ }
48
+
49
+ function AlertDescription({
50
+ className,
51
+ ...props
52
+ }: React.ComponentProps<"div">) {
53
+ return (
54
+ <div
55
+ data-slot="alert-description"
56
+ className={cn(
57
+ "text-sm text-balance text-muted-foreground md:text-pretty [&_a]:underline [&_a]:underline-offset-3 [&_a]:hover:text-foreground [&_p:not(:last-child)]:mb-4",
58
+ className
59
+ )}
60
+ {...props}
61
+ />
62
+ )
63
+ }
64
+
65
+ function AlertAction({ className, ...props }: React.ComponentProps<"div">) {
66
+ return (
67
+ <div
68
+ data-slot="alert-action"
69
+ className={cn("absolute top-2.5 right-3", className)}
70
+ {...props}
71
+ />
72
+ )
73
+ }
74
+
75
+ export { Alert, AlertTitle, AlertDescription, AlertAction }
@@ -0,0 +1,51 @@
1
+ import { mergeProps } from "@base-ui/react/merge-props"
2
+ import { useRender } from "@base-ui/react/use-render"
3
+ import { cva, type VariantProps } from "class-variance-authority"
4
+ import { cn } from "cn"
5
+
6
+ const badgeVariants = cva(
7
+ "group/badge inline-flex h-5 w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-4xl border border-transparent px-2 py-0.5 text-xs font-medium whitespace-nowrap transition-all focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&>svg]:pointer-events-none [&>svg]:size-3!",
8
+ {
9
+ variants: {
10
+ variant: {
11
+ default: "bg-primary text-primary-foreground [a]:hover:bg-primary/80",
12
+ secondary:
13
+ "bg-secondary text-secondary-foreground [a]:hover:bg-secondary/80",
14
+ destructive:
15
+ "bg-destructive/10 text-destructive focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:focus-visible:ring-destructive/40 [a]:hover:bg-destructive/20",
16
+ outline:
17
+ "border-border text-foreground [a]:hover:bg-muted [a]:hover:text-muted-foreground",
18
+ ghost:
19
+ "hover:bg-muted hover:text-muted-foreground dark:hover:bg-muted/50",
20
+ link: "text-primary underline-offset-4 hover:underline",
21
+ },
22
+ },
23
+ defaultVariants: {
24
+ variant: "default",
25
+ },
26
+ }
27
+ )
28
+
29
+ function Badge({
30
+ className,
31
+ variant = "default",
32
+ render,
33
+ ...props
34
+ }: useRender.ComponentProps<"span"> & VariantProps<typeof badgeVariants>) {
35
+ return useRender({
36
+ defaultTagName: "span",
37
+ props: mergeProps<"span">(
38
+ {
39
+ className: cn(badgeVariants({ variant }), className),
40
+ },
41
+ props
42
+ ),
43
+ render,
44
+ state: {
45
+ slot: "badge",
46
+ variant,
47
+ },
48
+ })
49
+ }
50
+
51
+ export { Badge, badgeVariants }