@withmata/blueprints 0.3.5 → 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.
- package/.claude/skills/audit/SKILL.md +4 -4
- package/.claude/skills/blueprint-catalog/SKILL.md +17 -7
- package/.claude/skills/copywrite/SKILL.md +187 -0
- package/.claude/skills/copywrite-landing/SKILL.md +489 -0
- package/.claude/skills/design-system/SKILL.md +970 -0
- package/.claude/skills/new-project/SKILL.md +168 -112
- package/.claude/skills/scaffold-auth/SKILL.md +9 -9
- package/.claude/skills/scaffold-db/SKILL.md +14 -14
- package/.claude/skills/scaffold-env/SKILL.md +4 -4
- package/.claude/skills/scaffold-foundation/SKILL.md +15 -15
- package/.claude/skills/scaffold-tailwind/SKILL.md +17 -3
- package/.claude/skills/scaffold-ui/SKILL.md +155 -36
- package/ENGINEERING.md +2 -2
- package/blueprints/discovery/design-system/BLUEPRINT.md +1479 -0
- package/blueprints/discovery/marketing-copywriting/BLUEPRINT.md +664 -0
- package/blueprints/features/auth-better-auth/BLUEPRINT.md +20 -22
- package/blueprints/features/db-drizzle-postgres/BLUEPRINT.md +12 -12
- package/blueprints/features/db-drizzle-postgres/files/db/src/example-entity.ts +1 -1
- package/blueprints/features/db-drizzle-postgres/files/db/src/scripts/seed.ts +1 -1
- package/blueprints/features/env-t3/BLUEPRINT.md +1 -1
- package/blueprints/features/tailwind-v4/BLUEPRINT.md +9 -2
- package/blueprints/features/tailwind-v4/files/tailwind-config/shared-styles.css +80 -1
- package/blueprints/features/ui-shared-components/BLUEPRINT.md +411 -78
- package/blueprints/features/ui-shared-components/files/ui/components/ui/alert-dialog.tsx +192 -0
- package/blueprints/features/ui-shared-components/files/ui/components/ui/avatar.tsx +71 -0
- package/blueprints/features/ui-shared-components/files/ui/components/ui/badge.tsx +52 -0
- package/blueprints/features/ui-shared-components/files/ui/components/ui/breadcrumb.tsx +122 -0
- package/blueprints/features/ui-shared-components/files/ui/components/ui/button.tsx +56 -0
- package/blueprints/features/ui-shared-components/files/ui/components/ui/card-select.tsx +72 -0
- package/blueprints/features/ui-shared-components/files/ui/components/ui/card.tsx +100 -0
- package/blueprints/features/ui-shared-components/files/ui/components/ui/collapsible.tsx +34 -0
- package/blueprints/features/ui-shared-components/files/ui/components/ui/combobox.tsx +301 -0
- package/blueprints/features/ui-shared-components/files/ui/components/ui/dropdown-menu.tsx +264 -0
- package/blueprints/features/ui-shared-components/files/ui/components/ui/empty-state.tsx +43 -0
- package/blueprints/features/ui-shared-components/files/ui/components/ui/entity-select.tsx +110 -0
- package/blueprints/features/ui-shared-components/files/ui/components/ui/field.tsx +237 -0
- package/blueprints/features/ui-shared-components/files/ui/components/ui/form-field.tsx +217 -0
- package/blueprints/features/ui-shared-components/files/ui/components/ui/input-group.tsx +161 -0
- package/blueprints/features/ui-shared-components/files/ui/components/ui/input.tsx +20 -0
- package/blueprints/features/ui-shared-components/files/ui/components/ui/label.tsx +20 -0
- package/blueprints/features/ui-shared-components/files/ui/components/ui/org-switcher.tsx +114 -0
- package/blueprints/features/ui-shared-components/files/ui/components/ui/page-header.tsx +45 -0
- package/blueprints/features/ui-shared-components/files/ui/components/ui/pagination.tsx +52 -0
- package/blueprints/features/ui-shared-components/files/ui/components/ui/pill-select.tsx +151 -0
- package/blueprints/features/ui-shared-components/files/ui/components/ui/popover.tsx +41 -0
- package/blueprints/features/ui-shared-components/files/ui/components/ui/search-input.tsx +49 -0
- package/blueprints/features/ui-shared-components/files/ui/components/ui/select.tsx +205 -0
- package/blueprints/features/ui-shared-components/files/ui/components/ui/selected-entity-card.tsx +47 -0
- package/blueprints/features/ui-shared-components/files/ui/components/ui/separator.tsx +25 -0
- package/blueprints/features/ui-shared-components/files/ui/components/ui/sidebar.tsx +389 -0
- package/blueprints/features/ui-shared-components/files/ui/components/ui/status-filter.tsx +43 -0
- package/blueprints/features/ui-shared-components/files/ui/components/ui/tag-input.tsx +131 -0
- package/blueprints/features/ui-shared-components/files/ui/components/ui/textarea.tsx +18 -0
- package/blueprints/features/ui-shared-components/files/ui/components/ui/user-menu.tsx +149 -0
- package/blueprints/features/ui-shared-components/files/ui/components.json +11 -8
- package/blueprints/features/ui-shared-components/files/ui/package.json +20 -11
- package/blueprints/foundation/monorepo-turbo/BLUEPRINT.md +19 -20
- package/blueprints/foundation/monorepo-turbo/files/root/package.json +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/blueprints/features/tailwind-v4/files/tailwind-config/package.json +0 -20
- 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 };
|