@zedtech/zedkit-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 (169) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +48 -2
  3. package/dist/alert.d.ts +9 -0
  4. package/dist/alert.d.ts.map +1 -0
  5. package/dist/alert.js +26 -0
  6. package/dist/alert.js.map +1 -0
  7. package/dist/app-shell.d.ts +9 -0
  8. package/dist/app-shell.d.ts.map +1 -0
  9. package/dist/app-shell.js +6 -0
  10. package/dist/app-shell.js.map +1 -0
  11. package/dist/badge.d.ts +10 -0
  12. package/dist/badge.d.ts.map +1 -0
  13. package/dist/badge.js +19 -0
  14. package/dist/badge.js.map +1 -0
  15. package/dist/button.d.ts +12 -0
  16. package/dist/button.d.ts.map +1 -0
  17. package/dist/button.js +31 -0
  18. package/dist/button.js.map +1 -0
  19. package/dist/card.d.ts +9 -0
  20. package/dist/card.d.ts.map +1 -0
  21. package/dist/card.js +17 -0
  22. package/dist/card.js.map +1 -0
  23. package/dist/checkbox.d.ts +5 -0
  24. package/dist/checkbox.d.ts.map +1 -0
  25. package/dist/checkbox.js +10 -0
  26. package/dist/checkbox.js.map +1 -0
  27. package/dist/confirm-dialog.d.ts +17 -0
  28. package/dist/confirm-dialog.d.ts.map +1 -0
  29. package/dist/confirm-dialog.js +18 -0
  30. package/dist/confirm-dialog.js.map +1 -0
  31. package/dist/data-table.d.ts +33 -0
  32. package/dist/data-table.d.ts.map +1 -0
  33. package/dist/data-table.js +40 -0
  34. package/dist/data-table.js.map +1 -0
  35. package/dist/detail-panel.d.ts +11 -0
  36. package/dist/detail-panel.d.ts.map +1 -0
  37. package/dist/detail-panel.js +6 -0
  38. package/dist/detail-panel.js.map +1 -0
  39. package/dist/dialog.d.ts +14 -0
  40. package/dist/dialog.d.ts.map +1 -0
  41. package/dist/dialog.js +26 -0
  42. package/dist/dialog.js.map +1 -0
  43. package/dist/field.d.ts +26 -0
  44. package/dist/field.d.ts.map +1 -0
  45. package/dist/field.js +74 -0
  46. package/dist/field.js.map +1 -0
  47. package/dist/form-submit.d.ts +16 -0
  48. package/dist/form-submit.d.ts.map +1 -0
  49. package/dist/form-submit.js +15 -0
  50. package/dist/form-submit.js.map +1 -0
  51. package/dist/form.d.ts +26 -0
  52. package/dist/form.d.ts.map +1 -0
  53. package/dist/form.js +65 -0
  54. package/dist/form.js.map +1 -0
  55. package/dist/index.d.ts +33 -0
  56. package/dist/index.d.ts.map +1 -0
  57. package/dist/index.js +33 -0
  58. package/dist/index.js.map +1 -0
  59. package/dist/input.d.ts +4 -0
  60. package/dist/input.d.ts.map +1 -0
  61. package/dist/input.js +9 -0
  62. package/dist/input.js.map +1 -0
  63. package/dist/label.d.ts +6 -0
  64. package/dist/label.d.ts.map +1 -0
  65. package/dist/label.js +11 -0
  66. package/dist/label.js.map +1 -0
  67. package/dist/layout.d.ts +22 -0
  68. package/dist/layout.d.ts.map +1 -0
  69. package/dist/layout.js +36 -0
  70. package/dist/layout.js.map +1 -0
  71. package/dist/live-pulse.d.ts +7 -0
  72. package/dist/live-pulse.d.ts.map +1 -0
  73. package/dist/live-pulse.js +6 -0
  74. package/dist/live-pulse.js.map +1 -0
  75. package/dist/option-cards.d.ts +15 -0
  76. package/dist/option-cards.d.ts.map +1 -0
  77. package/dist/option-cards.js +9 -0
  78. package/dist/option-cards.js.map +1 -0
  79. package/dist/page-header.d.ts +9 -0
  80. package/dist/page-header.d.ts.map +1 -0
  81. package/dist/page-header.js +6 -0
  82. package/dist/page-header.js.map +1 -0
  83. package/dist/secret-field.d.ts +22 -0
  84. package/dist/secret-field.d.ts.map +1 -0
  85. package/dist/secret-field.js +26 -0
  86. package/dist/secret-field.js.map +1 -0
  87. package/dist/select.d.ts +14 -0
  88. package/dist/select.d.ts.map +1 -0
  89. package/dist/select.js +30 -0
  90. package/dist/select.js.map +1 -0
  91. package/dist/separator.d.ts +5 -0
  92. package/dist/separator.d.ts.map +1 -0
  93. package/dist/separator.js +9 -0
  94. package/dist/separator.js.map +1 -0
  95. package/dist/skeleton.d.ts +3 -0
  96. package/dist/skeleton.d.ts.map +1 -0
  97. package/dist/skeleton.js +7 -0
  98. package/dist/skeleton.js.map +1 -0
  99. package/dist/sonner.d.ts +8 -0
  100. package/dist/sonner.d.ts.map +1 -0
  101. package/dist/sonner.js +19 -0
  102. package/dist/sonner.js.map +1 -0
  103. package/dist/switch.d.ts +5 -0
  104. package/dist/switch.d.ts.map +1 -0
  105. package/dist/switch.js +9 -0
  106. package/dist/switch.js.map +1 -0
  107. package/dist/table.d.ts +11 -0
  108. package/dist/table.d.ts.map +1 -0
  109. package/dist/table.js +28 -0
  110. package/dist/table.js.map +1 -0
  111. package/dist/tabs.d.ts +8 -0
  112. package/dist/tabs.d.ts.map +1 -0
  113. package/dist/tabs.js +14 -0
  114. package/dist/tabs.js.map +1 -0
  115. package/dist/textarea.d.ts +4 -0
  116. package/dist/textarea.d.ts.map +1 -0
  117. package/dist/textarea.js +9 -0
  118. package/dist/textarea.js.map +1 -0
  119. package/dist/typography.d.ts +22 -0
  120. package/dist/typography.d.ts.map +1 -0
  121. package/dist/typography.js +39 -0
  122. package/dist/typography.js.map +1 -0
  123. package/dist/use-validated-form.d.ts +10 -0
  124. package/dist/use-validated-form.d.ts.map +1 -0
  125. package/dist/use-validated-form.js +20 -0
  126. package/dist/use-validated-form.js.map +1 -0
  127. package/dist/utils.d.ts +8 -0
  128. package/dist/utils.d.ts.map +1 -0
  129. package/dist/utils.js +11 -0
  130. package/dist/utils.js.map +1 -0
  131. package/dist/wizard-steps.d.ts +8 -0
  132. package/dist/wizard-steps.d.ts.map +1 -0
  133. package/dist/wizard-steps.js +7 -0
  134. package/dist/wizard-steps.js.map +1 -0
  135. package/package.json +83 -3
  136. package/src/alert.tsx +47 -0
  137. package/src/app-shell.tsx +24 -0
  138. package/src/badge.tsx +30 -0
  139. package/src/button.tsx +48 -0
  140. package/src/card.tsx +47 -0
  141. package/src/checkbox.tsx +28 -0
  142. package/src/confirm-dialog.tsx +65 -0
  143. package/src/data-table.tsx +176 -0
  144. package/src/detail-panel.tsx +26 -0
  145. package/src/dialog.tsx +102 -0
  146. package/src/field.tsx +230 -0
  147. package/src/form-submit.tsx +29 -0
  148. package/src/form.tsx +157 -0
  149. package/src/index.ts +95 -0
  150. package/src/input.tsx +21 -0
  151. package/src/label.tsx +20 -0
  152. package/src/layout.tsx +69 -0
  153. package/src/live-pulse.tsx +27 -0
  154. package/src/option-cards.tsx +47 -0
  155. package/src/page-header.tsx +22 -0
  156. package/src/secret-field.tsx +50 -0
  157. package/src/select.tsx +146 -0
  158. package/src/separator.tsx +30 -0
  159. package/src/skeleton.tsx +7 -0
  160. package/src/sonner.tsx +25 -0
  161. package/src/styles.css +117 -0
  162. package/src/switch.tsx +29 -0
  163. package/src/table.tsx +68 -0
  164. package/src/tabs.tsx +52 -0
  165. package/src/textarea.tsx +21 -0
  166. package/src/typography.tsx +59 -0
  167. package/src/use-validated-form.ts +30 -0
  168. package/src/utils.ts +11 -0
  169. package/src/wizard-steps.tsx +40 -0
@@ -0,0 +1,22 @@
1
+ import type * as React from "react";
2
+
3
+ import { cn } from "./utils.js";
4
+
5
+ export interface PageHeaderProps {
6
+ title: React.ReactNode;
7
+ description?: React.ReactNode;
8
+ actions?: React.ReactNode;
9
+ className?: string;
10
+ }
11
+
12
+ export function PageHeader({ title, description, actions, className }: PageHeaderProps) {
13
+ return (
14
+ <div className={cn("mb-6 flex flex-wrap items-start justify-between gap-4", className)}>
15
+ <div className="space-y-1">
16
+ <h1 className="text-2xl font-semibold tracking-tight">{title}</h1>
17
+ {description ? <p className="text-sm text-muted-foreground">{description}</p> : null}
18
+ </div>
19
+ {actions ? <div className="flex items-center gap-2">{actions}</div> : null}
20
+ </div>
21
+ );
22
+ }
@@ -0,0 +1,50 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import { Check, Copy } from "lucide-react";
5
+ import { Button } from "./button.js";
6
+ import { Input } from "./input.js";
7
+
8
+ export interface SecretFieldProps {
9
+ /** Element id, so a Label can point at it. */
10
+ id?: string;
11
+ /** The secret text (shown once by the caller, never logged). */
12
+ value: string;
13
+ /** Accessible label for the copy button. */
14
+ copyLabel?: string;
15
+ /**
16
+ * Accessible name for the value input. Required unless an external
17
+ * Label points at `id` — every input must have a label.
18
+ */
19
+ "aria-label"?: string;
20
+ }
21
+
22
+ /**
23
+ * Shown-once secret with a copy button: read-only value, clipboard
24
+ * on click, check feedback for 2s. Falls back to no-op when the
25
+ * clipboard API is unavailable (insecure contexts).
26
+ */
27
+ function SecretField({ id, value, copyLabel = "Copy to clipboard", "aria-label": ariaLabel }: SecretFieldProps) {
28
+ const [copied, setCopied] = React.useState(false);
29
+ const copy = (): void => {
30
+ const clipboard = navigator.clipboard;
31
+ if (!clipboard) return;
32
+ clipboard.writeText(value).then(
33
+ () => {
34
+ setCopied(true);
35
+ setTimeout(() => setCopied(false), 2000);
36
+ },
37
+ () => {},
38
+ );
39
+ };
40
+ return (
41
+ <div className="flex w-full items-center gap-2">
42
+ <Input id={id} readOnly value={value} className="font-mono" aria-label={ariaLabel} />
43
+ <Button type="button" variant="outline" size="icon" aria-label={copyLabel} onClick={copy}>
44
+ {copied ? <Check className="h-4 w-4" /> : <Copy className="h-4 w-4" />}
45
+ </Button>
46
+ </div>
47
+ );
48
+ }
49
+
50
+ export { SecretField };
package/src/select.tsx ADDED
@@ -0,0 +1,146 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import * as SelectPrimitive from "@radix-ui/react-select";
5
+ import { Check, ChevronDown, ChevronUp } from "lucide-react";
6
+
7
+ import { cn } from "./utils.js";
8
+
9
+ const Select = SelectPrimitive.Root;
10
+
11
+ const SelectGroup = SelectPrimitive.Group;
12
+
13
+ const SelectValue = SelectPrimitive.Value;
14
+
15
+ const SelectTrigger = React.forwardRef<
16
+ React.ElementRef<typeof SelectPrimitive.Trigger>,
17
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>
18
+ >(({ className, children, ...props }, ref) => (
19
+ <SelectPrimitive.Trigger
20
+ ref={ref}
21
+ className={cn(
22
+ "flex h-9 w-full items-center justify-between rounded-md border border-input bg-transparent px-3 py-2 text-sm whitespace-nowrap shadow-sm ring-offset-background focus:ring-1 focus:ring-ring focus:outline-none disabled:cursor-not-allowed disabled:opacity-50 data-[placeholder]:text-muted-foreground [&>span]:line-clamp-1",
23
+ className,
24
+ )}
25
+ {...props}
26
+ >
27
+ {children}
28
+ <SelectPrimitive.Icon asChild>
29
+ <ChevronDown className="h-4 w-4 opacity-50" />
30
+ </SelectPrimitive.Icon>
31
+ </SelectPrimitive.Trigger>
32
+ ));
33
+ SelectTrigger.displayName = SelectPrimitive.Trigger.displayName;
34
+
35
+ const SelectScrollUpButton = React.forwardRef<
36
+ React.ElementRef<typeof SelectPrimitive.ScrollUpButton>,
37
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollUpButton>
38
+ >(({ className, ...props }, ref) => (
39
+ <SelectPrimitive.ScrollUpButton
40
+ ref={ref}
41
+ className={cn("flex cursor-default items-center justify-center py-1", className)}
42
+ {...props}
43
+ >
44
+ <ChevronUp className="h-4 w-4" />
45
+ </SelectPrimitive.ScrollUpButton>
46
+ ));
47
+ SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName;
48
+
49
+ const SelectScrollDownButton = React.forwardRef<
50
+ React.ElementRef<typeof SelectPrimitive.ScrollDownButton>,
51
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollDownButton>
52
+ >(({ className, ...props }, ref) => (
53
+ <SelectPrimitive.ScrollDownButton
54
+ ref={ref}
55
+ className={cn("flex cursor-default items-center justify-center py-1", className)}
56
+ {...props}
57
+ >
58
+ <ChevronDown className="h-4 w-4" />
59
+ </SelectPrimitive.ScrollDownButton>
60
+ ));
61
+ SelectScrollDownButton.displayName = SelectPrimitive.ScrollDownButton.displayName;
62
+
63
+ const SelectContent = React.forwardRef<
64
+ React.ElementRef<typeof SelectPrimitive.Content>,
65
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
66
+ >(({ className, children, position = "popper", ...props }, ref) => (
67
+ <SelectPrimitive.Portal>
68
+ <SelectPrimitive.Content
69
+ ref={ref}
70
+ className={cn(
71
+ // No animate/fade/zoom/slide utilities: the kit ships without
72
+ // the animation plugin (same as dialog), where they would be
73
+ // dead classes. Kept: positioning, overflow, theme, popper offsets.
74
+ "relative z-50 max-h-[--radix-select-content-available-height] min-w-[8rem] origin-[--radix-select-content-transform-origin] overflow-x-hidden overflow-y-auto rounded-md border bg-popover text-popover-foreground shadow-md",
75
+ position === "popper" &&
76
+ "data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
77
+ className,
78
+ )}
79
+ position={position}
80
+ {...props}
81
+ >
82
+ <SelectScrollUpButton />
83
+ <SelectPrimitive.Viewport
84
+ className={cn(
85
+ "p-1",
86
+ position === "popper" && "h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]",
87
+ )}
88
+ >
89
+ {children}
90
+ </SelectPrimitive.Viewport>
91
+ <SelectScrollDownButton />
92
+ </SelectPrimitive.Content>
93
+ </SelectPrimitive.Portal>
94
+ ));
95
+ SelectContent.displayName = SelectPrimitive.Content.displayName;
96
+
97
+ const SelectLabel = React.forwardRef<
98
+ React.ElementRef<typeof SelectPrimitive.Label>,
99
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>
100
+ >(({ className, ...props }, ref) => (
101
+ <SelectPrimitive.Label ref={ref} className={cn("px-2 py-1.5 text-sm font-semibold", className)} {...props} />
102
+ ));
103
+ SelectLabel.displayName = SelectPrimitive.Label.displayName;
104
+
105
+ const SelectItem = React.forwardRef<
106
+ React.ElementRef<typeof SelectPrimitive.Item>,
107
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>
108
+ >(({ className, children, ...props }, ref) => (
109
+ <SelectPrimitive.Item
110
+ ref={ref}
111
+ className={cn(
112
+ "relative flex w-full cursor-default items-center rounded-sm py-1.5 pr-8 pl-2 text-sm outline-none select-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
113
+ className,
114
+ )}
115
+ {...props}
116
+ >
117
+ <span className="absolute right-2 flex h-3.5 w-3.5 items-center justify-center">
118
+ <SelectPrimitive.ItemIndicator>
119
+ <Check className="h-4 w-4" />
120
+ </SelectPrimitive.ItemIndicator>
121
+ </span>
122
+ <SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
123
+ </SelectPrimitive.Item>
124
+ ));
125
+ SelectItem.displayName = SelectPrimitive.Item.displayName;
126
+
127
+ const SelectSeparator = React.forwardRef<
128
+ React.ElementRef<typeof SelectPrimitive.Separator>,
129
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>
130
+ >(({ className, ...props }, ref) => (
131
+ <SelectPrimitive.Separator ref={ref} className={cn("-mx-1 my-1 h-px bg-muted", className)} {...props} />
132
+ ));
133
+ SelectSeparator.displayName = SelectPrimitive.Separator.displayName;
134
+
135
+ export {
136
+ Select,
137
+ SelectGroup,
138
+ SelectValue,
139
+ SelectTrigger,
140
+ SelectContent,
141
+ SelectLabel,
142
+ SelectItem,
143
+ SelectSeparator,
144
+ SelectScrollUpButton,
145
+ SelectScrollDownButton,
146
+ };
@@ -0,0 +1,30 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import * as SeparatorPrimitive from "@radix-ui/react-separator";
5
+ import { cn } from "./utils.js";
6
+
7
+ const Separator = React.forwardRef<
8
+ React.ElementRef<typeof SeparatorPrimitive.Root>,
9
+ React.ComponentPropsWithoutRef<typeof SeparatorPrimitive.Root>
10
+ >(
11
+ (
12
+ { className, orientation = "horizontal", decorative = true, ...props },
13
+ ref,
14
+ ) => (
15
+ <SeparatorPrimitive.Root
16
+ ref={ref}
17
+ decorative={decorative}
18
+ orientation={orientation}
19
+ className={cn(
20
+ "shrink-0 bg-border",
21
+ orientation === "horizontal" ? "h-[1px] w-full" : "h-full w-[1px]",
22
+ className,
23
+ )}
24
+ {...props}
25
+ />
26
+ ),
27
+ );
28
+ Separator.displayName = SeparatorPrimitive.Root.displayName;
29
+
30
+ export { Separator };
@@ -0,0 +1,7 @@
1
+ import { cn } from "./utils.js";
2
+
3
+ function Skeleton({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
4
+ return <div className={cn("animate-pulse rounded-md bg-muted", className)} {...props} />;
5
+ }
6
+
7
+ export { Skeleton };
package/src/sonner.tsx ADDED
@@ -0,0 +1,25 @@
1
+ "use client";
2
+
3
+ import { Toaster as Sonner, type ToasterProps } from "sonner";
4
+
5
+ /**
6
+ * Themed toast viewport: render once near the app root, then call
7
+ * sonner's `toast()` anywhere. Styling follows the theme tokens.
8
+ */
9
+ function Toaster(props: ToasterProps) {
10
+ return (
11
+ <Sonner
12
+ toastOptions={{
13
+ classNames: {
14
+ toast: "bg-background text-foreground border-border",
15
+ description: "text-muted-foreground",
16
+ actionButton: "bg-primary text-primary-foreground",
17
+ cancelButton: "bg-muted text-muted-foreground",
18
+ },
19
+ }}
20
+ {...props}
21
+ />
22
+ );
23
+ }
24
+
25
+ export { Toaster };
package/src/styles.css ADDED
@@ -0,0 +1,117 @@
1
+ @import "tailwindcss";
2
+
3
+ /*
4
+ * zedkit admin theme: neutral, calm, readable. Dark/light via the
5
+ * `.dark` class on <html> (next-themes `attribute="class"`).
6
+ * Consumers import this file once (see README) and add one `@source`
7
+ * line so Tailwind scans the components. System font stack on
8
+ * purpose: no font dependency, no layout shift.
9
+ */
10
+ :root {
11
+ --background: 0 0% 100%;
12
+ --foreground: 240 10% 3.9%;
13
+ --card: 0 0% 100%;
14
+ --card-foreground: 240 10% 3.9%;
15
+ --popover: 0 0% 100%;
16
+ --popover-foreground: 240 10% 3.9%;
17
+ --primary: 240 5.9% 10%;
18
+ --primary-foreground: 0 0% 98%;
19
+ --secondary: 240 4.8% 95.9%;
20
+ --secondary-foreground: 240 5.9% 10%;
21
+ --muted: 240 4.8% 95.9%;
22
+ --muted-foreground: 240 3.8% 46.1%;
23
+ --accent: 240 4.8% 95.9%;
24
+ --accent-foreground: 240 5.9% 10%;
25
+ --destructive: 0 84.2% 60.2%;
26
+ --destructive-foreground: 0 0% 98%;
27
+ --success: 142.1 76.2% 36.3%;
28
+ --success-foreground: 0 0% 98%;
29
+ --warning: 32 95% 44%;
30
+ --warning-foreground: 0 0% 98%;
31
+ --overlay: 0 0% 0%;
32
+ --border: 240 5.9% 90%;
33
+ --input: 240 5.9% 90%;
34
+ --ring: 240 5% 64.9%;
35
+ --radius: 0.5rem;
36
+ --sidebar: 0 0% 98%;
37
+ --sidebar-foreground: 240 5.9% 10%;
38
+ --sidebar-border: 240 5.9% 90%;
39
+ --sidebar-accent: 240 4.8% 95.9%;
40
+ }
41
+
42
+ .dark {
43
+ --background: 240 10% 3.9%;
44
+ --foreground: 0 0% 98%;
45
+ --card: 240 10% 3.9%;
46
+ --card-foreground: 0 0% 98%;
47
+ --popover: 240 10% 3.9%;
48
+ --popover-foreground: 0 0% 98%;
49
+ --primary: 0 0% 98%;
50
+ --primary-foreground: 240 5.9% 10%;
51
+ --secondary: 240 3.7% 15.9%;
52
+ --secondary-foreground: 0 0% 98%;
53
+ --muted: 240 3.7% 15.9%;
54
+ --muted-foreground: 240 5% 64.9%;
55
+ --accent: 240 3.7% 15.9%;
56
+ --accent-foreground: 0 0% 98%;
57
+ --destructive: 0 62.8% 50.6%;
58
+ --destructive-foreground: 0 0% 98%;
59
+ --success: 142.1 70.6% 45.3%;
60
+ --success-foreground: 240 10% 3.9%;
61
+ --warning: 38 92% 50%;
62
+ --warning-foreground: 240 10% 3.9%;
63
+ --overlay: 0 0% 0%;
64
+ --border: 240 3.7% 15.9%;
65
+ --input: 240 3.7% 15.9%;
66
+ --ring: 240 4.9% 83.9%;
67
+ --sidebar: 240 10% 3.9%;
68
+ --sidebar-foreground: 0 0% 98%;
69
+ --sidebar-border: 240 3.7% 15.9%;
70
+ --sidebar-accent: 240 3.7% 15.9%;
71
+ }
72
+
73
+ @theme inline {
74
+ --color-background: hsl(var(--background));
75
+ --color-foreground: hsl(var(--foreground));
76
+ --color-card: hsl(var(--card));
77
+ --color-card-foreground: hsl(var(--card-foreground));
78
+ --color-popover: hsl(var(--popover));
79
+ --color-popover-foreground: hsl(var(--popover-foreground));
80
+ --color-primary: hsl(var(--primary));
81
+ --color-primary-foreground: hsl(var(--primary-foreground));
82
+ --color-secondary: hsl(var(--secondary));
83
+ --color-secondary-foreground: hsl(var(--secondary-foreground));
84
+ --color-muted: hsl(var(--muted));
85
+ --color-muted-foreground: hsl(var(--muted-foreground));
86
+ --color-accent: hsl(var(--accent));
87
+ --color-accent-foreground: hsl(var(--accent-foreground));
88
+ --color-destructive: hsl(var(--destructive));
89
+ --color-destructive-foreground: hsl(var(--destructive-foreground));
90
+ --color-success: hsl(var(--success));
91
+ --color-success-foreground: hsl(var(--success-foreground));
92
+ --color-warning: hsl(var(--warning));
93
+ --color-warning-foreground: hsl(var(--warning-foreground));
94
+ --color-overlay: hsl(var(--overlay));
95
+ --color-border: hsl(var(--border));
96
+ --color-input: hsl(var(--input));
97
+ --color-ring: hsl(var(--ring));
98
+ --color-sidebar: hsl(var(--sidebar));
99
+ --color-sidebar-foreground: hsl(var(--sidebar-foreground));
100
+ --color-sidebar-border: hsl(var(--sidebar-border));
101
+ --color-sidebar-accent: hsl(var(--sidebar-accent));
102
+ --radius-lg: var(--radius);
103
+ --radius-md: calc(var(--radius) - 2px);
104
+ --radius-sm: calc(var(--radius) - 4px);
105
+ }
106
+
107
+ @layer base {
108
+ * {
109
+ border-color: var(--color-border);
110
+ }
111
+ body {
112
+ background-color: var(--color-background);
113
+ color: var(--color-foreground);
114
+ font-family:
115
+ ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
116
+ }
117
+ }
package/src/switch.tsx ADDED
@@ -0,0 +1,29 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import * as SwitchPrimitives from "@radix-ui/react-switch";
5
+
6
+ import { cn } from "./utils.js";
7
+
8
+ const Switch = React.forwardRef<
9
+ React.ElementRef<typeof SwitchPrimitives.Root>,
10
+ React.ComponentPropsWithoutRef<typeof SwitchPrimitives.Root>
11
+ >(({ className, ...props }, ref) => (
12
+ <SwitchPrimitives.Root
13
+ className={cn(
14
+ "peer inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent shadow-sm transition-colors focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input",
15
+ className,
16
+ )}
17
+ {...props}
18
+ ref={ref}
19
+ >
20
+ <SwitchPrimitives.Thumb
21
+ className={cn(
22
+ "pointer-events-none block h-4 w-4 rounded-full bg-background shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-4 data-[state=unchecked]:translate-x-0",
23
+ )}
24
+ />
25
+ </SwitchPrimitives.Root>
26
+ ));
27
+ Switch.displayName = SwitchPrimitives.Root.displayName;
28
+
29
+ export { Switch };
package/src/table.tsx ADDED
@@ -0,0 +1,68 @@
1
+ import * as React from "react";
2
+
3
+ import { cn } from "./utils.js";
4
+
5
+ function Table({ className, ...props }: React.ComponentProps<"table">) {
6
+ return (
7
+ <div data-slot="table-container" className="relative w-full overflow-x-auto">
8
+ <table data-slot="table" className={cn("w-full caption-bottom text-sm", className)} {...props} />
9
+ </div>
10
+ );
11
+ }
12
+
13
+ function TableHeader({ className, ...props }: React.ComponentProps<"thead">) {
14
+ return <thead data-slot="table-header" className={cn("[&_tr]:border-b", className)} {...props} />;
15
+ }
16
+
17
+ function TableBody({ className, ...props }: React.ComponentProps<"tbody">) {
18
+ return <tbody data-slot="table-body" className={cn("[&_tr:last-child]:border-0", className)} {...props} />;
19
+ }
20
+
21
+ function TableFooter({ className, ...props }: React.ComponentProps<"tfoot">) {
22
+ return (
23
+ <tfoot
24
+ data-slot="table-footer"
25
+ className={cn("border-t bg-muted/50 font-medium [&>tr]:last:border-b-0", className)}
26
+ {...props}
27
+ />
28
+ );
29
+ }
30
+
31
+ function TableRow({ className, ...props }: React.ComponentProps<"tr">) {
32
+ return (
33
+ <tr
34
+ data-slot="table-row"
35
+ className={cn("border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted", className)}
36
+ {...props}
37
+ />
38
+ );
39
+ }
40
+
41
+ function TableHead({ className, ...props }: React.ComponentProps<"th">) {
42
+ return (
43
+ <th
44
+ data-slot="table-head"
45
+ className={cn(
46
+ "h-10 px-2 text-left align-middle font-medium whitespace-nowrap text-foreground [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
47
+ className,
48
+ )}
49
+ {...props}
50
+ />
51
+ );
52
+ }
53
+
54
+ function TableCell({ className, ...props }: React.ComponentProps<"td">) {
55
+ return (
56
+ <td
57
+ data-slot="table-cell"
58
+ className={cn("p-2 align-middle whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]", className)}
59
+ {...props}
60
+ />
61
+ );
62
+ }
63
+
64
+ function TableCaption({ className, ...props }: React.ComponentProps<"caption">) {
65
+ return <caption data-slot="table-caption" className={cn("mt-4 text-sm text-muted-foreground", className)} {...props} />;
66
+ }
67
+
68
+ export { Table, TableHeader, TableBody, TableFooter, TableHead, TableRow, TableCell, TableCaption };
package/src/tabs.tsx ADDED
@@ -0,0 +1,52 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import * as TabsPrimitive from "@radix-ui/react-tabs";
5
+
6
+ import { cn } from "./utils.js";
7
+
8
+ const Tabs = TabsPrimitive.Root;
9
+
10
+ const TabsList = React.forwardRef<
11
+ React.ElementRef<typeof TabsPrimitive.List>,
12
+ React.ComponentPropsWithoutRef<typeof TabsPrimitive.List>
13
+ >(({ className, ...props }, ref) => (
14
+ <TabsPrimitive.List
15
+ ref={ref}
16
+ className={cn("inline-flex h-9 items-center justify-center rounded-lg bg-muted p-1 text-muted-foreground", className)}
17
+ {...props}
18
+ />
19
+ ));
20
+ TabsList.displayName = TabsPrimitive.List.displayName;
21
+
22
+ const TabsTrigger = React.forwardRef<
23
+ React.ElementRef<typeof TabsPrimitive.Trigger>,
24
+ React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>
25
+ >(({ className, ...props }, ref) => (
26
+ <TabsPrimitive.Trigger
27
+ ref={ref}
28
+ className={cn(
29
+ "inline-flex items-center justify-center rounded-md px-3 py-1 text-sm font-medium whitespace-nowrap ring-offset-background transition-all focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:outline-none disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow",
30
+ className,
31
+ )}
32
+ {...props}
33
+ />
34
+ ));
35
+ TabsTrigger.displayName = TabsPrimitive.Trigger.displayName;
36
+
37
+ const TabsContent = React.forwardRef<
38
+ React.ElementRef<typeof TabsPrimitive.Content>,
39
+ React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content>
40
+ >(({ className, ...props }, ref) => (
41
+ <TabsPrimitive.Content
42
+ ref={ref}
43
+ className={cn(
44
+ "mt-2 ring-offset-background focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:outline-none",
45
+ className,
46
+ )}
47
+ {...props}
48
+ />
49
+ ));
50
+ TabsContent.displayName = TabsPrimitive.Content.displayName;
51
+
52
+ export { Tabs, TabsList, TabsTrigger, TabsContent };
@@ -0,0 +1,21 @@
1
+ import * as React from "react";
2
+
3
+ import { cn } from "./utils.js";
4
+
5
+ const Textarea = React.forwardRef<HTMLTextAreaElement, React.ComponentProps<"textarea">>(
6
+ ({ className, ...props }, ref) => {
7
+ return (
8
+ <textarea
9
+ className={cn(
10
+ "flex min-h-[60px] w-full rounded-md border border-input bg-transparent px-3 py-2 text-base shadow-sm placeholder:text-muted-foreground focus-visible:ring-1 focus-visible:ring-ring focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
11
+ className,
12
+ )}
13
+ ref={ref}
14
+ {...props}
15
+ />
16
+ );
17
+ },
18
+ );
19
+ Textarea.displayName = "Textarea";
20
+
21
+ export { Textarea };
@@ -0,0 +1,59 @@
1
+ import * as React from "react";
2
+ import { cva, type VariantProps } from "class-variance-authority";
3
+
4
+ import { cn } from "./utils.js";
5
+
6
+ // No shadcn registry item exists for typography (docs-only class
7
+ // recipes), so these two thin wrappers carry the documented styles.
8
+ // They exist so consumers can ban raw h1-h6/p tags entirely.
9
+
10
+ const headingVariants = cva("scroll-m-20 tracking-tight", {
11
+ variants: {
12
+ level: {
13
+ 1: "text-4xl font-extrabold lg:text-5xl",
14
+ 2: "text-3xl font-semibold",
15
+ 3: "text-2xl font-semibold",
16
+ 4: "text-xl font-semibold",
17
+ },
18
+ },
19
+ defaultVariants: {
20
+ level: 1,
21
+ },
22
+ });
23
+
24
+ export type HeadingProps = React.HTMLAttributes<HTMLHeadingElement> &
25
+ VariantProps<typeof headingVariants> & {
26
+ /** Heading level: picks the default tag (`h1`-`h4`) and size. */
27
+ level?: 1 | 2 | 3 | 4;
28
+ /** Tag override when the outline needs a different element. */
29
+ as?: "h1" | "h2" | "h3" | "h4" | "div";
30
+ };
31
+
32
+ function Heading({ level = 1, as, className, ...props }: HeadingProps) {
33
+ const Tag = (as ?? `h${level}`) as "h1";
34
+ return <Tag className={cn(headingVariants({ level }), className)} {...props} />;
35
+ }
36
+
37
+ const textVariants = cva("leading-7", {
38
+ variants: {
39
+ variant: {
40
+ body: "",
41
+ muted: "text-sm text-muted-foreground",
42
+ },
43
+ },
44
+ defaultVariants: {
45
+ variant: "body",
46
+ },
47
+ });
48
+
49
+ export type TextProps = React.HTMLAttributes<HTMLParagraphElement> &
50
+ VariantProps<typeof textVariants> & {
51
+ /** Tag override (`p` by default). */
52
+ as?: "p" | "span" | "div";
53
+ };
54
+
55
+ function Text({ variant, as: Tag = "p", className, ...props }: TextProps) {
56
+ return <Tag className={cn(textVariants({ variant }), className)} {...props} />;
57
+ }
58
+
59
+ export { Heading, headingVariants, Text, textVariants };
@@ -0,0 +1,30 @@
1
+ "use client";
2
+
3
+ import { zodResolver } from "@hookform/resolvers/zod";
4
+ import {
5
+ useForm,
6
+ type FieldValues,
7
+ type Resolver,
8
+ type UseFormProps,
9
+ type UseFormReturn,
10
+ } from "react-hook-form";
11
+
12
+ /**
13
+ * Validated form in one call: the zod schema is the single source of
14
+ * truth (in a real app it lives in the shared contracts package next
15
+ * to the API validators) and the resolver wiring stays inside the
16
+ * kit, so consumers never import the resolver library directly.
17
+ */
18
+ export function useValidatedForm<TFieldValues extends FieldValues>(
19
+ schema: Parameters<typeof zodResolver>[0],
20
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- mirrors useForm's own context type (any)
21
+ options: Omit<UseFormProps<TFieldValues, any>, "resolver"> = {},
22
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- mirrors useForm's own context type (any)
23
+ ): UseFormReturn<TFieldValues, any, TFieldValues> {
24
+ // The resolver is exact for this schema, but react-hook-form's
25
+ // Resolver is invariant in its options type, so a schema-typed
26
+ // resolver cannot flow through the generic untouched (same
27
+ // upstream-invariance situation as the data-table columns).
28
+ const resolver = zodResolver(schema) as Resolver<TFieldValues>;
29
+ return useForm<TFieldValues>({ ...options, resolver });
30
+ }
package/src/utils.ts ADDED
@@ -0,0 +1,11 @@
1
+ import { clsx, type ClassValue } from "clsx";
2
+ import { twMerge } from "tailwind-merge";
3
+
4
+ /**
5
+ * Merges class names, with tailwind-merge resolving conflicting
6
+ * Tailwind utilities (last one wins). Every component takes an
7
+ * optional className that flows through here.
8
+ */
9
+ export function cn(...inputs: ClassValue[]): string {
10
+ return twMerge(clsx(inputs));
11
+ }