@overdoser/react-toolkit 0.0.15 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/manifest.json CHANGED
@@ -4,9 +4,16 @@
4
4
  "version": "0.0.2",
5
5
  "description": "Machine-readable manifest of components, props, allowed values, and hooks. Generated for tooling and AI assistants.",
6
6
  "themeCssImport": "@overdoser/react-toolkit/theme.css",
7
+ "themeCssLayeredImport": "@overdoser/react-toolkit/theme.layered.css",
8
+ "stylingNotes": "className/classes.* overrides are plain CSS classes — they only beat built-in styles if inserted after them. Import theme.layered.css (cascade layer 'crk') instead of theme.css to make any unlayered consumer rule, including classes.*, win deterministically regardless of order. theme.css and theme.layered.css are identical except the layer wrapper — import exactly one (never both); --crk-* token theming works the same in either.",
7
9
  "peerDependencies": {
8
- "required": ["react", "react-dom"],
9
- "optional": ["react-hook-form"]
10
+ "required": [
11
+ "react",
12
+ "react-dom"
13
+ ],
14
+ "optional": [
15
+ "react-hook-form"
16
+ ]
10
17
  },
11
18
  "components": {
12
19
  "Button": {
@@ -15,13 +22,59 @@
15
22
  "extendsNativeProps": "button",
16
23
  "forwardsRef": true,
17
24
  "props": {
18
- "variant": { "type": "enum", "values": ["primary", "secondary", "danger", "ghost"], "default": "primary" },
19
- "size": { "type": "enum", "values": ["sm", "md", "lg"], "default": "md" },
20
- "loading": { "type": "boolean", "default": false, "notes": "Disables the button and sets aria-busy." },
21
- "loadingStyle": { "type": "enum", "values": ["dots", "shimmer", "border"], "default": "dots", "notes": "Only used when loading=true." },
22
- "fullWidth": { "type": "boolean", "default": false },
23
- "iconOnly": { "type": "boolean", "default": false, "notes": "Squares the button and renders content larger/bolder. For single-glyph buttons (‹, ›, ×, +). Always pair with aria-label." },
24
- "classes": { "type": "Partial<ButtonClasses>", "shape": ["root", "content", "shimmer", "dots", "dot"] }
25
+ "variant": {
26
+ "type": "enum",
27
+ "values": [
28
+ "primary",
29
+ "secondary",
30
+ "danger",
31
+ "ghost"
32
+ ],
33
+ "default": "primary"
34
+ },
35
+ "size": {
36
+ "type": "enum",
37
+ "values": [
38
+ "sm",
39
+ "md",
40
+ "lg"
41
+ ],
42
+ "default": "md"
43
+ },
44
+ "loading": {
45
+ "type": "boolean",
46
+ "default": false,
47
+ "notes": "Disables the button and sets aria-busy."
48
+ },
49
+ "loadingStyle": {
50
+ "type": "enum",
51
+ "values": [
52
+ "dots",
53
+ "shimmer",
54
+ "border"
55
+ ],
56
+ "default": "dots",
57
+ "notes": "Only used when loading=true."
58
+ },
59
+ "fullWidth": {
60
+ "type": "boolean",
61
+ "default": false
62
+ },
63
+ "iconOnly": {
64
+ "type": "boolean",
65
+ "default": false,
66
+ "notes": "Squares the button and renders content larger/bolder. For single-glyph buttons (‹, ›, ×, +). Always pair with aria-label."
67
+ },
68
+ "classes": {
69
+ "type": "Partial<ButtonClasses>",
70
+ "shape": [
71
+ "root",
72
+ "content",
73
+ "shimmer",
74
+ "dots",
75
+ "dot"
76
+ ]
77
+ }
25
78
  }
26
79
  },
27
80
  "Link": {
@@ -30,8 +83,25 @@
30
83
  "extendsNativeProps": "a",
31
84
  "forwardsRef": true,
32
85
  "props": {
33
- "variant": { "type": "enum", "values": ["default", "muted", "danger"], "default": "default" },
34
- "external": { "type": "boolean", "default": false, "notes": "Adds target=_blank, rel=noopener noreferrer, and an sr-only 'opens in a new tab' suffix." }
86
+ "variant": {
87
+ "type": "enum",
88
+ "values": [
89
+ "default",
90
+ "muted",
91
+ "danger"
92
+ ],
93
+ "default": "default"
94
+ },
95
+ "external": {
96
+ "type": "boolean",
97
+ "default": false,
98
+ "notes": "Adds target=_blank, rel=noopener noreferrer, an inline ↗ icon, and an sr-only 'opens in a new tab' suffix."
99
+ },
100
+ "hideExternalIcon": {
101
+ "type": "boolean",
102
+ "default": false,
103
+ "notes": "Hide the inline ↗ icon shown by default when the link opens in a new tab (external or manual target=_blank). The sr-only suffix is kept."
104
+ }
35
105
  }
36
106
  },
37
107
  "Typography": {
@@ -40,11 +110,53 @@
40
110
  "extendsNativeProps": "p (without color)",
41
111
  "forwardsRef": true,
42
112
  "props": {
43
- "variant": { "type": "enum", "values": ["h1", "h2", "h3", "h4", "h5", "h6", "p", "span", "label"], "default": "p" },
44
- "weight": { "type": "enum", "values": ["normal", "medium", "semibold", "bold"] },
45
- "color": { "type": "enum", "values": ["default", "muted", "primary", "danger", "success"], "notes": "For arbitrary colors, use style or className." },
46
- "align": { "type": "enum", "values": ["left", "center", "right"] },
47
- "truncate": { "type": "boolean", "default": false }
113
+ "variant": {
114
+ "type": "enum",
115
+ "values": [
116
+ "h1",
117
+ "h2",
118
+ "h3",
119
+ "h4",
120
+ "h5",
121
+ "h6",
122
+ "p",
123
+ "span",
124
+ "label"
125
+ ],
126
+ "default": "p"
127
+ },
128
+ "weight": {
129
+ "type": "enum",
130
+ "values": [
131
+ "normal",
132
+ "medium",
133
+ "semibold",
134
+ "bold"
135
+ ]
136
+ },
137
+ "color": {
138
+ "type": "enum",
139
+ "values": [
140
+ "default",
141
+ "muted",
142
+ "primary",
143
+ "danger",
144
+ "success"
145
+ ],
146
+ "notes": "For arbitrary colors, use style or className."
147
+ },
148
+ "align": {
149
+ "type": "enum",
150
+ "values": [
151
+ "left",
152
+ "center",
153
+ "right"
154
+ ]
155
+ },
156
+ "truncate": {
157
+ "type": "boolean",
158
+ "default": false
159
+ }
48
160
  }
49
161
  },
50
162
  "List": {
@@ -53,8 +165,24 @@
53
165
  "extendsNativeProps": "ul",
54
166
  "forwardsRef": true,
55
167
  "props": {
56
- "variant": { "type": "enum", "values": ["unordered", "ordered", "none"], "default": "unordered" },
57
- "spacing": { "type": "enum", "values": ["sm", "md", "lg"], "default": "md" }
168
+ "variant": {
169
+ "type": "enum",
170
+ "values": [
171
+ "unordered",
172
+ "ordered",
173
+ "none"
174
+ ],
175
+ "default": "unordered"
176
+ },
177
+ "spacing": {
178
+ "type": "enum",
179
+ "values": [
180
+ "sm",
181
+ "md",
182
+ "lg"
183
+ ],
184
+ "default": "md"
185
+ }
58
186
  }
59
187
  },
60
188
  "ListItem": {
@@ -69,40 +197,140 @@
69
197
  "element": "table (inside wrapper div)",
70
198
  "generic": "T extends Record<string, unknown>",
71
199
  "props": {
72
- "data": { "type": "T[]", "required": true },
73
- "columns": { "type": "ColumnDef<T>[]", "required": true },
74
- "sortConfig": { "type": "SortConfig[]", "notes": "Controlled sort. Provide with onSort for server-side mode." },
75
- "onSort": { "type": "(config: SortConfig[]) => void", "notes": "When set, Table is fully controlled — does not sort or paginate data internally." },
76
- "multiSort": { "type": "boolean", "default": true, "notes": "Ctrl/Cmd+click a sortable header to add a secondary sort." },
77
- "striped": { "type": "boolean", "default": false },
78
- "hoverable": { "type": "boolean", "default": false },
79
- "compact": { "type": "boolean", "default": false },
80
- "rowKey": { "type": "keyof T & string" },
81
- "rowClassName": { "type": "string | ((row: T, index: number) => string | undefined)", "notes": "Per-row class. Use a callback to highlight rows matching a predicate." },
82
- "rowStyle": { "type": "CSSProperties | ((row: T, index: number) => CSSProperties | undefined)", "notes": "Per-row inline style. Same shape as rowClassName." },
83
- "emptyMessage": { "type": "ReactNode", "default": "No data" },
84
- "pagination": { "type": "PaginationConfig" },
85
- "classes": { "type": "Partial<TableClasses>", "shape": ["wrapper", "root", "headerCell", "row", "cell", "emptyCell", "paginator", "pageButton"] }
200
+ "data": {
201
+ "type": "T[]",
202
+ "required": true
203
+ },
204
+ "columns": {
205
+ "type": "ColumnDef<T>[]",
206
+ "required": true
207
+ },
208
+ "sortConfig": {
209
+ "type": "SortConfig[]",
210
+ "notes": "Controlled sort. Provide with onSort for server-side mode."
211
+ },
212
+ "onSort": {
213
+ "type": "(config: SortConfig[]) => void",
214
+ "notes": "When set, Table is fully controlled — does not sort or paginate data internally."
215
+ },
216
+ "multiSort": {
217
+ "type": "boolean",
218
+ "default": true,
219
+ "notes": "Ctrl/Cmd+click a sortable header to add a secondary sort."
220
+ },
221
+ "striped": {
222
+ "type": "boolean",
223
+ "default": false
224
+ },
225
+ "hoverable": {
226
+ "type": "boolean",
227
+ "default": false
228
+ },
229
+ "compact": {
230
+ "type": "boolean",
231
+ "default": false
232
+ },
233
+ "rowKey": {
234
+ "type": "keyof T & string"
235
+ },
236
+ "rowClassName": {
237
+ "type": "string | ((row: T, index: number) => string | undefined)",
238
+ "notes": "Per-row class. Use a callback to highlight rows matching a predicate."
239
+ },
240
+ "rowStyle": {
241
+ "type": "CSSProperties | ((row: T, index: number) => CSSProperties | undefined)",
242
+ "notes": "Per-row inline style. Same shape as rowClassName."
243
+ },
244
+ "emptyMessage": {
245
+ "type": "ReactNode",
246
+ "default": "No data"
247
+ },
248
+ "pagination": {
249
+ "type": "PaginationConfig"
250
+ },
251
+ "classes": {
252
+ "type": "Partial<TableClasses>",
253
+ "shape": [
254
+ "wrapper",
255
+ "root",
256
+ "headerCell",
257
+ "row",
258
+ "cell",
259
+ "emptyCell",
260
+ "paginator",
261
+ "pageButton"
262
+ ]
263
+ }
86
264
  },
87
265
  "subTypes": {
88
266
  "ColumnDef<T>": {
89
- "key": { "type": "keyof T & string", "required": true },
90
- "header": { "type": "ReactNode", "required": true },
91
- "sortable": { "type": "boolean" },
92
- "render": { "type": "(value, row, index) => ReactNode" },
93
- "width": { "type": "string | number" },
94
- "align": { "type": "enum", "values": ["left", "center", "right"] }
267
+ "key": {
268
+ "type": "keyof T & string",
269
+ "required": true
270
+ },
271
+ "header": {
272
+ "type": "ReactNode",
273
+ "required": true
274
+ },
275
+ "sortable": {
276
+ "type": "boolean"
277
+ },
278
+ "render": {
279
+ "type": "(value, row, index) => ReactNode"
280
+ },
281
+ "width": {
282
+ "type": "string | number"
283
+ },
284
+ "align": {
285
+ "type": "enum",
286
+ "values": [
287
+ "left",
288
+ "center",
289
+ "right"
290
+ ]
291
+ }
95
292
  },
96
293
  "SortConfig": {
97
- "key": { "type": "string", "required": true },
98
- "direction": { "type": "enum", "values": ["asc", "desc"], "required": true }
294
+ "key": {
295
+ "type": "string",
296
+ "required": true
297
+ },
298
+ "direction": {
299
+ "type": "enum",
300
+ "values": [
301
+ "asc",
302
+ "desc"
303
+ ],
304
+ "required": true
305
+ }
99
306
  },
100
307
  "PaginationConfig": {
101
- "page": { "type": "number", "required": true, "notes": "1-based" },
102
- "pageSize": { "type": "number", "required": true },
103
- "totalRows": { "type": "number", "notes": "Required for server-side mode; defaults to data.length client-side." },
104
- "pageSizeOptions": { "type": "number[]", "default": [10, 25, 50, 100] },
105
- "onPageChange": { "type": "(page: number, pageSize: number) => void", "required": true }
308
+ "page": {
309
+ "type": "number",
310
+ "required": true,
311
+ "notes": "1-based"
312
+ },
313
+ "pageSize": {
314
+ "type": "number",
315
+ "required": true
316
+ },
317
+ "totalRows": {
318
+ "type": "number",
319
+ "notes": "Required for server-side mode; defaults to data.length client-side."
320
+ },
321
+ "pageSizeOptions": {
322
+ "type": "number[]",
323
+ "default": [
324
+ 10,
325
+ 25,
326
+ 50,
327
+ 100
328
+ ]
329
+ },
330
+ "onPageChange": {
331
+ "type": "(page: number, pageSize: number) => void",
332
+ "required": true
333
+ }
106
334
  }
107
335
  },
108
336
  "behavior": {
@@ -118,19 +346,66 @@
118
346
  "select": "Pass `options` + `value` + `onChange`. Trigger label shows the selected option."
119
347
  },
120
348
  "props": {
121
- "trigger": { "type": "ReactNode", "notes": "Required for menu mode; ignored when `options` is provided." },
122
- "options": { "type": "{ value: string; label: ReactNode; disabled?: boolean }[]" },
123
- "value": { "type": "string", "notes": "Select-mode controlled value." },
124
- "onChange": { "type": "(value: string) => void", "notes": "Select-mode callback." },
125
- "placeholder": { "type": "ReactNode", "default": "Select..." },
126
- "align": { "type": "enum", "values": ["left", "right"], "default": "left" },
127
- "error": { "type": "boolean", "default": false },
128
- "fullWidth": { "type": "boolean", "default": true },
129
- "portal": { "type": "boolean", "default": true, "notes": "Renders the open menu in a portal at document.body using --crk-z-floating (1060). Escapes overflow:hidden ancestors and sits above modals. Set false to keep the menu in the trigger's subtree." },
130
- "id": { "type": "string" },
131
- "onOpen": { "type": "() => void" },
132
- "onClose": { "type": "() => void" },
133
- "classes": { "type": "Partial<DropdownClasses>", "shape": ["root", "trigger", "triggerLabel", "chevron", "menu", "item"] }
349
+ "trigger": {
350
+ "type": "ReactNode",
351
+ "notes": "Required for menu mode; ignored when `options` is provided."
352
+ },
353
+ "options": {
354
+ "type": "{ value: string; label: ReactNode; disabled?: boolean }[]"
355
+ },
356
+ "value": {
357
+ "type": "string",
358
+ "notes": "Select-mode controlled value."
359
+ },
360
+ "onChange": {
361
+ "type": "(value: string) => void",
362
+ "notes": "Select-mode callback."
363
+ },
364
+ "placeholder": {
365
+ "type": "ReactNode",
366
+ "default": "Select..."
367
+ },
368
+ "align": {
369
+ "type": "enum",
370
+ "values": [
371
+ "left",
372
+ "right"
373
+ ],
374
+ "default": "left"
375
+ },
376
+ "error": {
377
+ "type": "boolean",
378
+ "default": false
379
+ },
380
+ "fullWidth": {
381
+ "type": "boolean",
382
+ "default": true
383
+ },
384
+ "portal": {
385
+ "type": "boolean",
386
+ "default": true,
387
+ "notes": "Renders the open menu in a portal at document.body using --crk-z-floating (1060). Escapes overflow:hidden ancestors and sits above modals. Set false to keep the menu in the trigger's subtree."
388
+ },
389
+ "id": {
390
+ "type": "string"
391
+ },
392
+ "onOpen": {
393
+ "type": "() => void"
394
+ },
395
+ "onClose": {
396
+ "type": "() => void"
397
+ },
398
+ "classes": {
399
+ "type": "Partial<DropdownClasses>",
400
+ "shape": [
401
+ "root",
402
+ "trigger",
403
+ "triggerLabel",
404
+ "chevron",
405
+ "menu",
406
+ "item"
407
+ ]
408
+ }
134
409
  }
135
410
  },
136
411
  "DropdownItem": {
@@ -139,86 +414,259 @@
139
414
  "extendsNativeProps": "button",
140
415
  "forwardsRef": true,
141
416
  "props": {
142
- "disabled": { "type": "boolean", "default": false }
417
+ "disabled": {
418
+ "type": "boolean",
419
+ "default": false
420
+ }
143
421
  }
144
422
  },
145
423
  "Popover": {
146
424
  "import": "import { Popover } from '@overdoser/react-toolkit'",
147
425
  "element": "div",
148
426
  "props": {
149
- "trigger": { "type": "ReactNode", "required": true, "notes": "Default mode wraps the value in an internal <button>. Set asChild=true to pass your own interactive element instead." },
150
- "asChild": { "type": "boolean", "default": false, "notes": "When true and trigger is a single React element, the element is rendered as-is with onClick/aria-*/ref cloned onto it (no wrapping <button>). Use to avoid the button-in-button hydration warning when passing <Button> as the trigger." },
151
- "content": { "type": "ReactNode", "required": true },
152
- "position": { "type": "enum", "values": ["top", "bottom", "left", "right"], "default": "bottom" },
153
- "open": { "type": "boolean", "notes": "Controlled mode." },
154
- "onOpenChange": { "type": "(open: boolean) => void" },
155
- "classes": { "type": "Partial<PopoverClasses>", "shape": ["root", "trigger", "popover"] }
427
+ "trigger": {
428
+ "type": "ReactNode",
429
+ "required": true,
430
+ "notes": "Default mode wraps the value in an internal <button>. Set asChild=true to pass your own interactive element instead."
431
+ },
432
+ "asChild": {
433
+ "type": "boolean",
434
+ "default": false,
435
+ "notes": "When true and trigger is a single React element, the element is rendered as-is with onClick/aria-*/ref cloned onto it (no wrapping <button>). Use to avoid the button-in-button hydration warning when passing <Button> as the trigger."
436
+ },
437
+ "content": {
438
+ "type": "ReactNode",
439
+ "required": true
440
+ },
441
+ "position": {
442
+ "type": "enum",
443
+ "values": [
444
+ "top",
445
+ "bottom",
446
+ "left",
447
+ "right"
448
+ ],
449
+ "default": "bottom",
450
+ "notes": "The requested side; honored whenever it fits."
451
+ },
452
+ "autoPosition": {
453
+ "type": "boolean",
454
+ "default": true,
455
+ "notes": "Flip to the opposite side when the requested one overflows the viewport, and shift along the cross-axis near a screen edge. Set false to pin strictly to position."
456
+ },
457
+ "open": {
458
+ "type": "boolean",
459
+ "notes": "Controlled mode."
460
+ },
461
+ "onOpenChange": {
462
+ "type": "(open: boolean) => void"
463
+ },
464
+ "classes": {
465
+ "type": "Partial<PopoverClasses>",
466
+ "shape": [
467
+ "root",
468
+ "trigger",
469
+ "popover"
470
+ ]
471
+ }
156
472
  },
157
473
  "behavior": {
158
- "closesOn": ["outside click", "Escape"],
474
+ "closesOn": [
475
+ "outside click",
476
+ "Escape"
477
+ ],
159
478
  "childrenTyped": "never — pass via `content` prop"
160
479
  }
161
480
  },
162
481
  "Modal": {
163
482
  "import": "import { Modal } from '@overdoser/react-toolkit'",
164
483
  "element": "div (portal at document.body)",
165
- "compoundComponents": ["Modal.Header", "Modal.Body", "Modal.Footer"],
484
+ "compoundComponents": [
485
+ "Modal.Header",
486
+ "Modal.Body",
487
+ "Modal.Footer"
488
+ ],
166
489
  "props": {
167
- "open": { "type": "boolean", "required": true },
168
- "onClose": { "type": "() => void", "required": true },
169
- "closeOnBackdrop": { "type": "boolean", "default": true },
170
- "closeOnEscape": { "type": "boolean", "default": true },
171
- "size": { "type": "enum", "values": ["sm", "md", "lg", "fullscreen"], "default": "md" },
172
- "aria-label": { "type": "string" },
173
- "aria-labelledby": { "type": "string" },
174
- "classes": { "type": "Partial<ModalClasses>", "shape": ["backdrop", "modal", "header", "closeButton", "body", "footer"] }
490
+ "open": {
491
+ "type": "boolean",
492
+ "required": true
493
+ },
494
+ "onClose": {
495
+ "type": "() => void",
496
+ "required": true
497
+ },
498
+ "closeOnBackdrop": {
499
+ "type": "boolean",
500
+ "default": true
501
+ },
502
+ "closeOnEscape": {
503
+ "type": "boolean",
504
+ "default": true
505
+ },
506
+ "size": {
507
+ "type": "enum",
508
+ "values": [
509
+ "sm",
510
+ "md",
511
+ "lg",
512
+ "fullscreen"
513
+ ],
514
+ "default": "md"
515
+ },
516
+ "aria-label": {
517
+ "type": "string"
518
+ },
519
+ "aria-labelledby": {
520
+ "type": "string"
521
+ },
522
+ "classes": {
523
+ "type": "Partial<ModalClasses>",
524
+ "shape": [
525
+ "backdrop",
526
+ "modal",
527
+ "header",
528
+ "closeButton",
529
+ "body",
530
+ "footer"
531
+ ]
532
+ }
175
533
  },
176
534
  "subComponents": {
177
535
  "Modal.Header": {
178
536
  "props": {
179
- "children": { "type": "ReactNode", "required": true },
180
- "onClose": { "type": "() => void", "notes": "When set, renders an × close button." }
537
+ "children": {
538
+ "type": "ReactNode",
539
+ "required": true
540
+ },
541
+ "onClose": {
542
+ "type": "() => void",
543
+ "notes": "When set, renders an × close button."
544
+ }
545
+ }
546
+ },
547
+ "Modal.Body": {
548
+ "props": {
549
+ "children": {
550
+ "type": "ReactNode",
551
+ "required": true
552
+ }
181
553
  }
182
554
  },
183
- "Modal.Body": { "props": { "children": { "type": "ReactNode", "required": true } } },
184
- "Modal.Footer": { "props": { "children": { "type": "ReactNode", "required": true } } }
555
+ "Modal.Footer": {
556
+ "props": {
557
+ "children": {
558
+ "type": "ReactNode",
559
+ "required": true
560
+ }
561
+ }
562
+ }
185
563
  },
186
- "behavior": ["body scroll lock", "focus trap", "restores prior focus on close"]
564
+ "behavior": [
565
+ "body scroll lock",
566
+ "focus trap",
567
+ "restores prior focus on close"
568
+ ]
187
569
  },
188
570
  "Form": {
189
571
  "import": "import { Form } from '@overdoser/react-toolkit'",
190
572
  "requires": "react-hook-form",
191
573
  "generic": "T extends FieldValues",
192
574
  "props": {
193
- "form": { "type": "UseFormReturn<T>", "required": true, "notes": "The result of useForm()." },
194
- "onSubmit": { "type": "SubmitHandler<T>", "required": true },
195
- "errors": { "type": "ReactNode[]", "notes": "Top-of-form errors rendered above children with role=alert." }
575
+ "form": {
576
+ "type": "UseFormReturn<T>",
577
+ "required": true,
578
+ "notes": "The result of useForm()."
579
+ },
580
+ "onSubmit": {
581
+ "type": "SubmitHandler<T>",
582
+ "required": true
583
+ },
584
+ "errors": {
585
+ "type": "ReactNode[]",
586
+ "notes": "Top-of-form errors rendered above children with role=alert."
587
+ },
588
+ "reserveErrorSpace": {
589
+ "type": "boolean | number | string",
590
+ "default": true,
591
+ "notes": "Form-wide default for every FormField reserveErrorSpace (fields can override). Same values as the field prop."
592
+ },
593
+ "fieldClasses": {
594
+ "type": "Partial<FormFieldClasses>",
595
+ "notes": "Default classes applied to every FormField, merged with each field own classes (both apply; fields add not replace). Keys: field, label, message, error, helperText."
596
+ }
196
597
  }
197
598
  },
198
599
  "FormField": {
199
600
  "import": "import { FormField } from '@overdoser/react-toolkit'",
200
601
  "requires": "react-hook-form (must be inside <Form>)",
201
602
  "props": {
202
- "name": { "type": "string", "required": true },
203
- "label": { "type": "ReactNode" },
204
- "helperText": { "type": "ReactNode" },
205
- "required": { "type": "boolean", "notes": "Adds a * indicator next to the label." },
206
- "rules": { "type": "Record<string, unknown>", "notes": "Passed to react-hook-form's useController." },
207
- "children": { "type": "ReactElement", "required": true, "notes": "Exactly one input element. FormField clones it to inject value/onChange/id/name/error/aria-*." },
208
- "classes": { "type": "Partial<FormFieldClasses>", "shape": ["field", "label", "error", "helperText"] }
603
+ "name": {
604
+ "type": "string",
605
+ "required": true
606
+ },
607
+ "label": {
608
+ "type": "ReactNode"
609
+ },
610
+ "helperText": {
611
+ "type": "ReactNode",
612
+ "notes": "Description below the input. Stays visible on error; the error appears above it (between input and description), not replacing it."
613
+ },
614
+ "required": {
615
+ "type": "boolean",
616
+ "notes": "Adds a * indicator next to the label."
617
+ },
618
+ "rules": {
619
+ "type": "Record<string, unknown>",
620
+ "notes": "Passed to react-hook-form's useController."
621
+ },
622
+ "reserveErrorSpace": {
623
+ "type": "boolean | number | string",
624
+ "default": true,
625
+ "notes": "Reserves a line AFTER the description so the field outer height stays constant when an error toggles. The error appears between input and description; the reserved spacer collapses 1:1, so fields below do not reflow (only the description shifts down). true=one line (--crk-field-error-min-height); false=no reservation; number=px; string=any CSS length."
626
+ },
627
+ "children": {
628
+ "type": "ReactElement",
629
+ "required": true,
630
+ "notes": "Exactly one input element. FormField clones it to inject value/onChange/id/name/error/aria-*."
631
+ },
632
+ "classes": {
633
+ "type": "Partial<FormFieldClasses>",
634
+ "shape": [
635
+ "field",
636
+ "label",
637
+ "message",
638
+ "error",
639
+ "helperText"
640
+ ]
641
+ }
209
642
  },
210
643
  "behavior": {
211
- "supportedChildren": ["Input", "Textarea", "Select", "Dropdown (select-mode)", "Checkbox", "Radio", "RadioGroup"],
644
+ "supportedChildren": [
645
+ "Input",
646
+ "Textarea",
647
+ "Select",
648
+ "Dropdown (select-mode)",
649
+ "Checkbox",
650
+ "Radio",
651
+ "RadioGroup",
652
+ "CheckboxGroup"
653
+ ],
212
654
  "bridges": {
213
655
  "onValueChange": "Bridged to react-hook-form onChange (Select single, Dropdown).",
214
656
  "onValuesChange": "Bridged to react-hook-form onChange (Select multi).",
215
- "checked": "Auto-set from value when value is boolean (Checkbox)."
657
+ "checked": "Auto-set from value when value is boolean (Checkbox).",
658
+ "values": "CheckboxGroup binds value:string[]/onChange(values) directly to the field — no bridge needed."
216
659
  }
217
660
  }
218
661
  },
219
662
  "FormRow": {
220
663
  "import": "import { FormRow } from '@overdoser/react-toolkit'",
221
- "props": { "children": { "type": "ReactNode", "required": true } }
664
+ "props": {
665
+ "children": {
666
+ "type": "ReactNode",
667
+ "required": true
668
+ }
669
+ }
222
670
  },
223
671
  "Input": {
224
672
  "import": "import { Input } from '@overdoser/react-toolkit'",
@@ -226,11 +674,35 @@
226
674
  "extendsNativeProps": "input (without prefix)",
227
675
  "forwardsRef": true,
228
676
  "props": {
229
- "inputSize": { "type": "enum", "values": ["sm", "md", "lg"], "default": "md", "notes": "Toolkit size scale; independent of the native `size` HTML attribute." },
230
- "error": { "type": "boolean", "default": false },
231
- "prefix": { "type": "ReactNode" },
232
- "suffix": { "type": "ReactNode" },
233
- "classes": { "type": "Partial<InputClasses>", "shape": ["root", "wrapper", "prefix", "suffix"] }
677
+ "inputSize": {
678
+ "type": "enum",
679
+ "values": [
680
+ "sm",
681
+ "md",
682
+ "lg"
683
+ ],
684
+ "default": "md",
685
+ "notes": "Toolkit size scale; independent of the native `size` HTML attribute."
686
+ },
687
+ "error": {
688
+ "type": "boolean",
689
+ "default": false
690
+ },
691
+ "prefix": {
692
+ "type": "ReactNode"
693
+ },
694
+ "suffix": {
695
+ "type": "ReactNode"
696
+ },
697
+ "classes": {
698
+ "type": "Partial<InputClasses>",
699
+ "shape": [
700
+ "root",
701
+ "wrapper",
702
+ "prefix",
703
+ "suffix"
704
+ ]
705
+ }
234
706
  }
235
707
  },
236
708
  "Select": {
@@ -244,24 +716,95 @@
244
716
  "multi": "When `multiple=true`. Chip-based multi with text filter."
245
717
  },
246
718
  "props": {
247
- "inputSize": { "type": "enum", "values": ["sm", "md", "lg"], "default": "md" },
248
- "error": { "type": "boolean", "default": false },
249
- "options": { "type": "{ value: string; label: string; content?: ReactNode; disabled?: boolean }[]" },
250
- "placeholder": { "type": "string", "default": "Select..." },
251
- "searchable": { "type": "boolean", "default": false },
252
- "searchPlaceholder": { "type": "string", "default": "Search..." },
253
- "multiple": { "type": "boolean", "default": false },
254
- "value": { "type": "string | string[]", "notes": "string for single-mode, string[] for multi." },
255
- "onChange": { "type": "(e: ChangeEvent<HTMLSelectElement>) => void", "notes": "Native + searchable (synthetic for searchable)." },
256
- "onValueChange": { "type": "(value: string) => void", "notes": "Single-mode value-only callback." },
257
- "onValuesChange": { "type": "(values: string[]) => void", "notes": "Multi-mode callback." },
258
- "clearSearchOnSelect": { "type": "boolean", "default": true, "notes": "Multi-mode only." },
259
- "closeOnSelect": { "type": "boolean", "default": true, "notes": "Multi-mode only. Menu closes after each add/remove/create. Set false for the stay-open pattern." },
260
- "allowCreate": { "type": "boolean", "default": false, "notes": "Multi-mode only. Adds a 'Create <query>' row when the query doesn't match an existing option; pressing Enter (or clicking it) pushes the trimmed query into onValuesChange and fires onCreate." },
261
- "onCreate": { "type": "(value: string) => void", "notes": "Multi-mode only. Paired with allowCreate." },
262
- "createLabel": { "type": "(query: string) => ReactNode", "notes": "Multi-mode only. Custom render for the Create row." },
263
- "portal": { "type": "boolean", "default": true, "notes": "Applies to searchable + multiple modes. Renders the open menu in a portal at document.body using --crk-z-floating (1060) — escapes overflow:hidden ancestors and sits above modals. Set false to keep the menu in the trigger's subtree." },
264
- "classes": { "type": "Partial<SelectClasses>", "shape": ["wrapper", "root", "arrow", "search", "menu", "item", "chip", "chipRemove"] }
719
+ "inputSize": {
720
+ "type": "enum",
721
+ "values": [
722
+ "sm",
723
+ "md",
724
+ "lg"
725
+ ],
726
+ "default": "md"
727
+ },
728
+ "error": {
729
+ "type": "boolean",
730
+ "default": false
731
+ },
732
+ "options": {
733
+ "type": "{ value: string; label: string; content?: ReactNode; disabled?: boolean }[]"
734
+ },
735
+ "placeholder": {
736
+ "type": "string",
737
+ "default": "Select..."
738
+ },
739
+ "searchable": {
740
+ "type": "boolean",
741
+ "default": false
742
+ },
743
+ "searchPlaceholder": {
744
+ "type": "string",
745
+ "default": "Search..."
746
+ },
747
+ "multiple": {
748
+ "type": "boolean",
749
+ "default": false
750
+ },
751
+ "value": {
752
+ "type": "string | string[]",
753
+ "notes": "string for single-mode, string[] for multi."
754
+ },
755
+ "onChange": {
756
+ "type": "(e: ChangeEvent<HTMLSelectElement>) => void",
757
+ "notes": "Native + searchable (synthetic for searchable)."
758
+ },
759
+ "onValueChange": {
760
+ "type": "(value: string) => void",
761
+ "notes": "Single-mode value-only callback."
762
+ },
763
+ "onValuesChange": {
764
+ "type": "(values: string[]) => void",
765
+ "notes": "Multi-mode callback."
766
+ },
767
+ "clearSearchOnSelect": {
768
+ "type": "boolean",
769
+ "default": true,
770
+ "notes": "Multi-mode only."
771
+ },
772
+ "closeOnSelect": {
773
+ "type": "boolean",
774
+ "default": true,
775
+ "notes": "Multi-mode only. Menu closes after each add/remove/create. Set false for the stay-open pattern."
776
+ },
777
+ "allowCreate": {
778
+ "type": "boolean",
779
+ "default": false,
780
+ "notes": "Multi-mode only. Adds a 'Create <query>' row when the query doesn't match an existing option; pressing Enter (or clicking it) pushes the trimmed query into onValuesChange and fires onCreate."
781
+ },
782
+ "onCreate": {
783
+ "type": "(value: string) => void",
784
+ "notes": "Multi-mode only. Paired with allowCreate."
785
+ },
786
+ "createLabel": {
787
+ "type": "(query: string) => ReactNode",
788
+ "notes": "Multi-mode only. Custom render for the Create row."
789
+ },
790
+ "portal": {
791
+ "type": "boolean",
792
+ "default": true,
793
+ "notes": "Applies to searchable + multiple modes. Renders the open menu in a portal at document.body using --crk-z-floating (1060) — escapes overflow:hidden ancestors and sits above modals. Set false to keep the menu in the trigger's subtree."
794
+ },
795
+ "classes": {
796
+ "type": "Partial<SelectClasses>",
797
+ "shape": [
798
+ "wrapper",
799
+ "root",
800
+ "arrow",
801
+ "search",
802
+ "menu",
803
+ "item",
804
+ "chip",
805
+ "chipRemove"
806
+ ]
807
+ }
265
808
  }
266
809
  },
267
810
  "Checkbox": {
@@ -270,9 +813,101 @@
270
813
  "extendsNativeProps": "input",
271
814
  "forwardsRef": true,
272
815
  "props": {
273
- "label": { "type": "ReactNode" },
274
- "indeterminate": { "type": "boolean", "default": false },
275
- "classes": { "type": "Partial<CheckboxClasses>", "shape": ["root", "input", "label"] }
816
+ "label": {
817
+ "type": "ReactNode"
818
+ },
819
+ "indeterminate": {
820
+ "type": "boolean",
821
+ "default": false
822
+ },
823
+ "classes": {
824
+ "type": "Partial<CheckboxClasses>",
825
+ "shape": [
826
+ "root",
827
+ "input",
828
+ "label"
829
+ ]
830
+ }
831
+ }
832
+ },
833
+ "CheckboxGroup": {
834
+ "import": "import { CheckboxGroup } from '@overdoser/react-toolkit'",
835
+ "element": "div[role=group]",
836
+ "forwardsRef": true,
837
+ "props": {
838
+ "options": {
839
+ "type": "{ value: string; label: ReactNode; disabled?: boolean }[]",
840
+ "required": true
841
+ },
842
+ "value": {
843
+ "type": "string[]",
844
+ "default": [],
845
+ "notes": "Controlled selected values."
846
+ },
847
+ "onChange": {
848
+ "type": "(values: string[]) => void",
849
+ "notes": "Fires with the next array on each toggle."
850
+ },
851
+ "variant": {
852
+ "type": "enum",
853
+ "values": [
854
+ "checkbox",
855
+ "chips"
856
+ ],
857
+ "default": "checkbox",
858
+ "notes": "chips renders toggleable buttons (role=checkbox, aria-checked)."
859
+ },
860
+ "chipShape": {
861
+ "type": "enum",
862
+ "values": [
863
+ "pill",
864
+ "rounded",
865
+ "square"
866
+ ],
867
+ "default": "pill",
868
+ "notes": "Chip corner style (chips only); square is the most rectangular."
869
+ },
870
+ "chipCheckmark": {
871
+ "type": "boolean",
872
+ "default": true,
873
+ "notes": "Leading checkmark on selected chips (chips only). Chip total width stays constant: when checked, horizontal padding shrinks on both sides by half the mark+gap, so the freed space fits the mark and the centered content does not jump."
874
+ },
875
+ "orientation": {
876
+ "type": "enum",
877
+ "values": [
878
+ "vertical",
879
+ "horizontal"
880
+ ],
881
+ "notes": "Defaults to vertical for checkbox, horizontal (wrapping) for chips."
882
+ },
883
+ "disabled": {
884
+ "type": "boolean",
885
+ "default": false,
886
+ "notes": "Disables every option; per-option via options[].disabled."
887
+ },
888
+ "error": {
889
+ "type": "boolean",
890
+ "default": false
891
+ },
892
+ "name": {
893
+ "type": "string"
894
+ },
895
+ "required": {
896
+ "type": "boolean",
897
+ "notes": "Sets aria-required on the group."
898
+ },
899
+ "classes": {
900
+ "type": "Partial<CheckboxGroupClasses>",
901
+ "shape": [
902
+ "group",
903
+ "option",
904
+ "chip",
905
+ "chipSelected"
906
+ ]
907
+ }
908
+ },
909
+ "behavior": {
910
+ "formField": "Works inside <FormField> directly (value:string[]/onChange(values) bind to the field)."
276
911
  }
277
912
  },
278
913
  "Radio": {
@@ -281,9 +916,21 @@
281
916
  "extendsNativeProps": "input (without type)",
282
917
  "forwardsRef": true,
283
918
  "props": {
284
- "value": { "type": "string", "required": true },
285
- "label": { "type": "ReactNode" },
286
- "classes": { "type": "Partial<RadioClasses>", "shape": ["root", "input", "label"] }
919
+ "value": {
920
+ "type": "string",
921
+ "required": true
922
+ },
923
+ "label": {
924
+ "type": "ReactNode"
925
+ },
926
+ "classes": {
927
+ "type": "Partial<RadioClasses>",
928
+ "shape": [
929
+ "root",
930
+ "input",
931
+ "label"
932
+ ]
933
+ }
287
934
  },
288
935
  "behavior": "Inside a <RadioGroup>, name/checked/onChange come from context — do not pass them on Radio."
289
936
  },
@@ -291,13 +938,29 @@
291
938
  "import": "import { RadioGroup } from '@overdoser/react-toolkit'",
292
939
  "element": "div[role=radiogroup]",
293
940
  "props": {
294
- "name": { "type": "string", "required": true },
295
- "value": { "type": "string", "notes": "Controlled selected value." },
296
- "onChange": { "type": "(value: string) => void" },
297
- "id": { "type": "string" },
298
- "aria-label": { "type": "string" },
299
- "aria-labelledby": { "type": "string" },
300
- "required": { "type": "boolean" }
941
+ "name": {
942
+ "type": "string",
943
+ "required": true
944
+ },
945
+ "value": {
946
+ "type": "string",
947
+ "notes": "Controlled selected value."
948
+ },
949
+ "onChange": {
950
+ "type": "(value: string) => void"
951
+ },
952
+ "id": {
953
+ "type": "string"
954
+ },
955
+ "aria-label": {
956
+ "type": "string"
957
+ },
958
+ "aria-labelledby": {
959
+ "type": "string"
960
+ },
961
+ "required": {
962
+ "type": "boolean"
963
+ }
301
964
  }
302
965
  },
303
966
  "Textarea": {
@@ -306,17 +969,53 @@
306
969
  "extendsNativeProps": "textarea",
307
970
  "forwardsRef": true,
308
971
  "props": {
309
- "inputSize": { "type": "enum", "values": ["sm", "md", "lg"], "default": "md" },
310
- "error": { "type": "boolean", "default": false },
311
- "resize": { "type": "enum", "values": ["none", "vertical", "horizontal", "both"], "default": "vertical", "notes": "Ignored when autoExpand=true." },
312
- "autoExpand": { "type": "boolean", "default": true, "notes": "When true, auto-grows height to fit content." }
972
+ "inputSize": {
973
+ "type": "enum",
974
+ "values": [
975
+ "sm",
976
+ "md",
977
+ "lg"
978
+ ],
979
+ "default": "md"
980
+ },
981
+ "error": {
982
+ "type": "boolean",
983
+ "default": false
984
+ },
985
+ "resize": {
986
+ "type": "enum",
987
+ "values": [
988
+ "none",
989
+ "vertical",
990
+ "horizontal",
991
+ "both"
992
+ ],
993
+ "default": "vertical",
994
+ "notes": "Ignored when autoExpand=true."
995
+ },
996
+ "autoExpand": {
997
+ "type": "boolean",
998
+ "default": true,
999
+ "notes": "When true, auto-grows height to fit content."
1000
+ }
313
1001
  }
314
1002
  },
315
1003
  "LineChart": {
316
1004
  "import": "import { LineChart, type ChartConfig } from '@overdoser/react-toolkit'",
317
1005
  "element": "div containing responsive SVG",
318
1006
  "generic": "T extends Record<string, unknown>",
319
- "themeTokens": ["--crk-chart-1", "--crk-chart-2", "--crk-chart-3", "--crk-chart-4", "--crk-chart-5", "--crk-chart-grid", "--crk-chart-axis", "--crk-chart-label-inside", "--crk-chart-tooltip-bg", "--crk-chart-tooltip-text"],
1007
+ "themeTokens": [
1008
+ "--crk-chart-1",
1009
+ "--crk-chart-2",
1010
+ "--crk-chart-3",
1011
+ "--crk-chart-4",
1012
+ "--crk-chart-5",
1013
+ "--crk-chart-grid",
1014
+ "--crk-chart-axis",
1015
+ "--crk-chart-label-inside",
1016
+ "--crk-chart-tooltip-bg",
1017
+ "--crk-chart-tooltip-text"
1018
+ ],
320
1019
  "shadcnVariantMapping": {
321
1020
  "default": "no extra props",
322
1021
  "multiple": "no extra props (multiple keys in config)",
@@ -330,31 +1029,123 @@
330
1029
  "interactive": "Series-toggle pattern: keep multiple series in `data` but pass only the active series in `config` (`{ [active]: fullConfig[active] }`). Consumer-side selector — tiles, prev/next navigator, or <Dropdown> (see recipes/interactive-line-chart.tsx)."
331
1030
  },
332
1031
  "props": {
333
- "data": { "type": "T[]", "required": true },
334
- "xKey": { "type": "keyof T & string", "required": true },
335
- "config": { "type": "ChartConfig", "required": true, "notes": "Record<string, { label, color }> keyed by data-field names." },
336
- "aspectRatio": { "type": "number", "default": "16/9" },
337
- "height": { "type": "number", "notes": "Fixed pixel height. Overrides aspectRatio." },
338
- "curve": { "type": "enum", "values": ["linear", "monotone", "step"], "default": "monotone", "notes": "'step' is step-after — value holds until the next x." },
339
- "showPoints": { "type": "boolean", "default": true },
340
- "dotColorKey": { "type": "keyof T & string", "notes": "Each dot's fill = row[dotColorKey]. Line keeps the series color. Tooltip swatch follows the dot color." },
341
- "renderDot": { "type": "(args: { row, value, seriesKey, index, x, y, color, active }) => ReactNode", "notes": "Replaces the default circle with custom SVG per point." },
342
- "showValues": { "type": "boolean", "default": false },
343
- "valuePosition": { "type": "enum", "values": ["top", "bottom"], "default": "top" },
344
- "renderLabel": { "type": "(args: { row, value, seriesKey, index }) => ReactNode", "notes": "Custom label content. Overrides valueFormat for labels. Return null to skip." },
345
- "activeIndex": { "type": "number", "notes": "Highlight a specific point index; other points fade." },
346
- "onPointClick": { "type": "(args: { row, value, seriesKey, index }) => void" },
347
- "showGrid": { "type": "boolean", "default": true },
348
- "showLegend": { "type": "boolean", "default": true },
349
- "showTooltip": { "type": "boolean", "default": true },
350
- "yTickCount": { "type": "number", "default": 5 },
351
- "yFormat": { "type": "(v: number) => string" },
352
- "xFormat": { "type": "(v: string) => string" },
353
- "valueFormat": { "type": "(v: number) => string" },
354
- "renderTooltip": { "type": "(row: T) => ReactNode" },
355
- "margin": { "type": "Partial<ChartMargin>", "default": "{ top: 12, right: 16, bottom: 28, left: 48 }" },
356
- "emptyMessage": { "type": "ReactNode", "default": "No data" },
357
- "classes": { "type": "Partial<ChartClasses>", "shape": ["root", "svg", "grid", "axis", "axisLabel", "series", "point", "legend", "legendItem", "tooltip"] }
1032
+ "data": {
1033
+ "type": "T[]",
1034
+ "required": true
1035
+ },
1036
+ "xKey": {
1037
+ "type": "keyof T & string",
1038
+ "required": true
1039
+ },
1040
+ "config": {
1041
+ "type": "ChartConfig",
1042
+ "required": true,
1043
+ "notes": "Record<string, { label, color }> keyed by data-field names."
1044
+ },
1045
+ "aspectRatio": {
1046
+ "type": "number",
1047
+ "default": "16/9"
1048
+ },
1049
+ "height": {
1050
+ "type": "number",
1051
+ "notes": "Fixed pixel height. Overrides aspectRatio."
1052
+ },
1053
+ "curve": {
1054
+ "type": "enum",
1055
+ "values": [
1056
+ "linear",
1057
+ "monotone",
1058
+ "step"
1059
+ ],
1060
+ "default": "monotone",
1061
+ "notes": "'step' is step-after — value holds until the next x."
1062
+ },
1063
+ "showPoints": {
1064
+ "type": "boolean",
1065
+ "default": true
1066
+ },
1067
+ "dotColorKey": {
1068
+ "type": "keyof T & string",
1069
+ "notes": "Each dot's fill = row[dotColorKey]. Line keeps the series color. Tooltip swatch follows the dot color."
1070
+ },
1071
+ "renderDot": {
1072
+ "type": "(args: { row, value, seriesKey, index, x, y, color, active }) => ReactNode",
1073
+ "notes": "Replaces the default circle with custom SVG per point."
1074
+ },
1075
+ "showValues": {
1076
+ "type": "boolean",
1077
+ "default": false
1078
+ },
1079
+ "valuePosition": {
1080
+ "type": "enum",
1081
+ "values": [
1082
+ "top",
1083
+ "bottom"
1084
+ ],
1085
+ "default": "top"
1086
+ },
1087
+ "renderLabel": {
1088
+ "type": "(args: { row, value, seriesKey, index }) => ReactNode",
1089
+ "notes": "Custom label content. Overrides valueFormat for labels. Return null to skip."
1090
+ },
1091
+ "activeIndex": {
1092
+ "type": "number",
1093
+ "notes": "Highlight a specific point index; other points fade."
1094
+ },
1095
+ "onPointClick": {
1096
+ "type": "(args: { row, value, seriesKey, index }) => void"
1097
+ },
1098
+ "showGrid": {
1099
+ "type": "boolean",
1100
+ "default": true
1101
+ },
1102
+ "showLegend": {
1103
+ "type": "boolean",
1104
+ "default": true
1105
+ },
1106
+ "showTooltip": {
1107
+ "type": "boolean",
1108
+ "default": true
1109
+ },
1110
+ "yTickCount": {
1111
+ "type": "number",
1112
+ "default": 5
1113
+ },
1114
+ "yFormat": {
1115
+ "type": "(v: number) => string"
1116
+ },
1117
+ "xFormat": {
1118
+ "type": "(v: string) => string"
1119
+ },
1120
+ "valueFormat": {
1121
+ "type": "(v: number) => string"
1122
+ },
1123
+ "renderTooltip": {
1124
+ "type": "(row: T) => ReactNode"
1125
+ },
1126
+ "margin": {
1127
+ "type": "Partial<ChartMargin>",
1128
+ "default": "{ top: 12, right: 16, bottom: 28, left: 48 }"
1129
+ },
1130
+ "emptyMessage": {
1131
+ "type": "ReactNode",
1132
+ "default": "No data"
1133
+ },
1134
+ "classes": {
1135
+ "type": "Partial<ChartClasses>",
1136
+ "shape": [
1137
+ "root",
1138
+ "svg",
1139
+ "grid",
1140
+ "axis",
1141
+ "axisLabel",
1142
+ "series",
1143
+ "point",
1144
+ "legend",
1145
+ "legendItem",
1146
+ "tooltip"
1147
+ ]
1148
+ }
358
1149
  }
359
1150
  },
360
1151
  "AreaChart": {
@@ -374,28 +1165,100 @@
374
1165
  "interactive": "Data-range filter: all series stay rendered (typically stacked + fillGradient); the selector swaps the slice of `data` shown (e.g. last 7d/14d/30d). Not a series toggle. Consumer-side selector — tiles, prev/next navigator, or <Dropdown>. See recipes/interactive-area-chart.tsx."
375
1166
  },
376
1167
  "props": {
377
- "data": { "type": "T[]", "required": true },
378
- "xKey": { "type": "keyof T & string", "required": true },
379
- "config": { "type": "ChartConfig", "required": true, "notes": "Each SeriesConfig may also include `icon?: ReactNode` to override the legend swatch." },
380
- "aspectRatio": { "type": "number", "default": "16/9" },
381
- "height": { "type": "number" },
382
- "curve": { "type": "enum", "values": ["linear", "monotone", "step"], "default": "monotone" },
383
- "stacked": { "type": "boolean", "default": false, "notes": "Stacks series bottom-up in config-key order." },
384
- "stackOffset": { "type": "enum", "values": ["none", "expand"], "default": "none", "notes": "`'expand'` normalizes each row to 100%. Requires stacked=true." },
385
- "fillGradient": { "type": "boolean", "default": false, "notes": "Per-series vertical linear gradient (opaque top → transparent bottom)." },
386
- "showGrid": { "type": "boolean", "default": true },
387
- "showXAxis": { "type": "boolean", "default": true },
388
- "showYAxis": { "type": "boolean", "default": true },
389
- "showLegend": { "type": "boolean", "default": true },
390
- "showTooltip": { "type": "boolean", "default": true },
391
- "yTickCount": { "type": "number", "default": 5 },
392
- "yFormat": { "type": "(v: number) => string" },
393
- "xFormat": { "type": "(v: string) => string" },
394
- "valueFormat": { "type": "(v: number) => string" },
395
- "renderTooltip": { "type": "(row: T) => ReactNode" },
396
- "margin": { "type": "Partial<ChartMargin>" },
397
- "emptyMessage": { "type": "ReactNode", "default": "No data" },
398
- "classes": { "type": "Partial<ChartClasses>" }
1168
+ "data": {
1169
+ "type": "T[]",
1170
+ "required": true
1171
+ },
1172
+ "xKey": {
1173
+ "type": "keyof T & string",
1174
+ "required": true
1175
+ },
1176
+ "config": {
1177
+ "type": "ChartConfig",
1178
+ "required": true,
1179
+ "notes": "Each SeriesConfig may also include `icon?: ReactNode` to override the legend swatch."
1180
+ },
1181
+ "aspectRatio": {
1182
+ "type": "number",
1183
+ "default": "16/9"
1184
+ },
1185
+ "height": {
1186
+ "type": "number"
1187
+ },
1188
+ "curve": {
1189
+ "type": "enum",
1190
+ "values": [
1191
+ "linear",
1192
+ "monotone",
1193
+ "step"
1194
+ ],
1195
+ "default": "monotone"
1196
+ },
1197
+ "stacked": {
1198
+ "type": "boolean",
1199
+ "default": false,
1200
+ "notes": "Stacks series bottom-up in config-key order."
1201
+ },
1202
+ "stackOffset": {
1203
+ "type": "enum",
1204
+ "values": [
1205
+ "none",
1206
+ "expand"
1207
+ ],
1208
+ "default": "none",
1209
+ "notes": "`'expand'` normalizes each row to 100%. Requires stacked=true."
1210
+ },
1211
+ "fillGradient": {
1212
+ "type": "boolean",
1213
+ "default": false,
1214
+ "notes": "Per-series vertical linear gradient (opaque top → transparent bottom)."
1215
+ },
1216
+ "showGrid": {
1217
+ "type": "boolean",
1218
+ "default": true
1219
+ },
1220
+ "showXAxis": {
1221
+ "type": "boolean",
1222
+ "default": true
1223
+ },
1224
+ "showYAxis": {
1225
+ "type": "boolean",
1226
+ "default": true
1227
+ },
1228
+ "showLegend": {
1229
+ "type": "boolean",
1230
+ "default": true
1231
+ },
1232
+ "showTooltip": {
1233
+ "type": "boolean",
1234
+ "default": true
1235
+ },
1236
+ "yTickCount": {
1237
+ "type": "number",
1238
+ "default": 5
1239
+ },
1240
+ "yFormat": {
1241
+ "type": "(v: number) => string"
1242
+ },
1243
+ "xFormat": {
1244
+ "type": "(v: string) => string"
1245
+ },
1246
+ "valueFormat": {
1247
+ "type": "(v: number) => string"
1248
+ },
1249
+ "renderTooltip": {
1250
+ "type": "(row: T) => ReactNode"
1251
+ },
1252
+ "margin": {
1253
+ "type": "Partial<ChartMargin>"
1254
+ },
1255
+ "emptyMessage": {
1256
+ "type": "ReactNode",
1257
+ "default": "No data"
1258
+ },
1259
+ "classes": {
1260
+ "type": "Partial<ChartClasses>"
1261
+ }
399
1262
  }
400
1263
  },
401
1264
  "BarChart": {
@@ -416,35 +1279,134 @@
416
1279
  "interactive": "Series-toggle pattern: keep multiple series in `data` but pass only the active series in `config` (`{ [active]: fullConfig[active] }`). Consumer-side selector — tiles, prev/next navigator, or <Dropdown> (see recipes/interactive-bar-chart.tsx)."
417
1280
  },
418
1281
  "props": {
419
- "data": { "type": "T[]", "required": true },
420
- "xKey": { "type": "keyof T & string", "required": true },
421
- "config": { "type": "ChartConfig", "required": true },
422
- "orientation": { "type": "enum", "values": ["vertical", "horizontal"], "default": "vertical" },
423
- "stacked": { "type": "boolean", "default": false, "notes": "Stack series within each category. Ignored with a single series." },
424
- "aspectRatio": { "type": "number", "default": "16/9" },
425
- "height": { "type": "number" },
426
- "groupPadding": { "type": "number", "default": 0.2, "notes": "Inner padding between category groups, fraction of band width." },
427
- "barPadding": { "type": "number", "default": 0.1, "notes": "Inner padding between bars within a grouped group, fraction of bar width. Ignored when stacked." },
428
- "barRadius": { "type": "number", "default": 4 },
429
- "showGrid": { "type": "boolean", "default": true },
430
- "showLegend": { "type": "boolean", "default": true },
431
- "showTooltip": { "type": "boolean", "default": true },
432
- "showValues": { "type": "boolean", "default": false, "notes": "Render a numeric label on every bar." },
433
- "valuePosition": { "type": "enum", "values": ["outside", "inside", "inside-start"], "default": "outside", "notes": "'inside-start' anchors at the baseline edge of the bar; on vertical bars the text rotates ±90° to read along the bar's length." },
434
- "renderLabel": { "type": "(args: { row, value, seriesKey, index }) => ReactNode", "notes": "Custom label content. Return null to skip. Overrides valueFormat for labels." },
435
- "colorBy": { "type": "enum", "values": ["series", "index"], "default": "series", "notes": "'index' picks color per row from `colors` (config colors are ignored)." },
436
- "colors": { "type": "string[]", "default": "the five --crk-chart-N tokens", "notes": "Palette used when colorBy='index'." },
437
- "negativeColor": { "type": "string", "notes": "Override fill applied to bars whose value is negative." },
438
- "activeIndex": { "type": "number", "notes": "Highlight one category index; other groups fade." },
439
- "onBarClick": { "type": "(args: { row, value, seriesKey, index }) => void" },
440
- "yTickCount": { "type": "number", "default": 5 },
441
- "yFormat": { "type": "(v: number) => string" },
442
- "xFormat": { "type": "(v: string) => string" },
443
- "valueFormat": { "type": "(v: number) => string" },
444
- "renderTooltip": { "type": "(row: T) => ReactNode" },
445
- "margin": { "type": "Partial<ChartMargin>" },
446
- "emptyMessage": { "type": "ReactNode", "default": "No data" },
447
- "classes": { "type": "Partial<ChartClasses>" }
1282
+ "data": {
1283
+ "type": "T[]",
1284
+ "required": true
1285
+ },
1286
+ "xKey": {
1287
+ "type": "keyof T & string",
1288
+ "required": true
1289
+ },
1290
+ "config": {
1291
+ "type": "ChartConfig",
1292
+ "required": true
1293
+ },
1294
+ "orientation": {
1295
+ "type": "enum",
1296
+ "values": [
1297
+ "vertical",
1298
+ "horizontal"
1299
+ ],
1300
+ "default": "vertical"
1301
+ },
1302
+ "stacked": {
1303
+ "type": "boolean",
1304
+ "default": false,
1305
+ "notes": "Stack series within each category. Ignored with a single series."
1306
+ },
1307
+ "aspectRatio": {
1308
+ "type": "number",
1309
+ "default": "16/9"
1310
+ },
1311
+ "height": {
1312
+ "type": "number"
1313
+ },
1314
+ "groupPadding": {
1315
+ "type": "number",
1316
+ "default": 0.2,
1317
+ "notes": "Inner padding between category groups, fraction of band width."
1318
+ },
1319
+ "barPadding": {
1320
+ "type": "number",
1321
+ "default": 0.1,
1322
+ "notes": "Inner padding between bars within a grouped group, fraction of bar width. Ignored when stacked."
1323
+ },
1324
+ "barRadius": {
1325
+ "type": "number",
1326
+ "default": 4
1327
+ },
1328
+ "showGrid": {
1329
+ "type": "boolean",
1330
+ "default": true
1331
+ },
1332
+ "showLegend": {
1333
+ "type": "boolean",
1334
+ "default": true
1335
+ },
1336
+ "showTooltip": {
1337
+ "type": "boolean",
1338
+ "default": true
1339
+ },
1340
+ "showValues": {
1341
+ "type": "boolean",
1342
+ "default": false,
1343
+ "notes": "Render a numeric label on every bar."
1344
+ },
1345
+ "valuePosition": {
1346
+ "type": "enum",
1347
+ "values": [
1348
+ "outside",
1349
+ "inside",
1350
+ "inside-start"
1351
+ ],
1352
+ "default": "outside",
1353
+ "notes": "'inside-start' anchors at the baseline edge of the bar; on vertical bars the text rotates ±90° to read along the bar's length."
1354
+ },
1355
+ "renderLabel": {
1356
+ "type": "(args: { row, value, seriesKey, index }) => ReactNode",
1357
+ "notes": "Custom label content. Return null to skip. Overrides valueFormat for labels."
1358
+ },
1359
+ "colorBy": {
1360
+ "type": "enum",
1361
+ "values": [
1362
+ "series",
1363
+ "index"
1364
+ ],
1365
+ "default": "series",
1366
+ "notes": "'index' picks color per row from `colors` (config colors are ignored)."
1367
+ },
1368
+ "colors": {
1369
+ "type": "string[]",
1370
+ "default": "the five --crk-chart-N tokens",
1371
+ "notes": "Palette used when colorBy='index'."
1372
+ },
1373
+ "negativeColor": {
1374
+ "type": "string",
1375
+ "notes": "Override fill applied to bars whose value is negative."
1376
+ },
1377
+ "activeIndex": {
1378
+ "type": "number",
1379
+ "notes": "Highlight one category index; other groups fade."
1380
+ },
1381
+ "onBarClick": {
1382
+ "type": "(args: { row, value, seriesKey, index }) => void"
1383
+ },
1384
+ "yTickCount": {
1385
+ "type": "number",
1386
+ "default": 5
1387
+ },
1388
+ "yFormat": {
1389
+ "type": "(v: number) => string"
1390
+ },
1391
+ "xFormat": {
1392
+ "type": "(v: string) => string"
1393
+ },
1394
+ "valueFormat": {
1395
+ "type": "(v: number) => string"
1396
+ },
1397
+ "renderTooltip": {
1398
+ "type": "(row: T) => ReactNode"
1399
+ },
1400
+ "margin": {
1401
+ "type": "Partial<ChartMargin>"
1402
+ },
1403
+ "emptyMessage": {
1404
+ "type": "ReactNode",
1405
+ "default": "No data"
1406
+ },
1407
+ "classes": {
1408
+ "type": "Partial<ChartClasses>"
1409
+ }
448
1410
  }
449
1411
  },
450
1412
  "PieChart": {
@@ -465,31 +1427,109 @@
465
1427
  "stacked": "Not currently supported (nested rings)."
466
1428
  },
467
1429
  "props": {
468
- "data": { "type": "T[]", "required": true },
469
- "valueKey": { "type": "keyof T & string", "required": true },
470
- "nameKey": { "type": "keyof T & string", "required": true },
471
- "colorKey": { "type": "keyof T & string", "notes": "Per-row fill from data; falls back to `colors` palette." },
472
- "colors": { "type": "string[]", "default": "the five --crk-chart-N tokens" },
473
- "aspectRatio": { "type": "number", "default": 1 },
474
- "height": { "type": "number" },
475
- "innerRadius": { "type": "number | string", "default": 0, "notes": "0 = pie, >0 = donut. Number = pixels, string = 'NN%' of half the minimum chart side." },
476
- "outerRadius": { "type": "number | string", "default": "'90%'" },
477
- "strokeWidth": { "type": "number", "default": 2, "notes": "Slice separator stroke width. 0 = 'separator-none'." },
478
- "strokeColor": { "type": "string", "default": "var(--crk-color-bg)" },
479
- "padAngle": { "type": "number", "default": 0, "notes": "Degrees of visual gap between slices." },
480
- "showLabels": { "type": "boolean", "default": false },
481
- "valuePosition": { "type": "enum", "values": ["outside", "inside"], "default": "outside" },
482
- "showLabelLines": { "type": "boolean", "default": true, "notes": "Thin slice-colored leader line from the slice edge to each outside label. No effect for inside labels." },
483
- "renderLabel": { "type": "(args: { row, value, fraction, index }) => ReactNode", "notes": "Overrides showLabels default content. Return null to skip a slice." },
484
- "centerLabel": { "type": "ReactNode", "notes": "Rendered via foreignObject in the centre of the chart (only useful with innerRadius > 0)." },
485
- "activeIndex": { "type": "number", "notes": "Pulls the slice outward; other slices fade." },
486
- "onSliceClick": { "type": "(args: { row, value, index }) => void" },
487
- "showLegend": { "type": "boolean", "default": true },
488
- "showTooltip": { "type": "boolean", "default": true },
489
- "valueFormat": { "type": "(v: number) => string" },
490
- "renderTooltip": { "type": "(row: T) => ReactNode" },
491
- "emptyMessage": { "type": "ReactNode", "default": "No data" },
492
- "classes": { "type": "Partial<ChartClasses>" }
1430
+ "data": {
1431
+ "type": "T[]",
1432
+ "required": true
1433
+ },
1434
+ "valueKey": {
1435
+ "type": "keyof T & string",
1436
+ "required": true
1437
+ },
1438
+ "nameKey": {
1439
+ "type": "keyof T & string",
1440
+ "required": true
1441
+ },
1442
+ "colorKey": {
1443
+ "type": "keyof T & string",
1444
+ "notes": "Per-row fill from data; falls back to `colors` palette."
1445
+ },
1446
+ "colors": {
1447
+ "type": "string[]",
1448
+ "default": "the five --crk-chart-N tokens"
1449
+ },
1450
+ "aspectRatio": {
1451
+ "type": "number",
1452
+ "default": 1
1453
+ },
1454
+ "height": {
1455
+ "type": "number"
1456
+ },
1457
+ "innerRadius": {
1458
+ "type": "number | string",
1459
+ "default": 0,
1460
+ "notes": "0 = pie, >0 = donut. Number = pixels, string = 'NN%' of half the minimum chart side."
1461
+ },
1462
+ "outerRadius": {
1463
+ "type": "number | string",
1464
+ "default": "'90%'"
1465
+ },
1466
+ "strokeWidth": {
1467
+ "type": "number",
1468
+ "default": 2,
1469
+ "notes": "Slice separator stroke width. 0 = 'separator-none'."
1470
+ },
1471
+ "strokeColor": {
1472
+ "type": "string",
1473
+ "default": "var(--crk-color-bg)"
1474
+ },
1475
+ "padAngle": {
1476
+ "type": "number",
1477
+ "default": 0,
1478
+ "notes": "Degrees of visual gap between slices."
1479
+ },
1480
+ "showLabels": {
1481
+ "type": "boolean",
1482
+ "default": false
1483
+ },
1484
+ "valuePosition": {
1485
+ "type": "enum",
1486
+ "values": [
1487
+ "outside",
1488
+ "inside"
1489
+ ],
1490
+ "default": "outside"
1491
+ },
1492
+ "showLabelLines": {
1493
+ "type": "boolean",
1494
+ "default": true,
1495
+ "notes": "Thin slice-colored leader line from the slice edge to each outside label. No effect for inside labels."
1496
+ },
1497
+ "renderLabel": {
1498
+ "type": "(args: { row, value, fraction, index }) => ReactNode",
1499
+ "notes": "Overrides showLabels default content. Return null to skip a slice."
1500
+ },
1501
+ "centerLabel": {
1502
+ "type": "ReactNode",
1503
+ "notes": "Rendered via foreignObject in the centre of the chart (only useful with innerRadius > 0)."
1504
+ },
1505
+ "activeIndex": {
1506
+ "type": "number",
1507
+ "notes": "Pulls the slice outward; other slices fade."
1508
+ },
1509
+ "onSliceClick": {
1510
+ "type": "(args: { row, value, index }) => void"
1511
+ },
1512
+ "showLegend": {
1513
+ "type": "boolean",
1514
+ "default": true
1515
+ },
1516
+ "showTooltip": {
1517
+ "type": "boolean",
1518
+ "default": true
1519
+ },
1520
+ "valueFormat": {
1521
+ "type": "(v: number) => string"
1522
+ },
1523
+ "renderTooltip": {
1524
+ "type": "(row: T) => ReactNode"
1525
+ },
1526
+ "emptyMessage": {
1527
+ "type": "ReactNode",
1528
+ "default": "No data"
1529
+ },
1530
+ "classes": {
1531
+ "type": "Partial<ChartClasses>"
1532
+ }
493
1533
  }
494
1534
  },
495
1535
  "RadarChart": {
@@ -513,28 +1553,95 @@
513
1553
  "radius": "showRadiusAxis"
514
1554
  },
515
1555
  "props": {
516
- "data": { "type": "T[]", "required": true },
517
- "axisKey": { "type": "keyof T & string", "required": true },
518
- "config": { "type": "ChartConfig", "required": true, "notes": "SeriesConfig may include `icon` for the icons variant." },
519
- "aspectRatio": { "type": "number", "default": 1 },
520
- "height": { "type": "number" },
521
- "gridType": { "type": "enum", "values": ["polygon", "circle"], "default": "polygon" },
522
- "showGrid": { "type": "boolean", "default": true },
523
- "showRadialLines": { "type": "boolean", "default": true },
524
- "showAxisLabels": { "type": "boolean", "default": true },
525
- "showRadiusAxis": { "type": "boolean", "default": false },
526
- "levels": { "type": "number", "default": 5 },
527
- "showPoints": { "type": "boolean", "default": true },
528
- "fillOpacity": { "type": "number", "default": 0.4, "notes": "0 = 'lines-only' variant." },
529
- "strokeWidth": { "type": "number", "default": 2 },
530
- "renderAxisLabel": { "type": "(args: { row, index, angle, x, y, textAnchor }) => ReactNode", "notes": "Return null to skip a vertex." },
531
- "axisFormat": { "type": "(label: string) => string" },
532
- "valueFormat": { "type": "(v: number) => string" },
533
- "renderTooltip": { "type": "(row: T) => ReactNode" },
534
- "showLegend": { "type": "boolean", "default": true },
535
- "showTooltip": { "type": "boolean", "default": true },
536
- "emptyMessage": { "type": "ReactNode", "default": "No data" },
537
- "classes": { "type": "Partial<ChartClasses>" }
1556
+ "data": {
1557
+ "type": "T[]",
1558
+ "required": true
1559
+ },
1560
+ "axisKey": {
1561
+ "type": "keyof T & string",
1562
+ "required": true
1563
+ },
1564
+ "config": {
1565
+ "type": "ChartConfig",
1566
+ "required": true,
1567
+ "notes": "SeriesConfig may include `icon` for the icons variant."
1568
+ },
1569
+ "aspectRatio": {
1570
+ "type": "number",
1571
+ "default": 1
1572
+ },
1573
+ "height": {
1574
+ "type": "number"
1575
+ },
1576
+ "gridType": {
1577
+ "type": "enum",
1578
+ "values": [
1579
+ "polygon",
1580
+ "circle"
1581
+ ],
1582
+ "default": "polygon"
1583
+ },
1584
+ "showGrid": {
1585
+ "type": "boolean",
1586
+ "default": true
1587
+ },
1588
+ "showRadialLines": {
1589
+ "type": "boolean",
1590
+ "default": true
1591
+ },
1592
+ "showAxisLabels": {
1593
+ "type": "boolean",
1594
+ "default": true
1595
+ },
1596
+ "showRadiusAxis": {
1597
+ "type": "boolean",
1598
+ "default": false
1599
+ },
1600
+ "levels": {
1601
+ "type": "number",
1602
+ "default": 5
1603
+ },
1604
+ "showPoints": {
1605
+ "type": "boolean",
1606
+ "default": true
1607
+ },
1608
+ "fillOpacity": {
1609
+ "type": "number",
1610
+ "default": 0.4,
1611
+ "notes": "0 = 'lines-only' variant."
1612
+ },
1613
+ "strokeWidth": {
1614
+ "type": "number",
1615
+ "default": 2
1616
+ },
1617
+ "renderAxisLabel": {
1618
+ "type": "(args: { row, index, angle, x, y, textAnchor }) => ReactNode",
1619
+ "notes": "Return null to skip a vertex."
1620
+ },
1621
+ "axisFormat": {
1622
+ "type": "(label: string) => string"
1623
+ },
1624
+ "valueFormat": {
1625
+ "type": "(v: number) => string"
1626
+ },
1627
+ "renderTooltip": {
1628
+ "type": "(row: T) => ReactNode"
1629
+ },
1630
+ "showLegend": {
1631
+ "type": "boolean",
1632
+ "default": true
1633
+ },
1634
+ "showTooltip": {
1635
+ "type": "boolean",
1636
+ "default": true
1637
+ },
1638
+ "emptyMessage": {
1639
+ "type": "ReactNode",
1640
+ "default": "No data"
1641
+ },
1642
+ "classes": {
1643
+ "type": "Partial<ChartClasses>"
1644
+ }
538
1645
  }
539
1646
  },
540
1647
  "TradingChart": {
@@ -555,27 +1662,99 @@
555
1662
  "volume": "{ type: 'volume', upColor?, downColor? } — sub-pane (histogram)"
556
1663
  },
557
1664
  "props": {
558
- "datafeed": { "type": "Datafeed", "required": true },
559
- "symbol": { "type": "string", "required": true },
560
- "resolution": { "type": "Resolution", "required": true, "notes": "Bar size (the 'tick interval'). TradingView-style: numeric minutes ('1', '5', '60') or 'D'/'W'/'M'. Any other string passes through to the datafeed." },
561
- "period": { "type": "Period", "notes": "Visible time-range window (the 'chart interval'): '1D' | '5D' | '1M' | '3M' | '6M' | '1Y' | '5Y' | '10Y' | '20Y' | 'MAX' or a `<n><unit>` string. Overrides initialLookback when set. 'MAX' fetches from epoch and renders whatever the datafeed returns — pair with a coarse resolution. The chart does not auto-adjust resolution; pair with suggestResolutionForPeriod() / suggestPeriodForResolution() to couple the two." },
562
- "seriesType": { "type": "enum", "values": ["candle", "bar", "line", "area"], "default": "candle", "notes": "'bar' = OHLC bars (vertical high-low line, left tick at open, right tick at close)." },
563
- "timezone": { "type": "Timezone", "notes": "Controlled timezone for the crosshair time label. 'local' | 'utc' | IANA name. Pair with onTimezoneChange." },
564
- "defaultTimezone": { "type": "Timezone", "default": "local", "notes": "Initial timezone when uncontrolled." },
565
- "onTimezoneChange": { "type": "(tz: Timezone) => void" },
566
- "showConfigPanel": { "type": "boolean", "default": true, "notes": "Gear button in the header that opens a Popover with a Timezone dropdown (Local / UTC / common IANA zones)." },
567
- "priceBadgeColorByDirection": { "type": "boolean", "default": false, "notes": "Tint the top cursor-value badge by bar direction (up/down) instead of the neutral palette." },
568
- "priceBadgeShowTime": { "type": "boolean", "default": false, "notes": "Append a compact date (D/W/M: '17 Sep 2025') or weekday+date+time (intraday: 'Wed 17 Sep 14:30') AFTER the price in the top cursor-value badge. Honours `timezone`." },
569
- "priceBadgeTimeFormat": { "type": "(unixSec: number, resolution: Resolution, timezone: Timezone) => string", "notes": "Optional override for the time portion when priceBadgeShowTime is on. Return any string; empty string suppresses the suffix." },
570
- "indicators": { "type": "IndicatorConfig[]" },
571
- "precision": { "type": "number", "default": 2 },
572
- "initialLookback": { "type": "number", "default": 500, "notes": "Used only when `period` is not set." },
573
- "height": { "type": "number", "default": 420 },
574
- "upColor": { "type": "string", "default": "var(--crk-color-success)" },
575
- "downColor": { "type": "string", "default": "var(--crk-color-danger)" },
576
- "lineColor": { "type": "string", "default": "var(--crk-chart-1)" },
577
- "onCrosshair": { "type": "(info: CrosshairInfo | null) => void" },
578
- "onVisibleRangeChange": { "type": "(range: VisibleRange, bars: Bar[]) => void" }
1665
+ "datafeed": {
1666
+ "type": "Datafeed",
1667
+ "required": true
1668
+ },
1669
+ "symbol": {
1670
+ "type": "string",
1671
+ "required": true
1672
+ },
1673
+ "resolution": {
1674
+ "type": "Resolution",
1675
+ "required": true,
1676
+ "notes": "Bar size (the 'tick interval'). TradingView-style: numeric minutes ('1', '5', '60') or 'D'/'W'/'M'. Any other string passes through to the datafeed."
1677
+ },
1678
+ "period": {
1679
+ "type": "Period",
1680
+ "notes": "Visible time-range window (the 'chart interval'): '1D' | '5D' | '1M' | '3M' | '6M' | '1Y' | '5Y' | '10Y' | '20Y' | 'MAX' or a `<n><unit>` string. Overrides initialLookback when set. 'MAX' fetches from epoch and renders whatever the datafeed returns — pair with a coarse resolution. The chart does not auto-adjust resolution; pair with suggestResolutionForPeriod() / suggestPeriodForResolution() to couple the two."
1681
+ },
1682
+ "seriesType": {
1683
+ "type": "enum",
1684
+ "values": [
1685
+ "candle",
1686
+ "bar",
1687
+ "line",
1688
+ "area"
1689
+ ],
1690
+ "default": "candle",
1691
+ "notes": "'bar' = OHLC bars (vertical high-low line, left tick at open, right tick at close)."
1692
+ },
1693
+ "timezone": {
1694
+ "type": "Timezone",
1695
+ "notes": "Controlled timezone for the crosshair time label. 'local' | 'utc' | IANA name. Pair with onTimezoneChange."
1696
+ },
1697
+ "defaultTimezone": {
1698
+ "type": "Timezone",
1699
+ "default": "local",
1700
+ "notes": "Initial timezone when uncontrolled."
1701
+ },
1702
+ "onTimezoneChange": {
1703
+ "type": "(tz: Timezone) => void"
1704
+ },
1705
+ "showConfigPanel": {
1706
+ "type": "boolean",
1707
+ "default": true,
1708
+ "notes": "Gear button in the header that opens a Popover with a Timezone dropdown (Local / UTC / common IANA zones)."
1709
+ },
1710
+ "priceBadgeColorByDirection": {
1711
+ "type": "boolean",
1712
+ "default": false,
1713
+ "notes": "Tint the top cursor-value badge by bar direction (up/down) instead of the neutral palette."
1714
+ },
1715
+ "priceBadgeShowTime": {
1716
+ "type": "boolean",
1717
+ "default": false,
1718
+ "notes": "Append a compact date (D/W/M: '17 Sep 2025') or weekday+date+time (intraday: 'Wed 17 Sep 14:30') AFTER the price in the top cursor-value badge. Honours `timezone`."
1719
+ },
1720
+ "priceBadgeTimeFormat": {
1721
+ "type": "(unixSec: number, resolution: Resolution, timezone: Timezone) => string",
1722
+ "notes": "Optional override for the time portion when priceBadgeShowTime is on. Return any string; empty string suppresses the suffix."
1723
+ },
1724
+ "indicators": {
1725
+ "type": "IndicatorConfig[]"
1726
+ },
1727
+ "precision": {
1728
+ "type": "number",
1729
+ "default": 2
1730
+ },
1731
+ "initialLookback": {
1732
+ "type": "number",
1733
+ "default": 500,
1734
+ "notes": "Used only when `period` is not set."
1735
+ },
1736
+ "height": {
1737
+ "type": "number",
1738
+ "default": 420
1739
+ },
1740
+ "upColor": {
1741
+ "type": "string",
1742
+ "default": "var(--crk-color-success)"
1743
+ },
1744
+ "downColor": {
1745
+ "type": "string",
1746
+ "default": "var(--crk-color-danger)"
1747
+ },
1748
+ "lineColor": {
1749
+ "type": "string",
1750
+ "default": "var(--crk-chart-1)"
1751
+ },
1752
+ "onCrosshair": {
1753
+ "type": "(info: CrosshairInfo | null) => void"
1754
+ },
1755
+ "onVisibleRangeChange": {
1756
+ "type": "(range: VisibleRange, bars: Bar[]) => void"
1757
+ }
579
1758
  },
580
1759
  "interactions": {
581
1760
  "drag": "pan",
@@ -583,8 +1762,19 @@
583
1762
  "arrow-keys": "nudge pan",
584
1763
  "hover": "full crosshair — dashed horizontal + vertical lines, price badge on the right Y axis at the cursor's price, and a time badge under the X axis showing `YYYY-MM-DD HH:MM:SS <tz>` at the cursor's snapped bar (timezone configurable via the gear panel or props). OHLC readout in the chart header."
585
1764
  },
586
- "exportedHelpers": ["periodToSeconds", "suggestResolutionForPeriod", "suggestPeriodForResolution", "formatCrosshairTime"],
587
- "exportedIndicatorMath": ["sma", "ema", "bollinger", "rsi", "volumeSeries"],
1765
+ "exportedHelpers": [
1766
+ "periodToSeconds",
1767
+ "suggestResolutionForPeriod",
1768
+ "suggestPeriodForResolution",
1769
+ "formatCrosshairTime"
1770
+ ],
1771
+ "exportedIndicatorMath": [
1772
+ "sma",
1773
+ "ema",
1774
+ "bollinger",
1775
+ "rsi",
1776
+ "volumeSeries"
1777
+ ],
588
1778
  "outOfScope": [
589
1779
  "Drawing tools (trend lines, fibonacci, shapes)",
590
1780
  "Custom indicator settings dialog",
@@ -599,15 +1789,46 @@
599
1789
  "forwardsRef": true,
600
1790
  "behavior": "Inline trend line, fixed pixel size, no axes/legend/tooltip — for table cells and KPI tiles.",
601
1791
  "props": {
602
- "data": { "type": "number[]", "required": true },
603
- "width": { "type": "number", "default": 80 },
604
- "height": { "type": "number", "default": 24 },
605
- "color": { "type": "string", "default": "var(--crk-chart-1)" },
606
- "strokeWidth": { "type": "number", "default": 1.5 },
607
- "curve": { "type": "enum", "values": ["linear", "monotone"], "default": "monotone" },
608
- "fill": { "type": "boolean", "default": false, "notes": "Render a low-opacity area under the line." },
609
- "showLastPoint": { "type": "boolean", "default": false },
610
- "aria-label": { "type": "string" }
1792
+ "data": {
1793
+ "type": "number[]",
1794
+ "required": true
1795
+ },
1796
+ "width": {
1797
+ "type": "number",
1798
+ "default": 80
1799
+ },
1800
+ "height": {
1801
+ "type": "number",
1802
+ "default": 24
1803
+ },
1804
+ "color": {
1805
+ "type": "string",
1806
+ "default": "var(--crk-chart-1)"
1807
+ },
1808
+ "strokeWidth": {
1809
+ "type": "number",
1810
+ "default": 1.5
1811
+ },
1812
+ "curve": {
1813
+ "type": "enum",
1814
+ "values": [
1815
+ "linear",
1816
+ "monotone"
1817
+ ],
1818
+ "default": "monotone"
1819
+ },
1820
+ "fill": {
1821
+ "type": "boolean",
1822
+ "default": false,
1823
+ "notes": "Render a low-opacity area under the line."
1824
+ },
1825
+ "showLastPoint": {
1826
+ "type": "boolean",
1827
+ "default": false
1828
+ },
1829
+ "aria-label": {
1830
+ "type": "string"
1831
+ }
611
1832
  }
612
1833
  }
613
1834
  },