@nanisoft/prism-ui 0.10.1 → 0.13.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/LICENSE +21 -21
- package/dist/.tsbuildinfo +1 -1
- package/dist/blocks/app-shell-01/app-shell.js +4 -4
- package/dist/blocks/app-shell-01/index.d.ts +2 -2
- package/dist/blocks/app-shell-01/index.js +1 -1
- package/dist/blocks/auth-form-01/auth-form.d.ts +11 -1
- package/dist/blocks/auth-form-01/auth-form.d.ts.map +1 -1
- package/dist/blocks/auth-form-01/auth-form.js +11 -8
- package/dist/blocks/auth-form-01/index.d.ts +2 -2
- package/dist/blocks/auth-form-01/index.js +1 -1
- package/dist/blocks/cta-01/cta.d.ts +26 -21
- package/dist/blocks/cta-01/cta.d.ts.map +1 -1
- package/dist/blocks/cta-01/cta.js +2 -2
- package/dist/blocks/cta-01/index.d.ts +2 -2
- package/dist/blocks/cta-01/index.js +1 -1
- package/dist/blocks/data-table-01/data-table.js +7 -7
- package/dist/blocks/data-table-01/index.d.ts +2 -2
- package/dist/blocks/data-table-01/index.js +1 -1
- package/dist/blocks/empty-state-01/empty-state.d.ts +143 -0
- package/dist/blocks/empty-state-01/empty-state.d.ts.map +1 -0
- package/dist/blocks/empty-state-01/empty-state.js +104 -0
- package/dist/blocks/empty-state-01/index.d.ts +3 -0
- package/dist/blocks/empty-state-01/index.d.ts.map +1 -0
- package/dist/blocks/empty-state-01/index.js +1 -0
- package/dist/blocks/feature-grid-01/feature-grid.d.ts +28 -1
- package/dist/blocks/feature-grid-01/feature-grid.d.ts.map +1 -1
- package/dist/blocks/feature-grid-01/feature-grid.js +34 -3
- package/dist/blocks/feature-grid-01/index.d.ts +2 -2
- package/dist/blocks/feature-grid-01/index.js +1 -1
- package/dist/blocks/hero-01/hero.d.ts +49 -17
- package/dist/blocks/hero-01/hero.d.ts.map +1 -1
- package/dist/blocks/hero-01/hero.js +14 -9
- package/dist/blocks/hero-01/index.d.ts +2 -1
- package/dist/blocks/hero-01/index.d.ts.map +1 -1
- package/dist/blocks/hero-01/index.js +1 -1
- package/dist/blocks/index.d.ts +20 -20
- package/dist/blocks/index.js +19 -19
- package/dist/blocks/instrument-panel-01/index.d.ts +2 -2
- package/dist/blocks/instrument-panel-01/index.js +1 -1
- package/dist/blocks/instrument-panel-01/instrument-panel.js +1 -1
- package/dist/blocks/logo-strip-01/index.d.ts +2 -2
- package/dist/blocks/logo-strip-01/index.js +1 -1
- package/dist/blocks/logo-strip-01/logo-strip.d.ts +1 -1
- package/dist/blocks/logo-strip-01/logo-strip.js +2 -2
- package/dist/blocks/note-grid-01/index.d.ts +2 -2
- package/dist/blocks/note-grid-01/index.js +1 -1
- package/dist/blocks/note-grid-01/note-grid.d.ts +1 -1
- package/dist/blocks/note-grid-01/note-grid.js +1 -1
- package/dist/blocks/page-header-01/index.d.ts +2 -2
- package/dist/blocks/page-header-01/index.js +1 -1
- package/dist/blocks/page-header-01/page-header.d.ts +1 -1
- package/dist/blocks/page-header-01/page-header.js +4 -4
- package/dist/blocks/pricing-01/index.d.ts +1 -1
- package/dist/blocks/pricing-01/index.js +1 -1
- package/dist/blocks/pricing-01/pricing.d.ts +8 -1
- package/dist/blocks/pricing-01/pricing.d.ts.map +1 -1
- package/dist/blocks/pricing-01/pricing.js +15 -5
- package/dist/blocks/process-flow-01/index.d.ts +3 -0
- package/dist/blocks/process-flow-01/index.d.ts.map +1 -0
- package/dist/blocks/process-flow-01/index.js +1 -0
- package/dist/blocks/process-flow-01/process-flow.d.ts +118 -0
- package/dist/blocks/process-flow-01/process-flow.d.ts.map +1 -0
- package/dist/blocks/process-flow-01/process-flow.js +71 -0
- package/dist/blocks/process-rail-01/index.d.ts +2 -2
- package/dist/blocks/process-rail-01/index.js +1 -1
- package/dist/blocks/process-rail-01/process-rail.d.ts +1 -1
- package/dist/blocks/process-rail-01/process-rail.js +2 -2
- package/dist/blocks/product-grid-01/index.d.ts +2 -2
- package/dist/blocks/product-grid-01/index.js +1 -1
- package/dist/blocks/product-grid-01/product-grid.d.ts +37 -5
- package/dist/blocks/product-grid-01/product-grid.d.ts.map +1 -1
- package/dist/blocks/product-grid-01/product-grid.js +20 -6
- package/dist/blocks/run-console-01/index.d.ts +3 -0
- package/dist/blocks/run-console-01/index.d.ts.map +1 -0
- package/dist/blocks/run-console-01/index.js +1 -0
- package/dist/blocks/run-console-01/run-console.d.ts +111 -0
- package/dist/blocks/run-console-01/run-console.d.ts.map +1 -0
- package/dist/blocks/run-console-01/run-console.js +48 -0
- package/dist/blocks/settings-panel-01/index.d.ts +2 -2
- package/dist/blocks/settings-panel-01/index.js +1 -1
- package/dist/blocks/settings-panel-01/settings-panel.d.ts +11 -1
- package/dist/blocks/settings-panel-01/settings-panel.d.ts.map +1 -1
- package/dist/blocks/settings-panel-01/settings-panel.js +15 -10
- package/dist/blocks/site-footer/index.d.ts +2 -2
- package/dist/blocks/site-footer/index.js +1 -1
- package/dist/blocks/site-footer/site-footer.d.ts +1 -1
- package/dist/blocks/site-footer/site-footer.js +2 -2
- package/dist/blocks/site-header/index.d.ts +2 -2
- package/dist/blocks/site-header/index.js +1 -1
- package/dist/blocks/site-header/site-header.d.ts +2 -2
- package/dist/blocks/site-header/site-header.js +3 -3
- package/dist/blocks/site-navbar/index.d.ts +3 -0
- package/dist/blocks/site-navbar/index.d.ts.map +1 -0
- package/dist/blocks/site-navbar/index.js +1 -0
- package/dist/blocks/site-navbar/mobile-nav.d.ts +48 -0
- package/dist/blocks/site-navbar/mobile-nav.d.ts.map +1 -0
- package/dist/blocks/site-navbar/mobile-nav.js +35 -0
- package/dist/blocks/site-navbar/mode-toggle.d.ts +39 -0
- package/dist/blocks/site-navbar/mode-toggle.d.ts.map +1 -0
- package/dist/blocks/site-navbar/mode-toggle.js +39 -0
- package/dist/blocks/site-navbar/navbar-controls.d.ts +79 -0
- package/dist/blocks/site-navbar/navbar-controls.d.ts.map +1 -0
- package/dist/blocks/site-navbar/navbar-controls.js +40 -0
- package/dist/blocks/site-navbar/search-entry.d.ts +29 -0
- package/dist/blocks/site-navbar/search-entry.d.ts.map +1 -0
- package/dist/blocks/site-navbar/search-entry.js +52 -0
- package/dist/blocks/site-navbar/site-navbar.d.ts +212 -0
- package/dist/blocks/site-navbar/site-navbar.d.ts.map +1 -0
- package/dist/blocks/site-navbar/site-navbar.js +61 -0
- package/dist/blocks/site-navbar/sites-menu.d.ts +40 -0
- package/dist/blocks/site-navbar/sites-menu.d.ts.map +1 -0
- package/dist/blocks/site-navbar/sites-menu.js +48 -0
- package/dist/blocks/site-navbar/theme-menu.d.ts +47 -0
- package/dist/blocks/site-navbar/theme-menu.d.ts.map +1 -0
- package/dist/blocks/site-navbar/theme-menu.js +55 -0
- package/dist/blocks/stack-grid-01/index.d.ts +2 -2
- package/dist/blocks/stack-grid-01/index.js +1 -1
- package/dist/blocks/stack-grid-01/stack-grid.d.ts +42 -11
- package/dist/blocks/stack-grid-01/stack-grid.d.ts.map +1 -1
- package/dist/blocks/stack-grid-01/stack-grid.js +15 -7
- package/dist/blocks/stats-01/index.d.ts +9 -1
- package/dist/blocks/stats-01/index.d.ts.map +1 -1
- package/dist/blocks/stats-01/index.js +8 -1
- package/dist/blocks/stats-01/stats.d.ts +1 -1
- package/dist/blocks/stats-01/stats.js +2 -2
- package/dist/blocks/status-ledger-01/index.d.ts +2 -2
- package/dist/blocks/status-ledger-01/index.js +1 -1
- package/dist/blocks/status-ledger-01/status-ledger.d.ts +1 -1
- package/dist/blocks/status-ledger-01/status-ledger.js +2 -2
- package/dist/catalog.d.ts +1 -1
- package/dist/catalog.d.ts.map +1 -1
- package/dist/catalog.js +473 -1
- package/dist/components/index.d.ts +104 -66
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +74 -40
- package/dist/components/ui/accordion.js +1 -1
- package/dist/components/ui/alert-dialog.d.ts +119 -0
- package/dist/components/ui/alert-dialog.d.ts.map +1 -0
- package/dist/components/ui/alert-dialog.js +82 -0
- package/dist/components/ui/alert.js +1 -1
- package/dist/components/ui/aspect-ratio.d.ts +60 -0
- package/dist/components/ui/aspect-ratio.d.ts.map +1 -0
- package/dist/components/ui/aspect-ratio.js +61 -0
- package/dist/components/ui/avatar.js +1 -1
- package/dist/components/ui/badge.js +1 -1
- package/dist/components/ui/breadcrumb.js +1 -1
- package/dist/components/ui/button-group.d.ts +68 -0
- package/dist/components/ui/button-group.d.ts.map +1 -0
- package/dist/components/ui/button-group.js +54 -0
- package/dist/components/ui/button.js +1 -1
- package/dist/components/ui/calendar.d.ts +158 -0
- package/dist/components/ui/calendar.d.ts.map +1 -0
- package/dist/components/ui/calendar.js +216 -0
- package/dist/components/ui/card.d.ts +18 -6
- package/dist/components/ui/card.d.ts.map +1 -1
- package/dist/components/ui/card.js +23 -8
- package/dist/components/ui/carousel.d.ts +118 -0
- package/dist/components/ui/carousel.d.ts.map +1 -0
- package/dist/components/ui/carousel.js +152 -0
- package/dist/components/ui/chart-frame.d.ts +216 -0
- package/dist/components/ui/chart-frame.d.ts.map +1 -0
- package/dist/components/ui/chart-frame.js +286 -0
- package/dist/components/ui/checkbox.js +1 -1
- package/dist/components/ui/collapsible.d.ts +97 -0
- package/dist/components/ui/collapsible.d.ts.map +1 -0
- package/dist/components/ui/collapsible.js +54 -0
- package/dist/components/ui/combobox.d.ts +141 -0
- package/dist/components/ui/combobox.d.ts.map +1 -0
- package/dist/components/ui/combobox.js +243 -0
- package/dist/components/ui/command-palette.d.ts +106 -0
- package/dist/components/ui/command-palette.d.ts.map +1 -0
- package/dist/components/ui/command-palette.js +176 -0
- package/dist/components/ui/command.d.ts +82 -0
- package/dist/components/ui/command.d.ts.map +1 -0
- package/dist/components/ui/command.js +37 -0
- package/dist/components/ui/context-menu.d.ts +151 -0
- package/dist/components/ui/context-menu.d.ts.map +1 -0
- package/dist/components/ui/context-menu.js +97 -0
- package/dist/components/ui/cta-link.js +1 -1
- package/dist/components/ui/date-picker.d.ts +111 -0
- package/dist/components/ui/date-picker.d.ts.map +1 -0
- package/dist/components/ui/date-picker.js +74 -0
- package/dist/components/ui/diagram.d.ts +84 -15
- package/dist/components/ui/diagram.d.ts.map +1 -1
- package/dist/components/ui/diagram.js +77 -7
- package/dist/components/ui/dialog.js +1 -1
- package/dist/components/ui/diff.d.ts +105 -0
- package/dist/components/ui/diff.d.ts.map +1 -0
- package/dist/components/ui/diff.js +113 -0
- package/dist/components/ui/dropdown-menu.d.ts +18 -0
- package/dist/components/ui/dropdown-menu.d.ts.map +1 -1
- package/dist/components/ui/dropdown-menu.js +1 -1
- package/dist/components/ui/fact-list.js +1 -1
- package/dist/components/ui/field.js +1 -1
- package/dist/components/ui/form.d.ts +151 -0
- package/dist/components/ui/form.d.ts.map +1 -0
- package/dist/components/ui/form.js +73 -0
- package/dist/components/ui/hover-card.d.ts +105 -0
- package/dist/components/ui/hover-card.d.ts.map +1 -0
- package/dist/components/ui/hover-card.js +49 -0
- package/dist/components/ui/input-group.d.ts +99 -0
- package/dist/components/ui/input-group.d.ts.map +1 -0
- package/dist/components/ui/input-group.js +83 -0
- package/dist/components/ui/input.js +1 -1
- package/dist/components/ui/item.d.ts +113 -0
- package/dist/components/ui/item.d.ts.map +1 -0
- package/dist/components/ui/item.js +61 -0
- package/dist/components/ui/kbd.js +1 -1
- package/dist/components/ui/label.d.ts +58 -0
- package/dist/components/ui/label.d.ts.map +1 -0
- package/dist/components/ui/label.js +29 -0
- package/dist/components/ui/live-region.d.ts +9 -0
- package/dist/components/ui/live-region.d.ts.map +1 -1
- package/dist/components/ui/live-region.js +10 -2
- package/dist/components/ui/mark.js +1 -1
- package/dist/components/ui/menubar.d.ts +197 -0
- package/dist/components/ui/menubar.d.ts.map +1 -0
- package/dist/components/ui/menubar.js +109 -0
- package/dist/components/ui/meter.d.ts +121 -0
- package/dist/components/ui/meter.d.ts.map +1 -0
- package/dist/components/ui/meter.js +82 -0
- package/dist/components/ui/native-select.d.ts +72 -0
- package/dist/components/ui/native-select.d.ts.map +1 -0
- package/dist/components/ui/native-select.js +55 -0
- package/dist/components/ui/navigation-menu.d.ts +186 -0
- package/dist/components/ui/navigation-menu.d.ts.map +1 -0
- package/dist/components/ui/navigation-menu.js +94 -0
- package/dist/components/ui/number-field.d.ts +111 -0
- package/dist/components/ui/number-field.d.ts.map +1 -0
- package/dist/components/ui/number-field.js +84 -0
- package/dist/components/ui/one-time-code.d.ts +127 -0
- package/dist/components/ui/one-time-code.d.ts.map +1 -0
- package/dist/components/ui/one-time-code.js +57 -0
- package/dist/components/ui/pagination.js +1 -1
- package/dist/components/ui/popover.js +1 -1
- package/dist/components/ui/product-mark.d.ts +1 -1
- package/dist/components/ui/product-mark.js +1 -1
- package/dist/components/ui/product-switcher.d.ts +1 -1
- package/dist/components/ui/product-switcher.js +2 -2
- package/dist/components/ui/progress.js +1 -1
- package/dist/components/ui/prose.js +1 -1
- package/dist/components/ui/pulse-graph.js +1 -1
- package/dist/components/ui/pulse-series.js +1 -1
- package/dist/components/ui/radio-group.js +1 -1
- package/dist/components/ui/resizable.d.ts +129 -0
- package/dist/components/ui/resizable.d.ts.map +1 -0
- package/dist/components/ui/resizable.js +205 -0
- package/dist/components/ui/scroll-area.d.ts +97 -0
- package/dist/components/ui/scroll-area.d.ts.map +1 -0
- package/dist/components/ui/scroll-area.js +68 -0
- package/dist/components/ui/search-dialog.d.ts +139 -0
- package/dist/components/ui/search-dialog.d.ts.map +1 -0
- package/dist/components/ui/search-dialog.js +178 -0
- package/dist/components/ui/section.d.ts +26 -0
- package/dist/components/ui/section.d.ts.map +1 -1
- package/dist/components/ui/section.js +32 -1
- package/dist/components/ui/select.js +1 -1
- package/dist/components/ui/separator.js +1 -1
- package/dist/components/ui/sheet.d.ts +103 -0
- package/dist/components/ui/sheet.d.ts.map +1 -0
- package/dist/components/ui/sheet.js +58 -0
- package/dist/components/ui/sidebar.d.ts +228 -0
- package/dist/components/ui/sidebar.d.ts.map +1 -0
- package/dist/components/ui/sidebar.js +172 -0
- package/dist/components/ui/signal-field.js +1 -1
- package/dist/components/ui/skeleton.js +1 -1
- package/dist/components/ui/slider.js +1 -1
- package/dist/components/ui/spinner.d.ts +79 -0
- package/dist/components/ui/spinner.d.ts.map +1 -0
- package/dist/components/ui/spinner.js +79 -0
- package/dist/components/ui/switch.js +1 -1
- package/dist/components/ui/table-sort.d.ts +97 -0
- package/dist/components/ui/table-sort.d.ts.map +1 -0
- package/dist/components/ui/table-sort.js +102 -0
- package/dist/components/ui/table.js +1 -1
- package/dist/components/ui/tabs.js +1 -1
- package/dist/components/ui/textarea.js +1 -1
- package/dist/components/ui/timeline.d.ts +106 -0
- package/dist/components/ui/timeline.d.ts.map +1 -0
- package/dist/components/ui/timeline.js +111 -0
- package/dist/components/ui/toast.d.ts +219 -0
- package/dist/components/ui/toast.d.ts.map +1 -0
- package/dist/components/ui/toast.js +270 -0
- package/dist/components/ui/toggle-group.d.ts +118 -0
- package/dist/components/ui/toggle-group.d.ts.map +1 -0
- package/dist/components/ui/toggle-group.js +194 -0
- package/dist/components/ui/toggle.d.ts +73 -0
- package/dist/components/ui/toggle.d.ts.map +1 -0
- package/dist/components/ui/toggle.js +44 -0
- package/dist/components/ui/tooltip.js +1 -1
- package/dist/components/ui/tree.js +1 -1
- package/dist/components/ui/typography.js +1 -1
- package/dist/fonts/Inter-OFL.txt +93 -93
- package/dist/index.d.ts +72 -70
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +61 -60
- package/dist/lib/rank.d.ts +69 -0
- package/dist/lib/rank.d.ts.map +1 -0
- package/dist/lib/rank.js +77 -0
- package/dist/live/index.d.ts +22 -0
- package/dist/live/index.d.ts.map +1 -0
- package/dist/live/index.js +20 -0
- package/dist/live/run-stream-01/index.d.ts +3 -0
- package/dist/live/run-stream-01/index.d.ts.map +1 -0
- package/dist/live/run-stream-01/index.js +1 -0
- package/dist/live/run-stream-01/run-stream.d.ts +125 -0
- package/dist/live/run-stream-01/run-stream.d.ts.map +1 -0
- package/dist/live/run-stream-01/run-stream.js +111 -0
- package/dist/pages/auth-page/index.d.ts +15 -2
- package/dist/pages/auth-page/index.d.ts.map +1 -1
- package/dist/pages/auth-page/index.js +8 -4
- package/dist/pages/blog-post-page/index.js +5 -5
- package/dist/pages/dashboard-page/index.d.ts +4 -4
- package/dist/pages/dashboard-page/index.js +4 -4
- package/dist/pages/docs-shell/docs-shell.d.ts +37 -4
- package/dist/pages/docs-shell/docs-shell.d.ts.map +1 -1
- package/dist/pages/docs-shell/docs-shell.js +107 -16
- package/dist/pages/docs-shell/index.d.ts +2 -2
- package/dist/pages/docs-shell/index.js +1 -1
- package/dist/pages/index.d.ts +14 -14
- package/dist/pages/index.js +7 -7
- package/dist/pages/marketing-page/index.d.ts +6 -6
- package/dist/pages/marketing-page/index.js +5 -5
- package/dist/pages/not-found-page/index.js +4 -4
- package/dist/pages/settings-page/index.d.ts +3 -3
- package/dist/pages/settings-page/index.js +4 -4
- package/dist/provider/index.d.ts +4 -4
- package/dist/provider/index.js +2 -2
- package/dist/provider/provider.d.ts +1 -1
- package/dist/provider/provider.js +1 -1
- package/dist/provider/theme-script.d.ts +1 -1
- package/dist/provider/theme-script.js +1 -1
- package/dist/styles.css +689 -0
- package/package.json +9 -1
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import type { ComponentProps, ReactNode } from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* The one of the five series roles a series is drawn in.
|
|
4
|
+
*
|
|
5
|
+
* These are the token names rather than a Prism-specific alias, and that is the
|
|
6
|
+
* whole contract: `chart-1` through `chart-5` is the only five-way colour set
|
|
7
|
+
* the token contract publishes, so a consumer who wants a particular series in a
|
|
8
|
+
* particular colour names the role and gets it in every pack and both modes. A
|
|
9
|
+
* wider type here would be a wider palette, and a wider palette is a second
|
|
10
|
+
* source of truth for something the tokens already own.
|
|
11
|
+
*/
|
|
12
|
+
export type ChartColor = 'chart-1' | 'chart-2' | 'chart-3' | 'chart-4' | 'chart-5';
|
|
13
|
+
/**
|
|
14
|
+
* How the series are drawn.
|
|
15
|
+
*
|
|
16
|
+
* Two marks, and the split is about what the reader compares. A line is for a
|
|
17
|
+
* value that moves continuously across the categories, where the shape between
|
|
18
|
+
* two points is the point. A bar is for a value read against a baseline, where
|
|
19
|
+
* the length is the measurement and the gap between two bars means nothing. A
|
|
20
|
+
* consumer that draws a bar chart of weekly signups is drawing the right mark
|
|
21
|
+
* and saying the wrong thing; the bars invite a comparison of lengths that only
|
|
22
|
+
* means something when the value starts at zero.
|
|
23
|
+
*/
|
|
24
|
+
export type ChartMark = 'line' | 'bar';
|
|
25
|
+
/** One series: a name, the role it is drawn in, and one value per category. */
|
|
26
|
+
export type ChartSeries = {
|
|
27
|
+
/**
|
|
28
|
+
* A stable key for the series.
|
|
29
|
+
*
|
|
30
|
+
* Required, because the same series is a line, a dot, a legend entry and a
|
|
31
|
+
* table column, and a reader whose focus is in the table must land in the
|
|
32
|
+
* column for the series they were reading. Keyed on the caller's own id rather
|
|
33
|
+
* than on the position in the array, so reordering the series does not move a
|
|
34
|
+
* reader's focus to a different series.
|
|
35
|
+
*/
|
|
36
|
+
id: string;
|
|
37
|
+
/**
|
|
38
|
+
* The name of the series, in the caller's words.
|
|
39
|
+
*
|
|
40
|
+
* It appears in the legend, in the table's column head, and nowhere else. This
|
|
41
|
+
* is the string that makes a colour a name: a chart drawn in `chart-2` and
|
|
42
|
+
* `chart-5` is unreadable to anyone who cannot separate teal from red, and
|
|
43
|
+
* `chart-2` measures 2.49:1 against a light card, which is a second reason the
|
|
44
|
+
* legend is not optional here.
|
|
45
|
+
*/
|
|
46
|
+
label: string;
|
|
47
|
+
/**
|
|
48
|
+
* The role this series is drawn in.
|
|
49
|
+
*
|
|
50
|
+
* The caller assigns it and the Component never does, because a series colour
|
|
51
|
+
* is a claim about what a thing is: the revenue line is the brand hue and the
|
|
52
|
+
* churn line is the alarming one, and a Component that picked by position
|
|
53
|
+
* would be making that claim on the caller's behalf and getting it wrong on
|
|
54
|
+
* every chart after the first.
|
|
55
|
+
*/
|
|
56
|
+
color: ChartColor;
|
|
57
|
+
/**
|
|
58
|
+
* One value per category, in the order the categories are given.
|
|
59
|
+
*
|
|
60
|
+
* A value may be `null` for a gap, and a series shorter than the categories
|
|
61
|
+
* is padded with gaps rather than shifted: a value under the wrong category is
|
|
62
|
+
* a wrong answer that looks right, and it is the failure this shape is most
|
|
63
|
+
* likely to produce. A value longer than the categories is ignored, and it does
|
|
64
|
+
* not set the vertical scale either, because a value with no category has
|
|
65
|
+
* nowhere to be drawn.
|
|
66
|
+
*/
|
|
67
|
+
values: readonly (number | null)[];
|
|
68
|
+
};
|
|
69
|
+
/** The props the ChartFrame accepts. */
|
|
70
|
+
export interface ChartFrameProps extends Omit<ComponentProps<'figure'>, 'children' | 'title'> {
|
|
71
|
+
/**
|
|
72
|
+
* The categories along the horizontal axis, in order. The caller's words.
|
|
73
|
+
*
|
|
74
|
+
* A month name, a release name, a region name, a build number. This is the
|
|
75
|
+
* array that makes a chart a claim about the caller's own domain rather than
|
|
76
|
+
* about a design system, which is why it is a prop and not a date range the
|
|
77
|
+
* Component generates: a chart that named its own months would be wrong in
|
|
78
|
+
* every product but the one it was written for.
|
|
79
|
+
*/
|
|
80
|
+
categories: readonly string[];
|
|
81
|
+
/** The series, each with the role it is drawn in. */
|
|
82
|
+
series: readonly ChartSeries[];
|
|
83
|
+
/**
|
|
84
|
+
* The mark the series are drawn with. @defaultValue 'line'
|
|
85
|
+
*
|
|
86
|
+
* Every series in one chart takes the same mark. Mixing them is a chart about
|
|
87
|
+
* two different things, and a reader cannot tell which series the mark is
|
|
88
|
+
* making a claim about.
|
|
89
|
+
*/
|
|
90
|
+
mark?: ChartMark;
|
|
91
|
+
/**
|
|
92
|
+
* The accessible name of the figure.
|
|
93
|
+
*
|
|
94
|
+
* Required rather than defaulted, because two charts on a dashboard are two
|
|
95
|
+
* figures a reader cannot tell apart, and because the name is the only thing
|
|
96
|
+
* that states what is being plotted. A figure named "Chart" is a Component
|
|
97
|
+
* shipping a sentence about a product it knows nothing about.
|
|
98
|
+
*/
|
|
99
|
+
label: string;
|
|
100
|
+
/** The visible heading above the plot, when the surrounding layout has none. */
|
|
101
|
+
title?: ReactNode;
|
|
102
|
+
/**
|
|
103
|
+
* The words the data table is captioned with.
|
|
104
|
+
*
|
|
105
|
+
* Required, and separate from `label`, because the two are read at different
|
|
106
|
+
* points and mean different things: the name identifies the figure, the
|
|
107
|
+
* caption identifies the table a reader has just navigated into. Collapsing
|
|
108
|
+
* them would leave a caption that names the figure rather than the numbers.
|
|
109
|
+
*/
|
|
110
|
+
tableCaption: ReactNode;
|
|
111
|
+
/**
|
|
112
|
+
* The head of the category column in the data table.
|
|
113
|
+
*
|
|
114
|
+
* Required for the same reason the series names are: the table's first column
|
|
115
|
+
* holds the caller's categories and a column of values with no head is a
|
|
116
|
+
* column of numbers a reader has to guess the meaning of. It is the word for
|
|
117
|
+
* whatever the categories are: "Month", "Region", "Build".
|
|
118
|
+
*/
|
|
119
|
+
categoryHeading: string;
|
|
120
|
+
/**
|
|
121
|
+
* Whether the data table is seen as well as announced. @defaultValue 'visible'
|
|
122
|
+
*
|
|
123
|
+
* The table is always in the document either way. `hidden` moves it to
|
|
124
|
+
* `sr-only`, so it is still announced, still reachable by a screen reader's
|
|
125
|
+
* table navigation and still findable by the reader's own find-in-page; what
|
|
126
|
+
* changes is whether a sighted reader can see it. A consumer with a dense
|
|
127
|
+
* dashboard, where five visible tables under five plots is more surface than
|
|
128
|
+
* the page can carry, passes `hidden` and keeps the representation. A consumer
|
|
129
|
+
* whose readers are looking up exact figures leaves it visible, because a table
|
|
130
|
+
* nobody can see is a table a reader does not know to ask for.
|
|
131
|
+
*/
|
|
132
|
+
table?: 'visible' | 'hidden';
|
|
133
|
+
/**
|
|
134
|
+
* The words for a value on the vertical axis, given the number.
|
|
135
|
+
*
|
|
136
|
+
* Defaults to the number itself, and deliberately not shared with the table:
|
|
137
|
+
* an axis has room for four characters and a table has room for a currency
|
|
138
|
+
* symbol, so the two jobs want different renderings and a consumer that
|
|
139
|
+
* compacts the axis wants the table to keep the exact figure. Passing `format`
|
|
140
|
+
* alone is the normal case and is exactly the split these two jobs want.
|
|
141
|
+
*/
|
|
142
|
+
format?: (value: number) => string;
|
|
143
|
+
/**
|
|
144
|
+
* The words for a value in the data table, given the number.
|
|
145
|
+
*
|
|
146
|
+
* Defaults to the number itself, so a table of currency is a `tableFormat` away
|
|
147
|
+
* and a table of raw counts needs nothing.
|
|
148
|
+
*/
|
|
149
|
+
tableFormat?: (value: number) => string;
|
|
150
|
+
/**
|
|
151
|
+
* What a reader is told when there is nothing to plot.
|
|
152
|
+
*
|
|
153
|
+
* An empty chart is a state a caller reaches on a first load, on a filter that
|
|
154
|
+
* matched nothing and on a range with no events in it, and an empty plot box
|
|
155
|
+
* with an axis and no marks reads as a broken chart rather than as an answer.
|
|
156
|
+
* The words are the caller's, because only the caller knows whether the answer
|
|
157
|
+
* is "no data yet" or "no results for this filter".
|
|
158
|
+
*/
|
|
159
|
+
empty?: ReactNode;
|
|
160
|
+
/** Layout only, exactly as on every Component. */
|
|
161
|
+
className?: string;
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* A chart: the frame every chart in a product shares, and the numbers behind it.
|
|
165
|
+
*
|
|
166
|
+
* **One frame is the design.** Five charts that each draw their own axes are
|
|
167
|
+
* five sets of numbers that do not line up, and a dashboard where two plots
|
|
168
|
+
* disagree by six pixels looks broken in a way nobody can name, because the
|
|
169
|
+
* reader cannot see the six pixels: they can only see that the two charts do not
|
|
170
|
+
* agree. So the axis gutter, the field's height, the gridline spacing and the
|
|
171
|
+
* category row are all fixed here, and two charts in a row have the same plot box
|
|
172
|
+
* whatever their data says. The gutter in particular is a fixed width rather than
|
|
173
|
+
* a width that fits the longest label, because a gutter that grows with the
|
|
174
|
+
* numbers is the mechanism by which two charts stop aligning.
|
|
175
|
+
*
|
|
176
|
+
* **The colour is assigned by the caller and drawn from the token contract.**
|
|
177
|
+
* `chart-1` through `chart-5` arrive as the five semantic utilities, and there
|
|
178
|
+
* is no hex, no ramp step and no `rgb()` anywhere in this Component. A series
|
|
179
|
+
* colour is a claim about what a thing is, so the Component that knows nothing
|
|
180
|
+
* about the caller's product has no business choosing one.
|
|
181
|
+
*
|
|
182
|
+
* **The table is part of the frame, not a prop beside it, and that is the other
|
|
183
|
+
* half of the design.** A chart whose accessible representation is a sibling
|
|
184
|
+
* prop is a chart whose table a consumer wrote once and updated when the data
|
|
185
|
+
* changed, which is to say a chart whose table says last quarter. Here the marks
|
|
186
|
+
* and the cells are two renderings of one `series` array, so they cannot drift:
|
|
187
|
+
* there is no second copy to fall behind. The table is always in the document,
|
|
188
|
+
* and `table` decides whether it is also seen.
|
|
189
|
+
*
|
|
190
|
+
* **The legend is always drawn.** It is the only place the series-to-colour
|
|
191
|
+
* assignment is stated in words, and it is not optional because two of the five
|
|
192
|
+
* roles measure below 3:1 against a light card: `chart-2` at 2.49:1 and
|
|
193
|
+
* `chart-3` at 2.15:1 in the default pack. A mark in either is found by its
|
|
194
|
+
* position and its shape, not by its colour, so the name beside it is what makes
|
|
195
|
+
* it a series rather than a shape. Past about three series, hue is the only
|
|
196
|
+
* channel left and the table is the answer, which is a fact about the data and
|
|
197
|
+
* not about this Component.
|
|
198
|
+
*
|
|
199
|
+
* **It is a server Component.** No hook, no state, no effect and no event
|
|
200
|
+
* handler: the plot is arithmetic over props and the table is a table. A chart
|
|
201
|
+
* that animated its draw-in would be refused by this system's motion rules, and
|
|
202
|
+
* the one motion here is a bar resizing when its value changes, which is state
|
|
203
|
+
* feedback and is the only kind this system allows.
|
|
204
|
+
*
|
|
205
|
+
* **What this is not.** There is no scale beyond the one described above, no
|
|
206
|
+
* tick generation to choose, no hover tooltip, no crosshair, no zoom, no
|
|
207
|
+
* stacking, no curve interpolation, and no draw-in animation. A tooltip would be
|
|
208
|
+
* a hover affordance over a picture, and a picture a reader cannot query is the
|
|
209
|
+
* failure the table exists to prevent, so the exact value is answered by the
|
|
210
|
+
* table rather than by a surface that appears under a pointer. A consumer that
|
|
211
|
+
* needs any of those is reaching for a charting library, and this Component is
|
|
212
|
+
* the frame that library's marks should sit in rather than the library.
|
|
213
|
+
*/
|
|
214
|
+
declare function ChartFrame({ categories, series, mark, label, title, tableCaption, categoryHeading, table, format, tableFormat, empty, className, ...props }: ChartFrameProps): import("react").JSX.Element;
|
|
215
|
+
export { ChartFrame };
|
|
216
|
+
//# sourceMappingURL=chart-frame.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chart-frame.d.ts","sourceRoot":"","sources":["../../../src/components/ui/chart-frame.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAatD;;;;;;;;;GASG;AACH,MAAM,MAAM,UAAU,GAAG,SAAS,GAAG,SAAS,GAAG,SAAS,GAAG,SAAS,GAAG,SAAS,CAAA;AAElF;;;;;;;;;;GAUG;AACH,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG,KAAK,CAAA;AAEtC,+EAA+E;AAC/E,MAAM,MAAM,WAAW,GAAG;IACxB;;;;;;;;OAQG;IACH,EAAE,EAAE,MAAM,CAAA;IACV;;;;;;;;OAQG;IACH,KAAK,EAAE,MAAM,CAAA;IACb;;;;;;;;OAQG;IACH,KAAK,EAAE,UAAU,CAAA;IACjB;;;;;;;;;OASG;IACH,MAAM,EAAE,SAAS,CAAC,MAAM,GAAG,IAAI,CAAC,EAAE,CAAA;CACnC,CAAA;AAED,wCAAwC;AACxC,MAAM,WAAW,eAAgB,SAAQ,IAAI,CAAC,cAAc,CAAC,QAAQ,CAAC,EAAE,UAAU,GAAG,OAAO,CAAC;IAC3F;;;;;;;;OAQG;IACH,UAAU,EAAE,SAAS,MAAM,EAAE,CAAA;IAC7B,qDAAqD;IACrD,MAAM,EAAE,SAAS,WAAW,EAAE,CAAA;IAC9B;;;;;;OAMG;IACH,IAAI,CAAC,EAAE,SAAS,CAAA;IAChB;;;;;;;OAOG;IACH,KAAK,EAAE,MAAM,CAAA;IACb,gFAAgF;IAChF,KAAK,CAAC,EAAE,SAAS,CAAA;IACjB;;;;;;;OAOG;IACH,YAAY,EAAE,SAAS,CAAA;IACvB;;;;;;;OAOG;IACH,eAAe,EAAE,MAAM,CAAA;IACvB;;;;;;;;;;;OAWG;IACH,KAAK,CAAC,EAAE,SAAS,GAAG,QAAQ,CAAA;IAC5B;;;;;;;;OAQG;IACH,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAA;IAClC;;;;;OAKG;IACH,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAA;IACvC;;;;;;;;OAQG;IACH,KAAK,CAAC,EAAE,SAAS,CAAA;IACjB,kDAAkD;IAClD,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAkKD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkDG;AACH,iBAAS,UAAU,CAAC,EAClB,UAAU,EACV,MAAM,EACN,IAAa,EACb,KAAK,EACL,KAAK,EACL,YAAY,EACZ,eAAe,EACf,KAAiB,EACjB,MAAiB,EACjB,WAAsB,EACtB,KAAK,EACL,SAAS,EACT,GAAG,KAAK,EACT,EAAE,eAAe,+BAqTjB;AAED,OAAO,EAAE,UAAU,EAAE,CAAA"}
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { cn } from '../../lib/utils.js';
|
|
3
|
+
import { Table, TableBody, TableCaption, TableCell, TableHead, TableHeader, TableRow, } from './table.js';
|
|
4
|
+
/** The fill a series is drawn in, from the role the caller assigned it. */
|
|
5
|
+
const FILL = {
|
|
6
|
+
'chart-1': 'bg-chart-1',
|
|
7
|
+
'chart-2': 'bg-chart-2',
|
|
8
|
+
'chart-3': 'bg-chart-3',
|
|
9
|
+
'chart-4': 'bg-chart-4',
|
|
10
|
+
'chart-5': 'bg-chart-5',
|
|
11
|
+
};
|
|
12
|
+
/** The stroke a line is drawn in, from the same role and no other. */
|
|
13
|
+
const STROKE = {
|
|
14
|
+
'chart-1': 'stroke-chart-1',
|
|
15
|
+
'chart-2': 'stroke-chart-2',
|
|
16
|
+
'chart-3': 'stroke-chart-3',
|
|
17
|
+
'chart-4': 'stroke-chart-4',
|
|
18
|
+
'chart-5': 'stroke-chart-5',
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* Where a category label is nudged to sit, so a label never hangs off the plot.
|
|
22
|
+
*
|
|
23
|
+
* A label is centred on its category, which puts the first and the last half
|
|
24
|
+
* outside the field. The edge labels are pinned to the edge instead, so a line
|
|
25
|
+
* whose first and last points are on the boundary have their labels on it too.
|
|
26
|
+
* A bar's categories are column centres with room on both sides, so every label
|
|
27
|
+
* is centred.
|
|
28
|
+
*/
|
|
29
|
+
const LABEL_ALIGN = {
|
|
30
|
+
first: 'translate-x-0',
|
|
31
|
+
last: '-translate-x-full',
|
|
32
|
+
middle: '-translate-x-1/2',
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* The step a gridline is drawn at: one, two, five or ten times a power of ten.
|
|
36
|
+
*
|
|
37
|
+
* The point is that the axis reads 0, 50, 100 rather than 0, 37, 74. A reader
|
|
38
|
+
* who has to do arithmetic to place a value on an axis is doing the chart's job,
|
|
39
|
+
* and a gridline at an arbitrary fraction of the range is the first thing that
|
|
40
|
+
* makes two charts look like they were built by two people.
|
|
41
|
+
*/
|
|
42
|
+
function niceStep(rough) {
|
|
43
|
+
if (!Number.isFinite(rough) || rough <= 0)
|
|
44
|
+
return 1;
|
|
45
|
+
const magnitude = 10 ** Math.floor(Math.log10(rough));
|
|
46
|
+
const normalised = rough / magnitude;
|
|
47
|
+
const step = normalised <= 1 ? 1 : normalised <= 2 ? 2 : normalised <= 5 ? 5 : 10;
|
|
48
|
+
return step * magnitude;
|
|
49
|
+
}
|
|
50
|
+
/** A value that has been through a floor, a multiply and an add, back to a readable number. */
|
|
51
|
+
function tidy(value) {
|
|
52
|
+
return Number(value.toFixed(10));
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* The vertical scale, derived from the values that have somewhere to be drawn.
|
|
56
|
+
*
|
|
57
|
+
* **The floor is zero, and that is a claim about bars rather than about lines.**
|
|
58
|
+
* A bar's length is only a measurement when it starts at a shared baseline, so
|
|
59
|
+
* the scale always includes zero. The cost is that a line whose values sit
|
|
60
|
+
* between 900 and 1,000 is drawn almost flat, and the answer to that is a scale
|
|
61
|
+
* the caller chooses, not a floor this Component drops: a reader who needs to
|
|
62
|
+
* see the shape of a narrow range reads the table, which carries the numbers at
|
|
63
|
+
* full precision.
|
|
64
|
+
*
|
|
65
|
+
* **A negative value gets a real region below the baseline** rather than a
|
|
66
|
+
* zero-height bar. A profit-and-loss chart that drew a loss as nothing is a
|
|
67
|
+
* chart that is confidently wrong, and the cost of the region is one more
|
|
68
|
+
* gridline. Zero is always one of the lines, because both ends are multiples of
|
|
69
|
+
* the step, so the baseline is always on the axis and always labelled.
|
|
70
|
+
*
|
|
71
|
+
* **A series whose values are all the same number, or all zero, has no range to
|
|
72
|
+
* divide by.** It is given one step of scale so the field has a top and a
|
|
73
|
+
* bottom and every mark inside it is a position rather than a NaN.
|
|
74
|
+
*/
|
|
75
|
+
function scaleOf(values) {
|
|
76
|
+
let lowest = 0;
|
|
77
|
+
let highest = 0;
|
|
78
|
+
for (const value of values) {
|
|
79
|
+
if (!Number.isFinite(value))
|
|
80
|
+
continue;
|
|
81
|
+
if (value < lowest)
|
|
82
|
+
lowest = value;
|
|
83
|
+
if (value > highest)
|
|
84
|
+
highest = value;
|
|
85
|
+
}
|
|
86
|
+
const step = niceStep((highest - lowest) / 4);
|
|
87
|
+
const first = Math.floor(lowest / step) * step;
|
|
88
|
+
// Ceiled rather than rounded, because a rounded count can leave the largest
|
|
89
|
+
// value above the top gridline, and a mark drawn above its own axis is a mark
|
|
90
|
+
// off the plot. The epsilon absorbs the float error that would otherwise add a
|
|
91
|
+
// whole extra gridline to an axis that already lands on one exactly.
|
|
92
|
+
const count = Math.max(1, Math.ceil((highest - first) / step - 1e-9));
|
|
93
|
+
const last = first + count * step;
|
|
94
|
+
const span = last - first;
|
|
95
|
+
const at = (value) => {
|
|
96
|
+
const clamped = Math.min(Math.max(value, first), last);
|
|
97
|
+
return ((last - clamped) / span) * 100;
|
|
98
|
+
};
|
|
99
|
+
return {
|
|
100
|
+
low: first,
|
|
101
|
+
high: last,
|
|
102
|
+
lines: Array.from({ length: count + 1 }, (_, index) => tidy(first + index * step)),
|
|
103
|
+
at,
|
|
104
|
+
// The bar spans between the value and the zero line, which are the same
|
|
105
|
+
// point when the value is zero. `at` counts from the top, so the top edge is
|
|
106
|
+
// whichever of the two is nearer the top and the height is the distance
|
|
107
|
+
// between them; a positive value therefore grows downward from its own top
|
|
108
|
+
// edge and a negative one hangs below the baseline.
|
|
109
|
+
bar: (value) => {
|
|
110
|
+
const here = at(value);
|
|
111
|
+
const baseline = at(0);
|
|
112
|
+
return { top: Math.min(here, baseline), height: Math.abs(here - baseline) };
|
|
113
|
+
},
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Where a category sits on the horizontal axis, as a percentage of the field.
|
|
118
|
+
*
|
|
119
|
+
* A line's points run from the first category to the last, so the axis is the
|
|
120
|
+
* gap between them; a single category has no gap to divide by and sits in the
|
|
121
|
+
* middle, which is the only place a lone category can be read against anything.
|
|
122
|
+
* A bar's category is a column rather than a point, so its position is the
|
|
123
|
+
* column's centre, and that is the same reason a bar chart's first bar is not
|
|
124
|
+
* flush with the axis.
|
|
125
|
+
*/
|
|
126
|
+
function categoryAt(index, total, mark) {
|
|
127
|
+
if (mark === 'bar')
|
|
128
|
+
return ((index + 0.5) / total) * 100;
|
|
129
|
+
if (total <= 1)
|
|
130
|
+
return 50;
|
|
131
|
+
return (index / (total - 1)) * 100;
|
|
132
|
+
}
|
|
133
|
+
/** Where a category label is nudged to sit, so no label hangs off the plot. */
|
|
134
|
+
function labelAlign(index, total, mark) {
|
|
135
|
+
if (mark === 'bar' || total < 2)
|
|
136
|
+
return LABEL_ALIGN.middle;
|
|
137
|
+
if (index === 0)
|
|
138
|
+
return LABEL_ALIGN.first;
|
|
139
|
+
if (index === total - 1)
|
|
140
|
+
return LABEL_ALIGN.last;
|
|
141
|
+
return LABEL_ALIGN.middle;
|
|
142
|
+
}
|
|
143
|
+
/** The default reading of a value: the number itself. */
|
|
144
|
+
function identity(value) {
|
|
145
|
+
return String(value);
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* A chart: the frame every chart in a product shares, and the numbers behind it.
|
|
149
|
+
*
|
|
150
|
+
* **One frame is the design.** Five charts that each draw their own axes are
|
|
151
|
+
* five sets of numbers that do not line up, and a dashboard where two plots
|
|
152
|
+
* disagree by six pixels looks broken in a way nobody can name, because the
|
|
153
|
+
* reader cannot see the six pixels: they can only see that the two charts do not
|
|
154
|
+
* agree. So the axis gutter, the field's height, the gridline spacing and the
|
|
155
|
+
* category row are all fixed here, and two charts in a row have the same plot box
|
|
156
|
+
* whatever their data says. The gutter in particular is a fixed width rather than
|
|
157
|
+
* a width that fits the longest label, because a gutter that grows with the
|
|
158
|
+
* numbers is the mechanism by which two charts stop aligning.
|
|
159
|
+
*
|
|
160
|
+
* **The colour is assigned by the caller and drawn from the token contract.**
|
|
161
|
+
* `chart-1` through `chart-5` arrive as the five semantic utilities, and there
|
|
162
|
+
* is no hex, no ramp step and no `rgb()` anywhere in this Component. A series
|
|
163
|
+
* colour is a claim about what a thing is, so the Component that knows nothing
|
|
164
|
+
* about the caller's product has no business choosing one.
|
|
165
|
+
*
|
|
166
|
+
* **The table is part of the frame, not a prop beside it, and that is the other
|
|
167
|
+
* half of the design.** A chart whose accessible representation is a sibling
|
|
168
|
+
* prop is a chart whose table a consumer wrote once and updated when the data
|
|
169
|
+
* changed, which is to say a chart whose table says last quarter. Here the marks
|
|
170
|
+
* and the cells are two renderings of one `series` array, so they cannot drift:
|
|
171
|
+
* there is no second copy to fall behind. The table is always in the document,
|
|
172
|
+
* and `table` decides whether it is also seen.
|
|
173
|
+
*
|
|
174
|
+
* **The legend is always drawn.** It is the only place the series-to-colour
|
|
175
|
+
* assignment is stated in words, and it is not optional because two of the five
|
|
176
|
+
* roles measure below 3:1 against a light card: `chart-2` at 2.49:1 and
|
|
177
|
+
* `chart-3` at 2.15:1 in the default pack. A mark in either is found by its
|
|
178
|
+
* position and its shape, not by its colour, so the name beside it is what makes
|
|
179
|
+
* it a series rather than a shape. Past about three series, hue is the only
|
|
180
|
+
* channel left and the table is the answer, which is a fact about the data and
|
|
181
|
+
* not about this Component.
|
|
182
|
+
*
|
|
183
|
+
* **It is a server Component.** No hook, no state, no effect and no event
|
|
184
|
+
* handler: the plot is arithmetic over props and the table is a table. A chart
|
|
185
|
+
* that animated its draw-in would be refused by this system's motion rules, and
|
|
186
|
+
* the one motion here is a bar resizing when its value changes, which is state
|
|
187
|
+
* feedback and is the only kind this system allows.
|
|
188
|
+
*
|
|
189
|
+
* **What this is not.** There is no scale beyond the one described above, no
|
|
190
|
+
* tick generation to choose, no hover tooltip, no crosshair, no zoom, no
|
|
191
|
+
* stacking, no curve interpolation, and no draw-in animation. A tooltip would be
|
|
192
|
+
* a hover affordance over a picture, and a picture a reader cannot query is the
|
|
193
|
+
* failure the table exists to prevent, so the exact value is answered by the
|
|
194
|
+
* table rather than by a surface that appears under a pointer. A consumer that
|
|
195
|
+
* needs any of those is reaching for a charting library, and this Component is
|
|
196
|
+
* the frame that library's marks should sit in rather than the library.
|
|
197
|
+
*/
|
|
198
|
+
function ChartFrame({ categories, series, mark = 'line', label, title, tableCaption, categoryHeading, table = 'visible', format = identity, tableFormat = identity, empty, className, ...props }) {
|
|
199
|
+
// The scale is built from the values that have a category to be drawn against.
|
|
200
|
+
// A value past the end of the categories is neither drawn nor tabulated, and
|
|
201
|
+
// letting one into the scale would stretch the axis around a mark that is not
|
|
202
|
+
// in the picture, which is the quietest way a chart can be wrong.
|
|
203
|
+
const plotted = series.flatMap((entry) => categories
|
|
204
|
+
.map((_, index) => entry.values[index])
|
|
205
|
+
.filter((value) => typeof value === 'number' && Number.isFinite(value)));
|
|
206
|
+
const heading = title === undefined ? null : (_jsx("figcaption", { "data-slot": "chart-title", className: "text-sm font-medium", children: title }));
|
|
207
|
+
if (plotted.length === 0) {
|
|
208
|
+
// The empty state keeps the height of the field it replaces. A chart whose
|
|
209
|
+
// empty state is one line of text is a row on a dashboard that changes height
|
|
210
|
+
// when its data arrives, and the row that moves is the row the reader was
|
|
211
|
+
// looking at.
|
|
212
|
+
return (_jsxs("figure", { "data-slot": "chart-frame", "aria-label": label, className: cn('flex w-full flex-col gap-3', className), ...props, children: [heading, _jsx("div", { "data-slot": "chart-empty", className: "text-muted-foreground flex h-48 items-center justify-center rounded-md border border-dashed text-sm", children: empty ?? null })] }));
|
|
213
|
+
}
|
|
214
|
+
const scale = scaleOf(plotted);
|
|
215
|
+
const total = categories.length;
|
|
216
|
+
return (_jsxs("figure", { "data-slot": "chart-frame", "aria-label": label, className: cn('flex w-full flex-col gap-3', className), ...props, children: [heading, _jsxs("div", { "data-slot": "chart-plot", className: "flex flex-col gap-3", children: [_jsxs("div", { "data-slot": "chart-plot-row", className: "flex items-stretch gap-2", children: [_jsx("div", { "data-slot": "chart-axis-y", className: "relative w-14 shrink-0", children: scale.lines.map((value) => (_jsx("span", { "data-slot": "chart-tick", className: "text-muted-foreground absolute right-0 -translate-y-1/2 text-xs tabular-nums", style: { top: `${scale.at(value)}%` }, children: format(value) }, value))) }), _jsxs("div", { "data-slot": "chart-field", className: "relative h-48 flex-1", children: [scale.lines.map((value) => (_jsx("span", { "data-slot": "chart-gridline", "aria-hidden": "true", className: "border-border absolute inset-x-0 border-t", style: { top: `${scale.at(value)}%` } }, value))), mark === 'bar' ? (
|
|
217
|
+
/*
|
|
218
|
+
* One column per category, each holding one bar per series, so the
|
|
219
|
+
* series are side by side inside a category rather than stacked
|
|
220
|
+
* across the whole field. The column is a flex child, so a plot 600
|
|
221
|
+
* pixels wide and a plot 240 pixels wide divide the same way, which
|
|
222
|
+
* is the other half of the alignment claim.
|
|
223
|
+
*/
|
|
224
|
+
_jsx("div", { "data-slot": "chart-columns", "aria-hidden": "true", className: "absolute inset-0 flex", children: categories.map((_, column) => (_jsx("div", { "data-slot": "chart-column", className: "relative flex-1", children: series.map((entry, seriesIndex) => {
|
|
225
|
+
const value = entry.values[column];
|
|
226
|
+
if (typeof value !== 'number' || !Number.isFinite(value))
|
|
227
|
+
return null;
|
|
228
|
+
const { top, height } = scale.bar(value);
|
|
229
|
+
const slot = 100 / series.length;
|
|
230
|
+
return (_jsx("span", { "data-slot": "chart-bar", "data-series": entry.id, "aria-hidden": "true", className: cn('absolute transition-[top,height] duration-base ease-out', value < 0 ? 'rounded-b-sm' : 'rounded-t-sm', FILL[entry.color]), style: {
|
|
231
|
+
// The one-pixel inset on each side is the gap between
|
|
232
|
+
// neighbouring series. A pixel rather than a
|
|
233
|
+
// percentage, because a percentage of a narrow column
|
|
234
|
+
// is less than a pixel, and a gap nobody can see is a
|
|
235
|
+
// grouping the colour has to carry on its own.
|
|
236
|
+
left: `calc(${seriesIndex * slot}% + 1px)`,
|
|
237
|
+
width: `calc(${slot}% - 2px)`,
|
|
238
|
+
top: `${top}%`,
|
|
239
|
+
height: `${height}%`,
|
|
240
|
+
} }, entry.id));
|
|
241
|
+
}) }, column))) })) : (series.map((entry) => {
|
|
242
|
+
const points = categories
|
|
243
|
+
.map((_, index) => ({ index, value: entry.values[index] }))
|
|
244
|
+
.filter((point) => typeof point.value === 'number' && Number.isFinite(point.value));
|
|
245
|
+
if (points.length === 0)
|
|
246
|
+
return null;
|
|
247
|
+
const place = (point) => ({
|
|
248
|
+
x: categoryAt(point.index, total, mark),
|
|
249
|
+
y: scale.at(point.value),
|
|
250
|
+
});
|
|
251
|
+
return (_jsxs("div", { "data-slot": "chart-line", "data-series": entry.id, className: "contents", children: [points.length > 1 ? (
|
|
252
|
+
/*
|
|
253
|
+
* The stroke is drawn as one `line` per pair of points, in
|
|
254
|
+
* percentages of the plot box, inside an `svg` with no
|
|
255
|
+
* `viewBox`. That is deliberate on both counts. No `viewBox`
|
|
256
|
+
* means no coordinate system to stretch, so a 2-pixel stroke
|
|
257
|
+
* is 2 pixels rather than 2 units in a box that was scaled
|
|
258
|
+
* unevenly, and it is why no `vector-effect` is needed
|
|
259
|
+
* either. Percentages mean a mark's position is a share of
|
|
260
|
+
* the plot box and not a number the caller has to agree on.
|
|
261
|
+
*
|
|
262
|
+
* One `line` per pair rather than a `polyline` is the
|
|
263
|
+
* other half, and it is a constraint rather than a
|
|
264
|
+
* preference: a Component here ships no reader-facing
|
|
265
|
+
* string, and a polyline's `points` is a list of coordinate
|
|
266
|
+
* pairs joined by a space, which is a word-shaped literal in
|
|
267
|
+
* a Component. Rounded caps on each segment meet at the
|
|
268
|
+
* joints as a 2-pixel circle, which is what a rounded line
|
|
269
|
+
* join draws anyway.
|
|
270
|
+
*/
|
|
271
|
+
_jsx("svg", { "aria-hidden": "true", className: "pointer-events-none absolute inset-0 size-full overflow-visible", children: points.slice(1).map((point, index) => {
|
|
272
|
+
const from = place(points[index]);
|
|
273
|
+
const to = place(point);
|
|
274
|
+
return (_jsx("line", { x1: `${from.x}%`, y1: `${from.y}%`, x2: `${to.x}%`, y2: `${to.y}%`, className: STROKE[entry.color], strokeWidth: 2, strokeLinecap: "round" }, point.index));
|
|
275
|
+
}) })) : null, points.map((point) => (_jsx("span", { "data-slot": "chart-point", "data-series": entry.id, "aria-hidden": "true", className: cn('absolute size-2 -translate-x-1/2 -translate-y-1/2 rounded-full', FILL[entry.color]), style: {
|
|
276
|
+
left: `${categoryAt(point.index, total, mark)}%`,
|
|
277
|
+
top: `${scale.at(point.value)}%`,
|
|
278
|
+
} }, point.index)))] }, entry.id));
|
|
279
|
+
}))] })] }), _jsxs("div", { "data-slot": "chart-axis-x-row", className: "flex items-stretch gap-2", children: [_jsx("span", { "data-slot": "chart-axis-x-gutter", "aria-hidden": "true", className: "w-14 shrink-0" }), _jsx("div", { "data-slot": "chart-axis-x", className: "relative h-4 flex-1", children: categories.map((category, index) => (_jsx("span", { "data-slot": "chart-category", className: cn('text-muted-foreground absolute top-0 max-w-20 truncate text-xs', labelAlign(index, total, mark)), style: { left: `${categoryAt(index, total, mark)}%` }, children: category }, index))) })] })] }), _jsx("ul", { "data-slot": "chart-legend", className: "text-muted-foreground flex flex-wrap gap-x-4 gap-y-1 text-xs", children: series.map((entry) => (_jsxs("li", { "data-slot": "chart-legend-entry", className: "flex items-center gap-1.5", children: [_jsx("span", { "data-slot": "chart-swatch", "aria-hidden": "true", className: cn('size-2.5 shrink-0 rounded-sm', FILL[entry.color]) }), entry.label] }, entry.id))) }), _jsx("div", { "data-slot": "chart-table", className: table === 'visible' ? undefined : 'sr-only', children: _jsxs(Table, { children: [_jsx(TableCaption, { children: tableCaption }), _jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { className: "w-32", children: categoryHeading }), series.map((entry) => (_jsx(TableHead, { className: "text-right tabular-nums", children: entry.label }, entry.id)))] }) }), _jsx(TableBody, { children: categories.map((category, index) => (_jsxs(TableRow, { children: [_jsx(TableHead, { scope: "row", className: "font-medium", children: category }), series.map((entry) => {
|
|
280
|
+
const value = entry.values[index];
|
|
281
|
+
return (_jsx(TableCell, { className: "text-right tabular-nums", "data-series": entry.id, children: typeof value === 'number' && Number.isFinite(value)
|
|
282
|
+
? tableFormat(value)
|
|
283
|
+
: null }, entry.id));
|
|
284
|
+
})] }, index))) })] }) })] }));
|
|
285
|
+
}
|
|
286
|
+
export { ChartFrame };
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
3
|
import { Checkbox as CheckboxPrimitive } from '@base-ui/react/checkbox';
|
|
4
4
|
import { CheckIcon, MinusIcon } from 'lucide-react';
|
|
5
|
-
import { cn } from '../../lib/utils';
|
|
5
|
+
import { cn } from '../../lib/utils.js';
|
|
6
6
|
/**
|
|
7
7
|
* A ticked or unticked choice, with an optional mixed state.
|
|
8
8
|
*
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import type { ComponentProps } from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* The props the Collapsible root forwards to Base UI.
|
|
4
|
+
*
|
|
5
|
+
* Declared here rather than re-exported from Base UI so no upstream type
|
|
6
|
+
* crosses the package seam (ticket 07 section 6).
|
|
7
|
+
*/
|
|
8
|
+
export interface CollapsibleProps extends Omit<ComponentProps<'div'>, 'onChange'> {
|
|
9
|
+
/** The controlled open state. */
|
|
10
|
+
open?: boolean;
|
|
11
|
+
/** The panel shown initially, for an uncontrolled collapsible. */
|
|
12
|
+
defaultOpen?: boolean;
|
|
13
|
+
/** Called when the panel opens or closes. */
|
|
14
|
+
onOpenChange?: (open: boolean) => void;
|
|
15
|
+
/** Whether the section ignores user interaction. @defaultValue false */
|
|
16
|
+
disabled?: boolean;
|
|
17
|
+
/**
|
|
18
|
+
* The stable id linking the trigger to the panel.
|
|
19
|
+
*
|
|
20
|
+
* A prop and not a generated one, because a section whose trigger and panel
|
|
21
|
+
* cannot both be found by name is hard to test, hard to restyle from a
|
|
22
|
+
* consumer's stylesheet, and impossible to wire an analytics event to without
|
|
23
|
+
* reaching into the DOM. It is the caller's own name for the section, so it is
|
|
24
|
+
* the caller's string.
|
|
25
|
+
*/
|
|
26
|
+
id?: string;
|
|
27
|
+
}
|
|
28
|
+
/** The props CollapsibleTrigger forwards to Base UI. */
|
|
29
|
+
export interface CollapsibleTriggerProps extends ComponentProps<'button'> {
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* The props CollapsibleContent forwards to Base UI.
|
|
33
|
+
*
|
|
34
|
+
* Declared here rather than re-exported from Base UI so no upstream type
|
|
35
|
+
* crosses the package seam (ticket 07 section 6).
|
|
36
|
+
*/
|
|
37
|
+
export interface CollapsibleContentProps extends ComponentProps<'div'> {
|
|
38
|
+
/**
|
|
39
|
+
* Whether the browser's page search may find and expand the closed panel.
|
|
40
|
+
*
|
|
41
|
+
* This is the decision a consumer would otherwise get wrong in the direction
|
|
42
|
+
* that loses content. A panel that unmounts when it closes cannot be found by
|
|
43
|
+
* the reader's own find-in-page, and the reader who searched for a word in a
|
|
44
|
+
* collapsed section is told the word is not on the page. Turning this on hides
|
|
45
|
+
* the panel with `hidden="until-found"` instead, so the browser can open it on
|
|
46
|
+
* a match and the reader lands on the text they searched for.
|
|
47
|
+
*
|
|
48
|
+
* @defaultValue false
|
|
49
|
+
*/
|
|
50
|
+
hiddenUntilFound?: boolean;
|
|
51
|
+
/** Whether the closed panel stays in the DOM. @defaultValue false */
|
|
52
|
+
keepMounted?: boolean;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* A section that shows and hides, in place.
|
|
56
|
+
*
|
|
57
|
+
* **A Collapsible is one section; an Accordion is a stack of them.** The
|
|
58
|
+
* difference is what a reader can do with the result. An accordion lets one
|
|
59
|
+
* section open by closing another, which is right for a set of peers and wrong
|
|
60
|
+
* for a page that needs two things visible at once. A collapsible keeps its own
|
|
61
|
+
* state and touches nothing else, so a reader can have three open, which is
|
|
62
|
+
* usually what a page of settings wants and an accordion forbids.
|
|
63
|
+
*
|
|
64
|
+
* **The trigger and the region are wired by the library, not by the caller.**
|
|
65
|
+
* A consumer who writes the two as siblings with their own `aria-expanded` gets a
|
|
66
|
+
* button that says "collapsed" while the panel is open, or a panel no button
|
|
67
|
+
* controls, and the failure is invisible until a reader meets it. Here the
|
|
68
|
+
* trigger is a real button carrying the expanded state and pointing at the
|
|
69
|
+
* panel, so Enter and Space open it and the announcement is right without the
|
|
70
|
+
* caller writing a line of it. The chevron turns on that same state rather than
|
|
71
|
+
* on a timer: something happened and the reader can see that it did.
|
|
72
|
+
*
|
|
73
|
+
* **A reader who collapses something must be able to find it again.** The
|
|
74
|
+
* trigger is always in the document and always carries its own words, so the way
|
|
75
|
+
* back never depends on the panel that was just closed, and a collapsed section
|
|
76
|
+
* never leaves a heading with nothing under it. Pass `hiddenUntilFound` on the
|
|
77
|
+
* content when the closed section holds text the reader may be searching for.
|
|
78
|
+
*/
|
|
79
|
+
declare function Collapsible({ className, ...props }: CollapsibleProps): import("react").JSX.Element;
|
|
80
|
+
/**
|
|
81
|
+
* The button that shows and hides the panel.
|
|
82
|
+
*
|
|
83
|
+
* It is the way back, so it keeps its own words when the panel is closed, and it
|
|
84
|
+
* is a real button so Enter and Space work and so the reader can find it again
|
|
85
|
+
* with the keyboard after closing the section without their hands.
|
|
86
|
+
*/
|
|
87
|
+
declare function CollapsibleTrigger({ className, children, ...props }: CollapsibleTriggerProps): import("react").JSX.Element;
|
|
88
|
+
/**
|
|
89
|
+
* The region the trigger shows and hides.
|
|
90
|
+
*
|
|
91
|
+
* The height transition is state feedback rather than decoration: the panel is
|
|
92
|
+
* opening or closing and the reader can watch it happen, which is the one motion
|
|
93
|
+
* this system allows on a surface like this.
|
|
94
|
+
*/
|
|
95
|
+
declare function CollapsibleContent({ className, ...props }: CollapsibleContentProps): import("react").JSX.Element;
|
|
96
|
+
export { Collapsible, CollapsibleTrigger, CollapsibleContent };
|
|
97
|
+
//# sourceMappingURL=collapsible.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"collapsible.d.ts","sourceRoot":"","sources":["../../../src/components/ui/collapsible.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,OAAO,CAAA;AAI3C;;;;;GAKG;AACH,MAAM,WAAW,gBAAiB,SAAQ,IAAI,CAAC,cAAc,CAAC,KAAK,CAAC,EAAE,UAAU,CAAC;IAC/E,iCAAiC;IACjC,IAAI,CAAC,EAAE,OAAO,CAAA;IACd,kEAAkE;IAClE,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,6CAA6C;IAC7C,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;IACtC,wEAAwE;IACxE,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB;;;;;;;;OAQG;IACH,EAAE,CAAC,EAAE,MAAM,CAAA;CACZ;AAED,wDAAwD;AACxD,MAAM,WAAW,uBAAwB,SAAQ,cAAc,CAAC,QAAQ,CAAC;CAAG;AAE5E;;;;;GAKG;AACH,MAAM,WAAW,uBAAwB,SAAQ,cAAc,CAAC,KAAK,CAAC;IACpE;;;;;;;;;;;OAWG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAA;IAC1B,qEAAqE;IACrE,WAAW,CAAC,EAAE,OAAO,CAAA;CACtB;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,iBAAS,WAAW,CAAC,EAAE,SAAS,EAAE,GAAG,KAAK,EAAE,EAAE,gBAAgB,+BAQ7D;AAED;;;;;;GAMG;AACH,iBAAS,kBAAkB,CAAC,EAAE,SAAS,EAAE,QAAQ,EAAE,GAAG,KAAK,EAAE,EAAE,uBAAuB,+BAiBrF;AAED;;;;;;GAMG;AACH,iBAAS,kBAAkB,CAAC,EAAE,SAAS,EAAE,GAAG,KAAK,EAAE,EAAE,uBAAuB,+BAc3E;AAED,OAAO,EAAE,WAAW,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,CAAA"}
|