@brokenrubik/ui 0.0.0-stage → 0.1.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 (190) hide show
  1. package/README.md +264 -2
  2. package/dist/components/accordion.d.ts +13 -0
  3. package/dist/components/accordion.d.ts.map +1 -0
  4. package/dist/components/accordion.js +25 -0
  5. package/dist/components/accordion.js.map +1 -0
  6. package/dist/components/alert-dialog.d.ts +21 -0
  7. package/dist/components/alert-dialog.d.ts.map +1 -0
  8. package/dist/components/alert-dialog.js +46 -0
  9. package/dist/components/alert-dialog.js.map +1 -0
  10. package/dist/components/alert.d.ts +24 -0
  11. package/dist/components/alert.d.ts.map +1 -0
  12. package/dist/components/alert.js +42 -0
  13. package/dist/components/alert.js.map +1 -0
  14. package/dist/components/avatar.d.ts +27 -0
  15. package/dist/components/avatar.d.ts.map +1 -0
  16. package/dist/components/avatar.js +53 -0
  17. package/dist/components/avatar.js.map +1 -0
  18. package/dist/components/badge.d.ts +21 -0
  19. package/dist/components/badge.d.ts.map +1 -0
  20. package/dist/components/badge.js +40 -0
  21. package/dist/components/badge.js.map +1 -0
  22. package/dist/components/button.d.ts +18 -0
  23. package/dist/components/button.d.ts.map +1 -0
  24. package/dist/components/button.js +39 -0
  25. package/dist/components/button.js.map +1 -0
  26. package/dist/components/calendar.d.ts +13 -0
  27. package/dist/components/calendar.d.ts.map +1 -0
  28. package/dist/components/calendar.js +68 -0
  29. package/dist/components/calendar.js.map +1 -0
  30. package/dist/components/card.d.ts +15 -0
  31. package/dist/components/card.d.ts.map +1 -0
  32. package/dist/components/card.js +31 -0
  33. package/dist/components/card.js.map +1 -0
  34. package/dist/components/checkbox.d.ts +6 -0
  35. package/dist/components/checkbox.d.ts.map +1 -0
  36. package/dist/components/checkbox.js +12 -0
  37. package/dist/components/checkbox.js.map +1 -0
  38. package/dist/components/command.d.ts +24 -0
  39. package/dist/components/command.d.ts.map +1 -0
  40. package/dist/components/command.js +41 -0
  41. package/dist/components/command.js.map +1 -0
  42. package/dist/components/date-picker.d.ts +24 -0
  43. package/dist/components/date-picker.d.ts.map +1 -0
  44. package/dist/components/date-picker.js +18 -0
  45. package/dist/components/date-picker.js.map +1 -0
  46. package/dist/components/dialog.d.ts +23 -0
  47. package/dist/components/dialog.d.ts.map +1 -0
  48. package/dist/components/dialog.js +45 -0
  49. package/dist/components/dialog.js.map +1 -0
  50. package/dist/components/dropdown-menu.d.ts +34 -0
  51. package/dist/components/dropdown-menu.d.ts.map +1 -0
  52. package/dist/components/dropdown-menu.js +61 -0
  53. package/dist/components/dropdown-menu.js.map +1 -0
  54. package/dist/components/empty-state.d.ts +13 -0
  55. package/dist/components/empty-state.d.ts.map +1 -0
  56. package/dist/components/empty-state.js +25 -0
  57. package/dist/components/empty-state.js.map +1 -0
  58. package/dist/components/field.d.ts +24 -0
  59. package/dist/components/field.d.ts.map +1 -0
  60. package/dist/components/field.js +34 -0
  61. package/dist/components/field.js.map +1 -0
  62. package/dist/components/input.d.ts +17 -0
  63. package/dist/components/input.d.ts.map +1 -0
  64. package/dist/components/input.js +18 -0
  65. package/dist/components/input.js.map +1 -0
  66. package/dist/components/kbd.d.ts +14 -0
  67. package/dist/components/kbd.d.ts.map +1 -0
  68. package/dist/components/kbd.js +31 -0
  69. package/dist/components/kbd.js.map +1 -0
  70. package/dist/components/label.d.ts +6 -0
  71. package/dist/components/label.d.ts.map +1 -0
  72. package/dist/components/label.js +11 -0
  73. package/dist/components/label.js.map +1 -0
  74. package/dist/components/pagination.d.ts +18 -0
  75. package/dist/components/pagination.d.ts.map +1 -0
  76. package/dist/components/pagination.js +33 -0
  77. package/dist/components/pagination.js.map +1 -0
  78. package/dist/components/popover.d.ts +12 -0
  79. package/dist/components/popover.d.ts.map +1 -0
  80. package/dist/components/popover.js +29 -0
  81. package/dist/components/popover.js.map +1 -0
  82. package/dist/components/progress.d.ts +17 -0
  83. package/dist/components/progress.d.ts.map +1 -0
  84. package/dist/components/progress.js +26 -0
  85. package/dist/components/progress.js.map +1 -0
  86. package/dist/components/radio-group.d.ts +7 -0
  87. package/dist/components/radio-group.d.ts.map +1 -0
  88. package/dist/components/radio-group.js +14 -0
  89. package/dist/components/radio-group.js.map +1 -0
  90. package/dist/components/select.d.ts +20 -0
  91. package/dist/components/select.d.ts.map +1 -0
  92. package/dist/components/select.js +46 -0
  93. package/dist/components/select.js.map +1 -0
  94. package/dist/components/separator.d.ts +6 -0
  95. package/dist/components/separator.d.ts.map +1 -0
  96. package/dist/components/separator.js +11 -0
  97. package/dist/components/separator.js.map +1 -0
  98. package/dist/components/sheet.d.ts +19 -0
  99. package/dist/components/sheet.d.ts.map +1 -0
  100. package/dist/components/sheet.js +47 -0
  101. package/dist/components/sheet.js.map +1 -0
  102. package/dist/components/skeleton.d.ts +5 -0
  103. package/dist/components/skeleton.d.ts.map +1 -0
  104. package/dist/components/skeleton.js +9 -0
  105. package/dist/components/skeleton.js.map +1 -0
  106. package/dist/components/sonner.d.ts +13 -0
  107. package/dist/components/sonner.d.ts.map +1 -0
  108. package/dist/components/sonner.js +40 -0
  109. package/dist/components/sonner.js.map +1 -0
  110. package/dist/components/switch.d.ts +6 -0
  111. package/dist/components/switch.d.ts.map +1 -0
  112. package/dist/components/switch.js +11 -0
  113. package/dist/components/switch.js.map +1 -0
  114. package/dist/components/tabs.d.ts +13 -0
  115. package/dist/components/tabs.d.ts.map +1 -0
  116. package/dist/components/tabs.js +24 -0
  117. package/dist/components/tabs.js.map +1 -0
  118. package/dist/components/textarea.d.ts +5 -0
  119. package/dist/components/textarea.d.ts.map +1 -0
  120. package/dist/components/textarea.js +10 -0
  121. package/dist/components/textarea.js.map +1 -0
  122. package/dist/components/toggle-group.d.ts +11 -0
  123. package/dist/components/toggle-group.d.ts.map +1 -0
  124. package/dist/components/toggle-group.js +18 -0
  125. package/dist/components/toggle-group.js.map +1 -0
  126. package/dist/components/tooltip.d.ts +13 -0
  127. package/dist/components/tooltip.d.ts.map +1 -0
  128. package/dist/components/tooltip.js +24 -0
  129. package/dist/components/tooltip.js.map +1 -0
  130. package/dist/index.d.ts +34 -0
  131. package/dist/index.d.ts.map +1 -0
  132. package/dist/index.js +36 -0
  133. package/dist/index.js.map +1 -0
  134. package/dist/lib/tone.d.ts +8 -0
  135. package/dist/lib/tone.d.ts.map +1 -0
  136. package/dist/lib/tone.js +14 -0
  137. package/dist/lib/tone.js.map +1 -0
  138. package/dist/lib/utils.d.ts +20 -0
  139. package/dist/lib/utils.d.ts.map +1 -0
  140. package/dist/lib/utils.js +40 -0
  141. package/dist/lib/utils.js.map +1 -0
  142. package/dist/styles/base.css +37 -0
  143. package/dist/styles/fonts.css +16 -0
  144. package/dist/styles/styles.css +14 -0
  145. package/dist/styles/theme.css +170 -0
  146. package/dist/styles/tokens.css +228 -0
  147. package/docs/design-handoff.md +112 -0
  148. package/docs/figma-vs-tailwind.md +135 -0
  149. package/docs/visual-testing.md +156 -0
  150. package/package.json +96 -4
  151. package/src/components/accordion.tsx +66 -0
  152. package/src/components/alert-dialog.tsx +139 -0
  153. package/src/components/alert.tsx +87 -0
  154. package/src/components/avatar.tsx +105 -0
  155. package/src/components/badge.tsx +63 -0
  156. package/src/components/button.tsx +70 -0
  157. package/src/components/calendar.tsx +131 -0
  158. package/src/components/card.tsx +72 -0
  159. package/src/components/checkbox.tsx +31 -0
  160. package/src/components/command.tsx +149 -0
  161. package/src/components/date-picker.tsx +86 -0
  162. package/src/components/dialog.tsx +126 -0
  163. package/src/components/dropdown-menu.tsx +225 -0
  164. package/src/components/empty-state.tsx +63 -0
  165. package/src/components/field.tsx +79 -0
  166. package/src/components/input.tsx +49 -0
  167. package/src/components/kbd.tsx +48 -0
  168. package/src/components/label.tsx +22 -0
  169. package/src/components/pagination.tsx +94 -0
  170. package/src/components/popover.tsx +65 -0
  171. package/src/components/progress.tsx +52 -0
  172. package/src/components/radio-group.tsx +42 -0
  173. package/src/components/select.tsx +167 -0
  174. package/src/components/separator.tsx +29 -0
  175. package/src/components/sheet.tsx +112 -0
  176. package/src/components/skeleton.tsx +16 -0
  177. package/src/components/sonner.tsx +58 -0
  178. package/src/components/switch.tsx +27 -0
  179. package/src/components/tabs.tsx +56 -0
  180. package/src/components/textarea.tsx +17 -0
  181. package/src/components/toggle-group.tsx +42 -0
  182. package/src/components/tooltip.tsx +55 -0
  183. package/src/index.ts +35 -0
  184. package/src/lib/tone.ts +16 -0
  185. package/src/lib/utils.ts +46 -0
  186. package/src/styles/base.css +37 -0
  187. package/src/styles/fonts.css +16 -0
  188. package/src/styles/styles.css +14 -0
  189. package/src/styles/theme.css +170 -0
  190. package/src/styles/tokens.css +228 -0
@@ -0,0 +1,149 @@
1
+ 'use client';
2
+
3
+ import * as React from 'react';
4
+ import { Command as CommandPrimitive } from 'cmdk';
5
+ import { SearchIcon } from 'lucide-react';
6
+
7
+ import { cn } from '../lib/utils.js';
8
+ import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from './dialog.js';
9
+
10
+ /**
11
+ * Figma: Components · Base › Command (2084:180). 560 px popover, 8 px radius, Shadow/lg.
12
+ * Search row: 12 px padding, 16 px muted icon, bottom border. List: 4 px padding, group
13
+ * heading Label/Caption Medium muted, items as Menu Item (32 px, selected = accent).
14
+ */
15
+ function Command({ className, ...props }: React.ComponentProps<typeof CommandPrimitive>) {
16
+ return (
17
+ <CommandPrimitive
18
+ data-slot="command"
19
+ className={cn(
20
+ 'flex size-full flex-col overflow-hidden rounded-lg bg-popover text-body text-popover-foreground',
21
+ className,
22
+ )}
23
+ {...props}
24
+ />
25
+ );
26
+ }
27
+
28
+ function CommandDialog({
29
+ title = 'Command palette',
30
+ description = 'Search for a command to run…',
31
+ children,
32
+ className,
33
+ showCloseButton = false,
34
+ ...props
35
+ }: React.ComponentProps<typeof Dialog> & {
36
+ title?: string;
37
+ description?: string;
38
+ className?: string;
39
+ showCloseButton?: boolean;
40
+ }) {
41
+ return (
42
+ <Dialog {...props}>
43
+ <DialogHeader className="sr-only">
44
+ <DialogTitle>{title}</DialogTitle>
45
+ <DialogDescription>{description}</DialogDescription>
46
+ </DialogHeader>
47
+ <DialogContent
48
+ className={cn('top-[20%] max-w-[560px] translate-y-0 gap-0 overflow-hidden rounded-lg bg-popover p-0', className)}
49
+ showCloseButton={showCloseButton}
50
+ >
51
+ <Command>{children}</Command>
52
+ </DialogContent>
53
+ </Dialog>
54
+ );
55
+ }
56
+
57
+ function CommandInput({ className, ...props }: React.ComponentProps<typeof CommandPrimitive.Input>) {
58
+ return (
59
+ <div data-slot="command-input-wrapper" className="flex items-center gap-2 border-b border-border p-3">
60
+ <SearchIcon className="size-4 shrink-0 text-muted-foreground" aria-hidden />
61
+ <CommandPrimitive.Input
62
+ data-slot="command-input"
63
+ className={cn(
64
+ 'flex h-5 w-full bg-transparent text-body outline-hidden placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50',
65
+ className,
66
+ )}
67
+ {...props}
68
+ />
69
+ </div>
70
+ );
71
+ }
72
+
73
+ function CommandList({ className, ...props }: React.ComponentProps<typeof CommandPrimitive.List>) {
74
+ return (
75
+ <CommandPrimitive.List
76
+ data-slot="command-list"
77
+ className={cn('max-h-[300px] scroll-py-1 overflow-x-hidden overflow-y-auto p-1', className)}
78
+ {...props}
79
+ />
80
+ );
81
+ }
82
+
83
+ function CommandEmpty({ className, ...props }: React.ComponentProps<typeof CommandPrimitive.Empty>) {
84
+ return (
85
+ <CommandPrimitive.Empty
86
+ data-slot="command-empty"
87
+ className={cn('py-6 text-center text-body text-muted-foreground', className)}
88
+ {...props}
89
+ />
90
+ );
91
+ }
92
+
93
+ function CommandGroup({ className, ...props }: React.ComponentProps<typeof CommandPrimitive.Group>) {
94
+ return (
95
+ <CommandPrimitive.Group
96
+ data-slot="command-group"
97
+ className={cn(
98
+ 'overflow-hidden text-foreground **:[[cmdk-group-heading]]:px-2 **:[[cmdk-group-heading]]:py-1.5 **:[[cmdk-group-heading]]:text-caption **:[[cmdk-group-heading]]:font-medium **:[[cmdk-group-heading]]:text-muted-foreground',
99
+ className,
100
+ )}
101
+ {...props}
102
+ />
103
+ );
104
+ }
105
+
106
+ function CommandSeparator({ className, ...props }: React.ComponentProps<typeof CommandPrimitive.Separator>) {
107
+ return (
108
+ <CommandPrimitive.Separator
109
+ data-slot="command-separator"
110
+ className={cn('-mx-1 my-1 h-px bg-border', className)}
111
+ {...props}
112
+ />
113
+ );
114
+ }
115
+
116
+ function CommandItem({ className, ...props }: React.ComponentProps<typeof CommandPrimitive.Item>) {
117
+ return (
118
+ <CommandPrimitive.Item
119
+ data-slot="command-item"
120
+ className={cn(
121
+ "relative flex h-8 cursor-default select-none items-center gap-2 rounded-sm px-2 text-body outline-hidden data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 data-[selected=true]:bg-accent data-[selected=true]:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground",
122
+ className,
123
+ )}
124
+ {...props}
125
+ />
126
+ );
127
+ }
128
+
129
+ function CommandShortcut({ className, ...props }: React.ComponentProps<'span'>) {
130
+ return (
131
+ <span
132
+ data-slot="command-shortcut"
133
+ className={cn('ml-auto text-caption text-muted-foreground', className)}
134
+ {...props}
135
+ />
136
+ );
137
+ }
138
+
139
+ export {
140
+ Command,
141
+ CommandDialog,
142
+ CommandInput,
143
+ CommandList,
144
+ CommandEmpty,
145
+ CommandGroup,
146
+ CommandItem,
147
+ CommandShortcut,
148
+ CommandSeparator,
149
+ };
@@ -0,0 +1,86 @@
1
+ 'use client';
2
+
3
+ import * as React from 'react';
4
+ import { CalendarIcon } from 'lucide-react';
5
+
6
+ import { cn } from '../lib/utils.js';
7
+ import { Calendar } from './calendar.js';
8
+ import { controlClassName } from './input.js';
9
+ import { Popover, PopoverContent, PopoverTrigger } from './popover.js';
10
+
11
+ /**
12
+ * Figma: Components · Base › Date Picker (2083:69), code `<Popover> + <Calendar>`.
13
+ * The trigger looks like Input (calendar icon, muted placeholder "Pick a date").
14
+ */
15
+ type DatePickerProps = {
16
+ value?: Date;
17
+ onChange?: (date: Date | undefined) => void;
18
+ placeholder?: string;
19
+ disabled?: boolean;
20
+ /** How the chosen date reads in the trigger. Default: Intl, medium date style. */
21
+ formatDate?: (date: Date) => string;
22
+ /** Locale for the default formatter (an Intl locale tag). */
23
+ locale?: string;
24
+ id?: string;
25
+ className?: string;
26
+ 'aria-invalid'?: boolean;
27
+ /** Extra props for the calendar (disabled days, start month, locale…). */
28
+ calendarProps?: Omit<React.ComponentProps<typeof Calendar>, 'mode' | 'selected' | 'onSelect'>;
29
+ };
30
+
31
+ function DatePicker({
32
+ value,
33
+ onChange,
34
+ placeholder = 'Pick a date',
35
+ disabled,
36
+ formatDate,
37
+ locale,
38
+ id,
39
+ className,
40
+ calendarProps,
41
+ ...aria
42
+ }: DatePickerProps) {
43
+ const [open, setOpen] = React.useState(false);
44
+ const format = React.useCallback(
45
+ (date: Date) =>
46
+ formatDate ? formatDate(date) : new Intl.DateTimeFormat(locale, { dateStyle: 'medium' }).format(date),
47
+ [formatDate, locale],
48
+ );
49
+
50
+ return (
51
+ <Popover open={open} onOpenChange={setOpen}>
52
+ <PopoverTrigger asChild>
53
+ <button
54
+ id={id}
55
+ type="button"
56
+ disabled={disabled}
57
+ data-slot="date-picker-trigger"
58
+ data-empty={!value || undefined}
59
+ aria-invalid={aria['aria-invalid']}
60
+ className={cn(
61
+ controlClassName,
62
+ 'flex h-8 items-center gap-2 px-2.5 text-left data-[empty=true]:text-muted-foreground',
63
+ className,
64
+ )}
65
+ >
66
+ <CalendarIcon className="size-4 shrink-0 text-muted-foreground" aria-hidden />
67
+ <span className="truncate">{value ? format(value) : placeholder}</span>
68
+ </button>
69
+ </PopoverTrigger>
70
+ <PopoverContent className="w-auto gap-0 p-0" align="start">
71
+ <Calendar
72
+ mode="single"
73
+ selected={value}
74
+ defaultMonth={value}
75
+ {...calendarProps}
76
+ onSelect={(date) => {
77
+ onChange?.(date);
78
+ setOpen(false);
79
+ }}
80
+ />
81
+ </PopoverContent>
82
+ </Popover>
83
+ );
84
+ }
85
+
86
+ export { DatePicker, type DatePickerProps };
@@ -0,0 +1,126 @@
1
+ 'use client';
2
+
3
+ import * as React from 'react';
4
+ import { Dialog as DialogPrimitive } from 'radix-ui';
5
+ import { XIcon } from 'lucide-react';
6
+
7
+ import { cn } from '../lib/utils.js';
8
+
9
+ /**
10
+ * Figma: Components · Base › Dialog (2084:160). 480 px, 12 px radius (radius/xl), 24 px
11
+ * padding, 16 px gap, Shadow/lg. Title Heading/Page title, description Body/Default muted,
12
+ * 24 px close button (5 px radius), footer buttons right-aligned 8 px apart.
13
+ */
14
+ const overlayClassName =
15
+ 'fixed inset-0 z-50 bg-overlay data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=closed]:animate-out data-[state=closed]:fade-out-0';
16
+
17
+ const closeButtonClassName =
18
+ "inline-flex size-6 items-center justify-center rounded-sm text-muted-foreground transition-colors outline-none hover:bg-accent hover:text-accent-foreground focus-visible:ring-3 focus-visible:ring-ring/40 disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4";
19
+
20
+ function Dialog(props: React.ComponentProps<typeof DialogPrimitive.Root>) {
21
+ return <DialogPrimitive.Root data-slot="dialog" {...props} />;
22
+ }
23
+
24
+ function DialogTrigger(props: React.ComponentProps<typeof DialogPrimitive.Trigger>) {
25
+ return <DialogPrimitive.Trigger data-slot="dialog-trigger" {...props} />;
26
+ }
27
+
28
+ function DialogPortal(props: React.ComponentProps<typeof DialogPrimitive.Portal>) {
29
+ return <DialogPrimitive.Portal data-slot="dialog-portal" {...props} />;
30
+ }
31
+
32
+ function DialogClose(props: React.ComponentProps<typeof DialogPrimitive.Close>) {
33
+ return <DialogPrimitive.Close data-slot="dialog-close" {...props} />;
34
+ }
35
+
36
+ function DialogOverlay({ className, ...props }: React.ComponentProps<typeof DialogPrimitive.Overlay>) {
37
+ return (
38
+ <DialogPrimitive.Overlay data-slot="dialog-overlay" className={cn(overlayClassName, className)} {...props} />
39
+ );
40
+ }
41
+
42
+ function DialogContent({
43
+ className,
44
+ children,
45
+ showCloseButton = true,
46
+ ...props
47
+ }: React.ComponentProps<typeof DialogPrimitive.Content> & { showCloseButton?: boolean }) {
48
+ return (
49
+ <DialogPortal>
50
+ <DialogOverlay />
51
+ <DialogPrimitive.Content
52
+ data-slot="dialog-content"
53
+ className={cn(
54
+ 'fixed top-1/2 left-1/2 z-50 flex max-h-[calc(100dvh-2rem)] w-[calc(100%-2rem)] max-w-[480px] -translate-x-1/2 -translate-y-1/2 flex-col gap-4 overflow-y-auto rounded-xl border border-border bg-background p-6 text-body text-foreground shadow-lg outline-none data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95',
55
+ className,
56
+ )}
57
+ {...props}
58
+ >
59
+ {children}
60
+ {showCloseButton && (
61
+ <DialogPrimitive.Close
62
+ data-slot="dialog-close"
63
+ className={cn(closeButtonClassName, 'absolute top-6 right-6')}
64
+ >
65
+ <XIcon />
66
+ <span className="sr-only">Close</span>
67
+ </DialogPrimitive.Close>
68
+ )}
69
+ </DialogPrimitive.Content>
70
+ </DialogPortal>
71
+ );
72
+ }
73
+
74
+ function DialogHeader({ className, ...props }: React.ComponentProps<'div'>) {
75
+ return (
76
+ <div
77
+ data-slot="dialog-header"
78
+ className={cn('flex flex-col gap-1.5 pr-9 text-left', className)}
79
+ {...props}
80
+ />
81
+ );
82
+ }
83
+
84
+ function DialogFooter({ className, ...props }: React.ComponentProps<'div'>) {
85
+ return (
86
+ <div
87
+ data-slot="dialog-footer"
88
+ className={cn('flex flex-col-reverse gap-2 sm:flex-row sm:justify-end', className)}
89
+ {...props}
90
+ />
91
+ );
92
+ }
93
+
94
+ function DialogTitle({ className, ...props }: React.ComponentProps<typeof DialogPrimitive.Title>) {
95
+ return (
96
+ <DialogPrimitive.Title data-slot="dialog-title" className={cn('text-page-title', className)} {...props} />
97
+ );
98
+ }
99
+
100
+ function DialogDescription({
101
+ className,
102
+ ...props
103
+ }: React.ComponentProps<typeof DialogPrimitive.Description>) {
104
+ return (
105
+ <DialogPrimitive.Description
106
+ data-slot="dialog-description"
107
+ className={cn('text-body text-muted-foreground', className)}
108
+ {...props}
109
+ />
110
+ );
111
+ }
112
+
113
+ export {
114
+ Dialog,
115
+ DialogClose,
116
+ DialogContent,
117
+ DialogDescription,
118
+ DialogFooter,
119
+ DialogHeader,
120
+ DialogOverlay,
121
+ DialogPortal,
122
+ DialogTitle,
123
+ DialogTrigger,
124
+ overlayClassName,
125
+ closeButtonClassName,
126
+ };
@@ -0,0 +1,225 @@
1
+ 'use client';
2
+
3
+ import * as React from 'react';
4
+ import { DropdownMenu as DropdownMenuPrimitive } from 'radix-ui';
5
+ import { CheckIcon, ChevronRightIcon, CircleIcon } from 'lucide-react';
6
+
7
+ import { cn } from '../lib/utils.js';
8
+
9
+ /**
10
+ * Figma: Components · Base › Dropdown Menu (2084:92) and Menu Item (2007:58).
11
+ * Panel: popover, 6 px radius, 4 px padding, Shadow/md. Item: 32 px, 8 px padding,
12
+ * 5 px radius, Body/Default; hover = accent. Label: Body/Small Medium, muted.
13
+ * Shortcut: Label/Caption, muted. Separator: 1 px border with 4 px around it.
14
+ */
15
+ const menuItemClassName =
16
+ "relative flex h-8 cursor-default select-none items-center gap-2 rounded-sm px-2 text-body text-foreground outline-hidden focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground";
17
+
18
+ const menuContentClassName =
19
+ 'z-50 min-w-[220px] overflow-hidden rounded-md border border-border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=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';
20
+
21
+ function DropdownMenu(props: React.ComponentProps<typeof DropdownMenuPrimitive.Root>) {
22
+ return <DropdownMenuPrimitive.Root data-slot="dropdown-menu" {...props} />;
23
+ }
24
+
25
+ function DropdownMenuPortal(props: React.ComponentProps<typeof DropdownMenuPrimitive.Portal>) {
26
+ return <DropdownMenuPrimitive.Portal data-slot="dropdown-menu-portal" {...props} />;
27
+ }
28
+
29
+ function DropdownMenuTrigger(props: React.ComponentProps<typeof DropdownMenuPrimitive.Trigger>) {
30
+ return <DropdownMenuPrimitive.Trigger data-slot="dropdown-menu-trigger" {...props} />;
31
+ }
32
+
33
+ function DropdownMenuContent({
34
+ className,
35
+ sideOffset = 4,
36
+ ...props
37
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.Content>) {
38
+ return (
39
+ <DropdownMenuPrimitive.Portal>
40
+ <DropdownMenuPrimitive.Content
41
+ data-slot="dropdown-menu-content"
42
+ sideOffset={sideOffset}
43
+ className={cn(
44
+ menuContentClassName,
45
+ 'max-h-(--radix-dropdown-menu-content-available-height) origin-(--radix-dropdown-menu-content-transform-origin) overflow-y-auto',
46
+ className,
47
+ )}
48
+ {...props}
49
+ />
50
+ </DropdownMenuPrimitive.Portal>
51
+ );
52
+ }
53
+
54
+ function DropdownMenuGroup(props: React.ComponentProps<typeof DropdownMenuPrimitive.Group>) {
55
+ return <DropdownMenuPrimitive.Group data-slot="dropdown-menu-group" {...props} />;
56
+ }
57
+
58
+ function DropdownMenuItem({
59
+ className,
60
+ inset,
61
+ variant = 'default',
62
+ ...props
63
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.Item> & {
64
+ inset?: boolean;
65
+ variant?: 'default' | 'destructive';
66
+ }) {
67
+ return (
68
+ <DropdownMenuPrimitive.Item
69
+ data-slot="dropdown-menu-item"
70
+ data-inset={inset}
71
+ data-variant={variant}
72
+ className={cn(
73
+ menuItemClassName,
74
+ 'data-[variant=destructive]:text-destructive-strong data-[variant=destructive]:focus:bg-destructive-tint data-[variant=destructive]:focus:text-destructive-strong data-[variant=destructive]:*:[svg]:!text-destructive-strong',
75
+ className,
76
+ )}
77
+ {...props}
78
+ />
79
+ );
80
+ }
81
+
82
+ function DropdownMenuCheckboxItem({
83
+ className,
84
+ children,
85
+ checked,
86
+ ...props
87
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.CheckboxItem>) {
88
+ return (
89
+ <DropdownMenuPrimitive.CheckboxItem
90
+ data-slot="dropdown-menu-checkbox-item"
91
+ className={cn(menuItemClassName, 'pl-8', className)}
92
+ checked={checked}
93
+ {...props}
94
+ >
95
+ <span className="pointer-events-none absolute left-2 flex size-4 items-center justify-center">
96
+ <DropdownMenuPrimitive.ItemIndicator>
97
+ <CheckIcon className="size-4 !text-foreground" />
98
+ </DropdownMenuPrimitive.ItemIndicator>
99
+ </span>
100
+ {children}
101
+ </DropdownMenuPrimitive.CheckboxItem>
102
+ );
103
+ }
104
+
105
+ function DropdownMenuRadioGroup(props: React.ComponentProps<typeof DropdownMenuPrimitive.RadioGroup>) {
106
+ return <DropdownMenuPrimitive.RadioGroup data-slot="dropdown-menu-radio-group" {...props} />;
107
+ }
108
+
109
+ function DropdownMenuRadioItem({
110
+ className,
111
+ children,
112
+ ...props
113
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.RadioItem>) {
114
+ return (
115
+ <DropdownMenuPrimitive.RadioItem
116
+ data-slot="dropdown-menu-radio-item"
117
+ className={cn(menuItemClassName, 'pl-8', className)}
118
+ {...props}
119
+ >
120
+ <span className="pointer-events-none absolute left-2 flex size-4 items-center justify-center">
121
+ <DropdownMenuPrimitive.ItemIndicator>
122
+ <CircleIcon className="size-2 fill-current !text-foreground" />
123
+ </DropdownMenuPrimitive.ItemIndicator>
124
+ </span>
125
+ {children}
126
+ </DropdownMenuPrimitive.RadioItem>
127
+ );
128
+ }
129
+
130
+ function DropdownMenuLabel({
131
+ className,
132
+ inset,
133
+ ...props
134
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.Label> & { inset?: boolean }) {
135
+ return (
136
+ <DropdownMenuPrimitive.Label
137
+ data-slot="dropdown-menu-label"
138
+ data-inset={inset}
139
+ className={cn(
140
+ 'px-2 py-1.5 text-body-sm font-medium text-muted-foreground data-[inset]:pl-8',
141
+ className,
142
+ )}
143
+ {...props}
144
+ />
145
+ );
146
+ }
147
+
148
+ function DropdownMenuSeparator({
149
+ className,
150
+ ...props
151
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.Separator>) {
152
+ return (
153
+ <DropdownMenuPrimitive.Separator
154
+ data-slot="dropdown-menu-separator"
155
+ className={cn('my-1 h-px bg-border', className)}
156
+ {...props}
157
+ />
158
+ );
159
+ }
160
+
161
+ function DropdownMenuShortcut({ className, ...props }: React.ComponentProps<'span'>) {
162
+ return (
163
+ <span
164
+ data-slot="dropdown-menu-shortcut"
165
+ className={cn('ml-auto text-caption text-muted-foreground', className)}
166
+ {...props}
167
+ />
168
+ );
169
+ }
170
+
171
+ function DropdownMenuSub(props: React.ComponentProps<typeof DropdownMenuPrimitive.Sub>) {
172
+ return <DropdownMenuPrimitive.Sub data-slot="dropdown-menu-sub" {...props} />;
173
+ }
174
+
175
+ function DropdownMenuSubTrigger({
176
+ className,
177
+ inset,
178
+ children,
179
+ ...props
180
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.SubTrigger> & { inset?: boolean }) {
181
+ return (
182
+ <DropdownMenuPrimitive.SubTrigger
183
+ data-slot="dropdown-menu-sub-trigger"
184
+ data-inset={inset}
185
+ className={cn(menuItemClassName, 'data-[state=open]:bg-accent data-[state=open]:text-accent-foreground', className)}
186
+ {...props}
187
+ >
188
+ {children}
189
+ <ChevronRightIcon className="ml-auto size-4" />
190
+ </DropdownMenuPrimitive.SubTrigger>
191
+ );
192
+ }
193
+
194
+ function DropdownMenuSubContent({
195
+ className,
196
+ ...props
197
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.SubContent>) {
198
+ return (
199
+ <DropdownMenuPrimitive.SubContent
200
+ data-slot="dropdown-menu-sub-content"
201
+ className={cn(menuContentClassName, 'origin-(--radix-dropdown-menu-content-transform-origin)', className)}
202
+ {...props}
203
+ />
204
+ );
205
+ }
206
+
207
+ export {
208
+ DropdownMenu,
209
+ DropdownMenuPortal,
210
+ DropdownMenuTrigger,
211
+ DropdownMenuContent,
212
+ DropdownMenuGroup,
213
+ DropdownMenuLabel,
214
+ DropdownMenuItem,
215
+ DropdownMenuCheckboxItem,
216
+ DropdownMenuRadioGroup,
217
+ DropdownMenuRadioItem,
218
+ DropdownMenuSeparator,
219
+ DropdownMenuShortcut,
220
+ DropdownMenuSub,
221
+ DropdownMenuSubTrigger,
222
+ DropdownMenuSubContent,
223
+ menuItemClassName,
224
+ menuContentClassName,
225
+ };
@@ -0,0 +1,63 @@
1
+ import * as React from 'react';
2
+
3
+ import { cn } from '../lib/utils.js';
4
+
5
+ /**
6
+ * Figma: Components · Base › Empty State (2085:215), custom. Bordered box, 40/24 px
7
+ * padding, centered: a 40 px muted icon tile, title (Heading/Card), description, action
8
+ * (usually <Button size="sm">).
9
+ */
10
+ function EmptyState({ className, ...props }: React.ComponentProps<'div'>) {
11
+ return (
12
+ <div
13
+ data-slot="empty-state"
14
+ className={cn(
15
+ 'flex w-full flex-col items-center justify-center gap-3 rounded-lg border border-border px-6 py-10 text-center',
16
+ className,
17
+ )}
18
+ {...props}
19
+ />
20
+ );
21
+ }
22
+
23
+ function EmptyStateIcon({ className, ...props }: React.ComponentProps<'div'>) {
24
+ return (
25
+ <div
26
+ data-slot="empty-state-icon"
27
+ aria-hidden
28
+ className={cn(
29
+ "flex size-10 items-center justify-center rounded-lg bg-muted text-muted-foreground [&_svg:not([class*='size-'])]:size-5",
30
+ className,
31
+ )}
32
+ {...props}
33
+ />
34
+ );
35
+ }
36
+
37
+ function EmptyStateTitle({ className, ...props }: React.ComponentProps<'h3'>) {
38
+ return (
39
+ <h3 data-slot="empty-state-title" className={cn('text-card-title text-foreground', className)} {...props} />
40
+ );
41
+ }
42
+
43
+ function EmptyStateDescription({ className, ...props }: React.ComponentProps<'p'>) {
44
+ return (
45
+ <p
46
+ data-slot="empty-state-description"
47
+ className={cn('max-w-sm text-body text-muted-foreground', className)}
48
+ {...props}
49
+ />
50
+ );
51
+ }
52
+
53
+ function EmptyStateActions({ className, ...props }: React.ComponentProps<'div'>) {
54
+ return (
55
+ <div
56
+ data-slot="empty-state-actions"
57
+ className={cn('flex items-center justify-center gap-2', className)}
58
+ {...props}
59
+ />
60
+ );
61
+ }
62
+
63
+ export { EmptyState, EmptyStateIcon, EmptyStateTitle, EmptyStateDescription, EmptyStateActions };