@multicomma/arkcn 0.1.2 → 0.1.3
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/package.json +1 -1
- package/registry/index.json +1 -1
- package/registry/items/accordion.json +302 -0
- package/registry/items/activity-feed.json +408 -0
- package/registry/items/alert-dialog.json +676 -0
- package/registry/items/alert.json +113 -0
- package/registry/items/angle-slider.json +378 -0
- package/registry/items/app-shell.json +2654 -1
- package/registry/items/aspect-ratio.json +36 -0
- package/registry/items/attachment.json +299 -1
- package/registry/items/avatar.json +213 -0
- package/registry/items/badge.json +44 -0
- package/registry/items/breadcrumb.json +191 -0
- package/registry/items/bubble.json +146 -0
- package/registry/items/button-group.json +110 -0
- package/registry/items/button.json +52 -0
- package/registry/items/calendar.json +499 -1
- package/registry/items/canvas.json +446 -1
- package/registry/items/card.json +192 -0
- package/registry/items/carousel.json +208 -1
- package/registry/items/cascader.json +797 -1
- package/registry/items/chart.json +1764 -1
- package/registry/items/chat.json +993 -0
- package/registry/items/checkbox.json +503 -0
- package/registry/items/clipboard.json +308 -0
- package/registry/items/collapsible.json +226 -0
- package/registry/items/color-picker.json +1261 -0
- package/registry/items/combobox.json +1123 -1
- package/registry/items/command.json +837 -1
- package/registry/items/comment-thread.json +952 -1
- package/registry/items/context-menu.json +1344 -1
- package/registry/items/data-grid.json +449 -1
- package/registry/items/data-table.json +1310 -1
- package/registry/items/date-input.json +476 -0
- package/registry/items/dialog.json +592 -0
- package/registry/items/direction.json +29 -1
- package/registry/items/drawer.json +797 -0
- package/registry/items/dropdown-menu.json +1311 -1
- package/registry/items/editable.json +503 -0
- package/registry/items/empty.json +166 -0
- package/registry/items/field.json +286 -1
- package/registry/items/file-upload.json +638 -0
- package/registry/items/floating-panel.json +720 -0
- package/registry/items/floating-toolbar.json +43 -0
- package/registry/items/format.json +109 -0
- package/registry/items/gantt.json +773 -1
- package/registry/items/hooks/use-mobile.json +1 -0
- package/registry/items/hotkeys.json +99 -1
- package/registry/items/hover-card.json +357 -0
- package/registry/items/image-cropper.json +431 -0
- package/registry/items/input-group.json +189 -0
- package/registry/items/input-otp.json +181 -0
- package/registry/items/input.json +28 -0
- package/registry/items/item.json +308 -0
- package/registry/items/json-tree-view.json +336 -0
- package/registry/items/kanban.json +624 -1
- package/registry/items/kbd.json +54 -0
- package/registry/items/label.json +28 -0
- package/registry/items/lib/controllable.json +1 -0
- package/registry/items/lib/data-table-adapter.json +1 -0
- package/registry/items/lib/drag-preview.json +1 -0
- package/registry/items/lib/form.json +1 -0
- package/registry/items/lib/history.json +1 -0
- package/registry/items/lib/row-layout.json +1 -0
- package/registry/items/lib/table.json +1 -0
- package/registry/items/lib/time.json +1 -0
- package/registry/items/lib/utils.json +1 -0
- package/registry/items/live-region.json +43 -0
- package/registry/items/marker.json +96 -0
- package/registry/items/marquee.json +300 -0
- package/registry/items/menubar.json +1282 -1
- package/registry/items/message.json +166 -0
- package/registry/items/navigation-menu.json +561 -1
- package/registry/items/node-graph.json +1037 -1
- package/registry/items/number-input.json +519 -0
- package/registry/items/pagination.json +244 -1
- package/registry/items/password-input.json +311 -0
- package/registry/items/popconfirm.json +566 -0
- package/registry/items/popover.json +615 -0
- package/registry/items/progress.json +516 -0
- package/registry/items/qr-code.json +249 -0
- package/registry/items/query-builder.json +1368 -1
- package/registry/items/radio-group.json +362 -0
- package/registry/items/rating-group.json +304 -0
- package/registry/items/resizable.json +242 -1
- package/registry/items/rich-text-editor.json +934 -1
- package/registry/items/scheduler.json +1013 -1
- package/registry/items/scroll-area.json +227 -0
- package/registry/items/segment-group.json +363 -0
- package/registry/items/select.json +956 -0
- package/registry/items/separator.json +45 -1
- package/registry/items/sheet.json +592 -0
- package/registry/items/sidebar.json +785 -1
- package/registry/items/signature-pad.json +330 -0
- package/registry/items/skeleton.json +28 -0
- package/registry/items/slider.json +799 -0
- package/registry/items/sonner.json +135 -0
- package/registry/items/spinner.json +28 -0
- package/registry/items/steps.json +483 -0
- package/registry/items/swap.json +107 -0
- package/registry/items/switch.json +362 -0
- package/registry/items/table.json +210 -0
- package/registry/items/tabs.json +326 -0
- package/registry/items/tags-input.json +640 -0
- package/registry/items/textarea.json +28 -0
- package/registry/items/timer.json +289 -0
- package/registry/items/toc.json +372 -0
- package/registry/items/toggle-group.json +214 -1
- package/registry/items/toggle.json +124 -0
- package/registry/items/tooltip.json +417 -0
- package/registry/items/tour.json +498 -0
- package/registry/items/transfer-list.json +609 -1
- package/registry/items/tree-select.json +742 -1
- package/registry/items/tree-view.json +794 -0
- package/registry/items/virtual-list.json +214 -1
- package/src/components/ui/app-shell.tsx +11 -0
- package/src/components/ui/attachment.tsx +1 -0
- package/src/components/ui/calendar.tsx +1 -0
- package/src/components/ui/canvas.tsx +8 -0
- package/src/components/ui/carousel.tsx +3 -0
- package/src/components/ui/cascader.tsx +1 -0
- package/src/components/ui/chart.tsx +10 -0
- package/src/components/ui/combobox.tsx +3 -0
- package/src/components/ui/command.tsx +1 -0
- package/src/components/ui/comment-thread.tsx +11 -0
- package/src/components/ui/context-menu.tsx +6 -0
- package/src/components/ui/data-grid.tsx +8 -0
- package/src/components/ui/data-table.tsx +6 -0
- package/src/components/ui/direction.tsx +2 -0
- package/src/components/ui/dropdown-menu.tsx +6 -0
- package/src/components/ui/field.tsx +1 -0
- package/src/components/ui/gantt.tsx +15 -0
- package/src/components/ui/hotkeys.tsx +3 -0
- package/src/components/ui/kanban.tsx +3 -0
- package/src/components/ui/menubar.tsx +6 -0
- package/src/components/ui/navigation-menu.tsx +1 -0
- package/src/components/ui/node-graph.tsx +23 -0
- package/src/components/ui/pagination.tsx +1 -0
- package/src/components/ui/query-builder.tsx +7 -0
- package/src/components/ui/resizable.tsx +1 -0
- package/src/components/ui/rich-text-editor.tsx +54 -0
- package/src/components/ui/scheduler.tsx +26 -0
- package/src/components/ui/separator.tsx +1 -0
- package/src/components/ui/sidebar.tsx +5 -0
- package/src/components/ui/toggle-group.tsx +3 -0
- package/src/components/ui/transfer-list.tsx +11 -0
- package/src/components/ui/tree-select.tsx +5 -0
- package/src/components/ui/virtual-list.tsx +7 -0
|
@@ -11,6 +11,219 @@
|
|
|
11
11
|
"VirtualListViewport",
|
|
12
12
|
"useVirtualList"
|
|
13
13
|
],
|
|
14
|
+
"parts": [
|
|
15
|
+
{
|
|
16
|
+
"name": "VirtualList",
|
|
17
|
+
"props": [
|
|
18
|
+
{
|
|
19
|
+
"name": "children",
|
|
20
|
+
"type": "ReactNode",
|
|
21
|
+
"required": true,
|
|
22
|
+
"description": "Content rendered inside the part.",
|
|
23
|
+
"from": "arkcn"
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"name": "count",
|
|
27
|
+
"type": "number",
|
|
28
|
+
"required": true,
|
|
29
|
+
"description": "Total number of rows.",
|
|
30
|
+
"from": "arkcn"
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"name": "estimateSize",
|
|
34
|
+
"type": "number | ((index: number) => number)",
|
|
35
|
+
"required": false,
|
|
36
|
+
"default": "36",
|
|
37
|
+
"description": "Row height before measurement (a number, or per index).",
|
|
38
|
+
"from": "arkcn"
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
"name": "gap",
|
|
42
|
+
"type": "number",
|
|
43
|
+
"required": false,
|
|
44
|
+
"default": "0",
|
|
45
|
+
"description": "Space between rows in px.",
|
|
46
|
+
"from": "arkcn"
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
"name": "getItemKey",
|
|
50
|
+
"type": "((index: number) => Key)",
|
|
51
|
+
"required": false,
|
|
52
|
+
"default": "(index) => index",
|
|
53
|
+
"description": "Stable React key for a row index.",
|
|
54
|
+
"from": "arkcn"
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
"name": "onRangeChange",
|
|
58
|
+
"type": "((range: { start: number; end: number; } | null) => void)",
|
|
59
|
+
"required": false,
|
|
60
|
+
"description": "Fires when the rendered window changes.",
|
|
61
|
+
"from": "arkcn"
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
"name": "overscan",
|
|
65
|
+
"type": "number",
|
|
66
|
+
"required": false,
|
|
67
|
+
"default": "6",
|
|
68
|
+
"description": "Rows rendered beyond the visible window on each side.",
|
|
69
|
+
"from": "arkcn"
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
"name": "paddingEnd",
|
|
73
|
+
"type": "number",
|
|
74
|
+
"required": false,
|
|
75
|
+
"default": "0",
|
|
76
|
+
"description": "Space after the last row in px.",
|
|
77
|
+
"from": "arkcn"
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
"name": "paddingStart",
|
|
81
|
+
"type": "number",
|
|
82
|
+
"required": false,
|
|
83
|
+
"default": "0",
|
|
84
|
+
"description": "Space before the first row in px.",
|
|
85
|
+
"from": "arkcn"
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
"name": "className",
|
|
89
|
+
"type": "string",
|
|
90
|
+
"required": false,
|
|
91
|
+
"description": "Extra classes merged into the part's own classes with `cn`.",
|
|
92
|
+
"from": "@types/react"
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
"name": "style",
|
|
96
|
+
"type": "CSSProperties",
|
|
97
|
+
"required": false,
|
|
98
|
+
"description": "Inline styles applied to the rendered element.",
|
|
99
|
+
"from": "@types/react"
|
|
100
|
+
}
|
|
101
|
+
]
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
"name": "VirtualListContent",
|
|
105
|
+
"props": [
|
|
106
|
+
{
|
|
107
|
+
"name": "children",
|
|
108
|
+
"type": "ReactNode",
|
|
109
|
+
"required": false,
|
|
110
|
+
"description": "Content rendered inside the part.",
|
|
111
|
+
"from": "@types/react"
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
"name": "className",
|
|
115
|
+
"type": "string",
|
|
116
|
+
"required": false,
|
|
117
|
+
"description": "Extra classes merged into the part's own classes with `cn`.",
|
|
118
|
+
"from": "@types/react"
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
"name": "style",
|
|
122
|
+
"type": "CSSProperties",
|
|
123
|
+
"required": false,
|
|
124
|
+
"description": "Inline styles applied to the rendered element.",
|
|
125
|
+
"from": "@types/react"
|
|
126
|
+
}
|
|
127
|
+
]
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
"name": "VirtualListEmpty",
|
|
131
|
+
"props": [
|
|
132
|
+
{
|
|
133
|
+
"name": "children",
|
|
134
|
+
"type": "ReactNode",
|
|
135
|
+
"required": false,
|
|
136
|
+
"description": "Content rendered inside the part.",
|
|
137
|
+
"from": "@types/react"
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
"name": "className",
|
|
141
|
+
"type": "string",
|
|
142
|
+
"required": false,
|
|
143
|
+
"description": "Extra classes merged into the part's own classes with `cn`.",
|
|
144
|
+
"from": "@types/react"
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
"name": "style",
|
|
148
|
+
"type": "CSSProperties",
|
|
149
|
+
"required": false,
|
|
150
|
+
"description": "Inline styles applied to the rendered element.",
|
|
151
|
+
"from": "@types/react"
|
|
152
|
+
}
|
|
153
|
+
]
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
"name": "VirtualListItem",
|
|
157
|
+
"props": [
|
|
158
|
+
{
|
|
159
|
+
"name": "index",
|
|
160
|
+
"type": "number",
|
|
161
|
+
"required": true,
|
|
162
|
+
"description": "Position of the item in its list.",
|
|
163
|
+
"from": "arkcn"
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
"name": "children",
|
|
167
|
+
"type": "ReactNode",
|
|
168
|
+
"required": false,
|
|
169
|
+
"description": "Content rendered inside the part.",
|
|
170
|
+
"from": "@types/react"
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
"name": "className",
|
|
174
|
+
"type": "string",
|
|
175
|
+
"required": false,
|
|
176
|
+
"description": "Extra classes merged into the part's own classes with `cn`.",
|
|
177
|
+
"from": "@types/react"
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
"name": "style",
|
|
181
|
+
"type": "CSSProperties",
|
|
182
|
+
"required": false,
|
|
183
|
+
"description": "Inline styles applied to the rendered element.",
|
|
184
|
+
"from": "@types/react"
|
|
185
|
+
}
|
|
186
|
+
]
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
"name": "VirtualListItems",
|
|
190
|
+
"props": [
|
|
191
|
+
{
|
|
192
|
+
"name": "children",
|
|
193
|
+
"type": "(item: VirtualItem) => ReactNode",
|
|
194
|
+
"required": true,
|
|
195
|
+
"description": "Content rendered inside the part.",
|
|
196
|
+
"from": "arkcn"
|
|
197
|
+
}
|
|
198
|
+
]
|
|
199
|
+
},
|
|
200
|
+
{
|
|
201
|
+
"name": "VirtualListViewport",
|
|
202
|
+
"props": [
|
|
203
|
+
{
|
|
204
|
+
"name": "children",
|
|
205
|
+
"type": "ReactNode",
|
|
206
|
+
"required": false,
|
|
207
|
+
"description": "Content rendered inside the part.",
|
|
208
|
+
"from": "@types/react"
|
|
209
|
+
},
|
|
210
|
+
{
|
|
211
|
+
"name": "className",
|
|
212
|
+
"type": "string",
|
|
213
|
+
"required": false,
|
|
214
|
+
"description": "Extra classes merged into the part's own classes with `cn`.",
|
|
215
|
+
"from": "@types/react"
|
|
216
|
+
},
|
|
217
|
+
{
|
|
218
|
+
"name": "style",
|
|
219
|
+
"type": "CSSProperties",
|
|
220
|
+
"required": false,
|
|
221
|
+
"description": "Inline styles applied to the rendered element.",
|
|
222
|
+
"from": "@types/react"
|
|
223
|
+
}
|
|
224
|
+
]
|
|
225
|
+
}
|
|
226
|
+
],
|
|
14
227
|
"dependencies": {},
|
|
15
228
|
"registryDependencies": [
|
|
16
229
|
"lib/utils"
|
|
@@ -18,7 +231,7 @@
|
|
|
18
231
|
"files": [
|
|
19
232
|
{
|
|
20
233
|
"path": "components/ui/virtual-list.tsx",
|
|
21
|
-
"content": "\"use client\"\n\nimport * as React from \"react\"\nimport { cn } from \"@/lib/utils\"\n\n/* -------------------------------- context -------------------------------- */\n\ntype VirtualItem = {\n index: number\n key: React.Key\n /** Offset from the top of the content, in px. */\n start: number\n size: number\n end: number\n /** False until the row's real height has been measured. */\n measured: boolean\n}\n\ntype ScrollAlign = \"auto\" | \"start\" | \"center\" | \"end\"\n\ntype VirtualListContextValue = {\n count: number\n items: VirtualItem[]\n totalSize: number\n /** First and last rendered index (overscan included), or null when empty. */\n range: { start: number; end: number } | null\n measure: (index: number, size: number) => void\n scrollToIndex: (index: number, options?: { align?: ScrollAlign; behavior?: ScrollBehavior }) => void\n scrollToOffset: (offset: number, behavior?: ScrollBehavior) => void\n viewportRef: React.RefObject<HTMLDivElement | null>\n getItemKey: (index: number) => React.Key\n}\n\nconst VirtualListContext = React.createContext<VirtualListContextValue | null>(null)\n\nfunction useVirtualList() {\n const ctx = React.useContext(VirtualListContext)\n if (!ctx) throw new Error(\"VirtualList parts must be used inside <VirtualList>\")\n return ctx\n}\n\n/* -------------------------------- helpers -------------------------------- */\n\n/** Largest index whose start is <= offset. */\nfunction findIndex(starts: Float64Array, count: number, offset: number) {\n let lo = 0\n let hi = count - 1\n while (lo < hi) {\n const mid = (lo + hi + 1) >> 1\n if (starts[mid] <= offset) lo = mid\n else hi = mid - 1\n }\n return lo\n}\n\n/* ---------------------------------- root --------------------------------- */\n\ntype VirtualListProps = Omit<React.ComponentProps<\"div\">, \"children\"> & {\n count: number\n /** Row height before measurement (a number, or per index). */\n estimateSize?: number | ((index: number) => number)\n /** Rows rendered beyond the visible window on each side. */\n overscan?: number\n gap?: number\n paddingStart?: number\n paddingEnd?: number\n getItemKey?: (index: number) => React.Key\n /** Fires when the rendered window changes. */\n onRangeChange?: (range: { start: number; end: number } | null) => void\n children: React.ReactNode\n}\n\nfunction VirtualList({\n count,\n estimateSize = 36,\n overscan = 6,\n gap = 0,\n paddingStart = 0,\n paddingEnd = 0,\n getItemKey = (index) => index,\n onRangeChange,\n className,\n children,\n ...props\n}: VirtualListProps) {\n const viewportRef = React.useRef<HTMLDivElement | null>(null)\n const sizes = React.useRef(new Map<number, number>())\n const [version, setVersion] = React.useState(0)\n const [scrollTop, setScrollTop] = React.useState(0)\n const [viewportSize, setViewportSize] = React.useState(0)\n const estimate = React.useCallback(\n (index: number) => (typeof estimateSize === \"function\" ? estimateSize(index) : estimateSize),\n [estimateSize]\n )\n\n // Prefix sums of row starts; recomputed when a measurement or the count changes.\n const layout = React.useMemo(() => {\n const starts = new Float64Array(count)\n let offset = paddingStart\n for (let i = 0; i < count; i++) {\n starts[i] = offset\n offset += (sizes.current.get(i) ?? estimate(i)) + (i < count - 1 ? gap : 0)\n }\n return { starts, totalSize: offset + paddingEnd }\n // `version` is the measurement tick.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [count, estimate, gap, paddingStart, paddingEnd, version])\n\n const range = React.useMemo(() => {\n if (count === 0 || viewportSize === 0) return count === 0 ? null : { start: 0, end: Math.min(count - 1, overscan) }\n const first = findIndex(layout.starts, count, scrollTop)\n const last = findIndex(layout.starts, count, scrollTop + viewportSize)\n return { start: Math.max(0, first - overscan), end: Math.min(count - 1, last + overscan) }\n }, [count, viewportSize, scrollTop, layout, overscan])\n\n const items = React.useMemo(() => {\n if (!range) return []\n const out: VirtualItem[] = []\n for (let i = range.start; i <= range.end; i++) {\n const measured = sizes.current.has(i)\n const size = sizes.current.get(i) ?? estimate(i)\n out.push({ index: i, key: getItemKey(i), start: layout.starts[i], size, end: layout.starts[i] + size, measured })\n }\n return out\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [range, layout, estimate, getItemKey, version])\n\n const onRangeRef = React.useRef(onRangeChange)\n onRangeRef.current = onRangeChange\n const lastRange = React.useRef<string>(\"\")\n React.useEffect(() => {\n const key = range ? `${range.start}-${range.end}` : \"none\"\n if (key === lastRange.current) return\n lastRange.current = key\n onRangeRef.current?.(range)\n }, [range])\n\n // Drop stale measurements when the list shrinks.\n React.useEffect(() => {\n let dirty = false\n for (const index of sizes.current.keys()) {\n if (index >= count) {\n sizes.current.delete(index)\n dirty = true\n }\n }\n if (dirty) setVersion((v) => v + 1)\n }, [count])\n\n const measure = React.useCallback((index: number, size: number) => {\n const prev = sizes.current.get(index)\n if (prev !== undefined && Math.abs(prev - size) < 0.5) return\n sizes.current.set(index, size)\n setVersion((v) => v + 1)\n }, [])\n\n React.useLayoutEffect(() => {\n const el = viewportRef.current\n if (!el) return\n const update = () => setViewportSize(el.clientHeight)\n update()\n const ro = new ResizeObserver(update)\n ro.observe(el)\n return () => ro.disconnect()\n }, [])\n\n const scrollToOffset = React.useCallback((offset: number, behavior: ScrollBehavior = \"auto\") => {\n viewportRef.current?.scrollTo({ top: offset, behavior })\n }, [])\n\n const pending = React.useRef<{ index: number; align: ScrollAlign; tries: number } | null>(null)\n const offsetFor = React.useCallback(\n (index: number, align: ScrollAlign) => {\n const el = viewportRef.current\n if (!el) return 0\n const start = layout.starts[index] ?? 0\n const size = sizes.current.get(index) ?? estimate(index)\n const height = el.clientHeight\n let resolved = align\n if (align === \"auto\") {\n if (start < el.scrollTop) resolved = \"start\"\n else if (start + size > el.scrollTop + height) resolved = \"end\"\n else return el.scrollTop\n }\n if (resolved === \"start\") return start\n if (resolved === \"end\") return start + size - height\n return start + size / 2 - height / 2\n },\n [layout, estimate]\n )\n\n const scrollToIndex = React.useCallback(\n (index: number, options?: { align?: ScrollAlign; behavior?: ScrollBehavior }) => {\n const clamped = Math.max(0, Math.min(count - 1, index))\n const align = options?.align ?? \"auto\"\n pending.current = { index: clamped, align, tries: 0 }\n scrollToOffset(offsetFor(clamped, align), options?.behavior)\n },\n [count, offsetFor, scrollToOffset]\n )\n\n // Estimated rows shift as they get measured: re-aim at the target a few times.\n React.useLayoutEffect(() => {\n const p = pending.current\n const el = viewportRef.current\n if (!p || !el) return\n if (p.tries >= 4 || !sizes.current.has(p.index)) {\n if (p.tries >= 4) pending.current = null\n return\n }\n const target = offsetFor(p.index, p.align === \"auto\" ? \"start\" : p.align)\n if (Math.abs(el.scrollTop - target) > 1) {\n p.tries += 1\n el.scrollTop = target\n } else pending.current = null\n })\n\n const ctx: VirtualListContextValue = {\n count,\n items,\n totalSize: layout.totalSize,\n range,\n measure,\n scrollToIndex,\n scrollToOffset,\n viewportRef,\n getItemKey,\n }\n\n return (\n <VirtualListContext.Provider value={ctx}>\n <VirtualListScroll onScroll={setScrollTop} />\n <div data-slot=\"virtual-list\" className={cn(\"flex min-h-0 flex-col\", className)} {...props}>\n {children}\n </div>\n </VirtualListContext.Provider>\n )\n}\n\n/** Attaches the scroll listener once the viewport mounts (kept out of the root's render). */\nfunction VirtualListScroll({ onScroll }: { onScroll: (top: number) => void }) {\n const ctx = useVirtualList()\n React.useEffect(() => {\n const el = ctx.viewportRef.current\n if (!el) return\n const handler = () => onScroll(el.scrollTop)\n el.addEventListener(\"scroll\", handler, { passive: true })\n return () => el.removeEventListener(\"scroll\", handler)\n }, [ctx.viewportRef, onScroll])\n return null\n}\n\n/* --------------------------------- parts --------------------------------- */\n\nfunction VirtualListViewport({ className, ...props }: React.ComponentProps<\"div\">) {\n const ctx = useVirtualList()\n return (\n <div\n ref={ctx.viewportRef}\n data-slot=\"virtual-list-viewport\"\n className={cn(\"relative min-h-0 overflow-y-auto overscroll-contain\", className)}\n {...props}\n />\n )\n}\n\n/** Sized to the whole list so the scrollbar is right; rows position inside it. */\nfunction VirtualListContent({ className, style, ...props }: React.ComponentProps<\"div\">) {\n const ctx = useVirtualList()\n return (\n <div\n data-slot=\"virtual-list-content\"\n className={cn(\"relative w-full\", className)}\n style={{ height: ctx.totalSize, ...style }}\n {...props}\n />\n )\n}\n\n/** Renders the visible window. The child renders one row and must return a `VirtualListItem`. */\nfunction VirtualListItems({ children }: { children: (item: VirtualItem) => React.ReactNode }) {\n const ctx = useVirtualList()\n return (\n <>\n {ctx.items.map((item) => (\n <React.Fragment key={item.key}>{children(item)}</React.Fragment>\n ))}\n </>\n )\n}\n\nfunction VirtualListItem({ index, className, style, ...props }: React.ComponentProps<\"div\"> & { index: number }) {\n const { items, measure } = useVirtualList()\n const item = items.find((i) => i.index === index)\n const ref = React.useCallback(\n (el: HTMLDivElement | null) => {\n if (!el) return\n const run = () => measure(index, el.getBoundingClientRect().height)\n run()\n const ro = new ResizeObserver(run)\n ro.observe(el)\n // React 19 ref cleanup\n return () => ro.disconnect()\n },\n [measure, index]\n )\n return (\n <div\n ref={ref}\n data-slot=\"virtual-list-item\"\n data-index={index}\n className={cn(\"absolute inset-x-0 top-0\", className)}\n style={{ transform: `translateY(${item?.start ?? 0}px)`, ...style }}\n {...props}\n />\n )\n}\n\nfunction VirtualListEmpty({ className, children, ...props }: React.ComponentProps<\"div\">) {\n const ctx = useVirtualList()\n if (ctx.count > 0) return null\n return (\n <div\n data-slot=\"virtual-list-empty\"\n className={cn(\"flex flex-1 items-center justify-center py-8 text-sm text-muted-foreground\", className)}\n {...props}\n >\n {children ?? \"Nothing to show\"}\n </div>\n )\n}\n\nexport {\n VirtualList,\n VirtualListContent,\n VirtualListEmpty,\n VirtualListItem,\n VirtualListItems,\n VirtualListViewport,\n useVirtualList,\n type VirtualItem,\n type VirtualListProps,\n}\n"
|
|
234
|
+
"content": "\"use client\"\n\nimport * as React from \"react\"\nimport { cn } from \"@/lib/utils\"\n\n/* -------------------------------- context -------------------------------- */\n\ntype VirtualItem = {\n index: number\n key: React.Key\n /** Offset from the top of the content, in px. */\n start: number\n size: number\n end: number\n /** False until the row's real height has been measured. */\n measured: boolean\n}\n\ntype ScrollAlign = \"auto\" | \"start\" | \"center\" | \"end\"\n\ntype VirtualListContextValue = {\n /** Total number of rows. */\n count: number\n items: VirtualItem[]\n totalSize: number\n /** First and last rendered index (overscan included), or null when empty. */\n range: { start: number; end: number } | null\n measure: (index: number, size: number) => void\n scrollToIndex: (index: number, options?: { align?: ScrollAlign; behavior?: ScrollBehavior }) => void\n scrollToOffset: (offset: number, behavior?: ScrollBehavior) => void\n viewportRef: React.RefObject<HTMLDivElement | null>\n /** Stable React key for a row index. */\n getItemKey: (index: number) => React.Key\n}\n\nconst VirtualListContext = React.createContext<VirtualListContextValue | null>(null)\n\nfunction useVirtualList() {\n const ctx = React.useContext(VirtualListContext)\n if (!ctx) throw new Error(\"VirtualList parts must be used inside <VirtualList>\")\n return ctx\n}\n\n/* -------------------------------- helpers -------------------------------- */\n\n/** Largest index whose start is <= offset. */\nfunction findIndex(starts: Float64Array, count: number, offset: number) {\n let lo = 0\n let hi = count - 1\n while (lo < hi) {\n const mid = (lo + hi + 1) >> 1\n if (starts[mid] <= offset) lo = mid\n else hi = mid - 1\n }\n return lo\n}\n\n/* ---------------------------------- root --------------------------------- */\n\ntype VirtualListProps = Omit<React.ComponentProps<\"div\">, \"children\"> & {\n /** Total number of rows. */\n count: number\n /** Row height before measurement (a number, or per index). */\n estimateSize?: number | ((index: number) => number)\n /** Rows rendered beyond the visible window on each side. */\n overscan?: number\n /** Space between rows in px. */\n gap?: number\n /** Space before the first row in px. */\n paddingStart?: number\n /** Space after the last row in px. */\n paddingEnd?: number\n /** Stable React key for a row index. */\n getItemKey?: (index: number) => React.Key\n /** Fires when the rendered window changes. */\n onRangeChange?: (range: { start: number; end: number } | null) => void\n children: React.ReactNode\n}\n\nfunction VirtualList({\n count,\n estimateSize = 36,\n overscan = 6,\n gap = 0,\n paddingStart = 0,\n paddingEnd = 0,\n getItemKey = (index) => index,\n onRangeChange,\n className,\n children,\n ...props\n}: VirtualListProps) {\n const viewportRef = React.useRef<HTMLDivElement | null>(null)\n const sizes = React.useRef(new Map<number, number>())\n const [version, setVersion] = React.useState(0)\n const [scrollTop, setScrollTop] = React.useState(0)\n const [viewportSize, setViewportSize] = React.useState(0)\n const estimate = React.useCallback(\n (index: number) => (typeof estimateSize === \"function\" ? estimateSize(index) : estimateSize),\n [estimateSize]\n )\n\n // Prefix sums of row starts; recomputed when a measurement or the count changes.\n const layout = React.useMemo(() => {\n const starts = new Float64Array(count)\n let offset = paddingStart\n for (let i = 0; i < count; i++) {\n starts[i] = offset\n offset += (sizes.current.get(i) ?? estimate(i)) + (i < count - 1 ? gap : 0)\n }\n return { starts, totalSize: offset + paddingEnd }\n // `version` is the measurement tick.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [count, estimate, gap, paddingStart, paddingEnd, version])\n\n const range = React.useMemo(() => {\n if (count === 0 || viewportSize === 0) return count === 0 ? null : { start: 0, end: Math.min(count - 1, overscan) }\n const first = findIndex(layout.starts, count, scrollTop)\n const last = findIndex(layout.starts, count, scrollTop + viewportSize)\n return { start: Math.max(0, first - overscan), end: Math.min(count - 1, last + overscan) }\n }, [count, viewportSize, scrollTop, layout, overscan])\n\n const items = React.useMemo(() => {\n if (!range) return []\n const out: VirtualItem[] = []\n for (let i = range.start; i <= range.end; i++) {\n const measured = sizes.current.has(i)\n const size = sizes.current.get(i) ?? estimate(i)\n out.push({ index: i, key: getItemKey(i), start: layout.starts[i], size, end: layout.starts[i] + size, measured })\n }\n return out\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [range, layout, estimate, getItemKey, version])\n\n const onRangeRef = React.useRef(onRangeChange)\n onRangeRef.current = onRangeChange\n const lastRange = React.useRef<string>(\"\")\n React.useEffect(() => {\n const key = range ? `${range.start}-${range.end}` : \"none\"\n if (key === lastRange.current) return\n lastRange.current = key\n onRangeRef.current?.(range)\n }, [range])\n\n // Drop stale measurements when the list shrinks.\n React.useEffect(() => {\n let dirty = false\n for (const index of sizes.current.keys()) {\n if (index >= count) {\n sizes.current.delete(index)\n dirty = true\n }\n }\n if (dirty) setVersion((v) => v + 1)\n }, [count])\n\n const measure = React.useCallback((index: number, size: number) => {\n const prev = sizes.current.get(index)\n if (prev !== undefined && Math.abs(prev - size) < 0.5) return\n sizes.current.set(index, size)\n setVersion((v) => v + 1)\n }, [])\n\n React.useLayoutEffect(() => {\n const el = viewportRef.current\n if (!el) return\n const update = () => setViewportSize(el.clientHeight)\n update()\n const ro = new ResizeObserver(update)\n ro.observe(el)\n return () => ro.disconnect()\n }, [])\n\n const scrollToOffset = React.useCallback((offset: number, behavior: ScrollBehavior = \"auto\") => {\n viewportRef.current?.scrollTo({ top: offset, behavior })\n }, [])\n\n const pending = React.useRef<{ index: number; align: ScrollAlign; tries: number } | null>(null)\n const offsetFor = React.useCallback(\n (index: number, align: ScrollAlign) => {\n const el = viewportRef.current\n if (!el) return 0\n const start = layout.starts[index] ?? 0\n const size = sizes.current.get(index) ?? estimate(index)\n const height = el.clientHeight\n let resolved = align\n if (align === \"auto\") {\n if (start < el.scrollTop) resolved = \"start\"\n else if (start + size > el.scrollTop + height) resolved = \"end\"\n else return el.scrollTop\n }\n if (resolved === \"start\") return start\n if (resolved === \"end\") return start + size - height\n return start + size / 2 - height / 2\n },\n [layout, estimate]\n )\n\n const scrollToIndex = React.useCallback(\n (index: number, options?: { align?: ScrollAlign; behavior?: ScrollBehavior }) => {\n const clamped = Math.max(0, Math.min(count - 1, index))\n const align = options?.align ?? \"auto\"\n pending.current = { index: clamped, align, tries: 0 }\n scrollToOffset(offsetFor(clamped, align), options?.behavior)\n },\n [count, offsetFor, scrollToOffset]\n )\n\n // Estimated rows shift as they get measured: re-aim at the target a few times.\n React.useLayoutEffect(() => {\n const p = pending.current\n const el = viewportRef.current\n if (!p || !el) return\n if (p.tries >= 4 || !sizes.current.has(p.index)) {\n if (p.tries >= 4) pending.current = null\n return\n }\n const target = offsetFor(p.index, p.align === \"auto\" ? \"start\" : p.align)\n if (Math.abs(el.scrollTop - target) > 1) {\n p.tries += 1\n el.scrollTop = target\n } else pending.current = null\n })\n\n const ctx: VirtualListContextValue = {\n count,\n items,\n totalSize: layout.totalSize,\n range,\n measure,\n scrollToIndex,\n scrollToOffset,\n viewportRef,\n getItemKey,\n }\n\n return (\n <VirtualListContext.Provider value={ctx}>\n <VirtualListScroll onScroll={setScrollTop} />\n <div data-slot=\"virtual-list\" className={cn(\"flex min-h-0 flex-col\", className)} {...props}>\n {children}\n </div>\n </VirtualListContext.Provider>\n )\n}\n\n/** Attaches the scroll listener once the viewport mounts (kept out of the root's render). */\nfunction VirtualListScroll({ onScroll }: { onScroll: (top: number) => void }) {\n const ctx = useVirtualList()\n React.useEffect(() => {\n const el = ctx.viewportRef.current\n if (!el) return\n const handler = () => onScroll(el.scrollTop)\n el.addEventListener(\"scroll\", handler, { passive: true })\n return () => el.removeEventListener(\"scroll\", handler)\n }, [ctx.viewportRef, onScroll])\n return null\n}\n\n/* --------------------------------- parts --------------------------------- */\n\nfunction VirtualListViewport({ className, ...props }: React.ComponentProps<\"div\">) {\n const ctx = useVirtualList()\n return (\n <div\n ref={ctx.viewportRef}\n data-slot=\"virtual-list-viewport\"\n className={cn(\"relative min-h-0 overflow-y-auto overscroll-contain\", className)}\n {...props}\n />\n )\n}\n\n/** Sized to the whole list so the scrollbar is right; rows position inside it. */\nfunction VirtualListContent({ className, style, ...props }: React.ComponentProps<\"div\">) {\n const ctx = useVirtualList()\n return (\n <div\n data-slot=\"virtual-list-content\"\n className={cn(\"relative w-full\", className)}\n style={{ height: ctx.totalSize, ...style }}\n {...props}\n />\n )\n}\n\n/** Renders the visible window. The child renders one row and must return a `VirtualListItem`. */\nfunction VirtualListItems({ children }: { children: (item: VirtualItem) => React.ReactNode }) {\n const ctx = useVirtualList()\n return (\n <>\n {ctx.items.map((item) => (\n <React.Fragment key={item.key}>{children(item)}</React.Fragment>\n ))}\n </>\n )\n}\n\nfunction VirtualListItem({ index, className, style, ...props }: React.ComponentProps<\"div\"> & { index: number }) {\n const { items, measure } = useVirtualList()\n const item = items.find((i) => i.index === index)\n const ref = React.useCallback(\n (el: HTMLDivElement | null) => {\n if (!el) return\n const run = () => measure(index, el.getBoundingClientRect().height)\n run()\n const ro = new ResizeObserver(run)\n ro.observe(el)\n // React 19 ref cleanup\n return () => ro.disconnect()\n },\n [measure, index]\n )\n return (\n <div\n ref={ref}\n data-slot=\"virtual-list-item\"\n data-index={index}\n className={cn(\"absolute inset-x-0 top-0\", className)}\n style={{ transform: `translateY(${item?.start ?? 0}px)`, ...style }}\n {...props}\n />\n )\n}\n\nfunction VirtualListEmpty({ className, children, ...props }: React.ComponentProps<\"div\">) {\n const ctx = useVirtualList()\n if (ctx.count > 0) return null\n return (\n <div\n data-slot=\"virtual-list-empty\"\n className={cn(\"flex flex-1 items-center justify-center py-8 text-sm text-muted-foreground\", className)}\n {...props}\n >\n {children ?? \"Nothing to show\"}\n </div>\n )\n}\n\nexport {\n VirtualList,\n VirtualListContent,\n VirtualListEmpty,\n VirtualListItem,\n VirtualListItems,\n VirtualListViewport,\n useVirtualList,\n type VirtualItem,\n type VirtualListProps,\n}\n"
|
|
22
235
|
}
|
|
23
236
|
],
|
|
24
237
|
"docs": "## Virtual list\n`src/components/ui/virtual-list.tsx` is a dependency-free windowed list. `VirtualList { count,\nestimateSize (number or per index), overscan, gap, paddingStart/End, getItemKey, onRangeChange }`\nowns scroll offset, viewport size, and measured row heights (prefix sums + binary search; rows\nmeasure themselves with a ResizeObserver and estimates are replaced as rows appear). Parts:\n`VirtualListViewport` (the scroll container; **the consumer gives it a height**, it must never be\n`flex-1` in an auto-height column or it grows to the whole content and renders every row) >\n`VirtualListContent` (sized to `totalSize`) > `VirtualListItems` (render-prop child gets\n`{ index, key, start, size, measured }` and returns a `VirtualListItem index=`, absolutely\npositioned via translateY, `data-index`), `VirtualListEmpty`. `useVirtualList()` exposes `items`,\n`range`, `totalSize`, `scrollToIndex(index, { align: auto|start|center|end })` (re-aims a few times\nwhile estimated rows get measured) and `scrollToOffset`. Keyboard is the consumer's: keep focus on\nthe viewport with `aria-activedescendant` and call `scrollToIndex` for the active row, as the\ncontacts listbox in `src/demo/virtual-list/` does (`/audit-log`, \"Audit log\": a 100k-line log with mixed\nheights and go-to-line, and a 10k-contact listbox with search)."
|
|
@@ -60,6 +60,7 @@ type SidebarCollapsible = "offcanvas" | "icon" | "none"
|
|
|
60
60
|
type AppShellContextValue = {
|
|
61
61
|
variant: SidebarVariant
|
|
62
62
|
setVariant: (variant: SidebarVariant) => void
|
|
63
|
+
/** Controlled sidebar collapse mode: `offcanvas`, `icon`, or `none`. */
|
|
63
64
|
collapsible: SidebarCollapsible
|
|
64
65
|
setCollapsible: (collapsible: SidebarCollapsible) => void
|
|
65
66
|
searchOpen: boolean
|
|
@@ -91,10 +92,15 @@ function AppShell({
|
|
|
91
92
|
...sidebarProviderProps
|
|
92
93
|
}: Omit<React.ComponentProps<typeof SidebarProvider>, "children"> & {
|
|
93
94
|
variant?: SidebarVariant
|
|
95
|
+
/** Initial sidebar variant when uncontrolled. */
|
|
94
96
|
defaultVariant?: SidebarVariant
|
|
97
|
+
/** Called when the sidebar variant changes. */
|
|
95
98
|
onVariantChange?: (variant: SidebarVariant) => void
|
|
99
|
+
/** Controlled sidebar collapse mode: `offcanvas`, `icon`, or `none`. */
|
|
96
100
|
collapsible?: SidebarCollapsible
|
|
101
|
+
/** Initial collapse mode when uncontrolled. */
|
|
97
102
|
defaultCollapsible?: SidebarCollapsible
|
|
103
|
+
/** Called when the collapse mode changes. */
|
|
98
104
|
onCollapsibleChange?: (collapsible: SidebarCollapsible) => void
|
|
99
105
|
/** Toggle the search dialog with Cmd/Ctrl+K. */
|
|
100
106
|
searchShortcut?: boolean
|
|
@@ -308,6 +314,7 @@ function AppShellNavCollapsible({
|
|
|
308
314
|
active?: boolean
|
|
309
315
|
defaultOpen?: boolean
|
|
310
316
|
tooltip?: string
|
|
317
|
+
/** Element rendered as the trigger for the collapsible group. */
|
|
311
318
|
trigger: React.ReactNode
|
|
312
319
|
/** Rendered inside the flyout when the sidebar is collapsed to icons. */
|
|
313
320
|
menu?: React.ReactNode
|
|
@@ -674,6 +681,7 @@ function AppShellNotificationsTrigger({
|
|
|
674
681
|
}: React.ComponentProps<typeof Button> & {
|
|
675
682
|
/** Unread count shown as a badge; hidden when 0. */
|
|
676
683
|
count?: number
|
|
684
|
+
/** Cap for the badge count; larger counts render as `max+`. */
|
|
677
685
|
max?: number
|
|
678
686
|
}) {
|
|
679
687
|
const label = count > max ? `${max}+` : String(count)
|
|
@@ -763,6 +771,7 @@ function AppShellNotificationItem({
|
|
|
763
771
|
children,
|
|
764
772
|
...props
|
|
765
773
|
}: React.ComponentProps<"li"> & {
|
|
774
|
+
/** Mark the notification unread; sets `data-unread` and shows the indicator. */
|
|
766
775
|
unread?: boolean
|
|
767
776
|
/** Render the item's child (e.g. an anchor or button) as the interactive surface. */
|
|
768
777
|
asChild?: boolean
|
|
@@ -894,7 +903,9 @@ function AppShellThemeToggle({
|
|
|
894
903
|
className,
|
|
895
904
|
...props
|
|
896
905
|
}: Omit<React.ComponentProps<typeof Button>, "onChange"> & {
|
|
906
|
+
/** Current theme: `light`, `dark`, or `system`. */
|
|
897
907
|
theme: Theme
|
|
908
|
+
/** Called with the next theme when the toggle is used. */
|
|
898
909
|
onThemeChange: (theme: Theme) => void
|
|
899
910
|
}) {
|
|
900
911
|
return (
|
|
@@ -31,6 +31,7 @@ function Attachment({
|
|
|
31
31
|
...props
|
|
32
32
|
}: React.ComponentProps<"div"> &
|
|
33
33
|
VariantProps<typeof attachmentVariants> & {
|
|
34
|
+
/** Upload state: `idle`, `uploading`, `processing`, `error`, or `done`; sets `data-state`. */
|
|
34
35
|
state?: "idle" | "uploading" | "processing" | "error" | "done"
|
|
35
36
|
}) {
|
|
36
37
|
return (
|
|
@@ -18,6 +18,7 @@ function Calendar({
|
|
|
18
18
|
components,
|
|
19
19
|
...props
|
|
20
20
|
}: React.ComponentProps<typeof DayPicker> & {
|
|
21
|
+
/** Button variant used for the navigation buttons. */
|
|
21
22
|
buttonVariant?: React.ComponentProps<typeof Button>["variant"]
|
|
22
23
|
}) {
|
|
23
24
|
const defaultClassNames = getDefaultClassNames()
|
|
@@ -154,6 +154,7 @@ function Canvas({
|
|
|
154
154
|
const data = source.data as DragData
|
|
155
155
|
setDragging(data.type === "node" ? { type: "node", id: data.id } : { type: "palette" })
|
|
156
156
|
},
|
|
157
|
+
/** Called with `{ source, target }` for every drop: a palette item or node onto a node edge or the empty area. */
|
|
157
158
|
onDrop: ({ source, location }) => {
|
|
158
159
|
setDragging(null)
|
|
159
160
|
const data = source.data as DragData
|
|
@@ -257,6 +258,7 @@ function CanvasPaletteItem({ data, className, onKeyDown, ...props }: React.Compo
|
|
|
257
258
|
getInitialData: (): DragData => ({ instanceId, type: "palette", data: dataRef.current }),
|
|
258
259
|
onGenerateDragPreview: (args) => cloneDragPreview(args),
|
|
259
260
|
onDragStart: () => setIsDragging(true),
|
|
261
|
+
/** Called with `{ source, target }` for every drop: a palette item or node onto a node edge or the empty area. */
|
|
260
262
|
onDrop: () => setIsDragging(false),
|
|
261
263
|
})
|
|
262
264
|
}, [instanceId])
|
|
@@ -310,6 +312,7 @@ function CanvasArea({ className, ...props }: React.ComponentProps<"div">) {
|
|
|
310
312
|
setIsOver((prev) => (prev === over ? prev : over))
|
|
311
313
|
},
|
|
312
314
|
onDragLeave: () => setIsOver(false),
|
|
315
|
+
/** Called with `{ source, target }` for every drop: a palette item or node onto a node edge or the empty area. */
|
|
313
316
|
onDrop: () => setIsOver(false),
|
|
314
317
|
}),
|
|
315
318
|
autoScrollForElements({ element: el, canScroll: ({ source }) => isOwnData(instanceId, source.data) })
|
|
@@ -437,6 +440,7 @@ function CanvasNode({
|
|
|
437
440
|
setClosestEdge((prev) => (prev === edge ? prev : edge))
|
|
438
441
|
},
|
|
439
442
|
onDragLeave: () => setClosestEdge(null),
|
|
443
|
+
/** Called with `{ source, target }` for every drop: a palette item or node onto a node edge or the empty area. */
|
|
440
444
|
onDrop: () => setClosestEdge(null),
|
|
441
445
|
}),
|
|
442
446
|
]
|
|
@@ -448,6 +452,7 @@ function CanvasNode({
|
|
|
448
452
|
getInitialData: (): DragData => ({ instanceId, type: "node", id: value }),
|
|
449
453
|
onGenerateDragPreview: (args) => cloneDragPreview(args),
|
|
450
454
|
onDragStart: () => setIsDragging(true),
|
|
455
|
+
/** Called with `{ source, target }` for every drop: a palette item or node onto a node edge or the empty area. */
|
|
451
456
|
onDrop: () => setIsDragging(false),
|
|
452
457
|
})
|
|
453
458
|
)
|
|
@@ -602,8 +607,11 @@ function CanvasResizeHandle({
|
|
|
602
607
|
className,
|
|
603
608
|
...props
|
|
604
609
|
}: Omit<React.ComponentProps<"div">, "onResize"> & {
|
|
610
|
+
/** Called while dragging with the width change as a fraction of the row width. */
|
|
605
611
|
onResize: (deltaFraction: number) => void
|
|
612
|
+
/** Called once when the resize drag starts. */
|
|
606
613
|
onResizeStart?: () => void
|
|
614
|
+
/** Called once when the resize drag ends. */
|
|
607
615
|
onResizeEnd?: () => void
|
|
608
616
|
}) {
|
|
609
617
|
const row = React.useContext(RowContext)
|
|
@@ -11,9 +11,12 @@ type CarouselOptions = UseCarouselParameters[0]
|
|
|
11
11
|
type CarouselPlugin = UseCarouselParameters[1]
|
|
12
12
|
|
|
13
13
|
type CarouselProps = {
|
|
14
|
+
/** Embla carousel options. */
|
|
14
15
|
opts?: CarouselOptions
|
|
16
|
+
/** Embla plugins. */
|
|
15
17
|
plugins?: CarouselPlugin
|
|
16
18
|
orientation?: "horizontal" | "vertical"
|
|
19
|
+
/** Receives the Embla API once the carousel mounts. */
|
|
17
20
|
setApi?: (api: CarouselApi) => void
|
|
18
21
|
}
|
|
19
22
|
|
|
@@ -410,6 +410,7 @@ function CascaderValue<T extends TreeNode>({
|
|
|
410
410
|
...props
|
|
411
411
|
}: Omit<React.ComponentProps<"span">, "children"> & {
|
|
412
412
|
placeholder?: React.ReactNode
|
|
413
|
+
/** Rendered between path segments. */
|
|
413
414
|
separator?: React.ReactNode
|
|
414
415
|
children?: (path: T[]) => React.ReactNode
|
|
415
416
|
}) {
|
|
@@ -20,6 +20,7 @@ export type ChartConfig = Record<
|
|
|
20
20
|
>
|
|
21
21
|
|
|
22
22
|
type ChartContextProps = {
|
|
23
|
+
/** Series config: label, icon, and color (or theme colors) per data key; exposed as `--color-<key>`. */
|
|
23
24
|
config: ChartConfig
|
|
24
25
|
}
|
|
25
26
|
|
|
@@ -43,8 +44,10 @@ function ChartContainer({
|
|
|
43
44
|
initialDimension = INITIAL_DIMENSION,
|
|
44
45
|
...props
|
|
45
46
|
}: React.ComponentProps<"div"> & {
|
|
47
|
+
/** Series config: label, icon, and color (or theme colors) per data key; exposed as `--color-<key>`. */
|
|
46
48
|
config: ChartConfig
|
|
47
49
|
children: React.ComponentProps<typeof RechartsPrimitive.ResponsiveContainer>["children"]
|
|
50
|
+
/** Width and height used before the container measures itself. */
|
|
48
51
|
initialDimension?: {
|
|
49
52
|
width: number
|
|
50
53
|
height: number
|
|
@@ -120,10 +123,15 @@ function ChartTooltipContent({
|
|
|
120
123
|
labelKey,
|
|
121
124
|
}: React.ComponentProps<typeof RechartsPrimitive.Tooltip> &
|
|
122
125
|
React.ComponentProps<"div"> & {
|
|
126
|
+
/** Hide the tooltip label. */
|
|
123
127
|
hideLabel?: boolean
|
|
128
|
+
/** Hide the color indicator in each row. */
|
|
124
129
|
hideIndicator?: boolean
|
|
130
|
+
/** Indicator style: `dot`, `line`, or `dashed`. */
|
|
125
131
|
indicator?: "line" | "dot" | "dashed"
|
|
132
|
+
/** Data key used for the series name. */
|
|
126
133
|
nameKey?: string
|
|
134
|
+
/** Data key used for the tooltip label. */
|
|
127
135
|
labelKey?: string
|
|
128
136
|
} & Omit<RechartsPrimitive.DefaultTooltipContentProps<TooltipValueType, TooltipNameType>, "accessibilityLayer">) {
|
|
129
137
|
const { config } = useChart()
|
|
@@ -238,7 +246,9 @@ function ChartLegendContent({
|
|
|
238
246
|
verticalAlign = "bottom",
|
|
239
247
|
nameKey,
|
|
240
248
|
}: React.ComponentProps<"div"> & {
|
|
249
|
+
/** Hide the series icon. */
|
|
241
250
|
hideIcon?: boolean
|
|
251
|
+
/** Data key used for the series name. */
|
|
242
252
|
nameKey?: string
|
|
243
253
|
} & RechartsPrimitive.DefaultLegendContentProps) {
|
|
244
254
|
const { config } = useChart()
|
|
@@ -120,7 +120,9 @@ function ComboboxInput({
|
|
|
120
120
|
showClear = false,
|
|
121
121
|
...props
|
|
122
122
|
}: Omit<React.ComponentProps<typeof ComboboxPrimitive.Input>, "id"> & {
|
|
123
|
+
/** Render the open/close trigger in the control. */
|
|
123
124
|
showTrigger?: boolean
|
|
125
|
+
/** Render the clear button in the control. */
|
|
124
126
|
showClear?: boolean
|
|
125
127
|
}) {
|
|
126
128
|
return (
|
|
@@ -316,6 +318,7 @@ function ComboboxChip({
|
|
|
316
318
|
}: React.ComponentProps<"div"> & {
|
|
317
319
|
/** The selected value this chip represents. */
|
|
318
320
|
value: string
|
|
321
|
+
/** Render the remove button on the chip. */
|
|
319
322
|
showRemove?: boolean
|
|
320
323
|
}) {
|
|
321
324
|
const combobox = useComboboxContext()
|
|
@@ -35,8 +35,11 @@ import { useControllable } from "@/lib/controllable"
|
|
|
35
35
|
type Person = { id: string; name: string; initials?: string; avatar?: string }
|
|
36
36
|
|
|
37
37
|
type ThreadContextValue = {
|
|
38
|
+
/** People who can be mentioned and whose names and initials are rendered. */
|
|
38
39
|
people: Person[]
|
|
40
|
+
/** Id of the signed-in person; their comments get `data-own` and mentions of them `data-self`. */
|
|
39
41
|
currentUserId?: string
|
|
42
|
+
/** Id of the comment being replied to (controlled). */
|
|
40
43
|
replyTo: string | null
|
|
41
44
|
setReplyTo: (id: string | null) => void
|
|
42
45
|
editing: string | null
|
|
@@ -77,9 +80,13 @@ function CommentThread({
|
|
|
77
80
|
className,
|
|
78
81
|
...props
|
|
79
82
|
}: React.ComponentProps<"div"> & {
|
|
83
|
+
/** People who can be mentioned and whose names and initials are rendered. */
|
|
80
84
|
people?: Person[]
|
|
85
|
+
/** Id of the signed-in person; their comments get `data-own` and mentions of them `data-self`. */
|
|
81
86
|
currentUserId?: string
|
|
87
|
+
/** Id of the comment being replied to (controlled). */
|
|
82
88
|
replyTo?: string | null
|
|
89
|
+
/** Called when the reply target changes. */
|
|
83
90
|
onReplyToChange?: (id: string | null) => void
|
|
84
91
|
}) {
|
|
85
92
|
const [replyTo, setReplyTo] = useControllable<string | null>(replyToProp, null, onReplyToChange)
|
|
@@ -383,6 +390,7 @@ function CommentEmpty({ className, ...props }: React.ComponentProps<"div">) {
|
|
|
383
390
|
type MentionState = { query: string; start: number; index: number } | null
|
|
384
391
|
|
|
385
392
|
type ComposerContextValue = {
|
|
393
|
+
/** People who can be mentioned and whose names and initials are rendered. */
|
|
386
394
|
people: Person[]
|
|
387
395
|
value: string
|
|
388
396
|
setValue: (value: string) => void
|
|
@@ -424,13 +432,16 @@ function CommentComposer({
|
|
|
424
432
|
children,
|
|
425
433
|
...props
|
|
426
434
|
}: Omit<React.ComponentProps<"div">, "onSubmit" | "defaultValue"> & {
|
|
435
|
+
/** People who can be mentioned and whose names and initials are rendered. */
|
|
427
436
|
people?: Person[]
|
|
428
437
|
value?: string
|
|
429
438
|
defaultValue?: string
|
|
430
439
|
onValueChange?: (value: string) => void
|
|
431
440
|
/** Called with the trimmed text and the ids of mentioned people. */
|
|
432
441
|
onSubmit?: (comment: { text: string; mentions: string[] }) => void
|
|
442
|
+
/** Called when the composer is cancelled with Escape or the cancel trigger. */
|
|
433
443
|
onCancel?: () => void
|
|
444
|
+
/** Maximum number of mention suggestions shown. */
|
|
434
445
|
maxSuggestions?: number
|
|
435
446
|
}) {
|
|
436
447
|
const [value, setValue] = useControllable(valueProp, defaultValue, onValueChange)
|
|
@@ -70,6 +70,7 @@ function ContextMenuItem({
|
|
|
70
70
|
...props
|
|
71
71
|
}: Omit<React.ComponentProps<typeof ContextMenuPrimitive.Item>, "value"> & {
|
|
72
72
|
value?: string
|
|
73
|
+
/** Indent the item to align with items that have an indicator. */
|
|
73
74
|
inset?: boolean
|
|
74
75
|
variant?: "default" | "destructive"
|
|
75
76
|
}) {
|
|
@@ -117,8 +118,10 @@ function ContextMenuCheckboxItem({
|
|
|
117
118
|
value,
|
|
118
119
|
...props
|
|
119
120
|
}: Omit<React.ComponentProps<typeof ContextMenuPrimitive.CheckboxItem>, "checked" | "value"> & {
|
|
121
|
+
/** Checked state of the item. */
|
|
120
122
|
checked?: boolean
|
|
121
123
|
value?: string
|
|
124
|
+
/** Indent the item to align with items that have an indicator. */
|
|
122
125
|
inset?: boolean
|
|
123
126
|
}) {
|
|
124
127
|
const id = React.useId()
|
|
@@ -149,6 +152,7 @@ function ContextMenuRadioItem({
|
|
|
149
152
|
inset,
|
|
150
153
|
...props
|
|
151
154
|
}: React.ComponentProps<typeof ContextMenuPrimitive.RadioItem> & {
|
|
155
|
+
/** Indent the item to align with items that have an indicator. */
|
|
152
156
|
inset?: boolean
|
|
153
157
|
}) {
|
|
154
158
|
return (
|
|
@@ -171,6 +175,7 @@ function ContextMenuLabel({
|
|
|
171
175
|
inset,
|
|
172
176
|
...props
|
|
173
177
|
}: React.ComponentProps<typeof ContextMenuPrimitive.ItemGroupLabel> & {
|
|
178
|
+
/** Indent the item to align with items that have an indicator. */
|
|
174
179
|
inset?: boolean
|
|
175
180
|
}) {
|
|
176
181
|
return (
|
|
@@ -220,6 +225,7 @@ function ContextMenuSubTrigger({
|
|
|
220
225
|
children,
|
|
221
226
|
...props
|
|
222
227
|
}: React.ComponentProps<typeof ContextMenuPrimitive.TriggerItem> & {
|
|
228
|
+
/** Indent the item to align with items that have an indicator. */
|
|
223
229
|
inset?: boolean
|
|
224
230
|
}) {
|
|
225
231
|
return (
|
|
@@ -102,6 +102,7 @@ type DataGridContextValue<TData = unknown> = {
|
|
|
102
102
|
layoutById: Map<string, ColumnLayout>
|
|
103
103
|
templateColumns: string
|
|
104
104
|
totalWidth: number
|
|
105
|
+
/** Fixed height of every row in px. */
|
|
105
106
|
rowHeight: number
|
|
106
107
|
setWidth: (columnId: string, width: number) => void
|
|
107
108
|
focus: CellAddress | null
|
|
@@ -166,12 +167,19 @@ function DataGrid<TData>({
|
|
|
166
167
|
...props
|
|
167
168
|
}: Omit<React.ComponentProps<"div">, "children"> & {
|
|
168
169
|
table: DataTableInstance<TData>
|
|
170
|
+
/** Per-column grid behaviour keyed by column id: editor type, width, pinning, options, format and parse. */
|
|
169
171
|
columns?: DataGridColumnConfig[]
|
|
172
|
+
/** Fixed height of every row in px. */
|
|
170
173
|
rowHeight?: number
|
|
174
|
+
/** Rows rendered beyond the visible window on each side. */
|
|
171
175
|
overscan?: number
|
|
176
|
+
/** Called with `{ row, columnId, value, previous }` when a cell edit commits; the consumer applies it. */
|
|
172
177
|
onCellChange?: (change: DataGridCellChange<TData>) => void
|
|
178
|
+
/** Controlled column widths by column id. */
|
|
173
179
|
columnSizing?: Record<string, number>
|
|
180
|
+
/** Initial column widths when uncontrolled. */
|
|
174
181
|
defaultColumnSizing?: Record<string, number>
|
|
182
|
+
/** Called when a column is resized. */
|
|
175
183
|
onColumnSizingChange?: (sizing: Record<string, number>) => void
|
|
176
184
|
children?: React.ReactNode
|
|
177
185
|
}) {
|