@withmata/blueprints 0.3.4 → 0.4.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 (62) hide show
  1. package/.claude/skills/audit/SKILL.md +4 -4
  2. package/.claude/skills/blueprint-catalog/SKILL.md +17 -7
  3. package/.claude/skills/copywrite/SKILL.md +187 -0
  4. package/.claude/skills/copywrite-landing/SKILL.md +489 -0
  5. package/.claude/skills/design-system/SKILL.md +970 -0
  6. package/.claude/skills/new-project/SKILL.md +168 -112
  7. package/.claude/skills/scaffold-auth/SKILL.md +9 -9
  8. package/.claude/skills/scaffold-db/SKILL.md +14 -14
  9. package/.claude/skills/scaffold-env/SKILL.md +4 -4
  10. package/.claude/skills/scaffold-foundation/SKILL.md +15 -15
  11. package/.claude/skills/scaffold-tailwind/SKILL.md +17 -3
  12. package/.claude/skills/scaffold-ui/SKILL.md +155 -36
  13. package/ENGINEERING.md +2 -2
  14. package/blueprints/discovery/design-system/BLUEPRINT.md +1479 -0
  15. package/blueprints/discovery/marketing-copywriting/BLUEPRINT.md +664 -0
  16. package/blueprints/features/auth-better-auth/BLUEPRINT.md +20 -22
  17. package/blueprints/features/db-drizzle-postgres/BLUEPRINT.md +12 -12
  18. package/blueprints/features/db-drizzle-postgres/files/db/src/example-entity.ts +1 -1
  19. package/blueprints/features/db-drizzle-postgres/files/db/src/scripts/seed.ts +1 -1
  20. package/blueprints/features/env-t3/BLUEPRINT.md +1 -1
  21. package/blueprints/features/tailwind-v4/BLUEPRINT.md +9 -2
  22. package/blueprints/features/tailwind-v4/files/tailwind-config/shared-styles.css +80 -1
  23. package/blueprints/features/ui-shared-components/BLUEPRINT.md +411 -78
  24. package/blueprints/features/ui-shared-components/files/ui/components/ui/alert-dialog.tsx +192 -0
  25. package/blueprints/features/ui-shared-components/files/ui/components/ui/avatar.tsx +71 -0
  26. package/blueprints/features/ui-shared-components/files/ui/components/ui/badge.tsx +52 -0
  27. package/blueprints/features/ui-shared-components/files/ui/components/ui/breadcrumb.tsx +122 -0
  28. package/blueprints/features/ui-shared-components/files/ui/components/ui/button.tsx +56 -0
  29. package/blueprints/features/ui-shared-components/files/ui/components/ui/card-select.tsx +72 -0
  30. package/blueprints/features/ui-shared-components/files/ui/components/ui/card.tsx +100 -0
  31. package/blueprints/features/ui-shared-components/files/ui/components/ui/collapsible.tsx +34 -0
  32. package/blueprints/features/ui-shared-components/files/ui/components/ui/combobox.tsx +301 -0
  33. package/blueprints/features/ui-shared-components/files/ui/components/ui/dropdown-menu.tsx +264 -0
  34. package/blueprints/features/ui-shared-components/files/ui/components/ui/empty-state.tsx +43 -0
  35. package/blueprints/features/ui-shared-components/files/ui/components/ui/entity-select.tsx +110 -0
  36. package/blueprints/features/ui-shared-components/files/ui/components/ui/field.tsx +237 -0
  37. package/blueprints/features/ui-shared-components/files/ui/components/ui/form-field.tsx +217 -0
  38. package/blueprints/features/ui-shared-components/files/ui/components/ui/input-group.tsx +161 -0
  39. package/blueprints/features/ui-shared-components/files/ui/components/ui/input.tsx +20 -0
  40. package/blueprints/features/ui-shared-components/files/ui/components/ui/label.tsx +20 -0
  41. package/blueprints/features/ui-shared-components/files/ui/components/ui/org-switcher.tsx +114 -0
  42. package/blueprints/features/ui-shared-components/files/ui/components/ui/page-header.tsx +45 -0
  43. package/blueprints/features/ui-shared-components/files/ui/components/ui/pagination.tsx +52 -0
  44. package/blueprints/features/ui-shared-components/files/ui/components/ui/pill-select.tsx +151 -0
  45. package/blueprints/features/ui-shared-components/files/ui/components/ui/popover.tsx +41 -0
  46. package/blueprints/features/ui-shared-components/files/ui/components/ui/search-input.tsx +49 -0
  47. package/blueprints/features/ui-shared-components/files/ui/components/ui/select.tsx +205 -0
  48. package/blueprints/features/ui-shared-components/files/ui/components/ui/selected-entity-card.tsx +47 -0
  49. package/blueprints/features/ui-shared-components/files/ui/components/ui/separator.tsx +25 -0
  50. package/blueprints/features/ui-shared-components/files/ui/components/ui/sidebar.tsx +389 -0
  51. package/blueprints/features/ui-shared-components/files/ui/components/ui/status-filter.tsx +43 -0
  52. package/blueprints/features/ui-shared-components/files/ui/components/ui/tag-input.tsx +131 -0
  53. package/blueprints/features/ui-shared-components/files/ui/components/ui/textarea.tsx +18 -0
  54. package/blueprints/features/ui-shared-components/files/ui/components/ui/user-menu.tsx +149 -0
  55. package/blueprints/features/ui-shared-components/files/ui/components.json +11 -8
  56. package/blueprints/features/ui-shared-components/files/ui/package.json +20 -11
  57. package/blueprints/foundation/monorepo-turbo/BLUEPRINT.md +19 -20
  58. package/blueprints/foundation/monorepo-turbo/files/root/package.json +1 -1
  59. package/dist/index.js +241 -100
  60. package/package.json +1 -1
  61. package/blueprints/features/tailwind-v4/files/tailwind-config/package.json +0 -20
  62. package/blueprints/foundation/monorepo-turbo/files/root/pnpm-workspace.yaml +0 -5
@@ -0,0 +1,192 @@
1
+ "use client";
2
+
3
+ import { AlertDialog as AlertDialogPrimitive } from "@base-ui/react/alert-dialog";
4
+ import type * as React from "react";
5
+ import { Button } from "#components/ui/button";
6
+ import { cn } from "#utils/cn";
7
+
8
+ function AlertDialog({ ...props }: AlertDialogPrimitive.Root.Props) {
9
+ return <AlertDialogPrimitive.Root data-slot="alert-dialog" {...props} />;
10
+ }
11
+
12
+ function AlertDialogTrigger({ ...props }: AlertDialogPrimitive.Trigger.Props) {
13
+ return (
14
+ <AlertDialogPrimitive.Trigger data-slot="alert-dialog-trigger" {...props} />
15
+ );
16
+ }
17
+
18
+ function AlertDialogPortal({ ...props }: AlertDialogPrimitive.Portal.Props) {
19
+ return (
20
+ <AlertDialogPrimitive.Portal data-slot="alert-dialog-portal" {...props} />
21
+ );
22
+ }
23
+
24
+ function AlertDialogOverlay({
25
+ className,
26
+ onClick,
27
+ ...props
28
+ }: AlertDialogPrimitive.Backdrop.Props & {
29
+ onClick?: () => void;
30
+ }) {
31
+ return (
32
+ <AlertDialogPrimitive.Backdrop
33
+ data-slot="alert-dialog-overlay"
34
+ className={cn(
35
+ "data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 bg-black/50 duration-100 fixed inset-0 isolate z-50",
36
+ className,
37
+ )}
38
+ onClick={onClick}
39
+ {...props}
40
+ />
41
+ );
42
+ }
43
+
44
+ function AlertDialogContent({
45
+ className,
46
+ size = "default",
47
+ onBackdropClick,
48
+ ...props
49
+ }: AlertDialogPrimitive.Popup.Props & {
50
+ size?: "default" | "sm";
51
+ onBackdropClick?: () => void;
52
+ }) {
53
+ return (
54
+ <AlertDialogPortal>
55
+ <AlertDialogOverlay onClick={onBackdropClick} />
56
+ <AlertDialogPrimitive.Popup
57
+ data-slot="alert-dialog-content"
58
+ data-size={size}
59
+ className={cn(
60
+ "data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 bg-background ring-foreground/5 gap-6 rounded-4xl p-6 ring-1 duration-100 data-[size=default]:max-w-xs data-[size=sm]:max-w-xs data-[size=default]:sm:max-w-md group/alert-dialog-content fixed top-1/2 left-1/2 z-50 grid w-full -translate-x-1/2 -translate-y-1/2 outline-none",
61
+ className,
62
+ )}
63
+ {...props}
64
+ />
65
+ </AlertDialogPortal>
66
+ );
67
+ }
68
+
69
+ function AlertDialogHeader({
70
+ className,
71
+ ...props
72
+ }: React.ComponentProps<"div">) {
73
+ return (
74
+ <div
75
+ data-slot="alert-dialog-header"
76
+ className={cn(
77
+ "flex flex-col place-items-center gap-2 text-center has-data-[slot=alert-dialog-media]:grid-rows-[auto_auto_1fr] has-data-[slot=alert-dialog-media]:gap-x-6 sm:group-data-[size=default]/alert-dialog-content:place-items-start sm:group-data-[size=default]/alert-dialog-content:text-left sm:group-data-[size=default]/alert-dialog-content:has-data-[slot=alert-dialog-media]:grid-rows-[auto_1fr]",
78
+ className,
79
+ )}
80
+ {...props}
81
+ />
82
+ );
83
+ }
84
+
85
+ function AlertDialogFooter({
86
+ className,
87
+ ...props
88
+ }: React.ComponentProps<"div">) {
89
+ return (
90
+ <div
91
+ data-slot="alert-dialog-footer"
92
+ className={cn(
93
+ "flex flex-col-reverse gap-2 group-data-[size=sm]/alert-dialog-content:grid group-data-[size=sm]/alert-dialog-content:grid-cols-2 sm:flex-row sm:justify-end",
94
+ className,
95
+ )}
96
+ {...props}
97
+ />
98
+ );
99
+ }
100
+
101
+ function AlertDialogMedia({
102
+ className,
103
+ ...props
104
+ }: React.ComponentProps<"div">) {
105
+ return (
106
+ <div
107
+ data-slot="alert-dialog-media"
108
+ className={cn(
109
+ "bg-muted inline-flex p-1.5 items-center justify-center rounded-full sm:group-data-[size=default]/alert-dialog-content:row-span-2 *:[svg:not([class*='size-'])]:size-8",
110
+ className,
111
+ )}
112
+ {...props}
113
+ />
114
+ );
115
+ }
116
+
117
+ function AlertDialogTitle({
118
+ className,
119
+ ...props
120
+ }: React.ComponentProps<typeof AlertDialogPrimitive.Title>) {
121
+ return (
122
+ <AlertDialogPrimitive.Title
123
+ data-slot="alert-dialog-title"
124
+ className={cn(
125
+ "text-lg font-medium sm:group-data-[size=default]/alert-dialog-content:group-has-data-[slot=alert-dialog-media]/alert-dialog-content:col-start-2",
126
+ className,
127
+ )}
128
+ {...props}
129
+ />
130
+ );
131
+ }
132
+
133
+ function AlertDialogDescription({
134
+ className,
135
+ ...props
136
+ }: React.ComponentProps<typeof AlertDialogPrimitive.Description>) {
137
+ return (
138
+ <AlertDialogPrimitive.Description
139
+ data-slot="alert-dialog-description"
140
+ className={cn(
141
+ "text-muted-foreground *:[a]:hover:text-foreground text-sm text-balance md:text-pretty *:[a]:underline *:[a]:underline-offset-3",
142
+ className,
143
+ )}
144
+ {...props}
145
+ />
146
+ );
147
+ }
148
+
149
+ function AlertDialogAction({
150
+ className,
151
+ ...props
152
+ }: React.ComponentProps<typeof Button>) {
153
+ return (
154
+ <Button
155
+ data-slot="alert-dialog-action"
156
+ className={cn(className)}
157
+ {...props}
158
+ />
159
+ );
160
+ }
161
+
162
+ function AlertDialogCancel({
163
+ className,
164
+ variant = "outline",
165
+ size = "default",
166
+ ...props
167
+ }: AlertDialogPrimitive.Close.Props &
168
+ Pick<React.ComponentProps<typeof Button>, "variant" | "size">) {
169
+ return (
170
+ <AlertDialogPrimitive.Close
171
+ data-slot="alert-dialog-cancel"
172
+ className={cn(className)}
173
+ render={<Button variant={variant} size={size} />}
174
+ {...props}
175
+ />
176
+ );
177
+ }
178
+
179
+ export {
180
+ AlertDialog,
181
+ AlertDialogAction,
182
+ AlertDialogCancel,
183
+ AlertDialogContent,
184
+ AlertDialogDescription,
185
+ AlertDialogFooter,
186
+ AlertDialogHeader,
187
+ AlertDialogMedia,
188
+ AlertDialogOverlay,
189
+ AlertDialogPortal,
190
+ AlertDialogTitle,
191
+ AlertDialogTrigger,
192
+ };
@@ -0,0 +1,71 @@
1
+ "use client";
2
+
3
+ import { cn } from "#utils/cn";
4
+ import { cva, type VariantProps } from "class-variance-authority";
5
+ import { useState } from "react";
6
+
7
+ const avatarVariants = cva(
8
+ "shrink-0 items-center justify-center rounded-full bg-muted font-medium overflow-hidden inline-flex",
9
+ {
10
+ variants: {
11
+ size: {
12
+ xs: "size-5 text-[9px]",
13
+ sm: "size-6 text-[10px]",
14
+ default: "size-8 text-xs",
15
+ lg: "size-10 text-sm",
16
+ xl: "size-12 text-base",
17
+ },
18
+ },
19
+ defaultVariants: {
20
+ size: "default",
21
+ },
22
+ },
23
+ );
24
+
25
+ function getInitials(name: string): string {
26
+ return name
27
+ .split(" ")
28
+ .map((n) => n[0])
29
+ .join("")
30
+ .toUpperCase()
31
+ .slice(0, 2);
32
+ }
33
+
34
+ export interface AvatarProps extends VariantProps<typeof avatarVariants> {
35
+ name: string;
36
+ image?: string | null;
37
+ fallback?: string;
38
+ className?: string;
39
+ }
40
+
41
+ export function Avatar({
42
+ name,
43
+ image,
44
+ fallback,
45
+ size = "default",
46
+ className,
47
+ }: AvatarProps) {
48
+ const [imgFailed, setImgFailed] = useState(false);
49
+
50
+ const initials = fallback || getInitials(name);
51
+
52
+ return (
53
+ <div
54
+ data-slot="avatar"
55
+ className={cn(avatarVariants({ size }), className)}
56
+ >
57
+ {image && !imgFailed ? (
58
+ <img
59
+ src={image}
60
+ alt={name}
61
+ className="size-full rounded-full object-cover"
62
+ onError={() => setImgFailed(true)}
63
+ />
64
+ ) : (
65
+ initials
66
+ )}
67
+ </div>
68
+ );
69
+ }
70
+
71
+ export { avatarVariants };
@@ -0,0 +1,52 @@
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
+
5
+ import { cn } from "#utils/cn";
6
+
7
+ const badgeVariants = cva(
8
+ "h-5 gap-1 rounded-4xl border border-transparent px-2 py-0.5 text-xs font-medium transition-all has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&>svg]:size-3! inline-flex items-center justify-center w-fit whitespace-nowrap shrink-0 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-colors overflow-hidden group/badge",
9
+ {
10
+ variants: {
11
+ variant: {
12
+ default: "bg-primary text-primary-foreground [a]:hover:bg-primary/80",
13
+ secondary:
14
+ "bg-secondary text-secondary-foreground [a]:hover:bg-secondary/80",
15
+ destructive:
16
+ "bg-destructive/10 [a]:hover:bg-destructive/20 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 text-destructive dark:bg-destructive/20",
17
+ outline:
18
+ "border-border text-foreground [a]:hover:bg-muted [a]:hover:text-muted-foreground bg-input/30",
19
+ ghost:
20
+ "hover:bg-muted hover:text-muted-foreground dark:hover:bg-muted/50",
21
+ link: "text-primary underline-offset-4 hover:underline",
22
+ },
23
+ },
24
+ defaultVariants: {
25
+ variant: "default",
26
+ },
27
+ },
28
+ );
29
+
30
+ function Badge({
31
+ className,
32
+ variant = "default",
33
+ render,
34
+ ...props
35
+ }: useRender.ComponentProps<"span"> & VariantProps<typeof badgeVariants>) {
36
+ return useRender({
37
+ defaultTagName: "span",
38
+ props: mergeProps<"span">(
39
+ {
40
+ className: cn(badgeVariants({ className, variant })),
41
+ },
42
+ props,
43
+ ),
44
+ render,
45
+ state: {
46
+ slot: "badge",
47
+ variant,
48
+ },
49
+ });
50
+ }
51
+
52
+ export { Badge, badgeVariants };
@@ -0,0 +1,122 @@
1
+ import { mergeProps } from "@base-ui/react/merge-props";
2
+ import { useRender } from "@base-ui/react/use-render";
3
+ import { CaretRightIcon } from "@phosphor-icons/react/CaretRight";
4
+ import { DotsThreeOutlineVerticalIcon } from "@phosphor-icons/react/DotsThreeOutlineVertical";
5
+ import type * as React from "react";
6
+ import { cn } from "#utils/cn";
7
+
8
+ function Breadcrumb({ className, ...props }: React.ComponentProps<"nav">) {
9
+ return (
10
+ <nav
11
+ aria-label="breadcrumb"
12
+ data-slot="breadcrumb"
13
+ className={cn(className)}
14
+ {...props}
15
+ />
16
+ );
17
+ }
18
+
19
+ function BreadcrumbList({ className, ...props }: React.ComponentProps<"ol">) {
20
+ return (
21
+ <ol
22
+ data-slot="breadcrumb-list"
23
+ className={cn(
24
+ "text-muted-foreground gap-1.5 text-sm flex flex-wrap items-center break-words",
25
+ className,
26
+ )}
27
+ {...props}
28
+ />
29
+ );
30
+ }
31
+
32
+ function BreadcrumbItem({ className, ...props }: React.ComponentProps<"li">) {
33
+ return (
34
+ <li
35
+ data-slot="breadcrumb-item"
36
+ className={cn("gap-1 inline-flex items-center", className)}
37
+ {...props}
38
+ />
39
+ );
40
+ }
41
+
42
+ function BreadcrumbLink({
43
+ className,
44
+ render,
45
+ ...props
46
+ }: useRender.ComponentProps<"a">) {
47
+ return useRender({
48
+ defaultTagName: "a",
49
+ props: mergeProps<"a">(
50
+ {
51
+ className: cn("hover:text-foreground transition-colors", className),
52
+ },
53
+ props,
54
+ ),
55
+ render,
56
+ state: {
57
+ slot: "breadcrumb-link",
58
+ },
59
+ });
60
+ }
61
+
62
+ function BreadcrumbPage({ className, ...props }: React.ComponentProps<"span">) {
63
+ return (
64
+ <span
65
+ data-slot="breadcrumb-page"
66
+ role="link"
67
+ aria-disabled="true"
68
+ aria-current="page"
69
+ className={cn("text-foreground font-normal", className)}
70
+ {...props}
71
+ />
72
+ );
73
+ }
74
+
75
+ function BreadcrumbSeparator({
76
+ children,
77
+ className,
78
+ ...props
79
+ }: React.ComponentProps<"li">) {
80
+ return (
81
+ <li
82
+ data-slot="breadcrumb-separator"
83
+ role="presentation"
84
+ aria-hidden="true"
85
+ className={cn("[&>svg]:size-3.5", className)}
86
+ {...props}
87
+ >
88
+ {children ?? <CaretRightIcon />}
89
+ </li>
90
+ );
91
+ }
92
+
93
+ function BreadcrumbEllipsis({
94
+ className,
95
+ ...props
96
+ }: React.ComponentProps<"span">) {
97
+ return (
98
+ <span
99
+ data-slot="breadcrumb-ellipsis"
100
+ role="presentation"
101
+ aria-hidden="true"
102
+ className={cn(
103
+ "size-5 [&>svg]:size-4 flex items-center justify-center",
104
+ className,
105
+ )}
106
+ {...props}
107
+ >
108
+ <DotsThreeOutlineVerticalIcon />
109
+ <span className="sr-only">More</span>
110
+ </span>
111
+ );
112
+ }
113
+
114
+ export {
115
+ Breadcrumb,
116
+ BreadcrumbList,
117
+ BreadcrumbItem,
118
+ BreadcrumbLink,
119
+ BreadcrumbPage,
120
+ BreadcrumbSeparator,
121
+ BreadcrumbEllipsis,
122
+ };
@@ -0,0 +1,56 @@
1
+ import { Button as ButtonPrimitive } from "@base-ui/react/button";
2
+ import { cva, type VariantProps } from "class-variance-authority";
3
+
4
+ import { cn } from "#utils/cn";
5
+
6
+ const buttonVariants = cva(
7
+ "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:aria-invalid:border-destructive/50 rounded-4xl border border-transparent bg-clip-padding text-sm font-medium focus-visible:ring-[3px] aria-invalid:ring-[3px] [&_svg:not([class*='size-'])]:size-4 inline-flex items-center justify-center whitespace-nowrap transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none shrink-0 [&_svg]:shrink-0 outline-none group/button select-none",
8
+ {
9
+ variants: {
10
+ variant: {
11
+ default: "bg-primary text-primary-foreground hover:bg-primary/80",
12
+ outline:
13
+ "border-border bg-input/30 hover:bg-input/50 hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground",
14
+ secondary:
15
+ "bg-secondary text-secondary-foreground hover:bg-secondary/80 aria-expanded:bg-secondary aria-expanded:text-secondary-foreground",
16
+ ghost:
17
+ "hover:bg-muted hover:text-foreground dark:hover:bg-muted/50 aria-expanded:bg-muted aria-expanded:text-foreground",
18
+ destructive:
19
+ "bg-destructive/10 hover:bg-destructive/20 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/20 text-destructive focus-visible:border-destructive/40 dark:hover:bg-destructive/30",
20
+ link: "text-primary underline-offset-4 hover:underline",
21
+ },
22
+ size: {
23
+ default:
24
+ "h-9 gap-1.5 px-3 has-data-[icon=inline-end]:pr-2.5 has-data-[icon=inline-start]:pl-2.5",
25
+ xs: "h-6 gap-1 px-2.5 text-xs has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2 [&_svg:not([class*='size-'])]:size-3",
26
+ sm: "h-8 gap-1 px-3 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
27
+ lg: "h-10 gap-1.5 px-4 has-data-[icon=inline-end]:pr-3 has-data-[icon=inline-start]:pl-3",
28
+ icon: "size-9",
29
+ "icon-xs": "size-6 [&_svg:not([class*='size-'])]:size-3",
30
+ "icon-sm": "size-8",
31
+ "icon-lg": "size-10",
32
+ },
33
+ },
34
+ defaultVariants: {
35
+ variant: "default",
36
+ size: "default",
37
+ },
38
+ },
39
+ );
40
+
41
+ function Button({
42
+ className,
43
+ variant = "default",
44
+ size = "default",
45
+ ...props
46
+ }: ButtonPrimitive.Props & VariantProps<typeof buttonVariants>) {
47
+ return (
48
+ <ButtonPrimitive
49
+ data-slot="button"
50
+ className={cn(buttonVariants({ variant, size, className }))}
51
+ {...props}
52
+ />
53
+ );
54
+ }
55
+
56
+ export { Button, buttonVariants };
@@ -0,0 +1,72 @@
1
+ "use client";
2
+
3
+ import { Badge } from "#components/ui/badge";
4
+ import { cn } from "#utils/cn";
5
+
6
+ export interface CardSelectOption<T extends string = string> {
7
+ value: T;
8
+ label: string;
9
+ description: string;
10
+ disabled?: boolean;
11
+ disabledLabel?: string;
12
+ }
13
+
14
+ export interface CardSelectProps<T extends string = string> {
15
+ options: CardSelectOption<T>[];
16
+ value?: T;
17
+ onChange: (value: T) => void;
18
+ columns?: 2 | 3 | 4;
19
+ className?: string;
20
+ }
21
+
22
+ export function CardSelect<T extends string = string>({
23
+ options,
24
+ value,
25
+ onChange,
26
+ columns = 2,
27
+ className,
28
+ }: CardSelectProps<T>) {
29
+ const gridClass =
30
+ columns === 2
31
+ ? "grid-cols-2"
32
+ : columns === 3
33
+ ? "grid-cols-3"
34
+ : "grid-cols-4";
35
+
36
+ return (
37
+ <div
38
+ data-slot="card-select"
39
+ className={cn("grid gap-3", gridClass, className)}
40
+ >
41
+ {options.map((option) => (
42
+ <button
43
+ key={option.value}
44
+ type="button"
45
+ disabled={option.disabled}
46
+ onClick={() => !option.disabled && onChange(option.value)}
47
+ className={cn(
48
+ "rounded-xl border p-3 text-left transition-colors relative",
49
+ value === option.value
50
+ ? "border-primary bg-primary/5"
51
+ : option.disabled
52
+ ? "border-border opacity-60 cursor-not-allowed"
53
+ : "border-border hover:border-primary/50",
54
+ )}
55
+ >
56
+ <p className="text-sm font-medium">{option.label}</p>
57
+ <p className="text-xs text-muted-foreground mt-1">
58
+ {option.description}
59
+ </p>
60
+ {option.disabled && option.disabledLabel && (
61
+ <Badge
62
+ variant="default"
63
+ className="text-xs absolute -right-2 -top-2"
64
+ >
65
+ {option.disabledLabel}
66
+ </Badge>
67
+ )}
68
+ </button>
69
+ ))}
70
+ </div>
71
+ );
72
+ }
@@ -0,0 +1,100 @@
1
+ import type * as React from "react";
2
+
3
+ import { cn } from "#utils/cn";
4
+
5
+ function Card({
6
+ className,
7
+ size = "default",
8
+ ...props
9
+ }: React.ComponentProps<"div"> & { size?: "default" | "sm" }) {
10
+ return (
11
+ <div
12
+ data-slot="card"
13
+ data-size={size}
14
+ className={cn(
15
+ "ring-foreground/10 bg-card text-card-foreground gap-6 overflow-hidden rounded-2xl py-6 text-sm ring-1 has-[>img:first-child]:pt-0 data-[size=sm]:gap-4 data-[size=sm]:py-4 *:[img:first-child]:rounded-t-xl *:[img:last-child]:rounded-b-xl group/card flex flex-col",
16
+ className,
17
+ )}
18
+ {...props}
19
+ />
20
+ );
21
+ }
22
+
23
+ function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
24
+ return (
25
+ <div
26
+ data-slot="card-header"
27
+ className={cn(
28
+ "gap-2 rounded-t-xl px-6 group-data-[size=sm]/card:px-4 [.border-b]:pb-6 group-data-[size=sm]/card:[.border-b]:pb-4 group/card-header @container/card-header grid auto-rows-min items-start has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-description]:grid-rows-[auto_auto]",
29
+ className,
30
+ )}
31
+ {...props}
32
+ />
33
+ );
34
+ }
35
+
36
+ function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
37
+ return (
38
+ <div
39
+ data-slot="card-title"
40
+ className={cn("text-base font-medium", className)}
41
+ {...props}
42
+ />
43
+ );
44
+ }
45
+
46
+ function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
47
+ return (
48
+ <div
49
+ data-slot="card-description"
50
+ className={cn("text-muted-foreground text-sm", className)}
51
+ {...props}
52
+ />
53
+ );
54
+ }
55
+
56
+ function CardAction({ className, ...props }: React.ComponentProps<"div">) {
57
+ return (
58
+ <div
59
+ data-slot="card-action"
60
+ className={cn(
61
+ "col-start-2 row-span-2 row-start-1 self-start justify-self-end",
62
+ className,
63
+ )}
64
+ {...props}
65
+ />
66
+ );
67
+ }
68
+
69
+ function CardContent({ className, ...props }: React.ComponentProps<"div">) {
70
+ return (
71
+ <div
72
+ data-slot="card-content"
73
+ className={cn("px-6 group-data-[size=sm]/card:px-4", className)}
74
+ {...props}
75
+ />
76
+ );
77
+ }
78
+
79
+ function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
80
+ return (
81
+ <div
82
+ data-slot="card-footer"
83
+ className={cn(
84
+ "rounded-b-xl px-6 group-data-[size=sm]/card:px-4 [.border-t]:pt-6 group-data-[size=sm]/card:[.border-t]:pt-4 flex items-center",
85
+ className,
86
+ )}
87
+ {...props}
88
+ />
89
+ );
90
+ }
91
+
92
+ export {
93
+ Card,
94
+ CardHeader,
95
+ CardFooter,
96
+ CardTitle,
97
+ CardAction,
98
+ CardDescription,
99
+ CardContent,
100
+ };
@@ -0,0 +1,34 @@
1
+ "use client";
2
+
3
+ import type React from "react";
4
+ import * as CollapsiblePrimitive from "@radix-ui/react-collapsible";
5
+
6
+ function Collapsible({
7
+ ...props
8
+ }: React.ComponentProps<typeof CollapsiblePrimitive.Root>) {
9
+ return <CollapsiblePrimitive.Root data-slot="collapsible" {...props} />;
10
+ }
11
+
12
+ function CollapsibleTrigger({
13
+ ...props
14
+ }: React.ComponentProps<typeof CollapsiblePrimitive.CollapsibleTrigger>) {
15
+ return (
16
+ <CollapsiblePrimitive.CollapsibleTrigger
17
+ data-slot="collapsible-trigger"
18
+ {...props}
19
+ />
20
+ );
21
+ }
22
+
23
+ function CollapsibleContent({
24
+ ...props
25
+ }: React.ComponentProps<typeof CollapsiblePrimitive.CollapsibleContent>) {
26
+ return (
27
+ <CollapsiblePrimitive.CollapsibleContent
28
+ data-slot="collapsible-content"
29
+ {...props}
30
+ />
31
+ );
32
+ }
33
+
34
+ export { Collapsible, CollapsibleTrigger, CollapsibleContent };