@overdoser/react-toolkit 0.0.6 → 0.0.8
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/AGENTS.md +15 -0
- package/components/Button/Button.d.ts +13 -0
- package/components/Chart/AreaChart.d.ts +85 -0
- package/components/Chart/Axis.d.ts +30 -0
- package/components/Chart/BarChart.d.ts +111 -0
- package/components/Chart/ChartContainer.d.ts +33 -0
- package/components/Chart/ChartLegend.d.ts +9 -0
- package/components/Chart/ChartTooltip.d.ts +32 -0
- package/components/Chart/LineChart.d.ts +112 -0
- package/components/Chart/PieChart.d.ts +100 -0
- package/components/Chart/RadarChart.d.ts +86 -0
- package/components/Chart/Sparkline.d.ts +31 -0
- package/components/Chart/TradingChart.d.ts +89 -0
- package/components/Chart/index.d.ts +18 -0
- package/components/Chart/scales.d.ts +21 -0
- package/components/Chart/trading/indicators.d.ts +28 -0
- package/components/Chart/trading/period.d.ts +19 -0
- package/components/Chart/trading/types.d.ts +122 -0
- package/components/Chart/types.d.ts +60 -0
- package/components/Chart/useChartDimensions.d.ts +12 -0
- package/components/Popover/Popover.d.ts +16 -1
- package/index.css +1 -1
- package/index.d.ts +2 -0
- package/index.js +3427 -1110
- package/llms.txt +373 -2
- package/manifest.json +307 -3
- package/package.json +1 -1
- package/recipes/dashboard-charts.tsx +123 -0
- package/recipes/interactive-area-chart.tsx +226 -0
- package/recipes/interactive-bar-chart.tsx +211 -0
- package/recipes/interactive-line-chart.tsx +221 -0
- package/recipes/interactive-pie-chart.tsx +191 -0
- package/recipes/trading-chart.tsx +188 -0
- package/components/Button/Button.stories.d.ts +0 -17
- package/components/Dropdown/Dropdown.stories.d.ts +0 -8
- package/components/Form/Form.stories.d.ts +0 -11
- package/components/Link/Link.stories.d.ts +0 -9
- package/components/List/List.stories.d.ts +0 -9
- package/components/Modal/Modal.stories.d.ts +0 -9
- package/components/Popover/Popover.stories.d.ts +0 -9
- package/components/Table/Table.stories.d.ts +0 -20
- package/components/Typography/Typography.stories.d.ts +0 -15
- package/components/inputs/Checkbox/Checkbox.stories.d.ts +0 -9
- package/components/inputs/Input/Input.stories.d.ts +0 -13
- package/components/inputs/Radio/Radio.stories.d.ts +0 -7
- package/components/inputs/Select/Select.stories.d.ts +0 -18
- package/components/inputs/Textarea/Textarea.stories.d.ts +0 -10
- package/test-setup.d.ts +0 -0
package/manifest.json
CHANGED
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
"loading": { "type": "boolean", "default": false, "notes": "Disables the button and sets aria-busy." },
|
|
21
21
|
"loadingStyle": { "type": "enum", "values": ["dots", "shimmer", "border"], "default": "dots", "notes": "Only used when loading=true." },
|
|
22
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." },
|
|
23
24
|
"classes": { "type": "Partial<ButtonClasses>", "shape": ["root", "content", "shimmer", "dots", "dot"] }
|
|
24
25
|
}
|
|
25
26
|
},
|
|
@@ -142,7 +143,8 @@
|
|
|
142
143
|
"import": "import { Popover } from '@overdoser/react-toolkit'",
|
|
143
144
|
"element": "div",
|
|
144
145
|
"props": {
|
|
145
|
-
"trigger": { "type": "ReactNode", "required": true },
|
|
146
|
+
"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." },
|
|
147
|
+
"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." },
|
|
146
148
|
"content": { "type": "ReactNode", "required": true },
|
|
147
149
|
"position": { "type": "enum", "values": ["top", "bottom", "left", "right"], "default": "bottom" },
|
|
148
150
|
"open": { "type": "boolean", "notes": "Controlled mode." },
|
|
@@ -301,6 +303,301 @@
|
|
|
301
303
|
"resize": { "type": "enum", "values": ["none", "vertical", "horizontal", "both"], "default": "vertical", "notes": "Ignored when autoExpand=true." },
|
|
302
304
|
"autoExpand": { "type": "boolean", "default": true, "notes": "When true, auto-grows height to fit content." }
|
|
303
305
|
}
|
|
306
|
+
},
|
|
307
|
+
"LineChart": {
|
|
308
|
+
"import": "import { LineChart, type ChartConfig } from '@overdoser/react-toolkit'",
|
|
309
|
+
"element": "div containing responsive SVG",
|
|
310
|
+
"generic": "T extends Record<string, unknown>",
|
|
311
|
+
"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"],
|
|
312
|
+
"shadcnVariantMapping": {
|
|
313
|
+
"default": "no extra props",
|
|
314
|
+
"multiple": "no extra props (multiple keys in config)",
|
|
315
|
+
"linear": "curve='linear'",
|
|
316
|
+
"step": "curve='step'",
|
|
317
|
+
"dots": "showPoints (on by default)",
|
|
318
|
+
"dots-custom": "renderDot",
|
|
319
|
+
"dots-colors": "dotColorKey (each row holds its own dot color)",
|
|
320
|
+
"label": "showValues + valuePosition",
|
|
321
|
+
"label-custom": "renderLabel",
|
|
322
|
+
"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)."
|
|
323
|
+
},
|
|
324
|
+
"props": {
|
|
325
|
+
"data": { "type": "T[]", "required": true },
|
|
326
|
+
"xKey": { "type": "keyof T & string", "required": true },
|
|
327
|
+
"config": { "type": "ChartConfig", "required": true, "notes": "Record<string, { label, color }> keyed by data-field names." },
|
|
328
|
+
"aspectRatio": { "type": "number", "default": "16/9" },
|
|
329
|
+
"height": { "type": "number", "notes": "Fixed pixel height. Overrides aspectRatio." },
|
|
330
|
+
"curve": { "type": "enum", "values": ["linear", "monotone", "step"], "default": "monotone", "notes": "'step' is step-after — value holds until the next x." },
|
|
331
|
+
"showPoints": { "type": "boolean", "default": true },
|
|
332
|
+
"dotColorKey": { "type": "keyof T & string", "notes": "Each dot's fill = row[dotColorKey]. Line keeps the series color. Tooltip swatch follows the dot color." },
|
|
333
|
+
"renderDot": { "type": "(args: { row, value, seriesKey, index, x, y, color, active }) => ReactNode", "notes": "Replaces the default circle with custom SVG per point." },
|
|
334
|
+
"showValues": { "type": "boolean", "default": false },
|
|
335
|
+
"valuePosition": { "type": "enum", "values": ["top", "bottom"], "default": "top" },
|
|
336
|
+
"renderLabel": { "type": "(args: { row, value, seriesKey, index }) => ReactNode", "notes": "Custom label content. Overrides valueFormat for labels. Return null to skip." },
|
|
337
|
+
"activeIndex": { "type": "number", "notes": "Highlight a specific point index; other points fade." },
|
|
338
|
+
"onPointClick": { "type": "(args: { row, value, seriesKey, index }) => void" },
|
|
339
|
+
"showGrid": { "type": "boolean", "default": true },
|
|
340
|
+
"showLegend": { "type": "boolean", "default": true },
|
|
341
|
+
"showTooltip": { "type": "boolean", "default": true },
|
|
342
|
+
"yTickCount": { "type": "number", "default": 5 },
|
|
343
|
+
"yFormat": { "type": "(v: number) => string" },
|
|
344
|
+
"xFormat": { "type": "(v: string) => string" },
|
|
345
|
+
"valueFormat": { "type": "(v: number) => string" },
|
|
346
|
+
"renderTooltip": { "type": "(row: T) => ReactNode" },
|
|
347
|
+
"margin": { "type": "Partial<ChartMargin>", "default": "{ top: 12, right: 16, bottom: 28, left: 48 }" },
|
|
348
|
+
"emptyMessage": { "type": "ReactNode", "default": "No data" },
|
|
349
|
+
"classes": { "type": "Partial<ChartClasses>", "shape": ["root", "svg", "grid", "axis", "axisLabel", "series", "point", "legend", "legendItem", "tooltip"] }
|
|
350
|
+
}
|
|
351
|
+
},
|
|
352
|
+
"AreaChart": {
|
|
353
|
+
"import": "import { AreaChart, type ChartConfig } from '@overdoser/react-toolkit'",
|
|
354
|
+
"element": "div containing responsive SVG",
|
|
355
|
+
"generic": "T extends Record<string, unknown>",
|
|
356
|
+
"shadcnVariantMapping": {
|
|
357
|
+
"default": "no extra props",
|
|
358
|
+
"linear": "curve='linear'",
|
|
359
|
+
"step": "curve='step'",
|
|
360
|
+
"legend": "showLegend (on by default)",
|
|
361
|
+
"axes": "no extra props (both axes shown by default)",
|
|
362
|
+
"stacked": "stacked",
|
|
363
|
+
"stacked-expand": "stacked + stackOffset='expand' (normalizes each row to 100%)",
|
|
364
|
+
"gradient": "fillGradient",
|
|
365
|
+
"icons": "set `icon` on each entry in `config` (rendered in legend in place of the swatch)",
|
|
366
|
+
"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."
|
|
367
|
+
},
|
|
368
|
+
"props": {
|
|
369
|
+
"data": { "type": "T[]", "required": true },
|
|
370
|
+
"xKey": { "type": "keyof T & string", "required": true },
|
|
371
|
+
"config": { "type": "ChartConfig", "required": true, "notes": "Each SeriesConfig may also include `icon?: ReactNode` to override the legend swatch." },
|
|
372
|
+
"aspectRatio": { "type": "number", "default": "16/9" },
|
|
373
|
+
"height": { "type": "number" },
|
|
374
|
+
"curve": { "type": "enum", "values": ["linear", "monotone", "step"], "default": "monotone" },
|
|
375
|
+
"stacked": { "type": "boolean", "default": false, "notes": "Stacks series bottom-up in config-key order." },
|
|
376
|
+
"stackOffset": { "type": "enum", "values": ["none", "expand"], "default": "none", "notes": "`'expand'` normalizes each row to 100%. Requires stacked=true." },
|
|
377
|
+
"fillGradient": { "type": "boolean", "default": false, "notes": "Per-series vertical linear gradient (opaque top → transparent bottom)." },
|
|
378
|
+
"showGrid": { "type": "boolean", "default": true },
|
|
379
|
+
"showXAxis": { "type": "boolean", "default": true },
|
|
380
|
+
"showYAxis": { "type": "boolean", "default": true },
|
|
381
|
+
"showLegend": { "type": "boolean", "default": true },
|
|
382
|
+
"showTooltip": { "type": "boolean", "default": true },
|
|
383
|
+
"yTickCount": { "type": "number", "default": 5 },
|
|
384
|
+
"yFormat": { "type": "(v: number) => string" },
|
|
385
|
+
"xFormat": { "type": "(v: string) => string" },
|
|
386
|
+
"valueFormat": { "type": "(v: number) => string" },
|
|
387
|
+
"renderTooltip": { "type": "(row: T) => ReactNode" },
|
|
388
|
+
"margin": { "type": "Partial<ChartMargin>" },
|
|
389
|
+
"emptyMessage": { "type": "ReactNode", "default": "No data" },
|
|
390
|
+
"classes": { "type": "Partial<ChartClasses>" }
|
|
391
|
+
}
|
|
392
|
+
},
|
|
393
|
+
"BarChart": {
|
|
394
|
+
"import": "import { BarChart, type ChartConfig } from '@overdoser/react-toolkit'",
|
|
395
|
+
"element": "div containing responsive SVG",
|
|
396
|
+
"generic": "T extends Record<string, unknown>",
|
|
397
|
+
"behavior": "Multi-series renders as grouped bars by default. The full set of shadcn bar-chart variants (default, horizontal, multiple, stacked, label, label-custom, mixed, negative, active, interactive) is reachable via the composable props below.",
|
|
398
|
+
"shadcnVariantMapping": {
|
|
399
|
+
"default": "no extra props",
|
|
400
|
+
"multiple": "no extra props (just multiple keys in config)",
|
|
401
|
+
"horizontal": "orientation='horizontal'",
|
|
402
|
+
"stacked": "stacked",
|
|
403
|
+
"label": "showValues + valuePosition",
|
|
404
|
+
"label-custom": "renderLabel + valuePosition='inside-start' (typically with orientation='horizontal')",
|
|
405
|
+
"mixed": "colorBy='index' (+ optional colors)",
|
|
406
|
+
"negative": "numeric values spanning zero; pair with negativeColor",
|
|
407
|
+
"active": "activeIndex={n}",
|
|
408
|
+
"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)."
|
|
409
|
+
},
|
|
410
|
+
"props": {
|
|
411
|
+
"data": { "type": "T[]", "required": true },
|
|
412
|
+
"xKey": { "type": "keyof T & string", "required": true },
|
|
413
|
+
"config": { "type": "ChartConfig", "required": true },
|
|
414
|
+
"orientation": { "type": "enum", "values": ["vertical", "horizontal"], "default": "vertical" },
|
|
415
|
+
"stacked": { "type": "boolean", "default": false, "notes": "Stack series within each category. Ignored with a single series." },
|
|
416
|
+
"aspectRatio": { "type": "number", "default": "16/9" },
|
|
417
|
+
"height": { "type": "number" },
|
|
418
|
+
"groupPadding": { "type": "number", "default": 0.2, "notes": "Inner padding between category groups, fraction of band width." },
|
|
419
|
+
"barPadding": { "type": "number", "default": 0.1, "notes": "Inner padding between bars within a grouped group, fraction of bar width. Ignored when stacked." },
|
|
420
|
+
"barRadius": { "type": "number", "default": 4 },
|
|
421
|
+
"showGrid": { "type": "boolean", "default": true },
|
|
422
|
+
"showLegend": { "type": "boolean", "default": true },
|
|
423
|
+
"showTooltip": { "type": "boolean", "default": true },
|
|
424
|
+
"showValues": { "type": "boolean", "default": false, "notes": "Render a numeric label on every bar." },
|
|
425
|
+
"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." },
|
|
426
|
+
"renderLabel": { "type": "(args: { row, value, seriesKey, index }) => ReactNode", "notes": "Custom label content. Return null to skip. Overrides valueFormat for labels." },
|
|
427
|
+
"colorBy": { "type": "enum", "values": ["series", "index"], "default": "series", "notes": "'index' picks color per row from `colors` (config colors are ignored)." },
|
|
428
|
+
"colors": { "type": "string[]", "default": "the five --crk-chart-N tokens", "notes": "Palette used when colorBy='index'." },
|
|
429
|
+
"negativeColor": { "type": "string", "notes": "Override fill applied to bars whose value is negative." },
|
|
430
|
+
"activeIndex": { "type": "number", "notes": "Highlight one category index; other groups fade." },
|
|
431
|
+
"onBarClick": { "type": "(args: { row, value, seriesKey, index }) => void" },
|
|
432
|
+
"yTickCount": { "type": "number", "default": 5 },
|
|
433
|
+
"yFormat": { "type": "(v: number) => string" },
|
|
434
|
+
"xFormat": { "type": "(v: string) => string" },
|
|
435
|
+
"valueFormat": { "type": "(v: number) => string" },
|
|
436
|
+
"renderTooltip": { "type": "(row: T) => ReactNode" },
|
|
437
|
+
"margin": { "type": "Partial<ChartMargin>" },
|
|
438
|
+
"emptyMessage": { "type": "ReactNode", "default": "No data" },
|
|
439
|
+
"classes": { "type": "Partial<ChartClasses>" }
|
|
440
|
+
}
|
|
441
|
+
},
|
|
442
|
+
"PieChart": {
|
|
443
|
+
"import": "import { PieChart } from '@overdoser/react-toolkit'",
|
|
444
|
+
"element": "div containing responsive SVG",
|
|
445
|
+
"generic": "T extends Record<string, unknown>",
|
|
446
|
+
"shadcnVariantMapping": {
|
|
447
|
+
"simple": "no extra props",
|
|
448
|
+
"donut": "innerRadius (number or 'NN%')",
|
|
449
|
+
"legend": "showLegend (on by default)",
|
|
450
|
+
"separator-none": "strokeWidth={0}",
|
|
451
|
+
"label": "showLabels (default content: name + percent). Outside labels get a leader line from slice → label by default; disable with showLabelLines=false.",
|
|
452
|
+
"label-custom": "renderLabel (leader line still drawn unless showLabelLines=false)",
|
|
453
|
+
"label-list": "valuePosition='inside' + renderLabel returning the row name. Each slice carries its category name on its colored surface (background-colored text for contrast).",
|
|
454
|
+
"donut-text": "innerRadius + centerLabel",
|
|
455
|
+
"donut-active": "activeIndex={n}",
|
|
456
|
+
"interactive": "Metric/period filter while showing all slices. See recipes/interactive-pie-chart.tsx.",
|
|
457
|
+
"stacked": "Not currently supported (nested rings)."
|
|
458
|
+
},
|
|
459
|
+
"props": {
|
|
460
|
+
"data": { "type": "T[]", "required": true },
|
|
461
|
+
"valueKey": { "type": "keyof T & string", "required": true },
|
|
462
|
+
"nameKey": { "type": "keyof T & string", "required": true },
|
|
463
|
+
"colorKey": { "type": "keyof T & string", "notes": "Per-row fill from data; falls back to `colors` palette." },
|
|
464
|
+
"colors": { "type": "string[]", "default": "the five --crk-chart-N tokens" },
|
|
465
|
+
"aspectRatio": { "type": "number", "default": 1 },
|
|
466
|
+
"height": { "type": "number" },
|
|
467
|
+
"innerRadius": { "type": "number | string", "default": 0, "notes": "0 = pie, >0 = donut. Number = pixels, string = 'NN%' of half the minimum chart side." },
|
|
468
|
+
"outerRadius": { "type": "number | string", "default": "'90%'" },
|
|
469
|
+
"strokeWidth": { "type": "number", "default": 2, "notes": "Slice separator stroke width. 0 = 'separator-none'." },
|
|
470
|
+
"strokeColor": { "type": "string", "default": "var(--crk-color-bg)" },
|
|
471
|
+
"padAngle": { "type": "number", "default": 0, "notes": "Degrees of visual gap between slices." },
|
|
472
|
+
"showLabels": { "type": "boolean", "default": false },
|
|
473
|
+
"valuePosition": { "type": "enum", "values": ["outside", "inside"], "default": "outside" },
|
|
474
|
+
"showLabelLines": { "type": "boolean", "default": true, "notes": "Thin slice-colored leader line from the slice edge to each outside label. No effect for inside labels." },
|
|
475
|
+
"renderLabel": { "type": "(args: { row, value, fraction, index }) => ReactNode", "notes": "Overrides showLabels default content. Return null to skip a slice." },
|
|
476
|
+
"centerLabel": { "type": "ReactNode", "notes": "Rendered via foreignObject in the centre of the chart (only useful with innerRadius > 0)." },
|
|
477
|
+
"activeIndex": { "type": "number", "notes": "Pulls the slice outward; other slices fade." },
|
|
478
|
+
"onSliceClick": { "type": "(args: { row, value, index }) => void" },
|
|
479
|
+
"showLegend": { "type": "boolean", "default": true },
|
|
480
|
+
"showTooltip": { "type": "boolean", "default": true },
|
|
481
|
+
"valueFormat": { "type": "(v: number) => string" },
|
|
482
|
+
"renderTooltip": { "type": "(row: T) => ReactNode" },
|
|
483
|
+
"emptyMessage": { "type": "ReactNode", "default": "No data" },
|
|
484
|
+
"classes": { "type": "Partial<ChartClasses>" }
|
|
485
|
+
}
|
|
486
|
+
},
|
|
487
|
+
"RadarChart": {
|
|
488
|
+
"import": "import { RadarChart } from '@overdoser/react-toolkit'",
|
|
489
|
+
"element": "div containing responsive SVG",
|
|
490
|
+
"generic": "T extends Record<string, unknown>",
|
|
491
|
+
"shadcnVariantMapping": {
|
|
492
|
+
"default": "no extra props",
|
|
493
|
+
"multiple": "no extra props (multiple keys in config)",
|
|
494
|
+
"legend": "showLegend (on by default)",
|
|
495
|
+
"dots": "showPoints (on by default)",
|
|
496
|
+
"lines-only": "fillOpacity={0}",
|
|
497
|
+
"grid-circle": "gridType='circle'",
|
|
498
|
+
"grid-circle-fill": "gridType='circle' (+ fillOpacity for the 'fill')",
|
|
499
|
+
"grid-circle-no-lines": "gridType='circle' + showRadialLines={false}",
|
|
500
|
+
"grid-fill": "defaults (filled polygon grid)",
|
|
501
|
+
"grid-custom": "override the grid via classes={{ grid: '…' }} or theme tokens",
|
|
502
|
+
"grid-none": "showGrid={false} (+ usually showRadialLines={false})",
|
|
503
|
+
"icons": "set `icon` on each entry in `config`",
|
|
504
|
+
"label-custom": "renderAxisLabel",
|
|
505
|
+
"radius": "showRadiusAxis"
|
|
506
|
+
},
|
|
507
|
+
"props": {
|
|
508
|
+
"data": { "type": "T[]", "required": true },
|
|
509
|
+
"axisKey": { "type": "keyof T & string", "required": true },
|
|
510
|
+
"config": { "type": "ChartConfig", "required": true, "notes": "SeriesConfig may include `icon` for the icons variant." },
|
|
511
|
+
"aspectRatio": { "type": "number", "default": 1 },
|
|
512
|
+
"height": { "type": "number" },
|
|
513
|
+
"gridType": { "type": "enum", "values": ["polygon", "circle"], "default": "polygon" },
|
|
514
|
+
"showGrid": { "type": "boolean", "default": true },
|
|
515
|
+
"showRadialLines": { "type": "boolean", "default": true },
|
|
516
|
+
"showAxisLabels": { "type": "boolean", "default": true },
|
|
517
|
+
"showRadiusAxis": { "type": "boolean", "default": false },
|
|
518
|
+
"levels": { "type": "number", "default": 5 },
|
|
519
|
+
"showPoints": { "type": "boolean", "default": true },
|
|
520
|
+
"fillOpacity": { "type": "number", "default": 0.4, "notes": "0 = 'lines-only' variant." },
|
|
521
|
+
"strokeWidth": { "type": "number", "default": 2 },
|
|
522
|
+
"renderAxisLabel": { "type": "(args: { row, index, angle, x, y, textAnchor }) => ReactNode", "notes": "Return null to skip a vertex." },
|
|
523
|
+
"axisFormat": { "type": "(label: string) => string" },
|
|
524
|
+
"valueFormat": { "type": "(v: number) => string" },
|
|
525
|
+
"renderTooltip": { "type": "(row: T) => ReactNode" },
|
|
526
|
+
"showLegend": { "type": "boolean", "default": true },
|
|
527
|
+
"showTooltip": { "type": "boolean", "default": true },
|
|
528
|
+
"emptyMessage": { "type": "ReactNode", "default": "No data" },
|
|
529
|
+
"classes": { "type": "Partial<ChartClasses>" }
|
|
530
|
+
}
|
|
531
|
+
},
|
|
532
|
+
"TradingChart": {
|
|
533
|
+
"import": "import { TradingChart, type Datafeed } from '@overdoser/react-toolkit'",
|
|
534
|
+
"element": "div containing a <canvas>",
|
|
535
|
+
"renderTech": "Canvas 2D (devicePixelRatio-aware). All other charts use SVG.",
|
|
536
|
+
"datafeedContract": {
|
|
537
|
+
"Bar": "{ time: number /* unix seconds */; open: number; high: number; low: number; close: number; volume?: number }",
|
|
538
|
+
"getBars": "({ symbol, resolution, from, to, countBack? }) => Promise<Bar[]> sorted ascending by time",
|
|
539
|
+
"subscribeBars": "({ symbol, resolution, onTick(bar) }) => SubscriptionHandle. A tick whose time equals the previous bar updates the in-progress bar; otherwise the chart appends a new bar.",
|
|
540
|
+
"unsubscribeBars": "(handle) => void"
|
|
541
|
+
},
|
|
542
|
+
"indicators": {
|
|
543
|
+
"sma": "{ type: 'sma', period, color? } — overlay",
|
|
544
|
+
"ema": "{ type: 'ema', period, color? } — overlay",
|
|
545
|
+
"bollinger": "{ type: 'bollinger', period, stdDev?, color?, fillOpacity? } — overlay (filled band)",
|
|
546
|
+
"rsi": "{ type: 'rsi', period?, color?, overbought?, oversold? } — sub-pane",
|
|
547
|
+
"volume": "{ type: 'volume', upColor?, downColor? } — sub-pane (histogram)"
|
|
548
|
+
},
|
|
549
|
+
"props": {
|
|
550
|
+
"datafeed": { "type": "Datafeed", "required": true },
|
|
551
|
+
"symbol": { "type": "string", "required": true },
|
|
552
|
+
"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." },
|
|
553
|
+
"period": { "type": "Period", "notes": "Visible time-range window (the 'chart interval'): '1D' | '5D' | '1M' | '3M' | '6M' | '1Y' | '5Y' or a `<n><unit>` string. Overrides initialLookback when set. The chart does not auto-adjust resolution; pair with suggestResolutionForPeriod() / suggestPeriodForResolution() to couple the two." },
|
|
554
|
+
"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)." },
|
|
555
|
+
"timezone": { "type": "Timezone", "notes": "Controlled timezone for the crosshair time label. 'local' | 'utc' | IANA name. Pair with onTimezoneChange." },
|
|
556
|
+
"defaultTimezone": { "type": "Timezone", "default": "local", "notes": "Initial timezone when uncontrolled." },
|
|
557
|
+
"onTimezoneChange": { "type": "(tz: Timezone) => void" },
|
|
558
|
+
"showConfigPanel": { "type": "boolean", "default": true, "notes": "Gear button in the header that opens a Popover with a Timezone dropdown (Local / UTC / common IANA zones)." },
|
|
559
|
+
"indicators": { "type": "IndicatorConfig[]" },
|
|
560
|
+
"precision": { "type": "number", "default": 2 },
|
|
561
|
+
"initialLookback": { "type": "number", "default": 500, "notes": "Used only when `period` is not set." },
|
|
562
|
+
"height": { "type": "number", "default": 420 },
|
|
563
|
+
"upColor": { "type": "string", "default": "var(--crk-color-success)" },
|
|
564
|
+
"downColor": { "type": "string", "default": "var(--crk-color-danger)" },
|
|
565
|
+
"lineColor": { "type": "string", "default": "var(--crk-chart-1)" },
|
|
566
|
+
"onCrosshair": { "type": "(info: CrosshairInfo | null) => void" },
|
|
567
|
+
"onVisibleRangeChange": { "type": "(range: VisibleRange, bars: Bar[]) => void" }
|
|
568
|
+
},
|
|
569
|
+
"interactions": {
|
|
570
|
+
"drag": "pan",
|
|
571
|
+
"wheel": "zoom (cursor-anchored)",
|
|
572
|
+
"arrow-keys": "nudge pan",
|
|
573
|
+
"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."
|
|
574
|
+
},
|
|
575
|
+
"exportedHelpers": ["periodToSeconds", "suggestResolutionForPeriod", "suggestPeriodForResolution", "formatCrosshairTime"],
|
|
576
|
+
"exportedIndicatorMath": ["sma", "ema", "bollinger", "rsi", "volumeSeries"],
|
|
577
|
+
"outOfScope": [
|
|
578
|
+
"Drawing tools (trend lines, fibonacci, shapes)",
|
|
579
|
+
"Custom indicator settings dialog",
|
|
580
|
+
"Pinch-zoom touch gestures",
|
|
581
|
+
"Multi-symbol overlays",
|
|
582
|
+
"Persistence"
|
|
583
|
+
]
|
|
584
|
+
},
|
|
585
|
+
"Sparkline": {
|
|
586
|
+
"import": "import { Sparkline } from '@overdoser/react-toolkit'",
|
|
587
|
+
"element": "svg",
|
|
588
|
+
"forwardsRef": true,
|
|
589
|
+
"behavior": "Inline trend line, fixed pixel size, no axes/legend/tooltip — for table cells and KPI tiles.",
|
|
590
|
+
"props": {
|
|
591
|
+
"data": { "type": "number[]", "required": true },
|
|
592
|
+
"width": { "type": "number", "default": 80 },
|
|
593
|
+
"height": { "type": "number", "default": 24 },
|
|
594
|
+
"color": { "type": "string", "default": "var(--crk-chart-1)" },
|
|
595
|
+
"strokeWidth": { "type": "number", "default": 1.5 },
|
|
596
|
+
"curve": { "type": "enum", "values": ["linear", "monotone"], "default": "monotone" },
|
|
597
|
+
"fill": { "type": "boolean", "default": false, "notes": "Render a low-opacity area under the line." },
|
|
598
|
+
"showLastPoint": { "type": "boolean", "default": false },
|
|
599
|
+
"aria-label": { "type": "string" }
|
|
600
|
+
}
|
|
304
601
|
}
|
|
305
602
|
},
|
|
306
603
|
"hooks": {
|
|
@@ -347,7 +644,8 @@
|
|
|
347
644
|
"Modal content lives in Modal.Header / Modal.Body / Modal.Footer compound components.",
|
|
348
645
|
"FormField clones a single child element to inject form props — pass exactly one input element.",
|
|
349
646
|
"Server-side Table mode (when `onSort` is provided): the consumer is responsible for sorting and slicing `data` server-side.",
|
|
350
|
-
"Theme stylesheet is imported separately: '@overdoser/react-toolkit/theme.css'."
|
|
647
|
+
"Theme stylesheet is imported separately: '@overdoser/react-toolkit/theme.css'.",
|
|
648
|
+
"Charts use `--crk-chart-1` … `--crk-chart-5` color tokens (overridable on `:root`). Each chart is responsive via ResizeObserver; ship no external chart library."
|
|
351
649
|
],
|
|
352
650
|
"recipes": [
|
|
353
651
|
"recipes/login-form.tsx",
|
|
@@ -355,6 +653,12 @@
|
|
|
355
653
|
"recipes/server-side-table.tsx",
|
|
356
654
|
"recipes/confirm-modal.tsx",
|
|
357
655
|
"recipes/searchable-multi-select.tsx",
|
|
358
|
-
"recipes/dropdown-menu.tsx"
|
|
656
|
+
"recipes/dropdown-menu.tsx",
|
|
657
|
+
"recipes/dashboard-charts.tsx",
|
|
658
|
+
"recipes/interactive-bar-chart.tsx",
|
|
659
|
+
"recipes/interactive-line-chart.tsx",
|
|
660
|
+
"recipes/interactive-area-chart.tsx",
|
|
661
|
+
"recipes/interactive-pie-chart.tsx",
|
|
662
|
+
"recipes/trading-chart.tsx"
|
|
359
663
|
]
|
|
360
664
|
}
|
package/package.json
CHANGED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import {
|
|
2
|
+
LineChart,
|
|
3
|
+
AreaChart,
|
|
4
|
+
BarChart,
|
|
5
|
+
Sparkline,
|
|
6
|
+
Typography,
|
|
7
|
+
type ChartConfig,
|
|
8
|
+
} from '@overdoser/react-toolkit';
|
|
9
|
+
|
|
10
|
+
interface MonthRow {
|
|
11
|
+
month: string;
|
|
12
|
+
revenue: number;
|
|
13
|
+
costs: number;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const monthly: MonthRow[] = [
|
|
17
|
+
{ month: 'Jan', revenue: 120, costs: 60 },
|
|
18
|
+
{ month: 'Feb', revenue: 150, costs: 80 },
|
|
19
|
+
{ month: 'Mar', revenue: 95, costs: 70 },
|
|
20
|
+
{ month: 'Apr', revenue: 180, costs: 110 },
|
|
21
|
+
{ month: 'May', revenue: 220, costs: 130 },
|
|
22
|
+
{ month: 'Jun', revenue: 200, costs: 120 },
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
const finance: ChartConfig = {
|
|
26
|
+
revenue: { label: 'Revenue', color: 'var(--crk-chart-1)' },
|
|
27
|
+
costs: { label: 'Costs', color: 'var(--crk-chart-2)' },
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const visitors: ChartConfig = {
|
|
31
|
+
visits: { label: 'Visits', color: 'var(--crk-chart-1)' },
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const traffic = [
|
|
35
|
+
{ day: 'Mon', visits: 320 },
|
|
36
|
+
{ day: 'Tue', visits: 410 },
|
|
37
|
+
{ day: 'Wed', visits: 280 },
|
|
38
|
+
{ day: 'Thu', visits: 550 },
|
|
39
|
+
{ day: 'Fri', visits: 720 },
|
|
40
|
+
{ day: 'Sat', visits: 640 },
|
|
41
|
+
{ day: 'Sun', visits: 490 },
|
|
42
|
+
];
|
|
43
|
+
|
|
44
|
+
const dollar = (v: number) => `$${v}k`;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Compact dashboard with three primary charts and a row of KPI tiles.
|
|
48
|
+
* Demonstrates: multi-series LineChart, AreaChart with fill, grouped BarChart,
|
|
49
|
+
* inline Sparkline inside a KPI tile, custom value formatter, theme tokens.
|
|
50
|
+
*/
|
|
51
|
+
export function ChartsDashboard() {
|
|
52
|
+
return (
|
|
53
|
+
<div style={{ display: 'grid', gap: 24 }}>
|
|
54
|
+
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 16 }}>
|
|
55
|
+
<KpiTile label="MRR" value="$24.2k" trend={[18, 21, 19, 22, 24, 23, 25]} />
|
|
56
|
+
<KpiTile label="DAU" value="3,184" trend={[12, 14, 13, 16, 18, 21, 24]} />
|
|
57
|
+
<KpiTile label="Churn" value="1.9%" trend={[3, 3, 2, 2, 2, 2, 1.9]} color="var(--crk-chart-4)" />
|
|
58
|
+
</div>
|
|
59
|
+
|
|
60
|
+
<section>
|
|
61
|
+
<Typography variant="h3" weight="semibold">Revenue vs costs</Typography>
|
|
62
|
+
<LineChart
|
|
63
|
+
data={monthly}
|
|
64
|
+
xKey="month"
|
|
65
|
+
config={finance}
|
|
66
|
+
yFormat={dollar}
|
|
67
|
+
valueFormat={dollar}
|
|
68
|
+
/>
|
|
69
|
+
</section>
|
|
70
|
+
|
|
71
|
+
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 24 }}>
|
|
72
|
+
<section>
|
|
73
|
+
<Typography variant="h3" weight="semibold">Weekly visits</Typography>
|
|
74
|
+
<AreaChart data={traffic} xKey="day" config={visitors} />
|
|
75
|
+
</section>
|
|
76
|
+
<section>
|
|
77
|
+
<Typography variant="h3" weight="semibold">Monthly P&L</Typography>
|
|
78
|
+
<BarChart
|
|
79
|
+
data={monthly}
|
|
80
|
+
xKey="month"
|
|
81
|
+
config={finance}
|
|
82
|
+
yFormat={dollar}
|
|
83
|
+
valueFormat={dollar}
|
|
84
|
+
/>
|
|
85
|
+
</section>
|
|
86
|
+
</div>
|
|
87
|
+
</div>
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function KpiTile({
|
|
92
|
+
label,
|
|
93
|
+
value,
|
|
94
|
+
trend,
|
|
95
|
+
color = 'var(--crk-chart-1)',
|
|
96
|
+
}: {
|
|
97
|
+
label: string;
|
|
98
|
+
value: string;
|
|
99
|
+
trend: number[];
|
|
100
|
+
color?: string;
|
|
101
|
+
}) {
|
|
102
|
+
return (
|
|
103
|
+
<div
|
|
104
|
+
style={{
|
|
105
|
+
padding: 16,
|
|
106
|
+
borderRadius: 12,
|
|
107
|
+
border: '1px solid var(--crk-color-border)',
|
|
108
|
+
background: 'var(--crk-color-bg)',
|
|
109
|
+
display: 'flex',
|
|
110
|
+
flexDirection: 'column',
|
|
111
|
+
gap: 8,
|
|
112
|
+
}}
|
|
113
|
+
>
|
|
114
|
+
<Typography variant="span" color="muted">{label}</Typography>
|
|
115
|
+
<div style={{ display: 'flex', alignItems: 'baseline', justifyContent: 'space-between' }}>
|
|
116
|
+
<Typography variant="span" weight="bold" style={{ fontSize: 'var(--crk-font-size-2xl)' }}>
|
|
117
|
+
{value}
|
|
118
|
+
</Typography>
|
|
119
|
+
<Sparkline data={trend} width={100} height={28} fill color={color} aria-label={`${label} trend`} />
|
|
120
|
+
</div>
|
|
121
|
+
</div>
|
|
122
|
+
);
|
|
123
|
+
}
|