@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.
- package/dist/index.d.ts +403 -1
- package/dist/index.js +1832 -2
- package/package.json +5 -2
- package/src/components/color-picker.tsx +352 -0
- package/src/components/comparison-slider.tsx +98 -0
- package/src/components/data-grid.tsx +304 -0
- package/src/components/diff-viewer.tsx +155 -0
- package/src/components/json-viewer.tsx +168 -0
- package/src/components/kanban-board.tsx +246 -0
- package/src/components/markdown-editor.tsx +288 -0
- package/src/components/marquee.tsx +57 -0
- package/src/components/message-bubble.tsx +83 -0
- package/src/components/multi-select.tsx +134 -0
- package/src/components/notification-center.tsx +119 -0
- package/src/components/page-header.tsx +43 -0
- package/src/components/tour.tsx +156 -0
- package/src/components/tree-view.tsx +273 -0
- package/src/components/virtual-list.tsx +92 -0
- package/src/index.ts +23 -0
- package/src/stories/ColorPicker.stories.tsx +28 -0
- package/src/stories/ComparisonSlider.stories.tsx +27 -0
- package/src/stories/DataGrid.stories.tsx +54 -0
- package/src/stories/DiffViewer.stories.tsx +26 -0
- package/src/stories/JsonViewer.stories.tsx +31 -0
- package/src/stories/KanbanBoard.stories.tsx +44 -0
- package/src/stories/MarkdownEditor.stories.tsx +25 -0
- package/src/stories/Marquee.stories.tsx +28 -0
- package/src/stories/MessageBubble.stories.tsx +32 -0
- package/src/stories/MultiSelect.stories.tsx +35 -0
- package/src/stories/NotificationCenter.stories.tsx +41 -0
- package/src/stories/PageHeader.stories.tsx +47 -0
- package/src/stories/Tour.stories.tsx +70 -0
- package/src/stories/TreeView.stories.tsx +38 -0
- package/src/stories/VirtualList.stories.tsx +36 -0
- 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 /> };
|