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,14 +1,17 @@
1
1
  import type { Metadata } from "next";
2
- import { Geist, Geist_Mono } from "next/font/google";
2
+ import { Geist_Mono, Inter } from "next/font/google";
3
+
4
+ import { cn } from "@/lib/utils";
5
+
3
6
  import "./globals.css";
4
7
 
5
- const geistSans = Geist({
6
- variable: "--font-geist-sans",
8
+ const fontSans = Inter({
9
+ variable: "--font-sans",
7
10
  subsets: ["latin"],
8
11
  });
9
12
 
10
- const geistMono = Geist_Mono({
11
- variable: "--font-geist-mono",
13
+ const fontMono = Geist_Mono({
14
+ variable: "--font-mono",
12
15
  subsets: ["latin"],
13
16
  });
14
17
 
@@ -23,12 +26,11 @@ export default function RootLayout({
23
26
  children: React.ReactNode;
24
27
  }>) {
25
28
  return (
26
- <html lang="en">
27
- <body
28
- className={`${geistSans.variable} ${geistMono.variable} bg-background text-foreground antialiased`}
29
- >
30
- {children}
31
- </body>
29
+ <html
30
+ lang="en"
31
+ className={cn("font-sans antialiased", fontSans.variable, fontMono.variable)}
32
+ >
33
+ <body>{children}</body>
32
34
  </html>
33
35
  );
34
36
  }
@@ -2,14 +2,21 @@ import Link from "next/link";
2
2
 
3
3
  import { ArrowRight } from "lucide-react";
4
4
 
5
- import { buttonVariants } from "@/components/ui/button";
6
- import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
7
- import { cn } from "@/lib/utils";
5
+ import { Alert, AlertDescription } from "@/components/ui/alert";
6
+ import { Badge } from "@/components/ui/badge";
7
+ import { Button } from "@/components/ui/button";
8
+ import {
9
+ Card,
10
+ CardContent,
11
+ CardFooter,
12
+ CardHeader,
13
+ CardTitle,
14
+ } from "@/components/ui/card";
8
15
 
9
16
  const featureList = [
10
17
  "Next.js 16 App Router",
11
18
  "React 19 and TypeScript",
12
- "Tailwind CSS v4",
19
+ "Tailwind CSS v4 and shadcn/ui on Base UI",
13
20
  {{#if useAuth}}"Better Auth-ready session flow",{{/if}}
14
21
  {{#if (eq database "prisma")}}"Prisma database tooling",{{/if}}
15
22
  {{#if (eq database "drizzle")}}"Drizzle database tooling",{{/if}}
@@ -18,66 +25,74 @@ const featureList = [
18
25
 
19
26
  export default function Home() {
20
27
  return (
21
- <main className="min-h-screen bg-background text-foreground">
22
- <div className="mx-auto flex min-h-screen max-w-6xl flex-col justify-center gap-12 px-6 py-16 sm:px-10 lg:px-12">
28
+ <main className="min-h-svh">
29
+ <div className="mx-auto flex min-h-svh max-w-6xl flex-col justify-center gap-12 px-6 py-16 sm:px-10 lg:px-12">
23
30
  <section className="grid gap-10 lg:grid-cols-[1.2fr_0.8fr] lg:items-center">
24
- <div className="space-y-6">
25
- <span className="inline-flex w-fit items-center rounded-full border border-border bg-card px-3 py-1 text-xs font-medium uppercase tracking-[0.24em] text-muted-foreground shadow-sm">
26
- Created with better-ts-stack
27
- </span>
28
- <div className="space-y-4">
31
+ <div className="flex flex-col gap-6">
32
+ <Badge variant="outline">Created with better-ts-stack</Badge>
33
+ <div className="flex flex-col gap-4">
29
34
  <h1 className="max-w-3xl text-4xl font-semibold tracking-tight sm:text-5xl">
30
35
  Ship your stack faster with a polished TypeScript foundation.
31
36
  </h1>
32
37
  <p className="max-w-2xl text-base leading-7 text-muted-foreground sm:text-lg">
33
38
  Your project already includes the core app shell, database wiring,
34
- and a shadcn-compatible UI foundation so you can keep building
35
- instead of setting up boilerplate.
39
+ and shadcn/ui components so you can keep building instead of
40
+ setting up boilerplate.
36
41
  </p>
37
42
  </div>
38
43
  <div className="flex flex-col gap-3 sm:flex-row">
39
44
  {{#if useAuth}}
40
- <Link
41
- href="/sign-up"
42
- className={cn(buttonVariants({ size: "lg" }), "gap-2")}
43
- >
45
+ <Button size="lg" nativeButton={false} render={<Link href="/sign-up" />}>
44
46
  Create an account
45
- <ArrowRight className="size-4" />
46
- </Link>
47
- <Link
48
- href="/sign-in"
49
- className={buttonVariants({ variant: "outline", size: "lg" })}
47
+ <ArrowRight data-icon="inline-end" />
48
+ </Button>
49
+ <Button
50
+ size="lg"
51
+ variant="outline"
52
+ nativeButton={false}
53
+ render={<Link href="/sign-in" />}
50
54
  >
51
55
  Login
52
- </Link>
56
+ </Button>
53
57
  {{else}}
54
- <Link
55
- href="https://better-ts-stack.abdullahtech.me/docs"
56
- target="_blank"
57
- rel="noreferrer"
58
- className={cn(buttonVariants({ size: "lg" }), "gap-2")}
58
+ <Button
59
+ size="lg"
60
+ nativeButton={false}
61
+ render={
62
+ <Link
63
+ href="https://better-ts-stack.abdullahtech.me/docs"
64
+ target="_blank"
65
+ rel="noreferrer"
66
+ />
67
+ }
59
68
  >
60
69
  Explore the docs
61
- <ArrowRight className="size-4" />
62
- </Link>
63
- <Link
64
- href="https://nextjs.org/docs"
65
- target="_blank"
66
- rel="noreferrer"
67
- className={buttonVariants({ variant: "outline", size: "lg" })}
70
+ <ArrowRight data-icon="inline-end" />
71
+ </Button>
72
+ <Button
73
+ size="lg"
74
+ variant="outline"
75
+ nativeButton={false}
76
+ render={
77
+ <Link
78
+ href="https://nextjs.org/docs"
79
+ target="_blank"
80
+ rel="noreferrer"
81
+ />
82
+ }
68
83
  >
69
84
  Next.js guide
70
- </Link>
85
+ </Button>
71
86
  {{/if}}
72
87
  </div>
73
88
  </div>
74
89
 
75
- <Card className="border-border bg-card shadow-sm">
90
+ <Card>
76
91
  <CardHeader>
77
92
  <CardTitle>Included in this starter</CardTitle>
78
93
  </CardHeader>
79
- <CardContent className="space-y-4">
80
- <ul className="space-y-3 text-sm text-muted-foreground">
94
+ <CardContent>
95
+ <ul className="flex flex-col gap-3 text-muted-foreground">
81
96
  {featureList.map((feature) => (
82
97
  <li key={feature} className="flex items-center gap-3">
83
98
  <span className="size-2 rounded-full bg-primary" />
@@ -85,11 +100,16 @@ export default function Home() {
85
100
  </li>
86
101
  ))}
87
102
  </ul>
88
- <div className="rounded-2xl border border-border bg-muted px-4 py-3 text-sm text-muted-foreground">
89
- Start editing <code className="font-mono">app/page.tsx</code> and
90
- shape the project around your product instead of setup chores.
91
- </div>
92
103
  </CardContent>
104
+ <CardFooter>
105
+ <Alert>
106
+ <AlertDescription>
107
+ Start editing <code className="font-mono">app/page.tsx</code>{" "}
108
+ and shape the project around your product instead of setup
109
+ chores.
110
+ </AlertDescription>
111
+ </Alert>
112
+ </CardFooter>
93
113
  </Card>
94
114
  </section>
95
115
  </div>
@@ -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 }
@@ -1,24 +1,35 @@
1
- import * as React from "react";
2
- import { cva, type VariantProps } from "class-variance-authority";
3
-
4
- import { cn } from "@/lib/utils";
1
+ import { Button as ButtonPrimitive } from "@base-ui/react/button"
2
+ import { cva, type VariantProps } from "class-variance-authority"
3
+ import { cn } from "cn"
5
4
 
6
5
  const buttonVariants = cva(
7
- "inline-flex items-center justify-center whitespace-nowrap rounded-full text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-sky-500 focus-visible:ring-offset-2 focus-visible:ring-offset-white disabled:pointer-events-none disabled:opacity-50",
6
+ "group/button inline-flex shrink-0 items-center justify-center rounded-md border border-transparent bg-clip-padding text-sm font-medium whitespace-nowrap transition-all outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
8
7
  {
9
8
  variants: {
10
9
  variant: {
11
- default:
12
- "bg-slate-950 text-white shadow-sm hover:bg-slate-800",
10
+ default: "bg-primary text-primary-foreground hover:bg-primary/80",
13
11
  outline:
14
- "border border-slate-200 bg-white text-slate-900 hover:bg-slate-100",
15
- ghost: "text-slate-700 hover:bg-slate-100 hover:text-slate-950",
12
+ "border-border bg-background shadow-xs hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50",
13
+ secondary:
14
+ "bg-secondary text-secondary-foreground hover:bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)] aria-expanded:bg-secondary aria-expanded:text-secondary-foreground",
15
+ ghost:
16
+ "hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50",
17
+ destructive:
18
+ "bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:hover:bg-destructive/30 dark:focus-visible:ring-destructive/40",
19
+ link: "text-primary underline-offset-4 hover:underline",
16
20
  },
17
21
  size: {
18
- default: "h-10 px-4 py-2",
19
- sm: "h-9 px-3",
20
- lg: "h-11 px-6 text-sm",
21
- icon: "size-10",
22
+ default:
23
+ "h-9 gap-1.5 px-2.5 in-data-[slot=button-group]:rounded-md has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
24
+ xs: "h-6 gap-1 rounded-[min(var(--radius-md),8px)] px-2 text-xs in-data-[slot=button-group]:rounded-md has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3",
25
+ sm: "h-8 gap-1 rounded-[min(var(--radius-md),10px)] px-2.5 in-data-[slot=button-group]:rounded-md has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5",
26
+ lg: "h-10 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
27
+ icon: "size-9",
28
+ "icon-xs":
29
+ "size-6 rounded-[min(var(--radius-md),8px)] in-data-[slot=button-group]:rounded-md [&_svg:not([class*='size-'])]:size-3",
30
+ "icon-sm":
31
+ "size-8 rounded-[min(var(--radius-md),10px)] in-data-[slot=button-group]:rounded-md",
32
+ "icon-lg": "size-10",
22
33
  },
23
34
  },
24
35
  defaultVariants: {
@@ -26,26 +37,21 @@ const buttonVariants = cva(
26
37
  size: "default",
27
38
  },
28
39
  }
29
- );
30
-
31
- export interface ButtonProps
32
- extends React.ButtonHTMLAttributes<HTMLButtonElement>,
33
- VariantProps<typeof buttonVariants> {}
40
+ )
34
41
 
35
42
  function Button({
36
43
  className,
37
- variant,
38
- size,
39
- type = "button",
44
+ variant = "default",
45
+ size = "default",
40
46
  ...props
41
- }: ButtonProps) {
47
+ }: ButtonPrimitive.Props & VariantProps<typeof buttonVariants>) {
42
48
  return (
43
- <button
44
- type={type}
45
- className={cn(buttonVariants({ variant, size }), className)}
49
+ <ButtonPrimitive
50
+ data-slot="button"
51
+ className={cn(buttonVariants({ variant, size, className }))}
46
52
  {...props}
47
53
  />
48
- );
54
+ )
49
55
  }
50
56
 
51
- export { Button, buttonVariants };
57
+ export { Button, buttonVariants }
@@ -1,60 +1,102 @@
1
- import * as React from "react";
2
-
3
- import { cn } from "@/lib/utils";
1
+ import * as React from "react"
2
+ import { cn } from "cn"
4
3
 
5
4
  function Card({
6
5
  className,
6
+ size = "default",
7
7
  ...props
8
- }: React.HTMLAttributes<HTMLDivElement>) {
8
+ }: React.ComponentProps<"div"> & { size?: "default" | "sm" }) {
9
9
  return (
10
10
  <div
11
+ data-slot="card"
12
+ data-size={size}
11
13
  className={cn(
12
- "rounded-3xl border border-slate-200/80 bg-white text-slate-950 shadow-sm",
14
+ "group/card flex flex-col gap-(--card-spacing) overflow-hidden rounded-xl bg-card py-(--card-spacing) text-sm text-card-foreground shadow-xs ring-1 ring-foreground/10 [--card-spacing:--spacing(6)] has-[>img:first-child]:pt-0 data-[size=sm]:[--card-spacing:--spacing(4)] *:[img:first-child]:rounded-t-xl *:[img:last-child]:rounded-b-xl",
13
15
  className
14
16
  )}
15
17
  {...props}
16
18
  />
17
- );
19
+ )
18
20
  }
19
21
 
20
- function CardHeader({
21
- className,
22
- ...props
23
- }: React.HTMLAttributes<HTMLDivElement>) {
24
- return <div className={cn("flex flex-col space-y-1.5 p-6", className)} {...props} />;
22
+ function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
23
+ return (
24
+ <div
25
+ data-slot="card-header"
26
+ className={cn(
27
+ "group/card-header @container/card-header grid auto-rows-min items-start gap-1 rounded-t-xl px-(--card-spacing) has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-description]:grid-rows-[auto_auto] [.border-b]:pb-(--card-spacing)",
28
+ className
29
+ )}
30
+ {...props}
31
+ />
32
+ )
25
33
  }
26
34
 
27
- function CardTitle({
28
- className,
29
- ...props
30
- }: React.HTMLAttributes<HTMLHeadingElement>) {
35
+ function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
31
36
  return (
32
- <h3
33
- className={cn("text-2xl font-semibold leading-none tracking-tight", className)}
37
+ <div
38
+ data-slot="card-title"
39
+ className={cn(
40
+ "font-heading text-base leading-normal font-medium group-data-[size=sm]/card:text-sm",
41
+ className
42
+ )}
34
43
  {...props}
35
44
  />
36
- );
45
+ )
37
46
  }
38
47
 
39
- function CardDescription({
40
- className,
41
- ...props
42
- }: React.HTMLAttributes<HTMLParagraphElement>) {
43
- return <p className={cn("text-sm text-slate-600", className)} {...props} />;
48
+ function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
49
+ return (
50
+ <div
51
+ data-slot="card-description"
52
+ className={cn("text-sm text-muted-foreground", className)}
53
+ {...props}
54
+ />
55
+ )
44
56
  }
45
57
 
46
- function CardContent({
47
- className,
48
- ...props
49
- }: React.HTMLAttributes<HTMLDivElement>) {
50
- return <div className={cn("p-6 pt-0", className)} {...props} />;
58
+ function CardAction({ className, ...props }: React.ComponentProps<"div">) {
59
+ return (
60
+ <div
61
+ data-slot="card-action"
62
+ className={cn(
63
+ "col-start-2 row-span-2 row-start-1 self-start justify-self-end",
64
+ className
65
+ )}
66
+ {...props}
67
+ />
68
+ )
51
69
  }
52
70
 
53
- function CardFooter({
54
- className,
55
- ...props
56
- }: React.HTMLAttributes<HTMLDivElement>) {
57
- return <div className={cn("flex items-center p-6 pt-0", className)} {...props} />;
71
+ function CardContent({ className, ...props }: React.ComponentProps<"div">) {
72
+ return (
73
+ <div
74
+ data-slot="card-content"
75
+ className={cn("flex flex-col gap-3 px-(--card-spacing)", className)}
76
+ {...props}
77
+ />
78
+ )
58
79
  }
59
80
 
60
- export { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle };
81
+ function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
82
+ return (
83
+ <div
84
+ data-slot="card-footer"
85
+ className={cn(
86
+ "flex items-center rounded-b-xl px-(--card-spacing) [.border-t]:pt-(--card-spacing)",
87
+ className
88
+ )}
89
+ {...props}
90
+ />
91
+ )
92
+ }
93
+
94
+ export {
95
+ Card,
96
+ CardHeader,
97
+ CardFooter,
98
+ CardTitle,
99
+ CardAction,
100
+ CardDescription,
101
+ CardContent,
102
+ }