@momoi-labs/kiso 0.9.0 → 0.10.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.
@@ -29,6 +29,11 @@ behavioral reference where one exists.
29
29
 
30
30
  ## Data
31
31
 
32
+ - [Chart](chart.md): Framed lines and stacked areas with synchronized inspection and exact values.
33
+ - [ChartLegend](chart-legend.md): Tabular series summaries.
34
+ - [Meter / Progress](meter.md): Measurements against limits and task completion.
35
+ - [BarGauge](bar-gauge.md): Labelled bars on a shared scale.
36
+
32
37
  - [CommandPalette](command-palette.md): Searches and runs global actions or navigation from a keyboard-first overlay.
33
38
  - [Dot](dot.md): Adds a decorative status mark beside readable text.
34
39
  - [DropdownMenu](dropdown-menu.md): Presents contextual actions anchored to a specific object or trigger.
@@ -42,6 +47,10 @@ behavioral reference where one exists.
42
47
 
43
48
  ## Navigation and structure
44
49
 
50
+ - [Disclosure](disclosure.md): Native collapsible sections.
51
+ - [TimeRangeControl](time-range-control.md): Presets and exact UTC collection windows.
52
+ - [DashboardGrid](dashboard-grid.md): Responsive twelve-column panel layout.
53
+
45
54
  - [AppShell / ApplicationShell](app-shell.md): Provides low-level columns or the complete shared application frame (sidebar console or top-bar surface).
46
55
  - [BrandMark](brand-mark.md): Decorative letter or icon beside a product name.
47
56
  - [Breadcrumb](breadcrumb.md): Shows the current location within a hierarchy.
@@ -0,0 +1,29 @@
1
+ # BarGauge
2
+
3
+ ## Purpose and anatomy
4
+
5
+ Compare labelled measurements on horizontal bars with a value column.
6
+ Each row composes [Meter](meter.md). All rows share a positive finite `max`
7
+ and start at zero. Give the group a descriptive `label`, including its unit.
8
+ Use [Chart](chart.md) when the task requires history.
9
+
10
+ ## Data, states, and behavior
11
+
12
+ Rows have a stable key, label, and numeric or null value. `formatValue` applies
13
+ the same unit and precision to every known value. Preserve row order across
14
+ refreshes so a reader can compare changes. Bars clip at their bounds while
15
+ text retains the actual number. A missing row value reads "Not collected";
16
+ zero draws an empty track. An empty group reads "No measurements."
17
+
18
+ ## Accessibility
19
+
20
+ The group has an accessible name; each measured row has Meter semantics and
21
+ its own name and value text. Labels and numbers remain visible. There is no
22
+ hover requirement, animation, or keyboard interaction. Wrap long labels and
23
+ keep values aligned. Color does not encode the identity of a row.
24
+
25
+ ## Tokens and implementation
26
+
27
+ Use Meter's track and fill tokens, `--spacing-md` between rows, and
28
+ `--spacing-sm` within each row. No separate charting or Radix primitive is
29
+ required. The old `.bars` vertical mini-bars are unrelated and stay compatible.
@@ -0,0 +1,42 @@
1
+ # ChartLegend
2
+
3
+ ## Purpose and anatomy
4
+
5
+ A native summary table for a [Chart](chart.md), available without hovering.
6
+ Each row has the series' numbered label and stroke sample, then min, max,
7
+ arithmetic average, and current. The visible caption names the metric and unit.
8
+
9
+ ## Data and states
10
+
11
+ Accept the same `data` and `series` as the chart. Keep ordering and slots
12
+ identical. Min, max, and average use finite measurements only. Current is the
13
+ measurement at the final timestamp. Missing statistics read "Not collected";
14
+ zero remains zero. An empty window retains the named series and headers.
15
+
16
+ `formatValue` controls all summary cells. Use one unit and precision across
17
+ the table. The chart's exact-values table preserves unrounded source data.
18
+ The legend alone is a summary, not a replacement for the full sample table.
19
+
20
+ ## Accessibility and behavior
21
+
22
+ Use native table, caption, column headers, and row headers. Numeric cells align
23
+ to the end with tabular figures. By default rows are informational. With `onHighlightSeriesChange`, series
24
+ labels become buttons with pressed state from `highlightSeries`. Selection
25
+ emphasizes a series without hiding values. The table scrolls horizontally inside its frame at narrow
26
+ widths. Numbered labels, highlighting, and the exact sample table supplement color.
27
+
28
+ ## Tokens and implementation
29
+
30
+ Text uses `--color-foreground` and `--color-muted-foreground`. Borders use
31
+ `--color-border`; spacing uses `--spacing-xs` and `--spacing-sm`. Series
32
+ swatches consume the same five semantic chart roles as Chart. There is no
33
+ Radix primitive; use a native table.
34
+
35
+ ## Display forms
36
+
37
+ `variant="table"` is the default. `inline` wraps series labels and current
38
+ values into a short row. `sidebar` places them in a column beside the plot,
39
+ falling below it on narrow screens. Both use native description lists.
40
+ `activeTimestamp` selects the sidebar sample, otherwise it shows the last
41
+ sample. `formatTime` formats its timestamp. A missing measurement stays
42
+ "Not collected". Sidebar changes use a polite live region.
@@ -0,0 +1,120 @@
1
+ # Chart
2
+
3
+ ## Purpose
4
+
5
+ Read several metric series across a collection window. pg-probe needs to
6
+ correlate CPU, memory, disk, and database activity at the same instant.
7
+ [Issue #93](https://github.com/momoi-labs/blueprint/issues/93) records that need.
8
+ Use [Sparkline](sparkline.md) for a small, single-series trend.
9
+
10
+ ## Anatomy and forms
11
+
12
+ A named figure contains axes, horizontal grid lines, a plot, an inspection
13
+ readout, [ChartLegend](chart-legend.md), and a disclosure of exact samples.
14
+ `line` is the default. `stacked-area` shows nonnegative contributions to a
15
+ total. No gradients, curves that overshoot observations, or animation.
16
+
17
+ One to five series share a unit and timestamp axis. Put the unit in the figure
18
+ label, such as "CPU by state (%)". Give each series a unique key and label.
19
+ An optional slot from 1 to 5 fixes its color and numbered label across
20
+ panels. Slots must be unique within a chart. Keep ascending slot order in stacks. Never encode health with a slot.
21
+
22
+ In standard layout, `height` defaults to 200 pixels. Width follows the parent. Axes and their labels
23
+ remain visible. `min` and `max` establish comparable scales across panels;
24
+ otherwise lines use their extent and stacks start at zero. Stacks reject
25
+ negative values. Use lines for signed data.
26
+
27
+ ## Data and gaps
28
+
29
+ `data` contains `{ timestamp, values }`, where timestamps are epoch
30
+ milliseconds in strictly increasing order. Supply every expected collection
31
+ timestamp, including slots the collector missed. Values are keyed by series.
32
+ A null, absent, or nonfinite measurement is unavailable, never zero. The
33
+ component does not infer a sampling interval or interpolate missing samples.
34
+
35
+ Lines break at gaps. A stack breaks across all series when any contribution
36
+ is missing, because its total is unknown. The readout and tables retain known
37
+ contributions at that timestamp. An isolated sample draws a point.
38
+
39
+ The summary ignores missing samples for min, max, and arithmetic mean. Current
40
+ means the final timestamp, including an unavailable value. It never silently
41
+ substitutes an older sample. Statistics describe the supplied window only.
42
+
43
+ ## Interaction and accessibility
44
+
45
+ The figure has a visible caption and an accessible name. Tab focuses the plot;
46
+ Left and Right inspect adjacent samples through Recharts' accessibility layer.
47
+ Pointer hover and touch inspect the same readout. The floating readout or sidebar names the
48
+ selected timestamp and every series, including "Not collected" values.
49
+
50
+ Pass the same `syncId` to related charts to synchronize by timestamp. Products
51
+ must supply the same expected timestamp grid and window to that group. Array
52
+ positions alone do not establish correspondence. Panels retain their own
53
+ series and scales.
54
+
55
+ Series use continuous strokes. Numbered labels, per-series highlighting,
56
+ and the exact table provide identification beyond color. Select a legend
57
+ label with pointer, touch, Enter, or Space to highlight that series; select
58
+ it again to clear. The active label is underlined and exposes `aria-pressed`.
59
+ Other series stay in the plot and in every readout. The
60
+ "View exact values" disclosure exposes every sample as a native table, with
61
+ full UTC timestamps and unrounded source numbers. Include units in the chart
62
+ label; formatters change display text, never the exact table. Scrolling stays
63
+ inside the table at narrow widths.
64
+
65
+ Tab reaches the exact-values summary. Enter or Space opens it. No essential
66
+ information requires hover, color discrimination, animation, or a live
67
+ announcement on every background refresh. Keep series labels descriptive.
68
+
69
+ ## States
70
+
71
+ - Empty or wholly unavailable data: retain the caption, summary, exact table,
72
+ and plot height; show "No collected samples in this window."
73
+ - Partial collection: draw gaps and preserve the available numbers.
74
+ - One sample: show a point and the tables.
75
+ - Loading: compose a [Skeleton](skeleton.md) at the expected plot height.
76
+ - Error: compose an [Alert](alert.md) with the cause and retry action. Label
77
+ retained data as stale. An empty chart is not an error message.
78
+
79
+ ## Tokens and implementation
80
+
81
+ Series use `--color-chart-1`, `--color-chart-2`, `--color-chart-3`,
82
+ `--color-chart-4`, and `--color-chart-5`. Axes use
83
+ `--color-muted-foreground`; grid lines use `--color-border`; the crosshair
84
+ uses `--color-border-strong`. Focus uses `--color-focus`. The readout uses
85
+ `--color-elevated-surface`, `--color-foreground`, and `--shadow-md`.
86
+
87
+ ## Presentation options
88
+
89
+ | Prop | Values | Default |
90
+ | --- | --- | --- |
91
+ | `variant` | `line`, `stacked-area` | `line` |
92
+ | `layout` | `standard`, `compact`, `split` | `standard` |
93
+ | `legend` | `table`, `inline`, `sidebar` | `table` |
94
+ | `highlightSeries` | A series key or null | Local selection |
95
+
96
+ Standard uses a 200-pixel plot; compact uses 165 pixels and tighter spacing.
97
+ Split gives each series a labelled 90-pixel lane on one shared numeric scale,
98
+ with synchronized timestamps. Explicit `height` overrides the plot or lane
99
+ height. Split accepts only line charts; combining it with stacked-area throws
100
+ an error because a stack must retain one common plot.
101
+
102
+ Table keeps min, max, average, and current visible. Inline shows current values
103
+ in a wrapping legend. Sidebar shows current values at rest and the inspected
104
+ instant on hover, keyboard navigation, or touch, without a floating readout.
105
+ At narrow widths it moves below the plot. All forms keep the exact table.
106
+
107
+ `highlightSeries` makes highlighting controlled; pass null to clear it. Pair
108
+ it with `onHighlightSeriesChange` to update selection from the legend. Omitting
109
+ it enables local selection. Unknown keys produce no highlight, including when
110
+ a series disappears after a data change. Highlighting changes emphasis only;
111
+ it never removes contributions or changes the stack's total or scale.
112
+
113
+ Keyboard instructions remain available through the plot's accessible
114
+ description instead of taking permanent space above every plot.
115
+
116
+ The React component composes Recharts `ComposedChart`, `Line`, `Area`, axes,
117
+ and Tooltip, with animation disabled and `connectNulls={false}`. Synchronization
118
+ uses `syncMethod="value"`. See the [Recharts API](https://recharts.github.io/en-US/api/AreaChart/).
119
+ The existing `.chart` SVG helpers remain compatible; the component frame uses
120
+ `.framed-chart` to avoid changing older SVG consumers.
@@ -0,0 +1,28 @@
1
+ # DashboardGrid
2
+
3
+ ## Purpose and anatomy
4
+
5
+ Arrange metric panels in a responsive twelve-column grid. DashboardGrid owns
6
+ layout; DashboardPanel owns a column span and wraps a Card or other widget.
7
+ The grid adds no visual panel treatment or ARIA roles of its own.
8
+
9
+ ## Sizes and responsive behavior
10
+
11
+ Panel `span` accepts 3, 4, 6 (default), 8, or 12. Above 1024 pixels it uses that
12
+ many columns. At 1024 pixels and below, panels use six columns, except full
13
+ width panels which keep twelve. At 640 pixels and below, every panel uses one
14
+ full-width column. Internal tables may scroll; the page must not overflow.
15
+
16
+ ## States and accessibility
17
+
18
+ Each widget owns its loading, empty, stale, and error states. Reserve its
19
+ space with Skeleton while loading. The layout never reorders DOM content or
20
+ adds tab stops. Reading order, focus order, and visual order stay aligned.
21
+ Give sections meaningful headings and each chart its own name.
22
+
23
+ ## Tokens and composition
24
+
25
+ Use `--spacing-lg` for gaps. Span is layout metadata, not a new spacing token.
26
+ Compose [Card](card.md), [Chart](chart.md), [Disclosure](disclosure.md), and
27
+ [TimeRangeControl](time-range-control.md) under the
28
+ [Dashboard pattern](../patterns/dashboard.md). CSS Grid needs no dependency.
@@ -0,0 +1,29 @@
1
+ # Disclosure
2
+
3
+ ## Purpose and anatomy
4
+
5
+ Collapse a related section without leaving the page. A native `details`
6
+ contains a `summary` and content region. `summary` provides the visible name;
7
+ children provide the section content. Do not put links or buttons inside the
8
+ summary. Multiple sections may remain open independently.
9
+
10
+ ## States and behavior
11
+
12
+ Closed is the default. Pass native `open` to start open. The browser maintains
13
+ open and closed states and dispatches `onToggle`. Closed content remains in
14
+ the document but is hidden and removed from keyboard navigation. Preserve
15
+ application state inside the content; opening does not fetch data by itself.
16
+
17
+ ## Accessibility
18
+
19
+ Use native semantics and the browser's disclosure marker. Tab reaches the
20
+ summary; Enter or Space toggles it and focus stays there. Content follows the
21
+ summary in DOM order. Keep heading levels appropriate to the surrounding page.
22
+ On a coarse pointer, the summary is at least the minimum touch target height.
23
+ Do not animate the content or add redundant expanded-state ARIA.
24
+
25
+ ## Tokens and implementation
26
+
27
+ Spacing uses `--spacing-sm`; the summary uses `--type-weight-medium`.
28
+ Focus uses `--color-focus`. Touch targets use `--size-touch-min` only inside
29
+ `@media (pointer: coarse)`. Native details needs no Radix dependency.
@@ -0,0 +1,37 @@
1
+ # Meter and Progress
2
+
3
+ ## Purpose
4
+
5
+ Meter shows a measurement against a known limit, such as 64 connections out
6
+ of 100. Progress shows completion of work. A task's completion is not a meter.
7
+
8
+ ## Anatomy and states
9
+
10
+ Both have a visible label, a value above a thin horizontal track. Meter takes
11
+ `value`, `min` (default 0), and `max` (default 100). Progress takes an optional
12
+ `value` and positive `max` (default 100). `valueText` supplies units or context.
13
+
14
+ Bounds must be finite and ordered. Clip the filled width and ARIA value to the
15
+ bounds, while retaining the original measurement in the visible value and
16
+ accessible value text. Do not hide an over-limit measurement by changing it
17
+ to the maximum. Compose a labelled Badge if that measurement is a warning.
18
+
19
+ A null or nonfinite Meter value reads "Not collected" with an unfilled hatch
20
+ track. It has no fabricated numeric ARIA state. An omitted, null, or nonfinite
21
+ Progress value means indeterminate work, reads "In progress", and has a static
22
+ hatch track. Zero is a known empty track; the maximum is a full track.
23
+
24
+ ## Accessibility
25
+
26
+ Known measurements use `role="meter"` with name, minimum, maximum, current
27
+ value, and value text. Progress uses `role="progressbar"`; omit its current
28
+ value while indeterminate. Neither adds a tab stop or announces every update.
29
+ Keep the label and value visible for touch and assistive technology.
30
+ No information depends on animation or color.
31
+
32
+ ## Tokens and implementation
33
+
34
+ The 6px track combines `--spacing-xs` and `--spacing-2xs`, with `--radius-xs`, and `--color-secondary`.
35
+ Meter fill uses `--color-secondary-foreground`; Progress uses `--color-chart-1`. Labels use `--type-size-label` and
36
+ `--color-foreground`. Unknown tracks use the existing hatch tokens. This
37
+ contract adds `.meter-track`; legacy `.progress` rules remain compatible.
@@ -44,12 +44,14 @@ line.
44
44
 
45
45
  | State | Behavior |
46
46
  | --- | --- |
47
- | fewer than two samples | Renders nothing. A flat rule across a cell reads as a border, not as a measurement. Keep the cell's number; reserve height with the surrounding layout. |
47
+ | fewer than two finite measurements | Renders nothing. A flat rule across a cell reads as a border, not as a measurement. Keep the cell's number; reserve height with the surrounding layout. |
48
48
  | loading | [Skeleton](skeleton.md) at the same height the Sparkline will occupy. |
49
49
  | error | Show the last known number as text; the shape is optional, the value is not. |
50
50
 
51
- The component takes plain values and does not model gaps. A series with holes
52
- is the caller's data problem; interpolate or truncate before passing it in.
51
+ The component accepts numbers and null gaps on an evenly spaced window.
52
+ Null and nonfinite values break the line and fill; they never become zero.
53
+ Keep every expected sample position. Domains use finite measurements only.
54
+ Do not interpolate or remove missing positions before passing them in.
53
55
 
54
56
  ## Accessibility
55
57
 
@@ -78,7 +80,7 @@ No keymap and no tab stop. A Sparkline is never an action.
78
80
  ## When NOT to use
79
81
 
80
82
  - Analysis that needs an axis, a legend, or crosshair reading. A framed,
81
- interactive chart is a separate, still-deferred contract.
83
+ interactive [Chart](chart.md) has its own contract.
82
84
  - Multi-series overlays. Stack several Sparklines only as separate rows with
83
85
  their own labels, never as one drawing with a homemade legend.
84
86
  - A single value with no history. Use [Stat](stat.md) alone.
@@ -89,5 +91,4 @@ No Radix primitive exists for this. The component composes Recharts
89
91
  `ResponsiveContainer`, `AreaChart`, and `Area` with animation, dots, and
90
92
  axes off. Color reaches the path through `currentColor` from the
91
93
  `.sparkline` rules in `ui.css`; never pass a hex value or invent a
92
- categorical palette. The framed `.chart` rules in `ui.css` stay reserved for
93
- the later interactive chart contract.
94
+ categorical palette. Use [Chart](chart.md) for categorical multi-series data.
@@ -0,0 +1,42 @@
1
+ # TimeRangeControl
2
+
3
+ ## Purpose and anatomy
4
+
5
+ Select the visible interval within a collected run. A labelled Button opens a
6
+ compact Popover with a vertical preset list. Custom range reveals exact
7
+ start/end fields. This control scopes an
8
+ artifact's timestamps, not a live polling schedule or date-only calendar.
9
+
10
+ ## Data and behavior
11
+
12
+ `bounds` and controlled `value` are `{ from, to }` epoch-millisecond pairs.
13
+ Both must be finite, valid dates with start before end; value must fit inside
14
+ bounds. `onValueChange` receives an applied range. The consumer filters panels
15
+ and recalculates their summaries from that interval.
16
+
17
+ Presets default to the last 5, 15, and 30 minutes relative to the end of the
18
+ run. Durations are positive milliseconds; clip their start to the run start.
19
+ "Entire run" restores bounds. Selecting a preset applies it and closes the
20
+ popover. Custom fields are explicitly UTC, including milliseconds; local
21
+ machine timezone and daylight-saving changes cannot shift the selection.
22
+
23
+ Editing a field does not apply it. Apply validates that the start precedes
24
+ the end and both fit inside the collection window. Invalid input keeps the
25
+ popover open with field-associated feedback. Escape or outside dismissal
26
+ cancels edits. Reopening starts with the applied value. `disabled` disables
27
+ the trigger while a run is unavailable.
28
+
29
+ ## Accessibility
30
+
31
+ Compose [Button](button.md), [Popover](popover.md), and labelled
32
+ [Input](input.md) controls. Tab follows the visible preset buttons or custom form fields and Apply. Native
33
+ date/time inputs retain platform keyboard behavior. Escape dismisses; Radix
34
+ restores focus to the trigger. Errors use an alert and field descriptions.
35
+ The trigger shows a short interval and UTC. Its accessible name includes
36
+ both full timestamps; visible dates appear when the interval crosses a day. Touch can use every action.
37
+
38
+ ## Tokens and implementation
39
+
40
+ Use existing Button, Input, and Popover tokens. Use `--spacing-xs` between
41
+ presets and `--spacing-md` between regions. The trigger wraps long ranges;
42
+ the popover fits the viewport. No new calendar, timezone, or date dependency.
@@ -10,7 +10,6 @@ the system needs them. This is the honest roadmap for those choices.
10
10
  | --- | --- | --- |
11
11
  | **Component implementation code** | V1 ships Markdown contracts and tokens, not React components. Keeping the specification separate lets product needs shape an implementation instead of freezing an assumed API. This boundary was set in [epic #3](https://github.com/momoi-labs/blueprint/issues/3). | When repeated product implementations make a stable reference API evident. Build it as Kiso v2 or in a separate `kiso-ui` repository, using shadcn/Radix behavior adapted to Kiso rather than copied unchanged. |
12
12
  | **Radio / RadioGroup** | Select and Switch cover the v1 choice cases, so [epic #3](https://github.com/momoi-labs/blueprint/issues/3) did not add another selection primitive without a product need. | When a product genuinely needs mutually exclusive selection from a small, fixed set whose options should remain visible. |
13
- | **Interactive framed charts** | [#84](https://github.com/momoi-labs/blueprint/issues/84) shipped [Sparkline](components/sparkline.md) for the single-series metric shapes products actually had. No product has yet needed an axis, a legend, or crosshair reading. | When a product needs exploratory reading of a series: axes, multi-series overlays, or hover inspection. Settle the framed `.chart` contract then. |
14
13
  | **Figma Tokens Studio integration** | [Epic #2](https://github.com/momoi-labs/blueprint/issues/2) kept the token pipeline focused on its committed outputs. Tokens Studio is a Figma plugin workflow built through Style Dictionary and `@tokens-studio/sd-transforms`, not a standalone emitter. | When design-to-code synchronization through Figma becomes a real team workflow rather than a hypothetical integration. |
15
14
  | **DTCG 2025.10 Resolver module** | The multiple-context and theme Resolver considered in [epic #2](https://github.com/momoi-labs/blueprint/issues/2) is a preview draft marked “do not implement.” V1 uses an explicit, stable theme model instead. | When the Resolver module reaches stable status and Kiso has a concrete context or theme problem it would solve. |
16
15
  | **`--shadow-lg`** | The elevation scale intentionally stops at `--shadow-sm` and `--shadow-md`; [#25](https://github.com/momoi-labs/blueprint/issues/25) fixed component references without inventing a larger elevation. | When a real overlay or hierarchy cannot be expressed clearly with `--shadow-md`. Propose the token in the source, then regenerate its outputs. |
@@ -21,6 +20,12 @@ the system needs them. This is the honest roadmap for those choices.
21
20
 
22
21
  ## Accepted additions
23
22
 
23
+ [Issue #93](https://github.com/momoi-labs/blueprint/issues/93) opens the framed
24
+ chart deferral with pg-probe collection data. Chart, ChartLegend, Meter,
25
+ Progress, BarGauge, Disclosure, TimeRangeControl, DashboardGrid, and Sparkline
26
+ gaps now cover that workflow. Five categorical slots cover the demonstrated
27
+ maximum. More slots require a product case and palette review.
28
+
24
29
  The growth model below is not theory. What it has produced so far:
25
30
 
26
31
  | What | Evidence | Where |
@@ -16,7 +16,7 @@ tables, or dedicated tool routes.
16
16
 
17
17
  | Region | Compose with | Role |
18
18
  | --- | --- | --- |
19
- | Page framing | [PageHeader](../components/page-header.md) | Dashboard title; optional time-range or environment [Select](../components/select.md); optional refresh [IconButton](../components/icon-button.md) / [Button](../components/button.md) |
19
+ | Page framing | [PageHeader](../components/page-header.md) | Dashboard title; optional [TimeRangeControl](../components/time-range-control.md) or environment [Select](../components/select.md); optional refresh [IconButton](../components/icon-button.md) / [Button](../components/button.md) |
20
20
  | Widget unit | [Card](../components/card.md) | One concern per Card (metric cluster, short table, status list) |
21
21
  | Status | [Badge](../components/badge.md) | Compact health/severity labels inside Cards |
22
22
  | Dense lists | [Table / DataTable](../components/table.md) | Short "needs attention" tables — still compose Search/EmptyState/Pagination only when those behaviors are truly present |
@@ -36,8 +36,11 @@ semantic colors, focus `--color-focus`. Prefer quiet surfaces and strong
36
36
  hierarchy ([principles](../principles.md)).
37
37
 
38
38
  [Sparkline](../components/sparkline.md) carries metric shape inside Cards and
39
- table cells; it composes like any other widget payload. An interactive framed
40
- chart remains deferred.
39
+ table cells; it composes like any other widget payload. Use
40
+ [Chart](../components/chart.md) with its legend and exact-values table for
41
+ exploratory metrics. Share a timestamp grid, time range, and syncId across
42
+ related panels. Compose [DashboardGrid](../components/dashboard-grid.md) and
43
+ [Disclosure](../components/disclosure.md) for responsive panel groups.
41
44
 
42
45
  ## Flow
43
46
 
@@ -311,3 +311,29 @@ Published releases expose the artifacts as `@momoi-labs/kiso/tokens.css`,
311
311
  `@momoi-labs/kiso/tokens.d.ts`. Kiso's Markdown contracts are available below
312
312
  `@momoi-labs/kiso/contracts/` so consumers can pin the contracts and generated
313
313
  tokens to the same version.
314
+
315
+ ## Categorical chart colors
316
+
317
+ Issue #93 adds `--color-chart-1` through `--color-chart-5` for series identity.
318
+ The roles derive from accent.base, status.success, status.warning, status.info,
319
+ and status.danger, in that order. Their meaning inside a chart is categorical,
320
+ never health or severity. Existing status roles keep their meaning elsewhere.
321
+
322
+ Each slot meets 3:1 on background, surface, and elevated-surface in both
323
+ themes, enforced by the contrast gate. Lines use full-opacity strokes; area
324
+ fills are secondary at 0.2 opacity. Every series also has a numbered label,
325
+ an interactive highlight, a legend value, and exact sample values. Do not
326
+ use filled bands or hue alone to identify data. Five slots cover pg-probe;
327
+ the epic's proposed eight-slot headroom is deferred until needed.
328
+
329
+ The palette gate also checks Oklab lightness bands (0.40 to 0.65 in light mode,
330
+ 0.70 to 0.90 in dark mode), chroma of at least 0.08, all-pair normal-vision
331
+ distance of at least 0.10, and adjacent-pair distance of at least 0.05 under
332
+ full protanopia and deuteranopia simulation. These are product regression
333
+ floors, not accessibility standards or a guarantee of hue discrimination.
334
+ Keep the canonical slot order in stacks; changing adjacency needs review.
335
+
336
+ The calculation uses [Oklab](https://bottosson.github.io/posts/oklab/) and
337
+ [Machado's simulation model](https://pubmed.ncbi.nlm.nih.gov/19834201/).
338
+ Run `node scripts/check-chart-palette.mjs` for both themes. Numbered labels,
339
+ highlighting, and tables remain required even when these checks pass.
package/kiso/ui.css CHANGED
@@ -1010,7 +1010,7 @@ pre code { display: block; overflow-x: auto; padding: 0; background: none; borde
1010
1010
  .logview .log-error{ color: var(--color-danger-on-dark); }
1011
1011
  .logview .log-info { color: var(--color-info-on-dark); }
1012
1012
 
1013
- /* Sparkline / bar chart placeholders, neutral-only per the brand rules. */
1013
+ /* Legacy compact bars remain neutral. Framed charts use categorical roles. */
1014
1014
  .bars { display: flex; align-items: flex-end; gap: 3px; height: 40px; }
1015
1015
  .bars i { flex: 1; background: var(--color-border-strong); border-radius: var(--radius-xs); }
1016
1016
  .bars i.on { background: var(--color-primary); }
@@ -1131,10 +1131,8 @@ pre::after {
1131
1131
  .splitter::after { content: ""; position: absolute; inset-block: 0; inset-inline: -3px; }
1132
1132
  .splitter:hover, .splitter.dragging { background: var(--color-ring); }
1133
1133
 
1134
- /* Chart frame: deliberate presentation, one featured series in primary,
1135
- gridlines at border strength. The area is the line color at low opacity, a
1136
- flat token fill: no document-level gradient id for a component to forget
1137
- to define. Supporting, incidental series belong in `.sparkline` instead. */
1134
+ /* Legacy single-series SVG helpers. Interactive metrics use .framed-chart
1135
+ below; these selectors remain compatible with existing consumers. */
1138
1136
  .chart { width: 100%; height: 200px; display: block; overflow: visible; color: var(--color-primary); }
1139
1137
  .chart .grid { stroke: var(--color-border); stroke-width: 1; }
1140
1138
  .chart .line { fill: none; stroke: currentColor; stroke-width: 1.5; }
@@ -1152,3 +1150,76 @@ pre::after {
1152
1150
  .sparkline { display: block; width: 100%; color: var(--color-border-strong); }
1153
1151
  .sparkline.primary { color: var(--color-primary); }
1154
1152
  .sparkline svg { display: block; overflow: visible; }
1153
+
1154
+ /* Metrics dashboard, issue #93. Series colors encode identity, not status. */
1155
+ .framed-chart { margin: 0; display: grid; gap: var(--spacing-sm); min-width: 0; }
1156
+ .chart-help { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
1157
+ .chart-plot { width: 100%; min-width: 0; font-size: var(--type-size-metadata); }
1158
+ .chart-plot :focus-visible { outline: 2px solid var(--color-focus); outline-offset: 2px; }
1159
+ .chart-empty { display: grid; place-items: center; padding: var(--spacing-md); color: var(--color-muted-foreground); }
1160
+ .chart-table-scroll { max-width: 100%; overflow-x: auto; }
1161
+ .chart-table { width: 100%; border-collapse: collapse; font-size: var(--type-size-label); font-variant-numeric: tabular-nums; }
1162
+ .chart-table caption { text-align: start; color: var(--color-muted-foreground); padding-block: var(--spacing-xs); }
1163
+ .chart-table th, .chart-table td { padding: var(--spacing-xs) var(--spacing-sm); border-bottom: 1px solid var(--color-border); white-space: nowrap; text-align: end; }
1164
+ .chart-table th:first-child { text-align: start; }
1165
+ .chart-table th { font-weight: var(--type-weight-medium); }
1166
+ .chart-series-label { display: inline-flex; align-items: center; gap: var(--spacing-xs); }
1167
+ .chart-series-label svg { flex: none; }
1168
+ .chart-inspection { padding: var(--spacing-sm); border: 1px solid var(--color-border); background: var(--color-elevated-surface); color: var(--color-foreground); box-shadow: var(--shadow-md); font-size: var(--type-size-label); }
1169
+ .chart-inspection dl { display: grid; grid-template-columns: auto auto; gap: var(--spacing-xs) var(--spacing-md); margin-block: var(--spacing-xs) 0; }
1170
+ .chart-inspection dd { margin: 0; text-align: end; font-variant-numeric: tabular-nums; }
1171
+ .meter, .bar-gauge { display: grid; gap: var(--spacing-sm); min-width: 0; }
1172
+ .bar-gauge { gap: var(--spacing-md); }
1173
+ .meter-label { display: flex; justify-content: space-between; gap: var(--spacing-md); font-size: var(--type-size-label); }
1174
+ .meter-label > :last-child { text-align: end; font-variant-numeric: tabular-nums; }
1175
+ .meter-track { height: calc(var(--spacing-xs) + var(--spacing-2xs)); border-radius: var(--radius-xs); background-color: var(--color-secondary); overflow: hidden; }
1176
+ .meter-track > span { display: block; height: 100%; background: var(--color-secondary-foreground); }
1177
+ .disclosure { min-width: 0; }
1178
+ .disclosure > summary { padding: var(--spacing-sm) 0; cursor: pointer; font-weight: var(--type-weight-medium); }
1179
+ .disclosure > summary:focus-visible { outline: 2px solid var(--color-focus); outline-offset: 2px; }
1180
+ .disclosure-content { padding: var(--spacing-sm) 0; }
1181
+ .time-range-trigger { justify-self: start; width: fit-content; max-width: 100%; white-space: normal; height: auto; min-height: var(--size-control-md); text-align: start; }
1182
+ .time-range-content { width: min(26rem, calc(100vw - 2 * var(--spacing-md))); }
1183
+ .time-range-presets { display: grid; gap: var(--spacing-xs); }
1184
+ .time-range-presets > button { height: auto; min-height: var(--size-touch-min); display: flex; align-items: flex-start; flex-direction: column; text-align: start; }
1185
+ .time-range-presets small { color: var(--color-muted-foreground); font-weight: var(--type-weight-regular); }
1186
+ .meter[data-slot="progress"] .meter-track > span { background: var(--color-chart-1); }
1187
+ .dashboard-grid { display: grid; grid-template-columns: repeat(12, minmax(0, 1fr)); gap: var(--spacing-lg); }
1188
+ .dashboard-panel { grid-column: span var(--dashboard-span, 6); min-width: 0; }
1189
+ @media (max-width: 1024px) {
1190
+ .dashboard-panel { grid-column: span 6; }
1191
+ .dashboard-panel[data-span="12"] { grid-column: span 12; }
1192
+ }
1193
+ @media (max-width: 640px) {
1194
+ .dashboard-grid { grid-template-columns: minmax(0, 1fr); }
1195
+ .dashboard-panel, .dashboard-panel[data-span="12"] { grid-column: 1; }
1196
+ }
1197
+ @media (pointer: coarse) {
1198
+ .disclosure > summary { min-height: var(--size-touch-min); display: list-item; align-content: center; }
1199
+ }
1200
+
1201
+ /* Chart presentation options. Exact values remain available in every layout. */
1202
+ .chart-body, .chart-plots { min-width: 0; }
1203
+ .framed-chart[data-legend="sidebar"] .chart-body { display: grid; grid-template-columns: minmax(0, 1fr) minmax(140px, 180px); gap: var(--spacing-lg); }
1204
+ .framed-chart[data-layout="compact"] { gap: var(--spacing-xs); }
1205
+ .chart-lane + .chart-lane { margin-top: var(--spacing-sm); }
1206
+ .chart-lane-heading { display: flex; justify-content: space-between; gap: var(--spacing-sm); font-size: var(--type-size-label); font-variant-numeric: tabular-nums; }
1207
+ .chart-legend { font-size: var(--type-size-label); }
1208
+ .chart-legend-caption { color: var(--color-muted-foreground); overflow-wrap: anywhere; }
1209
+ .chart-legend dl { margin: 0; }
1210
+ .chart-legend dl > div { display: flex; align-items: center; justify-content: space-between; gap: var(--spacing-sm); }
1211
+ .chart-legend dd { margin: 0; font-variant-numeric: tabular-nums; }
1212
+ .chart-legend-inline { display: flex; align-items: center; flex-wrap: wrap; gap: var(--spacing-sm) var(--spacing-md); }
1213
+ .chart-legend-inline dl { display: flex; flex-wrap: wrap; gap: var(--spacing-sm) var(--spacing-lg); }
1214
+ .chart-legend-sidebar { border-left: 1px solid var(--color-border); padding-left: var(--spacing-md); }
1215
+ .chart-legend-sidebar dl { display: grid; gap: var(--spacing-sm); margin-top: var(--spacing-sm); }
1216
+ .chart-series-button { display: inline-flex; align-items: center; padding: var(--spacing-2xs) 0; border: 0; background: transparent; color: var(--color-foreground); font: inherit; text-align: start; cursor: pointer; }
1217
+ .chart-series-button[aria-pressed="true"] { text-decoration: underline; text-underline-offset: var(--spacing-2xs); }
1218
+ .chart-series-button:focus-visible { outline: 2px solid var(--color-focus); outline-offset: 2px; }
1219
+ @media (max-width: 640px) {
1220
+ .framed-chart[data-legend="sidebar"] .chart-body { grid-template-columns: minmax(0, 1fr); }
1221
+ .chart-legend-sidebar { border-left: 0; padding-left: 0; border-top: 1px solid var(--color-border); padding-top: var(--spacing-sm); }
1222
+ }
1223
+ @media (pointer: coarse) {
1224
+ .chart-series-button { min-height: var(--size-touch-min); min-width: var(--size-touch-min); }
1225
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@momoi-labs/kiso",
3
- "version": "0.9.0",
3
+ "version": "0.10.0",
4
4
  "description": "Kiso design-system contracts and generated design tokens",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -96,6 +96,11 @@
96
96
  --color-warning-on-dark: #efc05b;
97
97
  --color-danger-on-dark: #f5807f;
98
98
  --color-info-on-dark: #75c4fa;
99
+ --color-chart-1: var(--color-accent-base);
100
+ --color-chart-2: var(--color-status-success);
101
+ --color-chart-3: var(--color-status-warning);
102
+ --color-chart-4: var(--color-status-info);
103
+ --color-chart-5: var(--color-status-danger);
99
104
  --font-heading: Inter, system-ui, sans-serif;
100
105
  --font-body: Inter, system-ui, sans-serif;
101
106
  --font-mono: JetBrains Mono, ui-monospace, monospace;
@@ -172,6 +172,16 @@ export const semanticWarningOnDark: string;
172
172
  export const semanticDangerOnDark: string;
173
173
  /** Info on a permanently dark surface (log view, terminal). Theme-invariant on purpose: the surface does not follow color-scheme, so neither can the text. */
174
174
  export const semanticInfoOnDark: string;
175
+ /** Categorical chart series 1. Identity only, never status. Pair with its numbered label, interactive highlighting, and exact values. */
176
+ export const semanticChart1: string;
177
+ /** Categorical chart series 2. Identity only, never status. Pair with its numbered label, interactive highlighting, and exact values. */
178
+ export const semanticChart2: string;
179
+ /** Categorical chart series 3. Identity only, never status. Pair with its numbered label, interactive highlighting, and exact values. */
180
+ export const semanticChart3: string;
181
+ /** Categorical chart series 4. Identity only, never status. Pair with its numbered label, interactive highlighting, and exact values. */
182
+ export const semanticChart4: string;
183
+ /** Categorical chart series 5. Identity only, never status. Pair with its numbered label, interactive highlighting, and exact values. */
184
+ export const semanticChart5: string;
175
185
  /** Heading family. Inter, same as body; headings differentiate by size and weight. */
176
186
  export const fontHeading: string;
177
187
  /** Body family. Inter for interface text. */
@@ -93,6 +93,11 @@
93
93
  "semantic-warning-on-dark": {"colorSpace":"srgb","components":[0.9373,0.7529,0.3569],"hex":"#efc05b"},
94
94
  "semantic-danger-on-dark": {"colorSpace":"srgb","components":[0.9608,0.502,0.498],"hex":"#f5807f"},
95
95
  "semantic-info-on-dark": {"colorSpace":"srgb","components":[0.4588,0.7686,0.9804],"hex":"#75c4fa"},
96
+ "semantic-chart-1": {"colorSpace":"srgb","components":[0.3569,0.2471,0.7686],"hex":"#5b3fc4"},
97
+ "semantic-chart-2": {"colorSpace":"srgb","components":[0.3569,0.8235,0.5843],"hex":"#5bd295"},
98
+ "semantic-chart-3": {"colorSpace":"srgb","components":[0.9373,0.7529,0.3569],"hex":"#efc05b"},
99
+ "semantic-chart-4": {"colorSpace":"srgb","components":[0.4588,0.7686,0.9804],"hex":"#75c4fa"},
100
+ "semantic-chart-5": {"colorSpace":"srgb","components":[0.9608,0.502,0.498],"hex":"#f5807f"},
96
101
  "font-heading": ["Inter","system-ui","sans-serif"],
97
102
  "font-body": ["Inter","system-ui","sans-serif"],
98
103
  "font-mono": ["JetBrains Mono","ui-monospace","monospace"],
@@ -95,6 +95,11 @@ $semantic-danger-foreground: #151411; // Text and icons on a danger fill. Invert
95
95
  $semantic-warning-on-dark: #efc05b; // Warning on a permanently dark surface (log view, terminal). Theme-invariant on purpose: the surface does not follow color-scheme, so neither can the text.
96
96
  $semantic-danger-on-dark: #f5807f; // Danger on a permanently dark surface (log view, terminal). Theme-invariant on purpose: the surface does not follow color-scheme, so neither can the text.
97
97
  $semantic-info-on-dark: #75c4fa; // Info on a permanently dark surface (log view, terminal). Theme-invariant on purpose: the surface does not follow color-scheme, so neither can the text.
98
+ $semantic-chart-1: #5b3fc4; // Categorical chart series 1. Identity only, never status. Pair with its numbered label, interactive highlighting, and exact values.
99
+ $semantic-chart-2: #5bd295; // Categorical chart series 2. Identity only, never status. Pair with its numbered label, interactive highlighting, and exact values.
100
+ $semantic-chart-3: #efc05b; // Categorical chart series 3. Identity only, never status. Pair with its numbered label, interactive highlighting, and exact values.
101
+ $semantic-chart-4: #75c4fa; // Categorical chart series 4. Identity only, never status. Pair with its numbered label, interactive highlighting, and exact values.
102
+ $semantic-chart-5: #f5807f; // Categorical chart series 5. Identity only, never status. Pair with its numbered label, interactive highlighting, and exact values.
98
103
  $font-heading: Inter, system-ui, sans-serif; // Heading family. Inter, same as body; headings differentiate by size and weight.
99
104
  $font-body: Inter, system-ui, sans-serif; // Body family. Inter for interface text.
100
105
  $font-mono: 'JetBrains Mono', ui-monospace, monospace; // Monospace family. Code and machine-readable values (hashes, identifiers). Numeric tables use body with tabular figures, not mono.