@brokenrubik/ui 0.0.0-stage → 0.1.1

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 +97 -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,79 @@
1
+ 'use client';
2
+
3
+ import * as React from 'react';
4
+
5
+ import { cn } from '../lib/utils.js';
6
+ import { Label } from './label.js';
7
+
8
+ /**
9
+ * Figma: Components · Base › Form Field (2083:229), code FormItem / FormLabel /
10
+ * FormDescription / FormMessage. Label (Body/Small Medium) · control · helper (Body/Small),
11
+ * 6 px apart. Not tied to a form library: pass `invalid` / `disabled` and wire the control
12
+ * with `aria-invalid` (the control turns its border red). FieldError replaces the helper.
13
+ *
14
+ * <Field invalid={!!error}>
15
+ * <FieldLabel htmlFor="name">Project name</FieldLabel>
16
+ * <Input id="name" aria-invalid={!!error} aria-describedby="name-help" />
17
+ * {error ? <FieldError id="name-help">{error}</FieldError>
18
+ * : <FieldDescription id="name-help">Shown on the sidebar.</FieldDescription>}
19
+ * </Field>
20
+ */
21
+ function Field({
22
+ className,
23
+ invalid,
24
+ disabled,
25
+ ...props
26
+ }: React.ComponentProps<'div'> & { invalid?: boolean; disabled?: boolean }) {
27
+ return (
28
+ <div
29
+ role="group"
30
+ data-slot="field"
31
+ data-invalid={invalid || undefined}
32
+ data-disabled={disabled || undefined}
33
+ className={cn(
34
+ 'group/field flex w-full flex-col gap-1.5 data-[disabled]:[&_[data-slot=input]]:opacity-50',
35
+ className,
36
+ )}
37
+ {...props}
38
+ />
39
+ );
40
+ }
41
+
42
+ function FieldLabel({ className, ...props }: React.ComponentProps<typeof Label>) {
43
+ return (
44
+ <Label
45
+ data-slot="field-label"
46
+ className={cn('group-data-[disabled]/field:text-muted-foreground', className)}
47
+ {...props}
48
+ />
49
+ );
50
+ }
51
+
52
+ function FieldDescription({ className, ...props }: React.ComponentProps<'p'>) {
53
+ return (
54
+ <p
55
+ data-slot="field-description"
56
+ className={cn(
57
+ 'text-body-sm text-muted-foreground group-data-[invalid]/field:text-destructive-strong',
58
+ className,
59
+ )}
60
+ {...props}
61
+ />
62
+ );
63
+ }
64
+
65
+ function FieldError({ className, children, ...props }: React.ComponentProps<'p'>) {
66
+ if (!children) return null;
67
+ return (
68
+ <p
69
+ data-slot="field-error"
70
+ role="alert"
71
+ className={cn('text-body-sm text-destructive-strong', className)}
72
+ {...props}
73
+ >
74
+ {children}
75
+ </p>
76
+ );
77
+ }
78
+
79
+ export { Field, FieldLabel, FieldDescription, FieldError };
@@ -0,0 +1,49 @@
1
+ import * as React from 'react';
2
+
3
+ import { cn } from '../lib/utils.js';
4
+
5
+ /**
6
+ * The shared look of text controls (Input, Select trigger, Date Picker, Textarea).
7
+ * Figma: 32 px, 6 px radius, `input` border (3:1), Shadow/xs, Body/Default; Focus = 1.5 px
8
+ * `ring` border; Disabled = 50 % opacity; Error = `destructive` border.
9
+ */
10
+ const controlClassName =
11
+ 'w-full min-w-0 rounded-md border border-input bg-background text-body text-foreground shadow-xs transition-[color,border-color,box-shadow] outline-none placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground focus-visible:border-ring focus-visible:ring-[0.5px] focus-visible:ring-ring disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20';
12
+
13
+ type InputProps = React.ComponentProps<'input'> & {
14
+ /** An icon before the text (16 px, muted), as Figma's Input "Show icon". */
15
+ startIcon?: React.ReactNode;
16
+ /** Classes for the wrapper that holds the icon (only with startIcon). */
17
+ wrapperClassName?: string;
18
+ };
19
+
20
+ /** Figma: Components · Base › Input (2007:32). */
21
+ function Input({ className, type, startIcon, wrapperClassName, ...props }: InputProps) {
22
+ const input = (
23
+ <input
24
+ type={type}
25
+ data-slot="input"
26
+ className={cn(
27
+ controlClassName,
28
+ 'h-8 px-2.5 py-1 file:inline-flex file:h-6 file:border-0 file:bg-transparent file:text-body file:font-medium',
29
+ startIcon && 'pl-[34px]',
30
+ className,
31
+ )}
32
+ {...props}
33
+ />
34
+ );
35
+ if (!startIcon) return input;
36
+ return (
37
+ <div data-slot="input-wrapper" className={cn('relative w-full', wrapperClassName)}>
38
+ <span
39
+ aria-hidden
40
+ className="pointer-events-none absolute inset-y-0 left-[11px] flex items-center text-muted-foreground [&_svg]:size-4"
41
+ >
42
+ {startIcon}
43
+ </span>
44
+ {input}
45
+ </div>
46
+ );
47
+ }
48
+
49
+ export { Input, controlClassName, type InputProps };
@@ -0,0 +1,48 @@
1
+ import * as React from 'react';
2
+ import { cva, type VariantProps } from 'class-variance-authority';
3
+
4
+ import { cn } from '../lib/utils.js';
5
+
6
+ /**
7
+ * Figma: Components · Base › Key Chip (2006:69), code `<Kbd>` (custom).
8
+ * highlight: the lime square of a space key (20 px / 5 px radius or 24 px / 6 px radius).
9
+ * muted: a keyboard hint or ID in Mono/Small on muted (20 px, 4 px radius).
10
+ */
11
+ const kbdVariants = cva(
12
+ 'inline-flex shrink-0 select-none items-center justify-center whitespace-nowrap',
13
+ {
14
+ variants: {
15
+ variant: {
16
+ highlight: 'bg-highlight font-sans font-semibold tracking-[0.15px] text-highlight-foreground',
17
+ muted: 'h-5 min-w-5 rounded-xs bg-muted px-1.5 font-mono text-mono-sm text-muted-foreground',
18
+ },
19
+ size: {
20
+ 20: '',
21
+ 24: '',
22
+ },
23
+ },
24
+ compoundVariants: [
25
+ { variant: 'highlight', size: 20, className: 'size-5 rounded-sm text-[8.4px] leading-none' },
26
+ { variant: 'highlight', size: 24, className: 'size-6 rounded-md text-[10px] leading-none' },
27
+ ],
28
+ defaultVariants: { variant: 'muted', size: 20 },
29
+ },
30
+ );
31
+
32
+ function Kbd({
33
+ className,
34
+ variant = 'muted',
35
+ size = 20,
36
+ ...props
37
+ }: React.ComponentProps<'kbd'> & VariantProps<typeof kbdVariants>) {
38
+ return (
39
+ <kbd
40
+ data-slot="kbd"
41
+ data-variant={variant}
42
+ className={cn(kbdVariants({ variant, size }), className)}
43
+ {...props}
44
+ />
45
+ );
46
+ }
47
+
48
+ export { Kbd, kbdVariants };
@@ -0,0 +1,22 @@
1
+ 'use client';
2
+
3
+ import * as React from 'react';
4
+ import { Label as LabelPrimitive } from 'radix-ui';
5
+
6
+ import { cn } from '../lib/utils.js';
7
+
8
+ /** Figma: Form Field label, Body/Small Medium. */
9
+ function Label({ className, ...props }: React.ComponentProps<typeof LabelPrimitive.Root>) {
10
+ return (
11
+ <LabelPrimitive.Root
12
+ data-slot="label"
13
+ className={cn(
14
+ 'flex select-none items-center gap-2 text-body-sm font-medium text-foreground group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:text-muted-foreground peer-disabled:cursor-not-allowed peer-disabled:opacity-50',
15
+ className,
16
+ )}
17
+ {...props}
18
+ />
19
+ );
20
+ }
21
+
22
+ export { Label };
@@ -0,0 +1,94 @@
1
+ import * as React from 'react';
2
+ import { ChevronLeftIcon, ChevronRightIcon, MoreHorizontalIcon } from 'lucide-react';
3
+
4
+ import { cn } from '../lib/utils.js';
5
+ import { buttonVariants } from './button.js';
6
+
7
+ /**
8
+ * Figma: Components · Base › Pagination (2085:245) and Pagination Item (2085:244).
9
+ * 32 px squares, 6 px radius, Body/Default; active = bordered on background + Medium.
10
+ * Items are links: pass href (or asChild with the router's Link).
11
+ */
12
+ function Pagination({ className, ...props }: React.ComponentProps<'nav'>) {
13
+ return (
14
+ <nav
15
+ role="navigation"
16
+ aria-label="pagination"
17
+ data-slot="pagination"
18
+ className={cn('mx-auto flex w-full justify-center', className)}
19
+ {...props}
20
+ />
21
+ );
22
+ }
23
+
24
+ function PaginationContent({ className, ...props }: React.ComponentProps<'ul'>) {
25
+ return <ul data-slot="pagination-content" className={cn('flex items-center gap-1', className)} {...props} />;
26
+ }
27
+
28
+ function PaginationItem(props: React.ComponentProps<'li'>) {
29
+ return <li data-slot="pagination-item" {...props} />;
30
+ }
31
+
32
+ type PaginationLinkProps = { isActive?: boolean } & React.ComponentProps<'a'>;
33
+
34
+ function PaginationLink({ className, isActive, ...props }: PaginationLinkProps) {
35
+ return (
36
+ <a
37
+ aria-current={isActive ? 'page' : undefined}
38
+ data-slot="pagination-link"
39
+ data-active={isActive}
40
+ className={cn(
41
+ buttonVariants({ variant: isActive ? 'outline' : 'ghost' }),
42
+ 'size-8 rounded-md px-0 font-normal data-[active=true]:font-medium data-[active=true]:shadow-none',
43
+ className,
44
+ )}
45
+ {...props}
46
+ />
47
+ );
48
+ }
49
+
50
+ function PaginationPrevious({ className, children = 'Previous', ...props }: React.ComponentProps<typeof PaginationLink>) {
51
+ return (
52
+ <PaginationLink
53
+ aria-label="Go to previous page"
54
+ className={cn('w-auto gap-1 px-2.5', className)}
55
+ {...props}
56
+ >
57
+ <ChevronLeftIcon />
58
+ <span>{children}</span>
59
+ </PaginationLink>
60
+ );
61
+ }
62
+
63
+ function PaginationNext({ className, children = 'Next', ...props }: React.ComponentProps<typeof PaginationLink>) {
64
+ return (
65
+ <PaginationLink aria-label="Go to next page" className={cn('w-auto gap-1 px-2.5', className)} {...props}>
66
+ <span>{children}</span>
67
+ <ChevronRightIcon />
68
+ </PaginationLink>
69
+ );
70
+ }
71
+
72
+ function PaginationEllipsis({ className, ...props }: React.ComponentProps<'span'>) {
73
+ return (
74
+ <span
75
+ aria-hidden
76
+ data-slot="pagination-ellipsis"
77
+ className={cn('flex size-8 items-center justify-center text-muted-foreground', className)}
78
+ {...props}
79
+ >
80
+ <MoreHorizontalIcon className="size-4" />
81
+ <span className="sr-only">More pages</span>
82
+ </span>
83
+ );
84
+ }
85
+
86
+ export {
87
+ Pagination,
88
+ PaginationContent,
89
+ PaginationLink,
90
+ PaginationItem,
91
+ PaginationPrevious,
92
+ PaginationNext,
93
+ PaginationEllipsis,
94
+ };
@@ -0,0 +1,65 @@
1
+ 'use client';
2
+
3
+ import * as React from 'react';
4
+ import { Popover as PopoverPrimitive } from 'radix-ui';
5
+
6
+ import { cn } from '../lib/utils.js';
7
+
8
+ /** Figma: Components · Base › Popover (2084:117). 300 px, 16 px padding, 8 px radius, Shadow/md. */
9
+ function Popover(props: React.ComponentProps<typeof PopoverPrimitive.Root>) {
10
+ return <PopoverPrimitive.Root data-slot="popover" {...props} />;
11
+ }
12
+
13
+ function PopoverTrigger(props: React.ComponentProps<typeof PopoverPrimitive.Trigger>) {
14
+ return <PopoverPrimitive.Trigger data-slot="popover-trigger" {...props} />;
15
+ }
16
+
17
+ function PopoverAnchor(props: React.ComponentProps<typeof PopoverPrimitive.Anchor>) {
18
+ return <PopoverPrimitive.Anchor data-slot="popover-anchor" {...props} />;
19
+ }
20
+
21
+ function PopoverContent({
22
+ className,
23
+ align = 'center',
24
+ sideOffset = 4,
25
+ ...props
26
+ }: React.ComponentProps<typeof PopoverPrimitive.Content>) {
27
+ return (
28
+ <PopoverPrimitive.Portal>
29
+ <PopoverPrimitive.Content
30
+ data-slot="popover-content"
31
+ align={align}
32
+ sideOffset={sideOffset}
33
+ className={cn(
34
+ 'z-50 flex w-[300px] origin-(--radix-popover-content-transform-origin) flex-col gap-3 rounded-lg border border-border bg-popover p-4 text-body text-popover-foreground shadow-md 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 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',
35
+ className,
36
+ )}
37
+ {...props}
38
+ />
39
+ </PopoverPrimitive.Portal>
40
+ );
41
+ }
42
+
43
+ function PopoverHeader({ className, ...props }: React.ComponentProps<'div'>) {
44
+ return <div data-slot="popover-header" className={cn('flex flex-col gap-1', className)} {...props} />;
45
+ }
46
+
47
+ function PopoverTitle({ className, ...props }: React.ComponentProps<'h2'>) {
48
+ return <h2 data-slot="popover-title" className={cn('text-card-title', className)} {...props} />;
49
+ }
50
+
51
+ function PopoverDescription({ className, ...props }: React.ComponentProps<'p'>) {
52
+ return (
53
+ <p data-slot="popover-description" className={cn('text-body text-muted-foreground', className)} {...props} />
54
+ );
55
+ }
56
+
57
+ export {
58
+ Popover,
59
+ PopoverTrigger,
60
+ PopoverAnchor,
61
+ PopoverContent,
62
+ PopoverHeader,
63
+ PopoverTitle,
64
+ PopoverDescription,
65
+ };
@@ -0,0 +1,52 @@
1
+ 'use client';
2
+
3
+ import * as React from 'react';
4
+ import { cva, type VariantProps } from 'class-variance-authority';
5
+ import { Progress as ProgressPrimitive } from 'radix-ui';
6
+
7
+ import { resolveTone } from '../lib/tone.js';
8
+ import { cn } from '../lib/utils.js';
9
+
10
+ /** Figma: Components · Base › Progress (2085:210). 8 px muted track, full radius, toned bar. */
11
+ const progressIndicatorVariants = cva('h-full w-full flex-1 rounded-full transition-transform', {
12
+ variants: {
13
+ tone: {
14
+ primary: 'bg-primary',
15
+ success: 'bg-success',
16
+ warning: 'bg-warning',
17
+ destructive: 'bg-destructive',
18
+ },
19
+ },
20
+ defaultVariants: { tone: 'primary' },
21
+ });
22
+
23
+ type ProgressTone = NonNullable<VariantProps<typeof progressIndicatorVariants>['tone']>;
24
+
25
+ type ProgressProps = React.ComponentProps<typeof ProgressPrimitive.Root> & {
26
+ /** Figma "Tone". Default `primary`. */
27
+ tone?: ProgressTone | null;
28
+ /** Same as `tone` (shadcn-style name). */
29
+ variant?: ProgressTone | null;
30
+ };
31
+
32
+ function Progress({ className, value, tone: toneProp, variant, ...props }: ProgressProps) {
33
+ const tone = resolveTone(toneProp, variant, {}, 'primary');
34
+ const pct = Math.min(100, Math.max(0, value ?? 0));
35
+ return (
36
+ <ProgressPrimitive.Root
37
+ data-slot="progress"
38
+ value={value}
39
+ className={cn('relative h-2 w-full overflow-hidden rounded-full bg-muted', className)}
40
+ {...props}
41
+ >
42
+ <ProgressPrimitive.Indicator
43
+ data-slot="progress-indicator"
44
+ data-tone={tone}
45
+ className={progressIndicatorVariants({ tone })}
46
+ style={{ transform: `translateX(-${100 - pct}%)` }}
47
+ />
48
+ </ProgressPrimitive.Root>
49
+ );
50
+ }
51
+
52
+ export { Progress, progressIndicatorVariants, type ProgressProps, type ProgressTone };
@@ -0,0 +1,42 @@
1
+ 'use client';
2
+
3
+ import * as React from 'react';
4
+ import { RadioGroup as RadioGroupPrimitive } from 'radix-ui';
5
+
6
+ import { cn } from '../lib/utils.js';
7
+
8
+ /** Figma: Components · Base › Radio (2083:207). 16 px ring; checked = violet ring + 8 px dot. */
9
+ function RadioGroup({ className, ...props }: React.ComponentProps<typeof RadioGroupPrimitive.Root>) {
10
+ return (
11
+ <RadioGroupPrimitive.Root
12
+ data-slot="radio-group"
13
+ className={cn('grid gap-3', className)}
14
+ {...props}
15
+ />
16
+ );
17
+ }
18
+
19
+ function RadioGroupItem({
20
+ className,
21
+ ...props
22
+ }: React.ComponentProps<typeof RadioGroupPrimitive.Item>) {
23
+ return (
24
+ <RadioGroupPrimitive.Item
25
+ data-slot="radio-group-item"
26
+ className={cn(
27
+ 'peer aspect-square size-4 shrink-0 rounded-full border border-input bg-background shadow-xs transition-[border-color,box-shadow] outline-none focus-visible:ring-3 focus-visible:ring-ring/40 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive data-[state=checked]:border-primary',
28
+ className,
29
+ )}
30
+ {...props}
31
+ >
32
+ <RadioGroupPrimitive.Indicator
33
+ data-slot="radio-group-indicator"
34
+ className="flex items-center justify-center"
35
+ >
36
+ <span className="size-2 rounded-full bg-primary" />
37
+ </RadioGroupPrimitive.Indicator>
38
+ </RadioGroupPrimitive.Item>
39
+ );
40
+ }
41
+
42
+ export { RadioGroup, RadioGroupItem };
@@ -0,0 +1,167 @@
1
+ 'use client';
2
+
3
+ import * as React from 'react';
4
+ import { Select as SelectPrimitive } from 'radix-ui';
5
+ import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from 'lucide-react';
6
+
7
+ import { cn } from '../lib/utils.js';
8
+ import { controlClassName } from './input.js';
9
+ import { menuContentClassName, menuItemClassName } from './dropdown-menu.js';
10
+
11
+ /**
12
+ * Figma: Components · Base › Select (2083:40). The trigger looks like Input (32 px,
13
+ * `input` border, ring on focus) with a muted chevron; the list is a Dropdown Menu panel.
14
+ */
15
+ function Select(props: React.ComponentProps<typeof SelectPrimitive.Root>) {
16
+ return <SelectPrimitive.Root data-slot="select" {...props} />;
17
+ }
18
+
19
+ function SelectGroup(props: React.ComponentProps<typeof SelectPrimitive.Group>) {
20
+ return <SelectPrimitive.Group data-slot="select-group" {...props} />;
21
+ }
22
+
23
+ function SelectValue(props: React.ComponentProps<typeof SelectPrimitive.Value>) {
24
+ return <SelectPrimitive.Value data-slot="select-value" {...props} />;
25
+ }
26
+
27
+ function SelectTrigger({
28
+ className,
29
+ size = 'default',
30
+ children,
31
+ ...props
32
+ }: React.ComponentProps<typeof SelectPrimitive.Trigger> & { size?: 'sm' | 'default' }) {
33
+ return (
34
+ <SelectPrimitive.Trigger
35
+ data-slot="select-trigger"
36
+ data-size={size}
37
+ className={cn(
38
+ controlClassName,
39
+ "flex h-8 items-center justify-between gap-2 whitespace-nowrap px-2.5 data-[size=sm]:h-7 data-[placeholder]:text-muted-foreground *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground",
40
+ className,
41
+ )}
42
+ {...props}
43
+ >
44
+ {children}
45
+ <SelectPrimitive.Icon asChild>
46
+ <ChevronDownIcon className="size-4 opacity-100" />
47
+ </SelectPrimitive.Icon>
48
+ </SelectPrimitive.Trigger>
49
+ );
50
+ }
51
+
52
+ function SelectContent({
53
+ className,
54
+ children,
55
+ position = 'popper',
56
+ align = 'start',
57
+ ...props
58
+ }: React.ComponentProps<typeof SelectPrimitive.Content>) {
59
+ return (
60
+ <SelectPrimitive.Portal>
61
+ <SelectPrimitive.Content
62
+ data-slot="select-content"
63
+ className={cn(
64
+ menuContentClassName,
65
+ 'relative max-h-(--radix-select-content-available-height) min-w-[8rem] origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto',
66
+ position === 'popper' &&
67
+ 'w-full min-w-(--radix-select-trigger-width) data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1',
68
+ className,
69
+ )}
70
+ position={position}
71
+ align={align}
72
+ {...props}
73
+ >
74
+ <SelectScrollUpButton />
75
+ <SelectPrimitive.Viewport
76
+ className={cn(
77
+ position === 'popper' &&
78
+ 'h-(--radix-select-trigger-height) w-full min-w-(--radix-select-trigger-width) scroll-my-1',
79
+ )}
80
+ >
81
+ {children}
82
+ </SelectPrimitive.Viewport>
83
+ <SelectScrollDownButton />
84
+ </SelectPrimitive.Content>
85
+ </SelectPrimitive.Portal>
86
+ );
87
+ }
88
+
89
+ function SelectLabel({ className, ...props }: React.ComponentProps<typeof SelectPrimitive.Label>) {
90
+ return (
91
+ <SelectPrimitive.Label
92
+ data-slot="select-label"
93
+ className={cn('px-2 py-1.5 text-body-sm font-medium text-muted-foreground', className)}
94
+ {...props}
95
+ />
96
+ );
97
+ }
98
+
99
+ function SelectItem({ className, children, ...props }: React.ComponentProps<typeof SelectPrimitive.Item>) {
100
+ return (
101
+ <SelectPrimitive.Item
102
+ data-slot="select-item"
103
+ className={cn(menuItemClassName, 'w-full pr-8', className)}
104
+ {...props}
105
+ >
106
+ <span className="absolute right-2 flex size-4 items-center justify-center">
107
+ <SelectPrimitive.ItemIndicator>
108
+ <CheckIcon className="size-4 !text-foreground" />
109
+ </SelectPrimitive.ItemIndicator>
110
+ </span>
111
+ <SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
112
+ </SelectPrimitive.Item>
113
+ );
114
+ }
115
+
116
+ function SelectSeparator({ className, ...props }: React.ComponentProps<typeof SelectPrimitive.Separator>) {
117
+ return (
118
+ <SelectPrimitive.Separator
119
+ data-slot="select-separator"
120
+ className={cn('pointer-events-none -mx-1 my-1 h-px bg-border', className)}
121
+ {...props}
122
+ />
123
+ );
124
+ }
125
+
126
+ function SelectScrollUpButton({
127
+ className,
128
+ ...props
129
+ }: React.ComponentProps<typeof SelectPrimitive.ScrollUpButton>) {
130
+ return (
131
+ <SelectPrimitive.ScrollUpButton
132
+ data-slot="select-scroll-up-button"
133
+ className={cn('flex cursor-default items-center justify-center py-1 text-muted-foreground', className)}
134
+ {...props}
135
+ >
136
+ <ChevronUpIcon className="size-4" />
137
+ </SelectPrimitive.ScrollUpButton>
138
+ );
139
+ }
140
+
141
+ function SelectScrollDownButton({
142
+ className,
143
+ ...props
144
+ }: React.ComponentProps<typeof SelectPrimitive.ScrollDownButton>) {
145
+ return (
146
+ <SelectPrimitive.ScrollDownButton
147
+ data-slot="select-scroll-down-button"
148
+ className={cn('flex cursor-default items-center justify-center py-1 text-muted-foreground', className)}
149
+ {...props}
150
+ >
151
+ <ChevronDownIcon className="size-4" />
152
+ </SelectPrimitive.ScrollDownButton>
153
+ );
154
+ }
155
+
156
+ export {
157
+ Select,
158
+ SelectContent,
159
+ SelectGroup,
160
+ SelectItem,
161
+ SelectLabel,
162
+ SelectScrollDownButton,
163
+ SelectScrollUpButton,
164
+ SelectSeparator,
165
+ SelectTrigger,
166
+ SelectValue,
167
+ };
@@ -0,0 +1,29 @@
1
+ 'use client';
2
+
3
+ import * as React from 'react';
4
+ import { Separator as SeparatorPrimitive } from 'radix-ui';
5
+
6
+ import { cn } from '../lib/utils.js';
7
+
8
+ /** Figma: Components · Base › Separator (2085:227). 1 px `border` line. */
9
+ function Separator({
10
+ className,
11
+ orientation = 'horizontal',
12
+ decorative = true,
13
+ ...props
14
+ }: React.ComponentProps<typeof SeparatorPrimitive.Root>) {
15
+ return (
16
+ <SeparatorPrimitive.Root
17
+ data-slot="separator"
18
+ decorative={decorative}
19
+ orientation={orientation}
20
+ className={cn(
21
+ 'shrink-0 bg-border data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px',
22
+ className,
23
+ )}
24
+ {...props}
25
+ />
26
+ );
27
+ }
28
+
29
+ export { Separator };