Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,52 @@ All notable changes to `@ossrandom/design-system` are documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/), and this project adheres to [Semantic Versioning](https://semver.org/).

## [Unreleased]

### Changed — `ServiceMap`

- **Visual refresh.** Nodes are now small dots (was: `round-rectangle` 80 × 36 cards). Diameter is computed from each node's degree (in + out edge count) using `√degree` scaling — 6 px (isolated) → 28 px (densest hub) — so hubs surface visually without dwarfing leaves. Status now drives the **fill color** (`success` / `warning` / `danger`), not just a border accent.
- **Node labels moved below the dot** (was: centered, inside the card). Light weight, no background pill — pills covered edges. Z-order keeps dots above edges and labels beside them.
- **Edges now declare direction and labels.**
- Cytoscape path: `target-arrow-shape: triangle`, `arrow-scale: 0.9`. `ServiceEdge.label` is rendered with `text-rotation: autorotate` but `text-opacity: 0` until the edge enters a focus set.
- deck.gl path: already directional via `ArcLayer` (gradient `source → target`); no behavior change.
- **Hover / touch focus highlight.** Hovering or tapping a node dims everything else to ~18 % opacity and lights the focused node (accent border, weight 500), its incident edges (accent stroke, 2 px, edge label revealed), and its direct neighbors. Hovering an edge lights both endpoints. Touch start triggers the same focus on the canvas path. `pointerleave` and an empty-area click clear focus.
- **WebGL highlight is in-place.** The deck.gl path builds an adjacency map up front and re-renders highlight state via `inst.setProps({ layers })` with `updateTriggers` keyed on the focus id — no full context re-init per hover.

### Internal

- New `computeDegrees(nodes, edges)` and `degreeRadius(deg, max)` helpers in `src/charts/ServiceMap.tsx`. `PositionedNode` extended with a precomputed `degree` field threaded through both rendering paths.
- New Cytoscape stylesheet classes `.rcs-dim`, `.rcs-focus`, `.rcs-focus-edge`, `.rcs-neighbor` with 120 ms transitions on opacity, color, and border-width.
- Tiny `cssEscape()` polyfill for Cytoscape ID selectors when `CSS.escape` is unavailable.

## [0.3.0] — 2026-04-28

### Added

- **Charts module** at the new `@ossrandom/design-system/charts` subpath — opt-in to keep the main entry zero-dep:
- **`Chart`** — line / area / bar / scatter time-series. Renders with `uplot` (canvas) when installed; falls back to inline SVG for small datasets. Auto-handoff to WebGL via `@deck.gl/core` + `@deck.gl/layers` once a series crosses ~100k points. Pan / zoom / crosshair / synced cursors across stacked panels; tabular tooltips with `Signal Red` accent.
- **`Sparkline`** — zero-dep inline SVG. `data: number[]`, optional `showArea`. Designed to drop into `Stat` tiles (80×24 default). Handles flat (range = 0) inputs without NaN.
- **`Donut`** + **`RadialGauge`** — zero-dep SVG. `Donut` takes typed `DonutSegment[]`, optional center label / value, optional legend, per-segment click handler. `RadialGauge` is a 270° arc with `tone: "good" | "warning" | "bad"`.
- **`UptimeBar`** — 90-day status grid on canvas. `UptimeCell[]` with `status: "operational" | "degraded" | "outage" | "maintenance" | "no-data"`; cursor-tracking tooltip; quadtree picking.
- **`Treemap`** — squarified treemap. Loads `d3-hierarchy` lazily; falls back to a built-in squarify pass when not installed. Canvas2d at any size, WebGL handoff on `>50k` leaves.
- **`ServiceMap`** — directed graph for production topology. Loads `cytoscape` + `cytoscape-cose-bilkent` lazily; falls back to a small force-directed canvas. Status-keyed node strokes (`healthy` / `degraded` / `failing` / `unknown`), arrow markers on edges, drag-to-pan, scroll-to-zoom.

- **Optional peer deps** (`peerDependenciesMeta`): `uplot`, `d3-hierarchy`, `d3-force`, `cytoscape`, `cytoscape-cose-bilkent`, `@deck.gl/core`, `@deck.gl/layers`. The main entry imports none of them — install only what the charts you render require.

- **Chart theming**: `readChartTheme()` + `onThemeChange()` exported from `/charts` — hook a custom renderer to the same tokens (`--accent`, `--fg-1`, `--bg-2`, `--font-mono`, …) and re-render on `data-theme` swap.

- **Tokens**: `--elevation-tooltip` for chart tooltips; engine-badge surfaces `[data-engine]` attribute on chart roots — opt-in dev badge via `--rcs-show-engine: 1`.

### Notes

No breaking changes. Existing imports from `@ossrandom/design-system` continue to work unchanged. To use charts:

```tsx
import { Chart, Sparkline, Donut } from "@ossrandom/design-system/charts";
```

…and `pnpm add` the peer deps for the charts you render.

## [0.2.1] — 2026-04-27

### Fixed
Expand Down
78 changes: 78 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ export function Dashboard({ services }: { services: readonly Service[] }) {
## What's in the box

- **50+ React components** — Buttons, Inputs, Form controls, Layout primitives, Navigation, Feedback, Data display, Chat, Code/Markdown/Terminal/RTE, plus `ThemeProvider` + imperative `toast`
- **Charts** (opt-in subpath) — `Chart` (line/area/bar/scatter), `Sparkline`, `Donut`, `RadialGauge`, `UptimeBar`, `Treemap`, `ServiceMap` — see [Charts](#charts) below
- **Strongly-typed token unions** (`Size`, `SpaceSize`, `Radius`, `ThemeMode`, `BrandColor`, `Direction`, `Axis`, …)
- **Generic components**: `Select<V>`, `Combobox<V>`, `Tabs<K>`, `Menu<K>`, `RadioGroup<V>`, `Table<T>`
- **Strict TypeScript** — full type definitions emitted to `dist/`, source maps + declaration maps included
Expand Down Expand Up @@ -135,6 +136,83 @@ import { ThemeProvider, ToastRegion, toast } from "@ossrandom/design-system";
</ThemeProvider>
```

## Charts

Charts ship behind an opt-in subpath because they pull heavier peer deps. The main entry stays zero-dep — you only pay for charts if you import them.

```tsx
import {
Chart, Sparkline, Donut, RadialGauge,
UptimeBar, Treemap, ServiceMap,
} from "@ossrandom/design-system/charts";
```

Install the peer deps for the charts you actually render:

| Component | Peer dep | Fallback |
| ------------------------- | ---------------------------------------------- | ----------------------------------- |
| `Chart` (time-series) | `uplot` | SVG renderer (small datasets only) |
| `Sparkline` | — | inline SVG, zero deps |
| `Donut` / `RadialGauge` | — | inline SVG, zero deps |
| `UptimeBar` | — | canvas2d, zero deps |
| `Treemap` | `d3-hierarchy` | canvas2d squarify |
| `ServiceMap` | `cytoscape` + `cytoscape-cose-bilkent` | force-directed canvas |

Charts auto-handoff to WebGL (`@deck.gl/core`, `@deck.gl/layers`) when the dataset crosses an engine threshold (`Chart` ≥100k points; `ServiceMap` ≥200 nodes). The `data-engine` attribute on the rendered root reflects the active backend (`svg` / `canvas` / `webgl`); set `--rcs-show-engine: 1` to surface a dev badge.

All chart components read tokens via `readChartTheme()` and re-render on `data-theme` swap — `Signal Red` accent, mono micro-labels, tabular numerics out of the box.

#### `ServiceMap` — interaction model

`ServiceMap` is a directed graph; semantics live in the data, not the visuals on top of it.

- **Direction.** Edges flow `source → target` with arrowheads at the target end. The deck.gl path uses `ArcLayer` (gradient source/target colors); the canvas path uses Cytoscape's bezier curve with a `triangle` arrow.
- **Node size = degree.** Each dot's radius is computed from `in + out` edge count using `√degree` scaling — 3 px (isolated) → 14 px (densest hub). Hubs surface visually without dwarfing leaves.
- **Status drives fill.** `healthy` / `degraded` / `failing` / `unknown` map to the `success` / `warning` / `danger` / `fg-3` tokens. Failing edges paint in `danger`.
- **Labels.** Node labels sit below each dot in a light weight (`font-weight: 400`, `color: --fg-3`) so they don't compete with edges. Edge labels (`label?: string` on `ServiceEdge`) are rendered but hidden by default — they reveal only when their edge is in the focus set.
- **Hover / touch focus.** Hovering or tapping a node dims the rest of the graph to ~18 % opacity and lights the focused node, its incident edges, its neighbors, and the edge labels for those incident edges. Hovering an edge lights the edge plus both endpoints. `pointerleave` clears the focus state.
- **Engine handoff.** Auto resolves to `webgpu` › `webgl` › `canvas` based on `nodes + edges` count vs. the threshold. The deck.gl path uses an internal adjacency map and `setProps({ layers })` to re-render highlight state without rebuilding the WebGL context.

```tsx
<ServiceMap
nodes={[
{ id: "lb", label: "load-balancer", status: "healthy" },
{ id: "au", label: "auth-service", status: "degraded" },
{ id: "bl", label: "billing", status: "failing" },
/* … */
]}
edges={[
{ source: "lb", target: "au", label: "http" },
{ source: "au", target: "bl", label: "grpc · 5xx", status: "failing" },
/* … */
]}
height={440}
onNodeClick={(n) => console.log("node:", n.id)}
/>
```

```tsx
<Chart
type="line"
series={[{ id: "p99", label: "p99 latency", data: points }]}
height={240}
/>

<Sparkline data={[12, 19, 14, 22, 28, 24, 31]} />

<Donut
segments={[
{ label: "Compute", value: 45 },
{ label: "Database", value: 25 },
{ label: "Cache", value: 18 },
{ label: "Other", value: 12 },
]}
centerLabel="cores"
centerValue="847"
showLegend
/>
```

## Local development

```bash
Expand Down
24 changes: 22 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@ossrandom/design-system",
"version": "0.2.1",
"version": "0.3.0",
"description": "RandomCodeSpace Design System — strongly-typed React component definitions.",
"keywords": [
"design-system",
Expand Down Expand Up @@ -36,6 +36,10 @@
"types": "./dist/tokens.d.ts",
"import": "./dist/tokens.js"
},
"./charts": {
"types": "./dist/charts/index.d.ts",
"import": "./dist/charts/index.js"
},
"./styles.css": "./dist/styles.css"
},
"files": [
Expand All @@ -62,7 +66,23 @@
},
"peerDependencies": {
"react": ">=18",
"react-dom": ">=18"
"react-dom": ">=18",
"uplot": "^1.6.0",
"d3-hierarchy": "^3.0.0",
"d3-force": "^3.0.0",
"cytoscape": "^3.30.0",
"cytoscape-cose-bilkent": "^4.1.0",
"@deck.gl/core": "^9.0.0",
"@deck.gl/layers": "^9.0.0"
},
"peerDependenciesMeta": {
"uplot": { "optional": true },
"d3-hierarchy": { "optional": true },
"d3-force": { "optional": true },
"cytoscape": { "optional": true },
"cytoscape-cose-bilkent": { "optional": true },
"@deck.gl/core": { "optional": true },
"@deck.gl/layers": { "optional": true }
},
"devDependencies": {
"@babel/standalone": "^7.29.0",
Expand Down
Loading
Loading