@kinetixui/ui 0.10.0 → 0.12.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 (35) hide show
  1. package/dist/index.d.ts +403 -1
  2. package/dist/index.js +1832 -2
  3. package/package.json +5 -2
  4. package/src/components/color-picker.tsx +352 -0
  5. package/src/components/comparison-slider.tsx +98 -0
  6. package/src/components/data-grid.tsx +304 -0
  7. package/src/components/diff-viewer.tsx +155 -0
  8. package/src/components/json-viewer.tsx +168 -0
  9. package/src/components/kanban-board.tsx +246 -0
  10. package/src/components/markdown-editor.tsx +288 -0
  11. package/src/components/marquee.tsx +57 -0
  12. package/src/components/message-bubble.tsx +83 -0
  13. package/src/components/multi-select.tsx +134 -0
  14. package/src/components/notification-center.tsx +119 -0
  15. package/src/components/page-header.tsx +43 -0
  16. package/src/components/tour.tsx +156 -0
  17. package/src/components/tree-view.tsx +273 -0
  18. package/src/components/virtual-list.tsx +92 -0
  19. package/src/index.ts +23 -0
  20. package/src/stories/ColorPicker.stories.tsx +28 -0
  21. package/src/stories/ComparisonSlider.stories.tsx +27 -0
  22. package/src/stories/DataGrid.stories.tsx +54 -0
  23. package/src/stories/DiffViewer.stories.tsx +26 -0
  24. package/src/stories/JsonViewer.stories.tsx +31 -0
  25. package/src/stories/KanbanBoard.stories.tsx +44 -0
  26. package/src/stories/MarkdownEditor.stories.tsx +25 -0
  27. package/src/stories/Marquee.stories.tsx +28 -0
  28. package/src/stories/MessageBubble.stories.tsx +32 -0
  29. package/src/stories/MultiSelect.stories.tsx +35 -0
  30. package/src/stories/NotificationCenter.stories.tsx +41 -0
  31. package/src/stories/PageHeader.stories.tsx +47 -0
  32. package/src/stories/Tour.stories.tsx +70 -0
  33. package/src/stories/TreeView.stories.tsx +38 -0
  34. package/src/stories/VirtualList.stories.tsx +36 -0
  35. package/tailwind.config.ts +4 -0
@@ -0,0 +1,273 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import { ChevronRight } from "lucide-react";
5
+ import { Checkbox } from "./checkbox";
6
+ import { cn } from "../lib/utils";
7
+
8
+ /**
9
+ * TreeView / TreeItem — nested expand/collapse rows with keyboard roving
10
+ * tabindex and optional checkboxes. Sidebar/file/nav trees have no
11
+ * primitive today (`Sidebar` is flat); Radix has no tree component to
12
+ * build on, so this is hand-rolled: `role="treeitem"` sits on each
13
+ * item's own container (not a separate "row" element) so a nested
14
+ * item's `closest('[role="treeitem"]')` correctly walks up to its real
15
+ * ancestor rather than a sibling row, and visible items are queried live
16
+ * from the DOM on every arrow-key press — collapsed subtrees simply
17
+ * don't render, so that query is always exactly the visible, in-order
18
+ * item list with no separate registration bookkeeping to keep in sync.
19
+ * `checkable` checkboxes are independent per item — no automatic
20
+ * parent-selects-all-children / indeterminate propagation. A real,
21
+ * documented simplification, not a silent one. Gap-fill addition (not in
22
+ * the original Figma source).
23
+ */
24
+ interface TreeViewContextValue {
25
+ isExpanded: (value: string) => boolean;
26
+ toggleExpanded: (value: string) => void;
27
+ selected?: string;
28
+ select: (value: string) => void;
29
+ checkable: boolean;
30
+ isChecked: (value: string) => boolean;
31
+ toggleChecked: (value: string) => void;
32
+ tabbableValue: string | null;
33
+ setTabbableValue: (value: string) => void;
34
+ firstValueRef: React.MutableRefObject<string | null>;
35
+ }
36
+
37
+ const TreeViewContext = React.createContext<TreeViewContextValue | null>(null);
38
+
39
+ function useTreeViewContext() {
40
+ const ctx = React.useContext(TreeViewContext);
41
+ if (!ctx) throw new Error("TreeItem must be rendered inside a TreeView");
42
+ return ctx;
43
+ }
44
+
45
+ const TreeLevelContext = React.createContext(0);
46
+
47
+ export interface TreeViewProps extends React.HTMLAttributes<HTMLDivElement> {
48
+ /** show a checkbox on every item */
49
+ checkable?: boolean;
50
+ expanded?: string[];
51
+ defaultExpanded?: string[];
52
+ onExpandedChange?: (expanded: string[]) => void;
53
+ selected?: string;
54
+ onSelectedChange?: (value: string) => void;
55
+ checkedValues?: string[];
56
+ defaultCheckedValues?: string[];
57
+ onCheckedChange?: (checkedValues: string[]) => void;
58
+ }
59
+
60
+ const TreeView = React.forwardRef<HTMLDivElement, TreeViewProps>(
61
+ (
62
+ {
63
+ className,
64
+ checkable = false,
65
+ expanded,
66
+ defaultExpanded = [],
67
+ onExpandedChange,
68
+ selected,
69
+ onSelectedChange,
70
+ checkedValues,
71
+ defaultCheckedValues = [],
72
+ onCheckedChange,
73
+ children,
74
+ ...props
75
+ },
76
+ ref,
77
+ ) => {
78
+ const [internalExpanded, setInternalExpanded] = React.useState(defaultExpanded);
79
+ const currentExpanded = expanded ?? internalExpanded;
80
+ const [internalChecked, setInternalChecked] = React.useState(defaultCheckedValues);
81
+ const currentChecked = checkedValues ?? internalChecked;
82
+ const [tabbableValue, setTabbableValue] = React.useState<string | null>(null);
83
+ const firstValueRef = React.useRef<string | null>(null);
84
+
85
+ const toggleExpanded = React.useCallback(
86
+ (value: string) => {
87
+ const next = currentExpanded.includes(value)
88
+ ? currentExpanded.filter((v) => v !== value)
89
+ : [...currentExpanded, value];
90
+ setInternalExpanded(next);
91
+ onExpandedChange?.(next);
92
+ },
93
+ [currentExpanded, onExpandedChange],
94
+ );
95
+
96
+ const toggleChecked = React.useCallback(
97
+ (value: string) => {
98
+ const next = currentChecked.includes(value)
99
+ ? currentChecked.filter((v) => v !== value)
100
+ : [...currentChecked, value];
101
+ setInternalChecked(next);
102
+ onCheckedChange?.(next);
103
+ },
104
+ [currentChecked, onCheckedChange],
105
+ );
106
+
107
+ const contextValue: TreeViewContextValue = {
108
+ isExpanded: (value) => currentExpanded.includes(value),
109
+ toggleExpanded,
110
+ selected,
111
+ select: (value) => onSelectedChange?.(value),
112
+ checkable,
113
+ isChecked: (value) => currentChecked.includes(value),
114
+ toggleChecked,
115
+ tabbableValue,
116
+ setTabbableValue,
117
+ firstValueRef,
118
+ };
119
+
120
+ return (
121
+ <div
122
+ ref={ref}
123
+ role="tree"
124
+ aria-multiselectable={checkable || undefined}
125
+ className={cn("flex flex-col font-sans", className)}
126
+ {...props}
127
+ >
128
+ <TreeViewContext.Provider value={contextValue}>
129
+ <TreeLevelContext.Provider value={0}>{children}</TreeLevelContext.Provider>
130
+ </TreeViewContext.Provider>
131
+ </div>
132
+ );
133
+ },
134
+ );
135
+ TreeView.displayName = "TreeView";
136
+
137
+ export interface TreeItemProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "children"> {
138
+ value: string;
139
+ label: React.ReactNode;
140
+ icon?: React.ReactNode;
141
+ disabled?: boolean;
142
+ children?: React.ReactNode;
143
+ }
144
+
145
+ const TreeItem = React.forwardRef<HTMLDivElement, TreeItemProps>(
146
+ ({ className, value, label, icon, disabled, children, onClick, onKeyDown, ...props }, ref) => {
147
+ const ctx = useTreeViewContext();
148
+ const level = React.useContext(TreeLevelContext);
149
+ const hasChildren = React.Children.count(children) > 0;
150
+ const expanded = ctx.isExpanded(value);
151
+ const isChecked = ctx.isChecked(value);
152
+
153
+ if (ctx.firstValueRef.current === null) ctx.firstValueRef.current = value;
154
+ const isTabbable = (ctx.tabbableValue ?? ctx.firstValueRef.current) === value;
155
+
156
+ const localRef = React.useRef<HTMLDivElement>(null);
157
+ const setRefs = (node: HTMLDivElement | null) => {
158
+ localRef.current = node;
159
+ if (typeof ref === "function") ref(node);
160
+ else if (ref) (ref as React.MutableRefObject<HTMLDivElement | null>).current = node;
161
+ };
162
+
163
+ const activate = () => {
164
+ ctx.select(value);
165
+ if (ctx.checkable) ctx.toggleChecked(value);
166
+ };
167
+
168
+ const handleKeyDown: React.KeyboardEventHandler<HTMLDivElement> = (e) => {
169
+ onKeyDown?.(e);
170
+ if (e.defaultPrevented || disabled) return;
171
+ const root = localRef.current?.closest('[role="tree"]');
172
+ const items = root ? Array.from(root.querySelectorAll<HTMLElement>('[role="treeitem"]')) : [];
173
+ const idx = items.indexOf(e.currentTarget);
174
+
175
+ switch (e.key) {
176
+ case "ArrowDown":
177
+ e.preventDefault();
178
+ items[idx + 1]?.focus();
179
+ break;
180
+ case "ArrowUp":
181
+ e.preventDefault();
182
+ items[idx - 1]?.focus();
183
+ break;
184
+ case "Home":
185
+ e.preventDefault();
186
+ items[0]?.focus();
187
+ break;
188
+ case "End":
189
+ e.preventDefault();
190
+ items[items.length - 1]?.focus();
191
+ break;
192
+ case "ArrowRight":
193
+ e.preventDefault();
194
+ if (hasChildren && !expanded) ctx.toggleExpanded(value);
195
+ else if (hasChildren) items[idx + 1]?.focus();
196
+ break;
197
+ case "ArrowLeft":
198
+ e.preventDefault();
199
+ if (hasChildren && expanded) ctx.toggleExpanded(value);
200
+ else {
201
+ const parent = localRef.current?.parentElement?.closest<HTMLElement>('[role="treeitem"]');
202
+ parent?.focus();
203
+ }
204
+ break;
205
+ case "Enter":
206
+ case " ":
207
+ e.preventDefault();
208
+ activate();
209
+ break;
210
+ }
211
+ };
212
+
213
+ return (
214
+ <div
215
+ ref={setRefs}
216
+ role="treeitem"
217
+ aria-expanded={hasChildren ? expanded : undefined}
218
+ aria-selected={ctx.selected === value}
219
+ aria-disabled={disabled || undefined}
220
+ tabIndex={disabled ? undefined : isTabbable ? 0 : -1}
221
+ onFocus={() => ctx.setTabbableValue(value)}
222
+ onKeyDown={handleKeyDown}
223
+ onClick={(e) => {
224
+ onClick?.(e);
225
+ if (e.defaultPrevented || disabled) return;
226
+ activate();
227
+ }}
228
+ className={cn("outline-none", disabled && "pointer-events-none opacity-disabled", className)}
229
+ {...props}
230
+ >
231
+ <div
232
+ style={{ paddingLeft: level * 20 + 8 }}
233
+ className={cn(
234
+ "flex items-center gap-1.5 rounded-sm py-1.5 pr-2 text-body-sm text-foreground",
235
+ "hover:bg-accent",
236
+ ctx.selected === value && "bg-accent",
237
+ "focus-visible:ring-1 focus-visible:ring-inset focus-visible:ring-ring",
238
+ )}
239
+ >
240
+ {hasChildren ? (
241
+ <ChevronRight
242
+ className={cn("size-4 shrink-0 text-muted-foreground transition-transform", expanded && "rotate-90")}
243
+ onClick={(e) => {
244
+ e.stopPropagation();
245
+ ctx.toggleExpanded(value);
246
+ }}
247
+ />
248
+ ) : (
249
+ <span className="size-4 shrink-0" />
250
+ )}
251
+ {ctx.checkable && (
252
+ <Checkbox
253
+ checked={isChecked}
254
+ onCheckedChange={() => ctx.toggleChecked(value)}
255
+ onClick={(e) => e.stopPropagation()}
256
+ tabIndex={-1}
257
+ />
258
+ )}
259
+ {icon}
260
+ <span className="truncate">{label}</span>
261
+ </div>
262
+ {hasChildren && expanded && (
263
+ <div role="group">
264
+ <TreeLevelContext.Provider value={level + 1}>{children}</TreeLevelContext.Provider>
265
+ </div>
266
+ )}
267
+ </div>
268
+ );
269
+ },
270
+ );
271
+ TreeItem.displayName = "TreeItem";
272
+
273
+ export { TreeView, TreeItem };
@@ -0,0 +1,92 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import { cn } from "../lib/utils";
5
+
6
+ /**
7
+ * VirtualList — a windowed-rendering primitive: only the rows visible in
8
+ * the scroll viewport (plus `overscan`) actually mount, so a list of
9
+ * thousands of items costs the same as rendering a couple dozen. Fixed
10
+ * row height only — variable-height virtualization needs a measurement
11
+ * pass per row (a real library's job, e.g. `@tanstack/react-virtual`),
12
+ * which is out of scope for this primitive. A prerequisite for `DataGrid`
13
+ * and any `Command`/`Combobox`/`Select` with a large option list. Gap-fill
14
+ * addition (not in the original Figma source).
15
+ */
16
+ export interface VirtualListProps<T>
17
+ extends Omit<React.HTMLAttributes<HTMLDivElement>, "children"> {
18
+ items: T[];
19
+ /** row height in px — every row is this tall */
20
+ itemHeight: number;
21
+ /** viewport height — a number (px) or any CSS height value */
22
+ height: number | string;
23
+ renderItem: (item: T, index: number) => React.ReactNode;
24
+ /** extra rows rendered above/below the viewport, to reduce blank flashes on fast scroll */
25
+ overscan?: number;
26
+ getItemKey?: (item: T, index: number) => React.Key;
27
+ }
28
+
29
+ function VirtualList<T>({
30
+ items,
31
+ itemHeight,
32
+ height,
33
+ renderItem,
34
+ overscan = 4,
35
+ getItemKey,
36
+ className,
37
+ style,
38
+ onScroll,
39
+ ...props
40
+ }: VirtualListProps<T>) {
41
+ const containerRef = React.useRef<HTMLDivElement>(null);
42
+ const [scrollTop, setScrollTop] = React.useState(0);
43
+ const [viewportHeight, setViewportHeight] = React.useState(typeof height === "number" ? height : 0);
44
+
45
+ React.useEffect(() => {
46
+ const el = containerRef.current;
47
+ if (!el) return;
48
+ const observer = new ResizeObserver((entries) => {
49
+ const entry = entries[0];
50
+ if (entry) setViewportHeight(entry.contentRect.height);
51
+ });
52
+ observer.observe(el);
53
+ return () => observer.disconnect();
54
+ }, []);
55
+
56
+ const totalHeight = items.length * itemHeight;
57
+ const startIndex = Math.max(0, Math.floor(scrollTop / itemHeight) - overscan);
58
+ const visibleCount = Math.ceil(viewportHeight / itemHeight) + overscan * 2;
59
+ const endIndex = Math.min(items.length, startIndex + Math.max(visibleCount, 0));
60
+ const visibleItems = items.slice(startIndex, endIndex);
61
+
62
+ return (
63
+ <div
64
+ ref={containerRef}
65
+ role="list"
66
+ className={cn("overflow-y-auto font-sans", className)}
67
+ style={{ height, ...style }}
68
+ onScroll={(e) => {
69
+ onScroll?.(e);
70
+ setScrollTop(e.currentTarget.scrollTop);
71
+ }}
72
+ {...props}
73
+ >
74
+ <div style={{ height: totalHeight, position: "relative" }}>
75
+ {visibleItems.map((item, i) => {
76
+ const index = startIndex + i;
77
+ return (
78
+ <div
79
+ key={getItemKey ? getItemKey(item, index) : index}
80
+ role="listitem"
81
+ style={{ position: "absolute", top: index * itemHeight, left: 0, right: 0, height: itemHeight }}
82
+ >
83
+ {renderItem(item, index)}
84
+ </div>
85
+ );
86
+ })}
87
+ </div>
88
+ </div>
89
+ );
90
+ }
91
+
92
+ export { VirtualList };
package/src/index.ts CHANGED
@@ -351,5 +351,28 @@ export {
351
351
  type SegmentedControlProps,
352
352
  } from "./components/segmented-control";
353
353
  export { Timeline, type TimelineItem, type TimelineProps } from "./components/timeline";
354
+ export { Marquee, type MarqueeProps } from "./components/marquee";
355
+ export { ComparisonSlider, type ComparisonSliderProps } from "./components/comparison-slider";
356
+ export { PageHeader, type PageHeaderProps } from "./components/page-header";
357
+ export {
358
+ NotificationCenter,
359
+ NotificationCenterTrigger,
360
+ NotificationCenterContent,
361
+ NotificationItem,
362
+ type NotificationCenterTriggerProps,
363
+ type NotificationCenterContentProps,
364
+ type NotificationItemProps,
365
+ } from "./components/notification-center";
366
+ export { TreeView, TreeItem, type TreeViewProps, type TreeItemProps } from "./components/tree-view";
367
+ export { MultiSelect, type MultiSelectOption, type MultiSelectProps } from "./components/multi-select";
368
+ export { MessageBubble, TypingIndicator, type MessageBubbleProps } from "./components/message-bubble";
369
+ export { Tour, type TourStep, type TourProps } from "./components/tour";
370
+ export { VirtualList, type VirtualListProps } from "./components/virtual-list";
371
+ export { DataGrid, type DataGridColumn, type DataGridProps } from "./components/data-grid";
372
+ export { JsonViewer, type JsonViewerProps } from "./components/json-viewer";
373
+ export { DiffViewer, type DiffViewerProps } from "./components/diff-viewer";
374
+ export { ColorPicker, type ColorPickerProps } from "./components/color-picker";
375
+ export { KanbanBoard, type KanbanBoardProps, type KanbanColumn, type KanbanCard } from "./components/kanban-board";
376
+ export { MarkdownEditor, type MarkdownEditorProps } from "./components/markdown-editor";
354
377
 
355
378
  export { cn } from "./lib/utils";
@@ -0,0 +1,28 @@
1
+ /* AUTO-GENERATED by scripts/gen-stories.mjs — do not edit.
2
+ Source of truth: apps/web/src/registry/demos.tsx */
3
+ import * as React from "react";
4
+ import type { Meta, StoryObj } from "@storybook/react-vite";
5
+ import { ColorPicker } from "@kinetixui/ui";
6
+
7
+ const Demo = () => {
8
+ const [color, setColor] = React.useState("#3b82f6");
9
+ return (
10
+ <ColorPicker
11
+ value={color}
12
+ onChange={setColor}
13
+ swatches={["#ef4444", "#f97316", "#eab308", "#22c55e", "#3b82f6", "#8b5cf6", "#ec4899"]}
14
+ />
15
+ );
16
+ };
17
+
18
+ const meta = {
19
+ title: "Form Inputs/ColorPicker",
20
+ parameters: {
21
+ layout: "centered",
22
+ docs: { source: { code: "<ColorPicker\\n value={color}\\n onChange={setColor}\\n swatches={[\"#ef4444\", \"#f97316\", \"#eab308\", \"#22c55e\", \"#3b82f6\"]}\\n/>", language: "tsx" } },
23
+ },
24
+ } satisfies Meta;
25
+
26
+ export default meta;
27
+
28
+ export const Default: StoryObj<typeof meta> = { render: () => <Demo /> };
@@ -0,0 +1,27 @@
1
+ /* AUTO-GENERATED by scripts/gen-stories.mjs — do not edit.
2
+ Source of truth: apps/web/src/registry/demos.tsx */
3
+ import * as React from "react";
4
+ import type { Meta, StoryObj } from "@storybook/react-vite";
5
+ import { ComparisonSlider } from "@kinetixui/ui";
6
+
7
+ const Demo = () => (
8
+ <ComparisonSlider
9
+ className="w-full max-w-md"
10
+ beforeLabel="Before"
11
+ afterLabel="After"
12
+ before={<div className="flex size-full items-center justify-center bg-muted text-body-sm text-muted-foreground">Original</div>}
13
+ after={<div className="flex size-full items-center justify-center bg-primary/15 text-body-sm text-primary">Redesign</div>}
14
+ />
15
+ );
16
+
17
+ const meta = {
18
+ title: "Controls & Actions/ComparisonSlider",
19
+ parameters: {
20
+ layout: "centered",
21
+ docs: { source: { code: "<ComparisonSlider\\n beforeLabel=\"Before\"\\n afterLabel=\"After\"\\n before={<img src=\"/before.jpg\" alt=\"\" />}\\n after={<img src=\"/after.jpg\" alt=\"\" />}\\n/>", language: "tsx" } },
22
+ },
23
+ } satisfies Meta;
24
+
25
+ export default meta;
26
+
27
+ export const Default: StoryObj<typeof meta> = { render: () => <Demo /> };
@@ -0,0 +1,54 @@
1
+ /* AUTO-GENERATED by scripts/gen-stories.mjs — do not edit.
2
+ Source of truth: apps/web/src/registry/demos.tsx */
3
+ import * as React from "react";
4
+ import type { Meta, StoryObj } from "@storybook/react-vite";
5
+ import { DataGrid } from "@kinetixui/ui";
6
+
7
+ const Demo = () => {
8
+ const rows = React.useMemo(
9
+ () =>
10
+ Array.from({ length: 2000 }, (_, i) => ({
11
+ id: i,
12
+ name: `Item ${i + 1}`,
13
+ category: ["Widgets", "Gadgets", "Gizmos"][i % 3],
14
+ qty: (i * 7) % 500,
15
+ })),
16
+ [],
17
+ );
18
+ const [data, setData] = React.useState(rows);
19
+ return (
20
+ <DataGrid
21
+ className="w-full"
22
+ height={280}
23
+ data={data}
24
+ getRowId={(row) => row.id}
25
+ columns={[
26
+ { id: "id", header: "ID", cell: (row) => row.id, value: (row) => row.id, sortable: true, width: 72, pinned: "left" },
27
+ {
28
+ id: "name",
29
+ header: "Name",
30
+ cell: (row) => row.name,
31
+ value: (row) => row.name,
32
+ sortable: true,
33
+ editable: true,
34
+ onCellEdit: (row, rowIndex, value) =>
35
+ setData((prev) => prev.map((r, i) => (i === rowIndex ? { ...r, name: value } : r))),
36
+ },
37
+ { id: "category", header: "Category", cell: (row) => row.category, value: (row) => row.category, sortable: true },
38
+ { id: "qty", header: "Qty", cell: (row) => row.qty, value: (row) => row.qty, sortable: true, width: 100 },
39
+ ]}
40
+ />
41
+ );
42
+ };
43
+
44
+ const meta = {
45
+ title: "Data Display/DataGrid",
46
+ parameters: {
47
+ layout: "padded",
48
+ docs: { source: { code: "<DataGrid\\n height={280}\\n data={data}\\n getRowId={(row) => row.id}\\n columns={[\\n { id: \"id\", header: \"ID\", cell: (row) => row.id, value: (row) => row.id, sortable: true, pinned: \"left\" },\\n { id: \"name\", header: \"Name\", cell: (row) => row.name, value: (row) => row.name, editable: true, onCellEdit: updateName },\\n { id: \"qty\", header: \"Qty\", cell: (row) => row.qty, value: (row) => row.qty, sortable: true },\\n ]}\\n/>", language: "tsx" } },
49
+ },
50
+ } satisfies Meta;
51
+
52
+ export default meta;
53
+
54
+ export const Default: StoryObj<typeof meta> = { render: () => <Demo /> };
@@ -0,0 +1,26 @@
1
+ /* AUTO-GENERATED by scripts/gen-stories.mjs — do not edit.
2
+ Source of truth: apps/web/src/registry/demos.tsx */
3
+ import * as React from "react";
4
+ import type { Meta, StoryObj } from "@storybook/react-vite";
5
+ import { DiffViewer } from "@kinetixui/ui";
6
+
7
+ const Demo = () => (
8
+ <DiffViewer
9
+ className="w-full max-w-md"
10
+ mode="split"
11
+ oldText={'{\n "name": "kinetixui",\n "version": "0.10.0",\n "private": true\n}'}
12
+ newText={'{\n "name": "kinetixui",\n "version": "0.11.0",\n "stable": false\n}'}
13
+ />
14
+ );
15
+
16
+ const meta = {
17
+ title: "Data Display/DiffViewer",
18
+ parameters: {
19
+ layout: "centered",
20
+ docs: { source: { code: "<DiffViewer\\n mode=\"split\"\\n oldText={before}\\n newText={after}\\n/>", language: "tsx" } },
21
+ },
22
+ } satisfies Meta;
23
+
24
+ export default meta;
25
+
26
+ export const Default: StoryObj<typeof meta> = { render: () => <Demo /> };
@@ -0,0 +1,31 @@
1
+ /* AUTO-GENERATED by scripts/gen-stories.mjs — do not edit.
2
+ Source of truth: apps/web/src/registry/demos.tsx */
3
+ import * as React from "react";
4
+ import type { Meta, StoryObj } from "@storybook/react-vite";
5
+ import { JsonViewer } from "@kinetixui/ui";
6
+
7
+ const Demo = () => (
8
+ <JsonViewer
9
+ className="w-full max-w-md"
10
+ data={{
11
+ name: "kinetixui",
12
+ version: "0.11.0",
13
+ platforms: ["react", "swiftui", "compose", "flutter"],
14
+ stable: false,
15
+ tokens: { spacing: 8, radius: { sm: 4, md: 8 } },
16
+ deprecated: null,
17
+ }}
18
+ />
19
+ );
20
+
21
+ const meta = {
22
+ title: "Data Display/JsonViewer",
23
+ parameters: {
24
+ layout: "centered",
25
+ docs: { source: { code: "<JsonViewer\\n data={{\\n name: \"kinetixui\",\\n version: \"0.11.0\",\\n platforms: [\"react\", \"swiftui\", \"compose\", \"flutter\"],\\n stable: false,\\n }}\\n/>", language: "tsx" } },
26
+ },
27
+ } satisfies Meta;
28
+
29
+ export default meta;
30
+
31
+ export const Default: StoryObj<typeof meta> = { render: () => <Demo /> };
@@ -0,0 +1,44 @@
1
+ /* AUTO-GENERATED by scripts/gen-stories.mjs — do not edit.
2
+ Source of truth: apps/web/src/registry/demos.tsx */
3
+ import * as React from "react";
4
+ import type { Meta, StoryObj } from "@storybook/react-vite";
5
+ import { ColorPicker, DiffViewer, JsonViewer, KanbanBoard, Progress } from "@kinetixui/ui";
6
+
7
+ const Demo = () => {
8
+ const [columns, setColumns] = React.useState<KanbanColumn[]>([
9
+ {
10
+ id: "todo",
11
+ title: "To Do",
12
+ cards: [
13
+ { id: "1", content: "Design the onboarding flow" },
14
+ { id: "2", content: "Write API docs for /r/registry.json" },
15
+ ],
16
+ },
17
+ {
18
+ id: "in-progress",
19
+ title: "In Progress",
20
+ cards: [{ id: "3", content: "Port ColorPicker to Compose" }],
21
+ },
22
+ {
23
+ id: "done",
24
+ title: "Done",
25
+ cards: [
26
+ { id: "4", content: "Ship DiffViewer" },
27
+ { id: "5", content: "Ship JsonViewer" },
28
+ ],
29
+ },
30
+ ]);
31
+ return <KanbanBoard columns={columns} onColumnsChange={setColumns} className="w-full" />;
32
+ };
33
+
34
+ const meta = {
35
+ title: "Data Display/KanbanBoard",
36
+ parameters: {
37
+ layout: "padded",
38
+ docs: { source: { code: "<KanbanBoard\\n columns={columns}\\n onColumnsChange={setColumns}\\n/>", language: "tsx" } },
39
+ },
40
+ } satisfies Meta;
41
+
42
+ export default meta;
43
+
44
+ export const Default: StoryObj<typeof meta> = { render: () => <Demo /> };
@@ -0,0 +1,25 @@
1
+ /* AUTO-GENERATED by scripts/gen-stories.mjs — do not edit.
2
+ Source of truth: apps/web/src/registry/demos.tsx */
3
+ import * as React from "react";
4
+ import type { Meta, StoryObj } from "@storybook/react-vite";
5
+ import { MarkdownEditor } from "@kinetixui/ui";
6
+ import { Bold } from "lucide-react";
7
+
8
+ const Demo = () => {
9
+ const [text, setText] = React.useState(
10
+ "## Release notes\n\nShipped **MarkdownEditor** — a toolbar over a plain textarea, not `contenteditable`.\n\n- Bold, italic, links\n- Bulleted and numbered lists\n- An optional preview pane",
11
+ );
12
+ return <MarkdownEditor className="w-full" value={text} onChange={setText} rows={8} />;
13
+ };
14
+
15
+ const meta = {
16
+ title: "Form Inputs/MarkdownEditor",
17
+ parameters: {
18
+ layout: "padded",
19
+ docs: { source: { code: "<MarkdownEditor\\n value={text}\\n onChange={setText}\\n rows={8}\\n/>", language: "tsx" } },
20
+ },
21
+ } satisfies Meta;
22
+
23
+ export default meta;
24
+
25
+ export const Default: StoryObj<typeof meta> = { render: () => <Demo /> };
@@ -0,0 +1,28 @@
1
+ /* AUTO-GENERATED by scripts/gen-stories.mjs — do not edit.
2
+ Source of truth: apps/web/src/registry/demos.tsx */
3
+ import * as React from "react";
4
+ import type { Meta, StoryObj } from "@storybook/react-vite";
5
+ import { Marquee } from "@kinetixui/ui";
6
+
7
+ const Demo = () => (
8
+ <Marquee durationSeconds={18} className="w-full max-w-md">
9
+ {["React", "SwiftUI", "Jetpack Compose", "Flutter"].map((p) => (
10
+ <span key={p} className="flex items-center gap-2 px-4 text-body-sm text-muted-foreground">
11
+ <span className="size-1.5 rounded-full bg-primary" />
12
+ {p}
13
+ </span>
14
+ ))}
15
+ </Marquee>
16
+ );
17
+
18
+ const meta = {
19
+ title: "Data Display/Marquee",
20
+ parameters: {
21
+ layout: "centered",
22
+ docs: { source: { code: "<Marquee durationSeconds={18}>\\n {items.map((item) => (\\n <span key={item}>{item}</span>\\n ))}\\n</Marquee>", language: "tsx" } },
23
+ },
24
+ } satisfies Meta;
25
+
26
+ export default meta;
27
+
28
+ export const Default: StoryObj<typeof meta> = { render: () => <Demo /> };