@kinetixui/ui 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 (81) hide show
  1. package/LICENSE +21 -0
  2. package/dist/index.d.ts +1176 -0
  3. package/dist/index.js +3939 -0
  4. package/package.json +106 -0
  5. package/src/components/accordion.tsx +52 -0
  6. package/src/components/alert-dialog.tsx +115 -0
  7. package/src/components/alert.tsx +47 -0
  8. package/src/components/aspect-ratio.tsx +7 -0
  9. package/src/components/audio-player.tsx +150 -0
  10. package/src/components/avatar.tsx +76 -0
  11. package/src/components/badge.tsx +40 -0
  12. package/src/components/breadcrumb.tsx +77 -0
  13. package/src/components/button.tsx +119 -0
  14. package/src/components/calendar.tsx +60 -0
  15. package/src/components/card.tsx +50 -0
  16. package/src/components/carousel.tsx +181 -0
  17. package/src/components/chart.tsx +177 -0
  18. package/src/components/checkbox.tsx +38 -0
  19. package/src/components/circular-progress.tsx +67 -0
  20. package/src/components/code-block.tsx +96 -0
  21. package/src/components/collapsible.tsx +9 -0
  22. package/src/components/command.tsx +123 -0
  23. package/src/components/context-menu.tsx +132 -0
  24. package/src/components/data-table.tsx +92 -0
  25. package/src/components/date-picker.tsx +68 -0
  26. package/src/components/dialog.tsx +101 -0
  27. package/src/components/drawer.tsx +82 -0
  28. package/src/components/dropdown-menu.tsx +132 -0
  29. package/src/components/fab.tsx +58 -0
  30. package/src/components/field.tsx +128 -0
  31. package/src/components/file-upload.tsx +183 -0
  32. package/src/components/footer.tsx +62 -0
  33. package/src/components/form.tsx +130 -0
  34. package/src/components/hover-card.tsx +28 -0
  35. package/src/components/image.tsx +87 -0
  36. package/src/components/inform.tsx +82 -0
  37. package/src/components/input-group.tsx +96 -0
  38. package/src/components/input-otp.tsx +63 -0
  39. package/src/components/input.tsx +72 -0
  40. package/src/components/label.tsx +20 -0
  41. package/src/components/list.tsx +69 -0
  42. package/src/components/menubar.tsx +168 -0
  43. package/src/components/metric.tsx +51 -0
  44. package/src/components/modal.tsx +126 -0
  45. package/src/components/navigation-bar.tsx +49 -0
  46. package/src/components/navigation-menu.tsx +117 -0
  47. package/src/components/number-input.tsx +86 -0
  48. package/src/components/pagination.tsx +77 -0
  49. package/src/components/password-input.tsx +36 -0
  50. package/src/components/popover.tsx +32 -0
  51. package/src/components/progress.tsx +24 -0
  52. package/src/components/quote.tsx +34 -0
  53. package/src/components/radio-group.tsx +44 -0
  54. package/src/components/rating.tsx +88 -0
  55. package/src/components/resizable.tsx +40 -0
  56. package/src/components/scroll-area.tsx +39 -0
  57. package/src/components/select.tsx +124 -0
  58. package/src/components/separator.tsx +25 -0
  59. package/src/components/sheet.tsx +104 -0
  60. package/src/components/sidebar.tsx +390 -0
  61. package/src/components/skeleton.tsx +9 -0
  62. package/src/components/slider.tsx +24 -0
  63. package/src/components/sonner.tsx +30 -0
  64. package/src/components/spinner.tsx +43 -0
  65. package/src/components/stepper.tsx +75 -0
  66. package/src/components/switch.tsx +37 -0
  67. package/src/components/tab-bar.tsx +58 -0
  68. package/src/components/table-of-contents.tsx +46 -0
  69. package/src/components/table.tsx +70 -0
  70. package/src/components/tabs.tsx +54 -0
  71. package/src/components/tag.tsx +56 -0
  72. package/src/components/textarea.tsx +56 -0
  73. package/src/components/toggle-group.tsx +41 -0
  74. package/src/components/toggle.tsx +34 -0
  75. package/src/components/tooltip.tsx +31 -0
  76. package/src/index.ts +305 -0
  77. package/src/lib/utils.ts +7 -0
  78. package/src/stories/Button.stories.tsx +155 -0
  79. package/src/stories/Input.stories.tsx +80 -0
  80. package/src/stories/Textarea.stories.tsx +69 -0
  81. package/tailwind.config.ts +107 -0
package/src/index.ts ADDED
@@ -0,0 +1,305 @@
1
+ "use client";
2
+
3
+ // @kinetixui/ui — public entrypoint
4
+
5
+ /* Design-source native API (variant props mirror Figma component properties) */
6
+ export { Button, buttonVariants, type ButtonProps } from "./components/button";
7
+ export { Input, inputVariants, type InputProps } from "./components/input";
8
+ export { Textarea, textareaVariants, type TextareaProps } from "./components/textarea";
9
+
10
+ /* Ported onto the KinetixUI token contract (common component API conventions) */
11
+ export { Accordion, AccordionItem, AccordionTrigger, AccordionContent } from "./components/accordion";
12
+ export { Alert, AlertTitle, AlertDescription } from "./components/alert";
13
+ export {
14
+ AlertDialog,
15
+ AlertDialogPortal,
16
+ AlertDialogOverlay,
17
+ AlertDialogTrigger,
18
+ AlertDialogContent,
19
+ AlertDialogHeader,
20
+ AlertDialogFooter,
21
+ AlertDialogTitle,
22
+ AlertDialogDescription,
23
+ AlertDialogAction,
24
+ AlertDialogCancel,
25
+ } from "./components/alert-dialog";
26
+ export { AspectRatio } from "./components/aspect-ratio";
27
+ export { Avatar, AvatarImage, AvatarFallback, AvatarGroup, type AvatarGroupProps } from "./components/avatar";
28
+ export { Badge, badgeVariants, type BadgeProps } from "./components/badge";
29
+ export {
30
+ Breadcrumb,
31
+ BreadcrumbList,
32
+ BreadcrumbItem,
33
+ BreadcrumbLink,
34
+ BreadcrumbPage,
35
+ BreadcrumbSeparator,
36
+ BreadcrumbEllipsis,
37
+ } from "./components/breadcrumb";
38
+ export {
39
+ Card,
40
+ CardHeader,
41
+ CardFooter,
42
+ CardTitle,
43
+ CardDescription,
44
+ CardContent,
45
+ } from "./components/card";
46
+ export { Checkbox } from "./components/checkbox";
47
+ export { Collapsible, CollapsibleTrigger, CollapsibleContent } from "./components/collapsible";
48
+ export {
49
+ Dialog,
50
+ DialogPortal,
51
+ DialogOverlay,
52
+ DialogClose,
53
+ DialogTrigger,
54
+ DialogContent,
55
+ DialogHeader,
56
+ DialogFooter,
57
+ DialogTitle,
58
+ DialogDescription,
59
+ } from "./components/dialog";
60
+ export {
61
+ DropdownMenu,
62
+ DropdownMenuTrigger,
63
+ DropdownMenuContent,
64
+ DropdownMenuItem,
65
+ DropdownMenuCheckboxItem,
66
+ DropdownMenuRadioItem,
67
+ DropdownMenuLabel,
68
+ DropdownMenuSeparator,
69
+ DropdownMenuShortcut,
70
+ DropdownMenuGroup,
71
+ DropdownMenuPortal,
72
+ DropdownMenuSub,
73
+ DropdownMenuSubContent,
74
+ DropdownMenuSubTrigger,
75
+ DropdownMenuRadioGroup,
76
+ } from "./components/dropdown-menu";
77
+ export { HoverCard, HoverCardTrigger, HoverCardContent } from "./components/hover-card";
78
+ export { Label } from "./components/label";
79
+ export {
80
+ Pagination,
81
+ PaginationContent,
82
+ PaginationItem,
83
+ PaginationLink,
84
+ PaginationPrevious,
85
+ PaginationNext,
86
+ PaginationEllipsis,
87
+ } from "./components/pagination";
88
+ export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor } from "./components/popover";
89
+ export { Progress } from "./components/progress";
90
+ export { RadioGroup, RadioGroupItem } from "./components/radio-group";
91
+ export { ScrollArea, ScrollBar } from "./components/scroll-area";
92
+ export {
93
+ Select,
94
+ SelectGroup,
95
+ SelectValue,
96
+ SelectTrigger,
97
+ SelectContent,
98
+ SelectLabel,
99
+ SelectItem,
100
+ SelectSeparator,
101
+ } from "./components/select";
102
+ export { Separator } from "./components/separator";
103
+ export {
104
+ Sheet,
105
+ SheetPortal,
106
+ SheetOverlay,
107
+ SheetTrigger,
108
+ SheetClose,
109
+ SheetContent,
110
+ SheetHeader,
111
+ SheetFooter,
112
+ SheetTitle,
113
+ SheetDescription,
114
+ } from "./components/sheet";
115
+ export { Modal, type ModalType, type ModalProps } from "./components/modal";
116
+ export { Skeleton } from "./components/skeleton";
117
+ export { Slider } from "./components/slider";
118
+ export { Toaster, toast } from "./components/sonner";
119
+ export { Switch } from "./components/switch";
120
+ export { Tag, tagVariants, type TagProps } from "./components/tag";
121
+ export {
122
+ Table,
123
+ TableHeader,
124
+ TableBody,
125
+ TableFooter,
126
+ TableHead,
127
+ TableRow,
128
+ TableCell,
129
+ TableCaption,
130
+ } from "./components/table";
131
+ export { Tabs, TabsList, TabsTrigger, TabsContent } from "./components/tabs";
132
+ export { Toggle, toggleVariants } from "./components/toggle";
133
+ export { ToggleGroup, ToggleGroupItem } from "./components/toggle-group";
134
+ export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider } from "./components/tooltip";
135
+
136
+ /* Batch 2 — compositions + charting + layout */
137
+ export { Calendar, type CalendarProps } from "./components/calendar";
138
+ export {
139
+ Carousel,
140
+ CarouselContent,
141
+ CarouselItem,
142
+ CarouselPrevious,
143
+ CarouselNext,
144
+ type CarouselApi,
145
+ } from "./components/carousel";
146
+ export {
147
+ ChartContainer,
148
+ ChartTooltip,
149
+ ChartTooltipContent,
150
+ ChartLegend,
151
+ ChartLegendContent,
152
+ ChartStyle,
153
+ type ChartConfig,
154
+ } from "./components/chart";
155
+ export {
156
+ Command,
157
+ CommandDialog,
158
+ CommandInput,
159
+ CommandList,
160
+ CommandEmpty,
161
+ CommandGroup,
162
+ CommandItem,
163
+ CommandShortcut,
164
+ CommandSeparator,
165
+ } from "./components/command";
166
+ export {
167
+ ContextMenu,
168
+ ContextMenuTrigger,
169
+ ContextMenuContent,
170
+ ContextMenuItem,
171
+ ContextMenuCheckboxItem,
172
+ ContextMenuRadioItem,
173
+ ContextMenuLabel,
174
+ ContextMenuSeparator,
175
+ ContextMenuShortcut,
176
+ ContextMenuGroup,
177
+ ContextMenuPortal,
178
+ ContextMenuSub,
179
+ ContextMenuSubContent,
180
+ ContextMenuSubTrigger,
181
+ ContextMenuRadioGroup,
182
+ } from "./components/context-menu";
183
+ export { DataTable } from "./components/data-table";
184
+ export {
185
+ Drawer,
186
+ DrawerPortal,
187
+ DrawerOverlay,
188
+ DrawerTrigger,
189
+ DrawerClose,
190
+ DrawerContent,
191
+ DrawerHeader,
192
+ DrawerFooter,
193
+ DrawerTitle,
194
+ DrawerDescription,
195
+ } from "./components/drawer";
196
+ export {
197
+ Field,
198
+ FieldLabel,
199
+ FieldControl,
200
+ FieldDescription,
201
+ FieldMessage,
202
+ messageVariants,
203
+ } from "./components/field";
204
+ export {
205
+ useFormField,
206
+ Form,
207
+ FormItem,
208
+ FormLabel,
209
+ FormControl,
210
+ FormDescription,
211
+ FormMessage,
212
+ FormField,
213
+ } from "./components/form";
214
+ export {
215
+ InputGroup,
216
+ InputGroupInput,
217
+ InputGroupAddon,
218
+ InputGroupText,
219
+ InputGroupButton,
220
+ } from "./components/input-group";
221
+ export { InputOTP, InputOTPGroup, InputOTPSlot, InputOTPSeparator } from "./components/input-otp";
222
+ export { PasswordInput } from "./components/password-input";
223
+ export {
224
+ Menubar,
225
+ MenubarMenu,
226
+ MenubarTrigger,
227
+ MenubarContent,
228
+ MenubarItem,
229
+ MenubarSeparator,
230
+ MenubarLabel,
231
+ MenubarCheckboxItem,
232
+ MenubarRadioGroup,
233
+ MenubarRadioItem,
234
+ MenubarPortal,
235
+ MenubarSubContent,
236
+ MenubarSubTrigger,
237
+ MenubarGroup,
238
+ MenubarSub,
239
+ MenubarShortcut,
240
+ } from "./components/menubar";
241
+ export {
242
+ navigationMenuTriggerStyle,
243
+ NavigationMenu,
244
+ NavigationMenuList,
245
+ NavigationMenuItem,
246
+ NavigationMenuContent,
247
+ NavigationMenuTrigger,
248
+ NavigationMenuLink,
249
+ NavigationMenuIndicator,
250
+ NavigationMenuViewport,
251
+ } from "./components/navigation-menu";
252
+ export { ResizablePanelGroup, ResizablePanel, ResizableHandle } from "./components/resizable";
253
+ export {
254
+ Sidebar,
255
+ SidebarContent,
256
+ SidebarFooter,
257
+ SidebarGroup,
258
+ SidebarGroupContent,
259
+ SidebarGroupLabel,
260
+ SidebarHeader,
261
+ SidebarInput,
262
+ SidebarInset,
263
+ SidebarMenu,
264
+ SidebarMenuButton,
265
+ SidebarMenuItem,
266
+ SidebarMenuSkeleton,
267
+ SidebarProvider,
268
+ SidebarSeparator,
269
+ SidebarTrigger,
270
+ useSidebar,
271
+ } from "./components/sidebar";
272
+
273
+ /* Batch 3 — additional gap-fill components */
274
+ export { AudioPlayer, type AudioPlayerProps } from "./components/audio-player";
275
+ export { CircularProgress, type CircularProgressProps } from "./components/circular-progress";
276
+ export { Image, type ImageProps } from "./components/image";
277
+ export { Inform, informVariants, type InformProps } from "./components/inform";
278
+
279
+ /* Batch 4 — mobile-pattern gap-fill components */
280
+ export { Rating, type RatingProps } from "./components/rating";
281
+ export { Spinner, spinnerVariants, type SpinnerProps } from "./components/spinner";
282
+ export { List, ListItem, type ListItemProps } from "./components/list";
283
+ export { Stepper, type StepperProps, type StepperStep } from "./components/stepper";
284
+ export { Fab, fabVariants, type FabProps } from "./components/fab";
285
+ export { TabBar, TabBarItem, type TabBarItemProps } from "./components/tab-bar";
286
+ export { NavigationBar, type NavigationBarProps } from "./components/navigation-bar";
287
+ export {
288
+ FileUpload,
289
+ FileUploadItem,
290
+ formatBytes,
291
+ type UploadFile,
292
+ type FileUploadProps,
293
+ type FileUploadItemProps,
294
+ } from "./components/file-upload";
295
+ export { DatePicker, type DatePickerProps } from "./components/date-picker";
296
+
297
+ /* Batch 5 — web-pattern gap-fill components */
298
+ export { CodeBlock, type CodeBlockProps, type CodeBlockFile } from "./components/code-block";
299
+ export { Metric, type MetricProps } from "./components/metric";
300
+ export { NumberInput, type NumberInputProps } from "./components/number-input";
301
+ export { Quote, type QuoteProps } from "./components/quote";
302
+ export { Footer, FooterColumn, FooterLink, FooterBottom, type FooterColumnProps } from "./components/footer";
303
+ export { TableOfContents, type TableOfContentsProps, type TocItem } from "./components/table-of-contents";
304
+
305
+ export { cn } from "./lib/utils";
@@ -0,0 +1,7 @@
1
+ import { type ClassValue, clsx } from "clsx";
2
+ import { twMerge } from "tailwind-merge";
3
+
4
+ /** Merges Tailwind class names, resolving conflicts (clsx + tailwind-merge). */
5
+ export function cn(...inputs: ClassValue[]) {
6
+ return twMerge(clsx(inputs));
7
+ }
@@ -0,0 +1,155 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import { Button } from "../components/button";
3
+
4
+ const VARIANTS = ["Primary", "Secondary", "Outline", "Destructive", "Ghost", "Link"] as const;
5
+ const SIZES = ["sm", "md", "lg", "icon"] as const;
6
+ const STATES = ["Default", "Hover", "Focus", "Active", "Disabled"] as const;
7
+
8
+ const meta = {
9
+ title: "Controls & Actions/Button",
10
+ component: Button,
11
+ parameters: {
12
+ layout: "centered",
13
+ docs: {
14
+ description: {
15
+ component:
16
+ "Generated from Figma `KinetixUI` › UI Components › 02. Controls & Actions › Button (node 54863:351). 6 variants × 4 sizes × 5 states = 120 permutations, mirrored 1:1 from the Figma component properties.",
17
+ },
18
+ },
19
+ },
20
+ args: { children: "Button", variant: "Primary", size: "md", state: "Default" },
21
+ argTypes: {
22
+ variant: { control: "inline-radio", options: VARIANTS },
23
+ size: { control: "inline-radio", options: SIZES },
24
+ state: { control: "inline-radio", options: STATES },
25
+ asChild: { control: "boolean" },
26
+ disabled: { control: "boolean" },
27
+ },
28
+ } satisfies Meta<typeof Button>;
29
+
30
+ export default meta;
31
+ type Story = StoryObj<typeof meta>;
32
+
33
+ /* ---- interactive playground ---------------------------------------------- */
34
+ export const Playground: Story = {};
35
+
36
+ /* ---- every variant ----------------------------------------------------- */
37
+ export const Variants: Story = {
38
+ render: (args) => (
39
+ <div className="flex flex-wrap items-center gap-3">
40
+ {VARIANTS.map((variant) => (
41
+ <Button key={variant} {...args} variant={variant}>
42
+ {variant}
43
+ </Button>
44
+ ))}
45
+ </div>
46
+ ),
47
+ };
48
+
49
+ /* ---- every size ------------------------------------------------------- */
50
+ export const Sizes: Story = {
51
+ render: (args) => (
52
+ <div className="flex flex-wrap items-center gap-3">
53
+ {SIZES.map((size) => (
54
+ <Button key={size} {...args} size={size}>
55
+ {size === "icon" ? "★" : `Size ${size}`}
56
+ </Button>
57
+ ))}
58
+ </div>
59
+ ),
60
+ };
61
+
62
+ /* ---- full matrix (mirrors the Figma component sheet) ------------------- */
63
+ export const Matrix: Story = {
64
+ parameters: { layout: "padded" },
65
+ render: (args) => (
66
+ <table className="border-separate border-spacing-3 text-left align-middle">
67
+ <thead>
68
+ <tr>
69
+ <th className="text-xs font-medium text-muted-foreground">variant \ state</th>
70
+ {STATES.map((s) => (
71
+ <th key={s} className="text-xs font-medium text-muted-foreground">
72
+ {s}
73
+ </th>
74
+ ))}
75
+ </tr>
76
+ </thead>
77
+ <tbody>
78
+ {VARIANTS.map((variant) => (
79
+ <tr key={variant}>
80
+ <td className="text-xs font-medium text-muted-foreground">{variant}</td>
81
+ {STATES.map((state) => (
82
+ <td key={state}>
83
+ <Button {...args} variant={variant} state={state}>
84
+ Button
85
+ </Button>
86
+ </td>
87
+ ))}
88
+ </tr>
89
+ ))}
90
+ </tbody>
91
+ </table>
92
+ ),
93
+ };
94
+
95
+ /* ---- asChild (Radix Slot) ------------------------------------------------ */
96
+ export const AsChildLink: Story = {
97
+ render: (args) => (
98
+ <Button {...args} asChild variant="Link">
99
+ <a href="https://figma.com" target="_blank" rel="noreferrer">
100
+ Open Figma
101
+ </a>
102
+ </Button>
103
+ ),
104
+ };
105
+
106
+ /* ---- multi-platform usage (rendered in the Docs tab) ------------------- */
107
+ export const CrossPlatformUsage: Story = {
108
+ parameters: {
109
+ docs: {
110
+ source: {
111
+ code: [
112
+ "/* ── Web · React (@kinetixui/ui) ────────────────────────────── */",
113
+ '<Button variant="Primary" size="md">Save</Button>',
114
+ "",
115
+ "/* ── Web · HTML + CSS (token contract) ───────────────────── */",
116
+ '<button class="kx-btn kx-btn--primary kx-btn--md">Save</button>',
117
+ "/* uses --primary / --primary-foreground / --radius from globals.css */",
118
+ "",
119
+ "/* ── Angular (kx-button) ─────────────────────────────── */",
120
+ '<button kx-button variant="primary" size="md">Save</button>',
121
+ "",
122
+ "/* ── Flutter (AppTheme) ──────────────────────────────────── */",
123
+ "FilledButton(",
124
+ " style: FilledButton.styleFrom(",
125
+ " backgroundColor: AppColors.primary,",
126
+ " foregroundColor: AppColors.primaryForeground,",
127
+ " shape: RoundedRectangleBorder(",
128
+ " borderRadius: BorderRadius.circular(AppTheme.radius)),",
129
+ " ),",
130
+ " onPressed: onSave,",
131
+ " child: const Text('Save'),",
132
+ ")",
133
+ "",
134
+ "/* ── iOS · SwiftUI (KinetixTheme) ─────────────────────────── */",
135
+ "Button('Save', action: save)",
136
+ " .padding(.horizontal, 16).padding(.vertical, 12)",
137
+ " .background(KinetixTheme.light.primary)",
138
+ " .foregroundStyle(KinetixTheme.light.primaryForeground)",
139
+ " .clipShape(RoundedRectangle(cornerRadius: KinetixTheme.light.radius))",
140
+ "",
141
+ "/* ── Android · Jetpack Compose (KinetixTheme) ─────────────── */",
142
+ "Button(",
143
+ " onClick = onSave,",
144
+ " colors = ButtonDefaults.buttonColors(",
145
+ " containerColor = KinetixTheme.primary,",
146
+ " contentColor = KinetixTheme.primaryForeground),",
147
+ " shape = RoundedCornerShape(KinetixTheme.radius),",
148
+ ") { Text(\"Save\") }",
149
+ ].join("\n"),
150
+ language: "tsx",
151
+ },
152
+ },
153
+ },
154
+ render: (args) => <Button {...args}>Save</Button>,
155
+ };
@@ -0,0 +1,80 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import { Input } from "../components/input";
3
+
4
+ const STATES = ["Default", "Focus", "Error", "Disabled"] as const;
5
+
6
+ const meta = {
7
+ title: "Form Inputs/Input",
8
+ component: Input,
9
+ parameters: {
10
+ layout: "centered",
11
+ docs: {
12
+ description: {
13
+ component:
14
+ "Generated from Figma `KinetixUI` › UI Components › 01. Form Inputs › Input (node 54855:13836). State matrix mirrors the Figma component property `state = Default | Focus | Error | Disabled`; Focus is `:focus-visible`, Disabled is native, `state=\"Error\"` sets `aria-invalid`.",
15
+ },
16
+ },
17
+ },
18
+ args: { placeholder: "Placeholder text" },
19
+ argTypes: {
20
+ state: { control: "inline-radio", options: [undefined, ...STATES] },
21
+ disabled: { control: "boolean" },
22
+ },
23
+ decorators: [
24
+ (Story) => (
25
+ <div style={{ width: 320 }}>
26
+ <Story />
27
+ </div>
28
+ ),
29
+ ],
30
+ } satisfies Meta<typeof Input>;
31
+
32
+ export default meta;
33
+ type Story = StoryObj<typeof meta>;
34
+
35
+ export const Playground: Story = {};
36
+
37
+ export const States: Story = {
38
+ render: (args) => (
39
+ <div className="flex flex-col gap-4">
40
+ {STATES.map((state) => (
41
+ <label key={state} className="flex flex-col gap-2">
42
+ <span className="text-[14px] font-medium tracking-[0.1px]">{state}</span>
43
+ <Input {...args} state={state} defaultValue={state === "Error" ? "Not quite right" : undefined} />
44
+ </label>
45
+ ))}
46
+ </div>
47
+ ),
48
+ };
49
+
50
+ /** reproduces the full Figma field: label + control + helper, colour tracks state */
51
+ export const WithFieldChrome: Story = {
52
+ render: (args) => (
53
+ <div className="flex flex-col gap-6">
54
+ {STATES.map((state) => {
55
+ const tone =
56
+ state === "Error" ? "text-destructive" : state === "Focus" ? "text-primary" : "text-foreground";
57
+ const helperTone = state === "Error" ? "text-destructive" : "text-muted-foreground";
58
+ return (
59
+ <div key={state} className={state === "Disabled" ? "opacity-50" : undefined}>
60
+ <label className="flex flex-col gap-2">
61
+ <span className={`text-[14px] font-medium leading-5 tracking-[0.1px] ${tone}`}>Label</span>
62
+ <Input {...args} state={state} />
63
+ <span className={`text-[12px] leading-4 ${helperTone}`}>Helper text</span>
64
+ </label>
65
+ </div>
66
+ );
67
+ })}
68
+ </div>
69
+ ),
70
+ };
71
+
72
+ export const Types: Story = {
73
+ render: (args) => (
74
+ <div className="flex flex-col gap-3">
75
+ <Input {...args} type="email" placeholder="you@example.com" />
76
+ <Input {...args} type="password" placeholder="••••••••" />
77
+ <Input {...args} type="number" placeholder="42" />
78
+ </div>
79
+ ),
80
+ };
@@ -0,0 +1,69 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import { Textarea } from "../components/textarea";
3
+
4
+ const STATES = ["Default", "Focus", "Error", "Disabled"] as const;
5
+
6
+ const meta = {
7
+ title: "Form Inputs/Textarea",
8
+ component: Textarea,
9
+ parameters: {
10
+ layout: "centered",
11
+ docs: {
12
+ description: {
13
+ component:
14
+ "Generated from Figma `KinetixUI` › UI Components › 01. Form Inputs › Textarea (node 54855:13857). Same state matrix and tokens as Input; multi-line with `min-h-[100px]` (Figma `h: 100`) and vertical resize.",
15
+ },
16
+ },
17
+ },
18
+ args: { placeholder: "Placeholder text", rows: 4 },
19
+ argTypes: {
20
+ state: { control: "inline-radio", options: [undefined, ...STATES] },
21
+ disabled: { control: "boolean" },
22
+ },
23
+ decorators: [
24
+ (Story) => (
25
+ <div style={{ width: 320 }}>
26
+ <Story />
27
+ </div>
28
+ ),
29
+ ],
30
+ } satisfies Meta<typeof Textarea>;
31
+
32
+ export default meta;
33
+ type Story = StoryObj<typeof meta>;
34
+
35
+ export const Playground: Story = {};
36
+
37
+ export const States: Story = {
38
+ render: (args) => (
39
+ <div className="flex flex-col gap-4">
40
+ {STATES.map((state) => (
41
+ <label key={state} className="flex flex-col gap-2">
42
+ <span className="text-[14px] font-medium tracking-[0.1px]">{state}</span>
43
+ <Textarea {...args} state={state} defaultValue={state === "Error" ? "Too short." : undefined} />
44
+ </label>
45
+ ))}
46
+ </div>
47
+ ),
48
+ };
49
+
50
+ export const WithFieldChrome: Story = {
51
+ render: (args) => (
52
+ <div className="flex flex-col gap-6">
53
+ {STATES.map((state) => {
54
+ const tone =
55
+ state === "Error" ? "text-destructive" : state === "Focus" ? "text-primary" : "text-foreground";
56
+ const helperTone = state === "Error" ? "text-destructive" : "text-muted-foreground";
57
+ return (
58
+ <div key={state} className={state === "Disabled" ? "opacity-50" : undefined}>
59
+ <label className="flex flex-col gap-2">
60
+ <span className={`text-[14px] font-medium leading-5 tracking-[0.1px] ${tone}`}>Label</span>
61
+ <Textarea {...args} state={state} />
62
+ <span className={`text-[12px] leading-4 ${helperTone}`}>Helper text</span>
63
+ </label>
64
+ </div>
65
+ );
66
+ })}
67
+ </div>
68
+ ),
69
+ };