open-ds 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 (201) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +66 -2
  3. package/assets/schemes/accessible-first.json +3093 -0
  4. package/assets/schemes/brand-expressive.json +3124 -0
  5. package/assets/schemes/content-first.json +3110 -0
  6. package/assets/schemes/dense-data.json +3489 -0
  7. package/assets/schemes/grammar.json +3903 -0
  8. package/assets/schemes/multi-brand.json +4359 -0
  9. package/assets/skills/open-ds/SKILL.md +50 -0
  10. package/assets/skills/open-ds-build/SKILL.md +19 -0
  11. package/assets/skills/open-ds-change/SKILL.md +19 -0
  12. package/assets/skills/open-ds-choose/SKILL.md +18 -0
  13. package/assets/skills/open-ds-review/SKILL.md +22 -0
  14. package/assets/skills/open-ds-write/SKILL.md +13 -0
  15. package/assets/stories/accordion.stories.tsx +46 -0
  16. package/assets/stories/alert-dialog.stories.tsx +65 -0
  17. package/assets/stories/alert.stories.tsx +44 -0
  18. package/assets/stories/aspect-ratio.stories.tsx +37 -0
  19. package/assets/stories/avatar.stories.tsx +42 -0
  20. package/assets/stories/badge.stories.tsx +39 -0
  21. package/assets/stories/breadcrumb.stories.tsx +54 -0
  22. package/assets/stories/button.stories.tsx +46 -0
  23. package/assets/stories/calendar.stories.tsx +30 -0
  24. package/assets/stories/card.stories.tsx +50 -0
  25. package/assets/stories/carousel.stories.tsx +64 -0
  26. package/assets/stories/chart.stories.tsx +61 -0
  27. package/assets/stories/checkbox.stories.tsx +29 -0
  28. package/assets/stories/collapsible.stories.tsx +39 -0
  29. package/assets/stories/combobox.stories.tsx +57 -0
  30. package/assets/stories/command.stories.tsx +60 -0
  31. package/assets/stories/context-menu.stories.tsx +55 -0
  32. package/assets/stories/data-table.stories.tsx +93 -0
  33. package/assets/stories/date-picker.stories.tsx +27 -0
  34. package/assets/stories/dialog.stories.tsx +70 -0
  35. package/assets/stories/drawer.stories.tsx +62 -0
  36. package/assets/stories/dropdown-menu.stories.tsx +65 -0
  37. package/assets/stories/empty.stories.tsx +39 -0
  38. package/assets/stories/field.stories.tsx +42 -0
  39. package/assets/stories/form.stories.tsx +56 -0
  40. package/assets/stories/input-group.stories.tsx +41 -0
  41. package/assets/stories/input-otp.stories.tsx +48 -0
  42. package/assets/stories/input.stories.tsx +32 -0
  43. package/assets/stories/kbd.stories.tsx +30 -0
  44. package/assets/stories/label.stories.tsx +26 -0
  45. package/assets/stories/native-select.stories.tsx +39 -0
  46. package/assets/stories/navigation-menu.stories.tsx +41 -0
  47. package/assets/stories/pagination.stories.tsx +61 -0
  48. package/assets/stories/popover.stories.tsx +37 -0
  49. package/assets/stories/progress.stories.tsx +30 -0
  50. package/assets/stories/radio-group.stories.tsx +45 -0
  51. package/assets/stories/resizable.stories.tsx +54 -0
  52. package/assets/stories/scroll-area.stories.tsx +47 -0
  53. package/assets/stories/select.stories.tsx +57 -0
  54. package/assets/stories/separator.stories.tsx +28 -0
  55. package/assets/stories/sheet.stories.tsx +46 -0
  56. package/assets/stories/sidebar.stories.tsx +62 -0
  57. package/assets/stories/skeleton.stories.tsx +27 -0
  58. package/assets/stories/slider.stories.tsx +44 -0
  59. package/assets/stories/spinner.stories.tsx +25 -0
  60. package/assets/stories/switch.stories.tsx +29 -0
  61. package/assets/stories/table.stories.tsx +97 -0
  62. package/assets/stories/tabs.stories.tsx +55 -0
  63. package/assets/stories/textarea.stories.tsx +28 -0
  64. package/assets/stories/toast.stories.tsx +30 -0
  65. package/assets/stories/toggle-group.stories.tsx +41 -0
  66. package/assets/stories/toggle.stories.tsx +20 -0
  67. package/assets/stories/tooltip.stories.tsx +49 -0
  68. package/assets/tokens/accessible-first/content.json +32 -0
  69. package/assets/tokens/accessible-first/dark.tokens.json +581 -0
  70. package/assets/tokens/accessible-first/light.tokens.json +581 -0
  71. package/assets/tokens/accessible-first/shadcn.css +21 -0
  72. package/assets/tokens/accessible-first/theme.css +101 -0
  73. package/assets/tokens/accessible-first/tokens.css +331 -0
  74. package/assets/tokens/accessible-first/tokens.json +1616 -0
  75. package/assets/tokens/accessible-first/tokens.scoped.css +333 -0
  76. package/assets/tokens/brand-expressive/content.json +32 -0
  77. package/assets/tokens/brand-expressive/dark.tokens.json +585 -0
  78. package/assets/tokens/brand-expressive/light.tokens.json +585 -0
  79. package/assets/tokens/brand-expressive/shadcn.css +21 -0
  80. package/assets/tokens/brand-expressive/theme.css +101 -0
  81. package/assets/tokens/brand-expressive/tokens.css +331 -0
  82. package/assets/tokens/brand-expressive/tokens.json +1651 -0
  83. package/assets/tokens/brand-expressive/tokens.scoped.css +333 -0
  84. package/assets/tokens/content-first/content.json +32 -0
  85. package/assets/tokens/content-first/dark.tokens.json +582 -0
  86. package/assets/tokens/content-first/light.tokens.json +582 -0
  87. package/assets/tokens/content-first/shadcn.css +21 -0
  88. package/assets/tokens/content-first/theme.css +101 -0
  89. package/assets/tokens/content-first/tokens.css +331 -0
  90. package/assets/tokens/content-first/tokens.json +1631 -0
  91. package/assets/tokens/content-first/tokens.scoped.css +333 -0
  92. package/assets/tokens/dense-data/content.json +32 -0
  93. package/assets/tokens/dense-data/dark.tokens.json +580 -0
  94. package/assets/tokens/dense-data/light.tokens.json +580 -0
  95. package/assets/tokens/dense-data/shadcn.css +21 -0
  96. package/assets/tokens/dense-data/theme.css +101 -0
  97. package/assets/tokens/dense-data/tokens.css +331 -0
  98. package/assets/tokens/dense-data/tokens.json +1623 -0
  99. package/assets/tokens/dense-data/tokens.scoped.css +333 -0
  100. package/assets/tokens/index.json +143 -0
  101. package/assets/tokens/multi-brand/aster-dark.tokens.json +625 -0
  102. package/assets/tokens/multi-brand/aster-light.tokens.json +625 -0
  103. package/assets/tokens/multi-brand/birch-dark.tokens.json +625 -0
  104. package/assets/tokens/multi-brand/birch-light.tokens.json +625 -0
  105. package/assets/tokens/multi-brand/content.json +32 -0
  106. package/assets/tokens/multi-brand/coral-dark.tokens.json +625 -0
  107. package/assets/tokens/multi-brand/coral-light.tokens.json +625 -0
  108. package/assets/tokens/multi-brand/shadcn.css +21 -0
  109. package/assets/tokens/multi-brand/theme.css +101 -0
  110. package/assets/tokens/multi-brand/tokens.css +1002 -0
  111. package/assets/tokens/multi-brand/tokens.json +1754 -0
  112. package/assets/tokens/multi-brand/tokens.scoped.css +1022 -0
  113. package/assets/tokens/shadcn-map.json +34 -0
  114. package/assets/ui/components/accordion.tsx +60 -0
  115. package/assets/ui/components/alert-dialog.tsx +171 -0
  116. package/assets/ui/components/alert.tsx +56 -0
  117. package/assets/ui/components/aspect-ratio.tsx +17 -0
  118. package/assets/ui/components/avatar.tsx +96 -0
  119. package/assets/ui/components/badge.tsx +36 -0
  120. package/assets/ui/components/breadcrumb.tsx +99 -0
  121. package/assets/ui/components/button.tsx +53 -0
  122. package/assets/ui/components/calendar.tsx +169 -0
  123. package/assets/ui/components/card.tsx +72 -0
  124. package/assets/ui/components/carousel.tsx +224 -0
  125. package/assets/ui/components/chart.tsx +315 -0
  126. package/assets/ui/components/checkbox.tsx +29 -0
  127. package/assets/ui/components/collapsible.tsx +25 -0
  128. package/assets/ui/components/combobox.tsx +275 -0
  129. package/assets/ui/components/command.tsx +145 -0
  130. package/assets/ui/components/context-menu.tsx +218 -0
  131. package/assets/ui/components/data-table.tsx +307 -0
  132. package/assets/ui/components/date-picker.tsx +79 -0
  133. package/assets/ui/components/dialog.tsx +68 -0
  134. package/assets/ui/components/drawer.tsx +115 -0
  135. package/assets/ui/components/dropdown-menu.tsx +58 -0
  136. package/assets/ui/components/empty.tsx +92 -0
  137. package/assets/ui/components/field.tsx +234 -0
  138. package/assets/ui/components/form.tsx +146 -0
  139. package/assets/ui/components/input-group.tsx +154 -0
  140. package/assets/ui/components/input-otp.tsx +75 -0
  141. package/assets/ui/components/input.tsx +25 -0
  142. package/assets/ui/components/kbd.tsx +30 -0
  143. package/assets/ui/components/label.tsx +22 -0
  144. package/assets/ui/components/native-select.tsx +59 -0
  145. package/assets/ui/components/navigation-menu.tsx +149 -0
  146. package/assets/ui/components/pagination.tsx +108 -0
  147. package/assets/ui/components/popover.tsx +61 -0
  148. package/assets/ui/components/progress.tsx +31 -0
  149. package/assets/ui/components/radio-group.tsx +40 -0
  150. package/assets/ui/components/resizable.tsx +54 -0
  151. package/assets/ui/components/scroll-area.tsx +55 -0
  152. package/assets/ui/components/select.tsx +81 -0
  153. package/assets/ui/components/separator.tsx +35 -0
  154. package/assets/ui/components/sheet.tsx +114 -0
  155. package/assets/ui/components/sidebar.tsx +685 -0
  156. package/assets/ui/components/skeleton.tsx +15 -0
  157. package/assets/ui/components/slider.tsx +70 -0
  158. package/assets/ui/components/spinner.tsx +16 -0
  159. package/assets/ui/components/switch.tsx +32 -0
  160. package/assets/ui/components/table.tsx +78 -0
  161. package/assets/ui/components/tabs.tsx +49 -0
  162. package/assets/ui/components/textarea.tsx +25 -0
  163. package/assets/ui/components/toast.tsx +54 -0
  164. package/assets/ui/components/toggle-group.tsx +88 -0
  165. package/assets/ui/components/toggle.tsx +48 -0
  166. package/assets/ui/components/tooltip.tsx +34 -0
  167. package/assets/ui/lib/use-mobile.ts +20 -0
  168. package/assets/ui/lib/utils.ts +7 -0
  169. package/assets/ui/styles.css +27 -0
  170. package/bin/open-ds.js +6 -0
  171. package/package.json +56 -4
  172. package/src/agents.mjs +150 -0
  173. package/src/cli.mjs +445 -0
  174. package/src/components.mjs +69 -0
  175. package/src/context.mjs +143 -0
  176. package/src/create.mjs +377 -0
  177. package/src/docs.mjs +570 -0
  178. package/src/guidance.mjs +63 -0
  179. package/src/index.mjs +20 -0
  180. package/src/mcp.mjs +660 -0
  181. package/src/readmes.mjs +106 -0
  182. package/src/schemes.mjs +188 -0
  183. package/src/tokens.mjs +127 -0
  184. package/templates/project/.github/workflows/ci.yml +15 -0
  185. package/templates/project/.storybook/component-docs.mdx +26 -0
  186. package/templates/project/.storybook/main.ts +17 -0
  187. package/templates/project/.storybook/manager-head.html +3 -0
  188. package/templates/project/.storybook/manager.ts +37 -0
  189. package/templates/project/.storybook/preview-head.html +3 -0
  190. package/templates/project/.storybook/preview.tsx +82 -0
  191. package/templates/project/docs/docusaurus.config.ts +64 -0
  192. package/templates/project/docs/sidebars.ts +8 -0
  193. package/templates/project/docs/src/css/custom.css +275 -0
  194. package/templates/project/gitignore +6 -0
  195. package/templates/project/src/styles/index.css +16 -0
  196. package/templates/project/src/vite-env.d.ts +7 -0
  197. package/templates/project/stories/docs-blocks.tsx +190 -0
  198. package/templates/project/stories/samples.tsx +102 -0
  199. package/templates/project/stories/usage.tsx +31 -0
  200. package/templates/project/tokens/build.mjs +468 -0
  201. package/templates/project/tsconfig.json +15 -0
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Date picker. A button that opens a calendar, for one date or a range.
3
+ *
4
+ * Built from the system's own button, popover and calendar, after shadcn/ui's Date Picker (MIT).
5
+ * Reads the shadcn variables, which theme.css maps onto the scheme's semantic tokens, and the
6
+ * radius, shadow and text scale that shadcn.css maps onto the scheme's tokens. The placeholder is a
7
+ * prop, so the words come from the scheme.
8
+ */
9
+ "use client";
10
+
11
+ import { format } from "date-fns";
12
+ import { CalendarIcon } from "lucide-react";
13
+ import * as React from "react";
14
+ import type { DateRange } from "react-day-picker";
15
+ import { cn } from "../lib/utils";
16
+ import { Button } from "./button";
17
+ import { Calendar } from "./calendar";
18
+ import { Popover, PopoverContent, PopoverTrigger } from "./popover";
19
+
20
+ export type { DateRange };
21
+
22
+ type Common = { placeholder?: string; className?: string; dateFormat?: string };
23
+
24
+ export type DatePickerProps =
25
+ | (Common & { mode?: "single"; value?: Date; defaultValue?: Date; onChange?: (date: Date | undefined) => void })
26
+ | (Common & { mode: "range"; value?: DateRange; defaultValue?: DateRange; onChange?: (range: DateRange | undefined) => void });
27
+
28
+ export function DatePicker(props: DatePickerProps) {
29
+ const { placeholder = "Pick a date", className, dateFormat = "d MMM yyyy" } = props;
30
+ const [inner, setInner] = React.useState<Date | DateRange | undefined>(props.defaultValue);
31
+ const value = props.value ?? inner;
32
+ const label =
33
+ props.mode === "range"
34
+ ? (() => {
35
+ const r = value as DateRange | undefined;
36
+ if (!r?.from) return placeholder;
37
+ return r.to ? `${format(r.from, dateFormat)} – ${format(r.to, dateFormat)}` : format(r.from, dateFormat);
38
+ })()
39
+ : value
40
+ ? format(value as Date, dateFormat)
41
+ : placeholder;
42
+ return (
43
+ <Popover>
44
+ <PopoverTrigger asChild>
45
+ <Button
46
+ variant="secondary"
47
+ data-slot="date-picker"
48
+ data-empty={!value}
49
+ className={cn("justify-start text-left font-normal data-[empty=true]:text-muted-foreground", className)}
50
+ >
51
+ <CalendarIcon />
52
+ {label}
53
+ </Button>
54
+ </PopoverTrigger>
55
+ <PopoverContent className="w-auto p-0" align="start">
56
+ {props.mode === "range" ? (
57
+ <Calendar
58
+ mode="range"
59
+ numberOfMonths={2}
60
+ selected={value as DateRange | undefined}
61
+ onSelect={(r) => {
62
+ setInner(r);
63
+ props.onChange?.(r);
64
+ }}
65
+ />
66
+ ) : (
67
+ <Calendar
68
+ mode="single"
69
+ selected={value as Date | undefined}
70
+ onSelect={(d) => {
71
+ setInner(d);
72
+ (props.onChange as ((d: Date | undefined) => void) | undefined)?.(d);
73
+ }}
74
+ />
75
+ )}
76
+ </PopoverContent>
77
+ </Popover>
78
+ );
79
+ }
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Dialog. Interrupts to ask for a decision, such as confirming a delete. The scrim dims the
3
+ * page where the scheme answers the menu-or-dialog situation with a dimmed page.
4
+ *
5
+ * Reads only component keys: --overlay-*.
6
+ */
7
+ import { X } from "lucide-react";
8
+ import { Dialog as DialogPrimitive } from "radix-ui";
9
+ import * as React from "react";
10
+ import { cn } from "../lib/utils";
11
+
12
+ export const Dialog = DialogPrimitive.Root;
13
+ export const DialogTrigger = DialogPrimitive.Trigger;
14
+ export const DialogClose = DialogPrimitive.Close;
15
+
16
+ export function DialogContent({ className, children, ...props }: React.ComponentProps<typeof DialogPrimitive.Content>) {
17
+ return (
18
+ <DialogPrimitive.Portal>
19
+ <DialogPrimitive.Overlay data-slot="dialog-overlay" className="fixed inset-0 z-50 bg-[var(--overlay-scrim)]" />
20
+ <DialogPrimitive.Content
21
+ data-slot="dialog-content"
22
+ className={cn(
23
+ "fixed left-1/2 top-1/2 z-50 grid w-full max-w-[calc(100%-2rem)] sm:max-w-lg -translate-x-1/2 -translate-y-1/2",
24
+ "gap-[var(--space-gap-group)] p-[var(--space-inset-container)] rounded-[var(--overlay-radius)]",
25
+ "bg-[var(--overlay-background)] text-[var(--color-text-primary)] [box-shadow:var(--overlay-shadow)]",
26
+ "border-[length:var(--border-width-default)] border-[var(--color-border-default)]",
27
+ className,
28
+ )}
29
+ {...props}
30
+ >
31
+ {children}
32
+ <DialogPrimitive.Close className="absolute right-3 top-3 rounded-[var(--radius-badge)] p-1 text-[var(--color-text-secondary)] hover:bg-[var(--color-surface-sunken)]">
33
+ <X className="size-[var(--size-icon)]" />
34
+ <span className="sr-only">Close</span>
35
+ </DialogPrimitive.Close>
36
+ </DialogPrimitive.Content>
37
+ </DialogPrimitive.Portal>
38
+ );
39
+ }
40
+
41
+ export function DialogHeader({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
42
+ return <div className={cn("flex flex-col gap-[var(--space-gap-tight)]", className)} {...props} />;
43
+ }
44
+
45
+ export function DialogFooter({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
46
+ return <div className={cn("flex justify-end gap-[var(--space-gap-default)]", className)} {...props} />;
47
+ }
48
+
49
+ export function DialogTitle({ className, ...props }: React.ComponentProps<typeof DialogPrimitive.Title>) {
50
+ return (
51
+ <DialogPrimitive.Title
52
+ className={cn(
53
+ "[font-family:var(--type-heading-font-family)] text-[length:var(--type-heading-font-size)] [font-weight:var(--type-heading-font-weight)]",
54
+ className,
55
+ )}
56
+ {...props}
57
+ />
58
+ );
59
+ }
60
+
61
+ export function DialogDescription({ className, ...props }: React.ComponentProps<typeof DialogPrimitive.Description>) {
62
+ return (
63
+ <DialogPrimitive.Description
64
+ className={cn("text-[var(--color-text-secondary)] text-[length:var(--type-body-font-size)]", className)}
65
+ {...props}
66
+ />
67
+ );
68
+ }
@@ -0,0 +1,115 @@
1
+ /**
2
+ * Drawer. A panel that slides up from the bottom, for phones.
3
+ *
4
+ * From shadcn/ui (new-york-v4, MIT), carried by the schemes whose inventory names it.
5
+ * Reads the shadcn variables, which theme.css maps onto the scheme's semantic tokens, and the
6
+ * radius, shadow and text scale that shadcn.css maps onto the scheme's tokens. Dark mode comes
7
+ * from the tokens, so no dark: classes.
8
+ */
9
+ "use client";
10
+
11
+ import * as React from "react";
12
+ import { cn } from "../lib/utils";
13
+ import { Drawer as DrawerPrimitive } from "vaul";
14
+
15
+ function Drawer({ ...props }: React.ComponentProps<typeof DrawerPrimitive.Root>) {
16
+ return <DrawerPrimitive.Root data-slot="drawer" {...props} />;
17
+ }
18
+
19
+ function DrawerTrigger({ ...props }: React.ComponentProps<typeof DrawerPrimitive.Trigger>) {
20
+ return <DrawerPrimitive.Trigger data-slot="drawer-trigger" {...props} />;
21
+ }
22
+
23
+ function DrawerPortal({ ...props }: React.ComponentProps<typeof DrawerPrimitive.Portal>) {
24
+ return <DrawerPrimitive.Portal data-slot="drawer-portal" {...props} />;
25
+ }
26
+
27
+ function DrawerClose({ ...props }: React.ComponentProps<typeof DrawerPrimitive.Close>) {
28
+ return <DrawerPrimitive.Close data-slot="drawer-close" {...props} />;
29
+ }
30
+
31
+ function DrawerOverlay({ className, ...props }: React.ComponentProps<typeof DrawerPrimitive.Overlay>) {
32
+ return (
33
+ <DrawerPrimitive.Overlay
34
+ data-slot="drawer-overlay"
35
+ className={cn(
36
+ "fixed inset-0 z-50 bg-[var(--color-surface-scrim)] data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:animate-in data-[state=open]:fade-in-0",
37
+ className,
38
+ )}
39
+ {...props}
40
+ />
41
+ );
42
+ }
43
+
44
+ function DrawerContent({ className, children, ...props }: React.ComponentProps<typeof DrawerPrimitive.Content>) {
45
+ return (
46
+ <DrawerPortal data-slot="drawer-portal">
47
+ <DrawerOverlay />
48
+ <DrawerPrimitive.Content
49
+ data-slot="drawer-content"
50
+ className={cn(
51
+ "group/drawer-content fixed z-50 flex h-auto flex-col bg-background",
52
+ "data-[vaul-drawer-direction=top]:inset-x-0 data-[vaul-drawer-direction=top]:top-0 data-[vaul-drawer-direction=top]:mb-24 data-[vaul-drawer-direction=top]:max-h-[80vh] data-[vaul-drawer-direction=top]:rounded-b-lg data-[vaul-drawer-direction=top]:border-b",
53
+ "data-[vaul-drawer-direction=bottom]:inset-x-0 data-[vaul-drawer-direction=bottom]:bottom-0 data-[vaul-drawer-direction=bottom]:mt-24 data-[vaul-drawer-direction=bottom]:max-h-[80vh] data-[vaul-drawer-direction=bottom]:rounded-t-lg data-[vaul-drawer-direction=bottom]:border-t",
54
+ "data-[vaul-drawer-direction=right]:inset-y-0 data-[vaul-drawer-direction=right]:right-0 data-[vaul-drawer-direction=right]:w-3/4 data-[vaul-drawer-direction=right]:border-l data-[vaul-drawer-direction=right]:sm:max-w-sm",
55
+ "data-[vaul-drawer-direction=left]:inset-y-0 data-[vaul-drawer-direction=left]:left-0 data-[vaul-drawer-direction=left]:w-3/4 data-[vaul-drawer-direction=left]:border-r data-[vaul-drawer-direction=left]:sm:max-w-sm",
56
+ className,
57
+ )}
58
+ {...props}
59
+ >
60
+ <div className="mx-auto mt-4 hidden h-2 w-[100px] shrink-0 rounded-full bg-muted group-data-[vaul-drawer-direction=bottom]/drawer-content:block" />
61
+ {children}
62
+ </DrawerPrimitive.Content>
63
+ </DrawerPortal>
64
+ );
65
+ }
66
+
67
+ function DrawerHeader({ className, ...props }: React.ComponentProps<"div">) {
68
+ return (
69
+ <div
70
+ data-slot="drawer-header"
71
+ className={cn(
72
+ "flex flex-col gap-0.5 p-4 group-data-[vaul-drawer-direction=bottom]/drawer-content:text-center group-data-[vaul-drawer-direction=top]/drawer-content:text-center md:gap-1.5 md:text-left",
73
+ className,
74
+ )}
75
+ {...props}
76
+ />
77
+ );
78
+ }
79
+
80
+ function DrawerFooter({ className, ...props }: React.ComponentProps<"div">) {
81
+ return <div data-slot="drawer-footer" className={cn("mt-auto flex flex-col gap-2 p-4", className)} {...props} />;
82
+ }
83
+
84
+ function DrawerTitle({ className, ...props }: React.ComponentProps<typeof DrawerPrimitive.Title>) {
85
+ return (
86
+ <DrawerPrimitive.Title
87
+ data-slot="drawer-title"
88
+ className={cn("font-semibold text-foreground", className)}
89
+ {...props}
90
+ />
91
+ );
92
+ }
93
+
94
+ function DrawerDescription({ className, ...props }: React.ComponentProps<typeof DrawerPrimitive.Description>) {
95
+ return (
96
+ <DrawerPrimitive.Description
97
+ data-slot="drawer-description"
98
+ className={cn("text-sm text-muted-foreground", className)}
99
+ {...props}
100
+ />
101
+ );
102
+ }
103
+
104
+ export {
105
+ Drawer,
106
+ DrawerPortal,
107
+ DrawerOverlay,
108
+ DrawerTrigger,
109
+ DrawerClose,
110
+ DrawerContent,
111
+ DrawerHeader,
112
+ DrawerFooter,
113
+ DrawerTitle,
114
+ DrawerDescription,
115
+ };
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Dropdown menu. A list of actions behind one button, such as the row actions behind a
3
+ * "more" icon button.
4
+ *
5
+ * Reads only component keys: --overlay-*.
6
+ */
7
+ import { DropdownMenu as MenuPrimitive } from "radix-ui";
8
+ import * as React from "react";
9
+ import { cn } from "../lib/utils";
10
+
11
+ export const DropdownMenu = MenuPrimitive.Root;
12
+ export const DropdownMenuTrigger = MenuPrimitive.Trigger;
13
+ export const DropdownMenuGroup = MenuPrimitive.Group;
14
+
15
+ export function DropdownMenuContent({
16
+ className,
17
+ sideOffset = 4,
18
+ ...props
19
+ }: React.ComponentProps<typeof MenuPrimitive.Content>) {
20
+ return (
21
+ <MenuPrimitive.Portal>
22
+ <MenuPrimitive.Content
23
+ data-slot="dropdown-menu-content"
24
+ sideOffset={sideOffset}
25
+ className={cn(
26
+ "z-50 min-w-[10rem] p-1 rounded-[var(--overlay-radius)] bg-[var(--overlay-background)] text-[var(--color-text-primary)]",
27
+ "[box-shadow:var(--overlay-shadow)] border-[length:var(--border-width-default)] border-[var(--color-border-default)]",
28
+ className,
29
+ )}
30
+ {...props}
31
+ />
32
+ </MenuPrimitive.Portal>
33
+ );
34
+ }
35
+
36
+ export function DropdownMenuItem({
37
+ className,
38
+ tone = "default",
39
+ ...props
40
+ }: React.ComponentProps<typeof MenuPrimitive.Item> & { tone?: "default" | "danger" }) {
41
+ return (
42
+ <MenuPrimitive.Item
43
+ data-slot="dropdown-menu-item"
44
+ className={cn(
45
+ "flex cursor-default select-none items-center gap-2 px-[var(--space-inset-cell-x)] min-h-[var(--overlay-item-height)] rounded-[var(--radius-badge)]",
46
+ "text-[length:var(--type-body-font-size)] outline-none focus:bg-[var(--color-surface-sunken)] data-[disabled]:opacity-[var(--opacity-disabled)]",
47
+ "[&_svg]:size-[var(--size-icon)]",
48
+ tone === "danger" && "text-[var(--color-status-danger)]",
49
+ className,
50
+ )}
51
+ {...props}
52
+ />
53
+ );
54
+ }
55
+
56
+ export function DropdownMenuSeparator({ className, ...props }: React.ComponentProps<typeof MenuPrimitive.Separator>) {
57
+ return <MenuPrimitive.Separator className={cn("my-1 h-px bg-[var(--color-border-default)]", className)} {...props} />;
58
+ }
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Empty. What to show when there is nothing yet.
3
+ *
4
+ * From shadcn/ui (new-york-v4, MIT), carried by the schemes whose inventory names it.
5
+ * Reads the shadcn variables, which theme.css maps onto the scheme's semantic tokens, and the
6
+ * radius, shadow and text scale that shadcn.css maps onto the scheme's tokens. Dark mode comes
7
+ * from the tokens, so no dark: classes.
8
+ */
9
+ import { cva, type VariantProps } from "class-variance-authority";
10
+ import { cn } from "../lib/utils";
11
+
12
+ function Empty({ className, ...props }: React.ComponentProps<"div">) {
13
+ return (
14
+ <div
15
+ data-slot="empty"
16
+ className={cn(
17
+ "flex min-w-0 flex-1 flex-col items-center justify-center gap-6 rounded-lg border-dashed p-6 text-center text-balance md:p-12",
18
+ className,
19
+ )}
20
+ {...props}
21
+ />
22
+ );
23
+ }
24
+
25
+ function EmptyHeader({ className, ...props }: React.ComponentProps<"div">) {
26
+ return (
27
+ <div
28
+ data-slot="empty-header"
29
+ className={cn("flex max-w-sm flex-col items-center gap-2 text-center", className)}
30
+ {...props}
31
+ />
32
+ );
33
+ }
34
+
35
+ const emptyMediaVariants = cva(
36
+ "mb-2 flex shrink-0 items-center justify-center [&_svg]:pointer-events-none [&_svg]:shrink-0",
37
+ {
38
+ variants: {
39
+ variant: {
40
+ default: "bg-transparent",
41
+ icon: "flex size-10 shrink-0 items-center justify-center rounded-lg bg-muted text-foreground [&_svg:not([class*='size-'])]:size-6",
42
+ },
43
+ },
44
+ defaultVariants: {
45
+ variant: "default",
46
+ },
47
+ },
48
+ );
49
+
50
+ function EmptyMedia({
51
+ className,
52
+ variant = "default",
53
+ ...props
54
+ }: React.ComponentProps<"div"> & VariantProps<typeof emptyMediaVariants>) {
55
+ return (
56
+ <div
57
+ data-slot="empty-icon"
58
+ data-variant={variant}
59
+ className={cn(emptyMediaVariants({ variant, className }))}
60
+ {...props}
61
+ />
62
+ );
63
+ }
64
+
65
+ function EmptyTitle({ className, ...props }: React.ComponentProps<"div">) {
66
+ return <div data-slot="empty-title" className={cn("text-lg font-medium tracking-tight", className)} {...props} />;
67
+ }
68
+
69
+ function EmptyDescription({ className, ...props }: React.ComponentProps<"p">) {
70
+ return (
71
+ <div
72
+ data-slot="empty-description"
73
+ className={cn(
74
+ "text-sm/relaxed text-muted-foreground [&>a]:underline [&>a]:underline-offset-4 [&>a:hover]:text-primary",
75
+ className,
76
+ )}
77
+ {...props}
78
+ />
79
+ );
80
+ }
81
+
82
+ function EmptyContent({ className, ...props }: React.ComponentProps<"div">) {
83
+ return (
84
+ <div
85
+ data-slot="empty-content"
86
+ className={cn("flex w-full max-w-sm min-w-0 flex-col items-center gap-4 text-sm text-balance", className)}
87
+ {...props}
88
+ />
89
+ );
90
+ }
91
+
92
+ export { Empty, EmptyHeader, EmptyTitle, EmptyDescription, EmptyContent, EmptyMedia };
@@ -0,0 +1,234 @@
1
+ /**
2
+ * Field. A label, control, hint and error laid out together.
3
+ *
4
+ * From shadcn/ui (new-york-v4, MIT), carried by the schemes whose inventory names it.
5
+ * Reads the shadcn variables, which theme.css maps onto the scheme's semantic tokens, and the
6
+ * radius, shadow and text scale that shadcn.css maps onto the scheme's tokens. Dark mode comes
7
+ * from the tokens, so no dark: classes.
8
+ */
9
+ "use client";
10
+
11
+ import { useMemo } from "react";
12
+ import { cva, type VariantProps } from "class-variance-authority";
13
+ import { cn } from "../lib/utils";
14
+
15
+ import { Label } from "./label";
16
+ import { Separator } from "./separator";
17
+
18
+ function FieldSet({ className, ...props }: React.ComponentProps<"fieldset">) {
19
+ return (
20
+ <fieldset
21
+ data-slot="field-set"
22
+ className={cn(
23
+ "flex flex-col gap-6",
24
+ "has-[>[data-slot=checkbox-group]]:gap-3 has-[>[data-slot=radio-group]]:gap-3",
25
+ className,
26
+ )}
27
+ {...props}
28
+ />
29
+ );
30
+ }
31
+
32
+ function FieldLegend({
33
+ className,
34
+ variant = "legend",
35
+ ...props
36
+ }: React.ComponentProps<"legend"> & { variant?: "legend" | "label" }) {
37
+ return (
38
+ <legend
39
+ data-slot="field-legend"
40
+ data-variant={variant}
41
+ className={cn("mb-3 font-medium", "data-[variant=legend]:text-base", "data-[variant=label]:text-sm", className)}
42
+ {...props}
43
+ />
44
+ );
45
+ }
46
+
47
+ function FieldGroup({ className, ...props }: React.ComponentProps<"div">) {
48
+ return (
49
+ <div
50
+ data-slot="field-group"
51
+ className={cn(
52
+ "group/field-group @container/field-group flex w-full flex-col gap-7 data-[slot=checkbox-group]:gap-3 [&>[data-slot=field-group]]:gap-4",
53
+ className,
54
+ )}
55
+ {...props}
56
+ />
57
+ );
58
+ }
59
+
60
+ const fieldVariants = cva("group/field flex w-full gap-3 data-[invalid=true]:text-destructive", {
61
+ variants: {
62
+ orientation: {
63
+ vertical: ["flex-col [&>*]:w-full [&>.sr-only]:w-auto"],
64
+ horizontal: [
65
+ "flex-row items-center",
66
+ "[&>[data-slot=field-label]]:flex-auto",
67
+ "has-[>[data-slot=field-content]]:items-start has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px",
68
+ ],
69
+ responsive: [
70
+ "flex-col @md/field-group:flex-row @md/field-group:items-center [&>*]:w-full @md/field-group:[&>*]:w-auto [&>.sr-only]:w-auto",
71
+ "@md/field-group:[&>[data-slot=field-label]]:flex-auto",
72
+ "@md/field-group:has-[>[data-slot=field-content]]:items-start @md/field-group:has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px",
73
+ ],
74
+ },
75
+ },
76
+ defaultVariants: {
77
+ orientation: "vertical",
78
+ },
79
+ });
80
+
81
+ function Field({
82
+ className,
83
+ orientation = "vertical",
84
+ ...props
85
+ }: React.ComponentProps<"div"> & VariantProps<typeof fieldVariants>) {
86
+ return (
87
+ <div
88
+ role="group"
89
+ data-slot="field"
90
+ data-orientation={orientation}
91
+ className={cn(fieldVariants({ orientation }), className)}
92
+ {...props}
93
+ />
94
+ );
95
+ }
96
+
97
+ function FieldContent({ className, ...props }: React.ComponentProps<"div">) {
98
+ return (
99
+ <div
100
+ data-slot="field-content"
101
+ className={cn("group/field-content flex flex-1 flex-col gap-1.5 leading-snug", className)}
102
+ {...props}
103
+ />
104
+ );
105
+ }
106
+
107
+ function FieldLabel({ className, ...props }: React.ComponentProps<typeof Label>) {
108
+ return (
109
+ <Label
110
+ data-slot="field-label"
111
+ className={cn(
112
+ "group/field-label peer/field-label flex w-fit gap-2 leading-snug group-data-[disabled=true]/field:opacity-50",
113
+ "has-[>[data-slot=field]]:w-full has-[>[data-slot=field]]:flex-col has-[>[data-slot=field]]:rounded-md has-[>[data-slot=field]]:border [&>*]:data-[slot=field]:p-4",
114
+ "has-data-[state=checked]:border-primary has-data-[state=checked]:bg-primary/5",
115
+ className,
116
+ )}
117
+ {...props}
118
+ />
119
+ );
120
+ }
121
+
122
+ function FieldTitle({ className, ...props }: React.ComponentProps<"div">) {
123
+ return (
124
+ <div
125
+ data-slot="field-label"
126
+ className={cn(
127
+ "flex w-fit items-center gap-2 text-sm leading-snug font-medium group-data-[disabled=true]/field:opacity-50",
128
+ className,
129
+ )}
130
+ {...props}
131
+ />
132
+ );
133
+ }
134
+
135
+ function FieldDescription({ className, ...props }: React.ComponentProps<"p">) {
136
+ return (
137
+ <p
138
+ data-slot="field-description"
139
+ className={cn(
140
+ "text-sm leading-normal font-normal text-muted-foreground group-has-[[data-orientation=horizontal]]/field:text-balance",
141
+ "last:mt-0 nth-last-2:-mt-1 [[data-variant=legend]+&]:-mt-1.5",
142
+ "[&>a]:underline [&>a]:underline-offset-4 [&>a:hover]:text-primary",
143
+ className,
144
+ )}
145
+ {...props}
146
+ />
147
+ );
148
+ }
149
+
150
+ function FieldSeparator({
151
+ children,
152
+ className,
153
+ ...props
154
+ }: React.ComponentProps<"div"> & {
155
+ children?: React.ReactNode;
156
+ }) {
157
+ return (
158
+ <div
159
+ data-slot="field-separator"
160
+ data-content={!!children}
161
+ className={cn("relative -my-2 h-5 text-sm group-data-[variant=outline]/field-group:-mb-2", className)}
162
+ {...props}
163
+ >
164
+ <Separator className="absolute inset-0 top-1/2" />
165
+ {children && (
166
+ <span
167
+ className="relative mx-auto block w-fit bg-background px-2 text-muted-foreground"
168
+ data-slot="field-separator-content"
169
+ >
170
+ {children}
171
+ </span>
172
+ )}
173
+ </div>
174
+ );
175
+ }
176
+
177
+ function FieldError({
178
+ className,
179
+ children,
180
+ errors,
181
+ ...props
182
+ }: React.ComponentProps<"div"> & {
183
+ errors?: Array<{ message?: string } | undefined>;
184
+ }) {
185
+ const content = useMemo(() => {
186
+ if (children) {
187
+ return children;
188
+ }
189
+
190
+ if (!errors?.length) {
191
+ return null;
192
+ }
193
+
194
+ const uniqueErrors = [...new Map(errors.map((error) => [error?.message, error])).values()];
195
+
196
+ if (uniqueErrors?.length == 1) {
197
+ return uniqueErrors[0]?.message;
198
+ }
199
+
200
+ return (
201
+ <ul className="ml-4 flex list-disc flex-col gap-1">
202
+ {uniqueErrors.map((error, index) => error?.message && <li key={index}>{error.message}</li>)}
203
+ </ul>
204
+ );
205
+ }, [children, errors]);
206
+
207
+ if (!content) {
208
+ return null;
209
+ }
210
+
211
+ return (
212
+ <div
213
+ role="alert"
214
+ data-slot="field-error"
215
+ className={cn("text-sm font-normal text-destructive", className)}
216
+ {...props}
217
+ >
218
+ {content}
219
+ </div>
220
+ );
221
+ }
222
+
223
+ export {
224
+ Field,
225
+ FieldLabel,
226
+ FieldDescription,
227
+ FieldError,
228
+ FieldGroup,
229
+ FieldLegend,
230
+ FieldSeparator,
231
+ FieldSet,
232
+ FieldContent,
233
+ FieldTitle,
234
+ };