@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,301 @@
1
+ "use client";
2
+
3
+ import { Combobox as ComboboxPrimitive } from "@base-ui/react";
4
+ import { CaretDownIcon } from "@phosphor-icons/react/CaretDown";
5
+ import { CheckIcon } from "@phosphor-icons/react/Check";
6
+ import { XIcon } from "@phosphor-icons/react/X";
7
+ import * as React from "react";
8
+ import { Button } from "#components/ui/button";
9
+ import {
10
+ InputGroup,
11
+ InputGroupAddon,
12
+ InputGroupButton,
13
+ InputGroupInput,
14
+ } from "#components/ui/input-group";
15
+ import { cn } from "#utils/cn";
16
+
17
+ const Combobox = ComboboxPrimitive.Root;
18
+
19
+ function ComboboxValue({ ...props }: ComboboxPrimitive.Value.Props) {
20
+ return <ComboboxPrimitive.Value data-slot="combobox-value" {...props} />;
21
+ }
22
+
23
+ function ComboboxTrigger({
24
+ className,
25
+ children,
26
+ ...props
27
+ }: ComboboxPrimitive.Trigger.Props) {
28
+ return (
29
+ <ComboboxPrimitive.Trigger
30
+ data-slot="combobox-trigger"
31
+ className={cn("[&_svg:not([class*='size-'])]:size-4", className)}
32
+ {...props}
33
+ >
34
+ {children}
35
+ <CaretDownIcon className="text-muted-foreground size-4 pointer-events-none" />
36
+ </ComboboxPrimitive.Trigger>
37
+ );
38
+ }
39
+
40
+ function ComboboxClear({ className, ...props }: ComboboxPrimitive.Clear.Props) {
41
+ return (
42
+ <ComboboxPrimitive.Clear
43
+ data-slot="combobox-clear"
44
+ render={<InputGroupButton variant="ghost" size="icon-xs" />}
45
+ className={cn(className)}
46
+ {...props}
47
+ >
48
+ <XIcon className="pointer-events-none" />
49
+ </ComboboxPrimitive.Clear>
50
+ );
51
+ }
52
+
53
+ function ComboboxInput({
54
+ className,
55
+ children,
56
+ disabled = false,
57
+ showTrigger = true,
58
+ showClear = false,
59
+ ...props
60
+ }: ComboboxPrimitive.Input.Props & {
61
+ showTrigger?: boolean;
62
+ showClear?: boolean;
63
+ }) {
64
+ return (
65
+ <InputGroup className={cn("w-auto", className)}>
66
+ <ComboboxPrimitive.Input
67
+ render={<InputGroupInput disabled={disabled} />}
68
+ {...props}
69
+ />
70
+ <InputGroupAddon align="inline-end">
71
+ {showTrigger && (
72
+ <InputGroupButton
73
+ size="icon-xs"
74
+ variant="ghost"
75
+ render={<ComboboxTrigger />}
76
+ data-slot="input-group-button"
77
+ className="group-has-data-[slot=combobox-clear]/input-group:hidden data-pressed:bg-transparent"
78
+ disabled={disabled}
79
+ />
80
+ )}
81
+ {showClear && <ComboboxClear disabled={disabled} />}
82
+ </InputGroupAddon>
83
+ {children}
84
+ </InputGroup>
85
+ );
86
+ }
87
+
88
+ function ComboboxContent({
89
+ className,
90
+ side = "bottom",
91
+ sideOffset = 6,
92
+ align = "start",
93
+ alignOffset = 0,
94
+ anchor,
95
+ ...props
96
+ }: ComboboxPrimitive.Popup.Props &
97
+ Pick<
98
+ ComboboxPrimitive.Positioner.Props,
99
+ "side" | "align" | "sideOffset" | "alignOffset" | "anchor"
100
+ >) {
101
+ return (
102
+ <ComboboxPrimitive.Portal>
103
+ <ComboboxPrimitive.Positioner
104
+ side={side}
105
+ sideOffset={sideOffset}
106
+ align={align}
107
+ alignOffset={alignOffset}
108
+ anchor={anchor}
109
+ className="isolate z-50"
110
+ >
111
+ <ComboboxPrimitive.Popup
112
+ data-slot="combobox-content"
113
+ data-chips={!!anchor}
114
+ className={cn(
115
+ "bg-popover text-popover-foreground 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 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 ring-foreground/5 *:data-[slot=input-group]:bg-input/30 max-h-72 min-w-36 overflow-hidden rounded-2xl shadow-2xl ring-1 duration-100 *:data-[slot=input-group]:m-1 *:data-[slot=input-group]:mb-0 *:data-[slot=input-group]:h-9 *:data-[slot=input-group]:border-none *:data-[slot=input-group]:shadow-none group/combobox-content relative max-h-(--available-height) w-(--anchor-width) max-w-(--available-width) min-w-[calc(var(--anchor-width)+--spacing(7))] origin-(--transform-origin) data-[chips=true]:min-w-(--anchor-width)",
116
+ className,
117
+ )}
118
+ {...props}
119
+ />
120
+ </ComboboxPrimitive.Positioner>
121
+ </ComboboxPrimitive.Portal>
122
+ );
123
+ }
124
+
125
+ function ComboboxList({ className, ...props }: ComboboxPrimitive.List.Props) {
126
+ return (
127
+ <ComboboxPrimitive.List
128
+ data-slot="combobox-list"
129
+ className={cn(
130
+ "no-scrollbar max-h-[min(calc(--spacing(72)---spacing(9)),calc(var(--available-height)---spacing(9)))] scroll-py-1 overflow-y-auto p-1 data-empty:p-0 overflow-y-auto overscroll-contain",
131
+ className,
132
+ )}
133
+ {...props}
134
+ />
135
+ );
136
+ }
137
+
138
+ function ComboboxItem({
139
+ className,
140
+ children,
141
+ ...props
142
+ }: ComboboxPrimitive.Item.Props) {
143
+ return (
144
+ <ComboboxPrimitive.Item
145
+ data-slot="combobox-item"
146
+ className={cn(
147
+ "data-highlighted:bg-accent data-highlighted:text-accent-foreground not-data-[variant=destructive]:data-highlighted:**:text-accent-foreground gap-2.5 rounded-xl py-2 pr-8 pl-3 text-sm [&_svg:not([class*='size-'])]:size-4 relative flex w-full cursor-default items-center outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0",
148
+ className,
149
+ )}
150
+ {...props}
151
+ >
152
+ {children}
153
+ <ComboboxPrimitive.ItemIndicator
154
+ render={
155
+ <span className="pointer-events-none absolute right-2 flex size-4 items-center justify-center" />
156
+ }
157
+ >
158
+ <CheckIcon className="pointer-events-none" />
159
+ </ComboboxPrimitive.ItemIndicator>
160
+ </ComboboxPrimitive.Item>
161
+ );
162
+ }
163
+
164
+ function ComboboxGroup({ className, ...props }: ComboboxPrimitive.Group.Props) {
165
+ return (
166
+ <ComboboxPrimitive.Group
167
+ data-slot="combobox-group"
168
+ className={cn(className)}
169
+ {...props}
170
+ />
171
+ );
172
+ }
173
+
174
+ function ComboboxLabel({
175
+ className,
176
+ ...props
177
+ }: ComboboxPrimitive.GroupLabel.Props) {
178
+ return (
179
+ <ComboboxPrimitive.GroupLabel
180
+ data-slot="combobox-label"
181
+ className={cn("text-muted-foreground px-3.5 py-2.5 text-xs", className)}
182
+ {...props}
183
+ />
184
+ );
185
+ }
186
+
187
+ function ComboboxCollection({ ...props }: ComboboxPrimitive.Collection.Props) {
188
+ return (
189
+ <ComboboxPrimitive.Collection data-slot="combobox-collection" {...props} />
190
+ );
191
+ }
192
+
193
+ function ComboboxEmpty({ className, ...props }: ComboboxPrimitive.Empty.Props) {
194
+ return (
195
+ <ComboboxPrimitive.Empty
196
+ data-slot="combobox-empty"
197
+ className={cn(
198
+ "text-muted-foreground hidden w-full justify-center py-2 text-center text-sm group-data-empty/combobox-content:flex",
199
+ className,
200
+ )}
201
+ {...props}
202
+ />
203
+ );
204
+ }
205
+
206
+ function ComboboxSeparator({
207
+ className,
208
+ ...props
209
+ }: ComboboxPrimitive.Separator.Props) {
210
+ return (
211
+ <ComboboxPrimitive.Separator
212
+ data-slot="combobox-separator"
213
+ className={cn("bg-border/50 -mx-1 my-1 h-px", className)}
214
+ {...props}
215
+ />
216
+ );
217
+ }
218
+
219
+ function ComboboxChips({
220
+ className,
221
+ ...props
222
+ }: React.ComponentPropsWithRef<typeof ComboboxPrimitive.Chips> &
223
+ ComboboxPrimitive.Chips.Props) {
224
+ return (
225
+ <ComboboxPrimitive.Chips
226
+ data-slot="combobox-chips"
227
+ className={cn(
228
+ "bg-input/30 border-input focus-within:border-ring focus-within:ring-ring/50 has-aria-invalid:ring-destructive/20 dark:has-aria-invalid:ring-destructive/40 has-aria-invalid:border-destructive dark:has-aria-invalid:border-destructive/50 flex min-h-9 flex-wrap items-center gap-1.5 rounded-4xl border bg-clip-padding px-2.5 py-1.5 text-sm transition-colors focus-within:ring-[3px] has-aria-invalid:ring-[3px] has-data-[slot=combobox-chip]:px-1.5",
229
+ className,
230
+ )}
231
+ {...props}
232
+ />
233
+ );
234
+ }
235
+
236
+ function ComboboxChip({
237
+ className,
238
+ children,
239
+ showRemove = true,
240
+ ...props
241
+ }: ComboboxPrimitive.Chip.Props & {
242
+ showRemove?: boolean;
243
+ }) {
244
+ return (
245
+ <ComboboxPrimitive.Chip
246
+ data-slot="combobox-chip"
247
+ className={cn(
248
+ "bg-muted-foreground/10 text-foreground flex h-[calc(--spacing(5.5))] w-fit items-center justify-center gap-1 rounded-4xl px-2 text-xs font-medium whitespace-nowrap has-data-[slot=combobox-chip-remove]:pr-0 has-disabled:pointer-events-none has-disabled:cursor-not-allowed has-disabled:opacity-50",
249
+ className,
250
+ )}
251
+ {...props}
252
+ >
253
+ {children}
254
+ {showRemove && (
255
+ <ComboboxPrimitive.ChipRemove
256
+ render={<Button variant="ghost" size="icon-xs" />}
257
+ className="-ml-1 opacity-50 hover:opacity-100"
258
+ data-slot="combobox-chip-remove"
259
+ >
260
+ <XIcon className="pointer-events-none" />
261
+ </ComboboxPrimitive.ChipRemove>
262
+ )}
263
+ </ComboboxPrimitive.Chip>
264
+ );
265
+ }
266
+
267
+ function ComboboxChipsInput({
268
+ className,
269
+ ...props
270
+ }: ComboboxPrimitive.Input.Props) {
271
+ return (
272
+ <ComboboxPrimitive.Input
273
+ data-slot="combobox-chip-input"
274
+ className={cn("min-w-16 flex-1 outline-none", className)}
275
+ {...props}
276
+ />
277
+ );
278
+ }
279
+
280
+ function useComboboxAnchor() {
281
+ return React.useRef<HTMLDivElement | null>(null);
282
+ }
283
+
284
+ export {
285
+ Combobox,
286
+ ComboboxInput,
287
+ ComboboxContent,
288
+ ComboboxList,
289
+ ComboboxItem,
290
+ ComboboxGroup,
291
+ ComboboxLabel,
292
+ ComboboxCollection,
293
+ ComboboxEmpty,
294
+ ComboboxSeparator,
295
+ ComboboxChips,
296
+ ComboboxChip,
297
+ ComboboxChipsInput,
298
+ ComboboxTrigger,
299
+ ComboboxValue,
300
+ useComboboxAnchor,
301
+ };
@@ -0,0 +1,264 @@
1
+ "use client";
2
+
3
+ import { Menu as MenuPrimitive } from "@base-ui/react/menu";
4
+ import { CaretRightIcon } from "@phosphor-icons/react/CaretRight";
5
+ import { CheckIcon } from "@phosphor-icons/react/Check";
6
+ import type * as React from "react";
7
+ import { cn } from "#utils/cn";
8
+
9
+ function DropdownMenu({ ...props }: MenuPrimitive.Root.Props) {
10
+ return <MenuPrimitive.Root data-slot="dropdown-menu" {...props} />;
11
+ }
12
+
13
+ function DropdownMenuPortal({ ...props }: MenuPrimitive.Portal.Props) {
14
+ return <MenuPrimitive.Portal data-slot="dropdown-menu-portal" {...props} />;
15
+ }
16
+
17
+ function DropdownMenuTrigger({ ...props }: MenuPrimitive.Trigger.Props) {
18
+ return <MenuPrimitive.Trigger data-slot="dropdown-menu-trigger" {...props} />;
19
+ }
20
+
21
+ function DropdownMenuContent({
22
+ align = "start",
23
+ alignOffset = 0,
24
+ side = "bottom",
25
+ sideOffset = 4,
26
+ className,
27
+ ...props
28
+ }: MenuPrimitive.Popup.Props &
29
+ Pick<
30
+ MenuPrimitive.Positioner.Props,
31
+ "align" | "alignOffset" | "side" | "sideOffset"
32
+ >) {
33
+ return (
34
+ <MenuPrimitive.Portal>
35
+ <MenuPrimitive.Positioner
36
+ className="isolate z-50 outline-none"
37
+ align={align}
38
+ alignOffset={alignOffset}
39
+ side={side}
40
+ sideOffset={sideOffset}
41
+ >
42
+ <MenuPrimitive.Popup
43
+ data-slot="dropdown-menu-content"
44
+ className={cn(
45
+ "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 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 ring-foreground/5 bg-popover text-popover-foreground min-w-48 rounded-2xl p-1 shadow-2xl ring-1 duration-100 z-50 max-h-(--available-height) w-(--anchor-width) origin-(--transform-origin) overflow-x-hidden overflow-y-auto outline-none data-closed:overflow-hidden",
46
+ className,
47
+ )}
48
+ {...props}
49
+ />
50
+ </MenuPrimitive.Positioner>
51
+ </MenuPrimitive.Portal>
52
+ );
53
+ }
54
+
55
+ function DropdownMenuGroup({ ...props }: MenuPrimitive.Group.Props) {
56
+ return <MenuPrimitive.Group data-slot="dropdown-menu-group" {...props} />;
57
+ }
58
+
59
+ function DropdownMenuLabel({
60
+ className,
61
+ inset,
62
+ ...props
63
+ }: MenuPrimitive.GroupLabel.Props & {
64
+ inset?: boolean;
65
+ }) {
66
+ return (
67
+ <MenuPrimitive.GroupLabel
68
+ data-slot="dropdown-menu-label"
69
+ data-inset={inset}
70
+ className={cn(
71
+ "text-muted-foreground px-3 py-2.5 text-xs data-[inset]:pl-8",
72
+ className,
73
+ )}
74
+ {...props}
75
+ />
76
+ );
77
+ }
78
+
79
+ function DropdownMenuItem({
80
+ className,
81
+ inset,
82
+ variant = "default",
83
+ ...props
84
+ }: MenuPrimitive.Item.Props & {
85
+ inset?: boolean;
86
+ variant?: "default" | "destructive";
87
+ }) {
88
+ return (
89
+ <MenuPrimitive.Item
90
+ data-slot="dropdown-menu-item"
91
+ data-inset={inset}
92
+ data-variant={variant}
93
+ className={cn(
94
+ "focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[svg]:text-destructive not-data-[variant=destructive]:focus:**:text-accent-foreground gap-2.5 rounded-xl px-3 py-2 text-sm [&_svg:not([class*='size-'])]:size-4 group/dropdown-menu-item relative flex cursor-default items-center outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0",
95
+ className,
96
+ )}
97
+ {...props}
98
+ />
99
+ );
100
+ }
101
+
102
+ function DropdownMenuSub({ ...props }: MenuPrimitive.SubmenuRoot.Props) {
103
+ return <MenuPrimitive.SubmenuRoot data-slot="dropdown-menu-sub" {...props} />;
104
+ }
105
+
106
+ function DropdownMenuSubTrigger({
107
+ className,
108
+ inset,
109
+ children,
110
+ ...props
111
+ }: MenuPrimitive.SubmenuTrigger.Props & {
112
+ inset?: boolean;
113
+ }) {
114
+ return (
115
+ <MenuPrimitive.SubmenuTrigger
116
+ data-slot="dropdown-menu-sub-trigger"
117
+ data-inset={inset}
118
+ className={cn(
119
+ "focus:bg-accent focus:text-accent-foreground data-open:bg-accent data-open:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground gap-2 rounded-xl px-3 py-2 text-sm [&_svg:not([class*='size-'])]:size-4 flex cursor-default items-center outline-hidden select-none data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0",
120
+ className,
121
+ )}
122
+ {...props}
123
+ >
124
+ {children}
125
+ <CaretRightIcon className="ml-auto" />
126
+ </MenuPrimitive.SubmenuTrigger>
127
+ );
128
+ }
129
+
130
+ function DropdownMenuSubContent({
131
+ align = "start",
132
+ alignOffset = -3,
133
+ side = "right",
134
+ sideOffset = 0,
135
+ className,
136
+ ...props
137
+ }: React.ComponentProps<typeof DropdownMenuContent>) {
138
+ return (
139
+ <DropdownMenuContent
140
+ data-slot="dropdown-menu-sub-content"
141
+ className={cn(
142
+ "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 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 ring-foreground/5 bg-popover text-popover-foreground min-w-36 rounded-2xl p-1 shadow-2xl ring-1 duration-100 w-auto",
143
+ className,
144
+ )}
145
+ align={align}
146
+ alignOffset={alignOffset}
147
+ side={side}
148
+ sideOffset={sideOffset}
149
+ {...props}
150
+ />
151
+ );
152
+ }
153
+
154
+ function DropdownMenuCheckboxItem({
155
+ className,
156
+ children,
157
+ checked,
158
+ ...props
159
+ }: MenuPrimitive.CheckboxItem.Props) {
160
+ return (
161
+ <MenuPrimitive.CheckboxItem
162
+ data-slot="dropdown-menu-checkbox-item"
163
+ className={cn(
164
+ "focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground gap-2.5 rounded-xl py-2 pr-8 pl-3 text-sm [&_svg:not([class*='size-'])]:size-4 relative flex cursor-default items-center outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0",
165
+ className,
166
+ )}
167
+ checked={checked}
168
+ {...props}
169
+ >
170
+ <span
171
+ className="pointer-events-none absolute right-2 flex items-center justify-center pointer-events-none"
172
+ data-slot="dropdown-menu-checkbox-item-indicator"
173
+ >
174
+ <MenuPrimitive.CheckboxItemIndicator>
175
+ <CheckIcon />
176
+ </MenuPrimitive.CheckboxItemIndicator>
177
+ </span>
178
+ {children}
179
+ </MenuPrimitive.CheckboxItem>
180
+ );
181
+ }
182
+
183
+ function DropdownMenuRadioGroup({ ...props }: MenuPrimitive.RadioGroup.Props) {
184
+ return (
185
+ <MenuPrimitive.RadioGroup
186
+ data-slot="dropdown-menu-radio-group"
187
+ {...props}
188
+ />
189
+ );
190
+ }
191
+
192
+ function DropdownMenuRadioItem({
193
+ className,
194
+ children,
195
+ ...props
196
+ }: MenuPrimitive.RadioItem.Props) {
197
+ return (
198
+ <MenuPrimitive.RadioItem
199
+ data-slot="dropdown-menu-radio-item"
200
+ className={cn(
201
+ "focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground gap-2.5 rounded-xl py-2 pr-8 pl-3 text-sm [&_svg:not([class*='size-'])]:size-4 relative flex cursor-default items-center outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0",
202
+ className,
203
+ )}
204
+ {...props}
205
+ >
206
+ <span
207
+ className="pointer-events-none absolute right-2 flex items-center justify-center pointer-events-none"
208
+ data-slot="dropdown-menu-radio-item-indicator"
209
+ >
210
+ <MenuPrimitive.RadioItemIndicator>
211
+ <CheckIcon />
212
+ </MenuPrimitive.RadioItemIndicator>
213
+ </span>
214
+ {children}
215
+ </MenuPrimitive.RadioItem>
216
+ );
217
+ }
218
+
219
+ function DropdownMenuSeparator({
220
+ className,
221
+ ...props
222
+ }: MenuPrimitive.Separator.Props) {
223
+ return (
224
+ <MenuPrimitive.Separator
225
+ data-slot="dropdown-menu-separator"
226
+ className={cn("bg-border/50 -mx-1 my-1 h-px", className)}
227
+ {...props}
228
+ />
229
+ );
230
+ }
231
+
232
+ function DropdownMenuShortcut({
233
+ className,
234
+ ...props
235
+ }: React.ComponentProps<"span">) {
236
+ return (
237
+ <span
238
+ data-slot="dropdown-menu-shortcut"
239
+ className={cn(
240
+ "text-muted-foreground group-focus/dropdown-menu-item:text-accent-foreground ml-auto text-xs tracking-widest",
241
+ className,
242
+ )}
243
+ {...props}
244
+ />
245
+ );
246
+ }
247
+
248
+ export {
249
+ DropdownMenu,
250
+ DropdownMenuPortal,
251
+ DropdownMenuTrigger,
252
+ DropdownMenuContent,
253
+ DropdownMenuGroup,
254
+ DropdownMenuLabel,
255
+ DropdownMenuItem,
256
+ DropdownMenuCheckboxItem,
257
+ DropdownMenuRadioGroup,
258
+ DropdownMenuRadioItem,
259
+ DropdownMenuSeparator,
260
+ DropdownMenuShortcut,
261
+ DropdownMenuSub,
262
+ DropdownMenuSubTrigger,
263
+ DropdownMenuSubContent,
264
+ };
@@ -0,0 +1,43 @@
1
+ import { Button } from "#components/ui/button";
2
+ import { cn } from "#utils/cn";
3
+ import type * as React from "react";
4
+
5
+ export interface EmptyStateProps {
6
+ icon: React.ReactNode;
7
+ heading: string;
8
+ description: string;
9
+ actionLabel?: string;
10
+ onAction?: () => void;
11
+ className?: string;
12
+ children?: React.ReactNode;
13
+ }
14
+
15
+ export function EmptyState({
16
+ icon,
17
+ heading,
18
+ description,
19
+ actionLabel,
20
+ onAction,
21
+ className,
22
+ children,
23
+ }: EmptyStateProps) {
24
+ return (
25
+ <div
26
+ data-slot="empty-state"
27
+ className={cn(
28
+ "flex flex-col items-center justify-center py-12 px-4",
29
+ className,
30
+ )}
31
+ >
32
+ <div className="bg-muted rounded-full p-6 mb-4">{icon}</div>
33
+ <h3 className="text-lg font-medium mb-2">{heading}</h3>
34
+ <p className="text-sm text-muted-foreground text-center max-w-sm mb-6">
35
+ {description}
36
+ </p>
37
+ {actionLabel && onAction && (
38
+ <Button onClick={onAction}>{actionLabel}</Button>
39
+ )}
40
+ {children}
41
+ </div>
42
+ );
43
+ }