strata-react 0.1.0 → 0.1.3
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/README.md +8 -0
- package/dist/components/app-shell.d.ts +3 -0
- package/dist/components/async-button.d.ts +1 -0
- package/dist/components/button.d.ts +2 -0
- package/dist/components/data-table.d.ts +4 -0
- package/dist/components/data-view.d.ts +4 -1
- package/dist/components/done.d.ts +1 -0
- package/dist/components/field.d.ts +5 -0
- package/dist/components/line-chart.d.ts +1 -0
- package/dist/components/metric.d.ts +2 -0
- package/dist/components/page-header.d.ts +1 -0
- package/dist/components/status.d.ts +1 -0
- package/dist/components/tabs.d.ts +1 -0
- package/dist/components/undo.d.ts +7 -5
- package/dist/components/virtual-list.d.ts +1 -0
- package/dist/index.d.ts +8 -1
- package/dist/index.js +32 -1
- package/dist/index.js.map +1 -1
- package/dist/lib/format.d.ts +2 -0
- package/dist/lib/utils.d.ts +1 -0
- package/dist/query/index.d.ts +4 -1
- package/dist/{query-BXH-Gfj7.js → query-DZVmYJSx.js} +5 -2
- package/dist/query-DZVmYJSx.js.map +1 -0
- package/dist/testing/index.d.ts +6 -0
- package/dist/testing.js +1 -1
- package/dist/testing.js.map +1 -1
- package/package.json +5 -2
- package/dist/query-BXH-Gfj7.js.map +0 -1
package/README.md
CHANGED
|
@@ -102,8 +102,16 @@ npm test # typecheck + unit tests
|
|
|
102
102
|
npm run test:integration # build, then check the package and its CSS as an app sees them
|
|
103
103
|
npm run test:visual # screenshot every story (light, dark) and compare with baselines
|
|
104
104
|
npm run test:visual:update # accept the current screenshots as baselines
|
|
105
|
+
npm run site # build the docs site into site/
|
|
105
106
|
```
|
|
106
107
|
|
|
108
|
+
## Docs site
|
|
109
|
+
|
|
110
|
+
GitHub Pages serves one site, built by `.github/workflows/pages.yml` on every push to main:
|
|
111
|
+
Storybook at the root, the TypeDoc API reference at `/api/`, and a gallery of every visual
|
|
112
|
+
baseline (light and dark, linked to its story) at `/visuals/`. One-time setup: Settings → Pages →
|
|
113
|
+
Source: GitHub Actions.
|
|
114
|
+
|
|
107
115
|
## Licence
|
|
108
116
|
|
|
109
117
|
MIT
|
|
@@ -7,6 +7,7 @@ export type NavItem = {
|
|
|
7
7
|
/** Match only this exact path, for an index route such as "/". */
|
|
8
8
|
end?: boolean;
|
|
9
9
|
};
|
|
10
|
+
/** The app frame: product name, side nav on the page ground (a scrollable row on phones), optional top bar, and the main region. */
|
|
10
11
|
export declare function AppShell({ product, nav, navLabel, topBar, children, className, }: {
|
|
11
12
|
/** The product's name or mark, linking home. */
|
|
12
13
|
product: React.ReactNode;
|
|
@@ -16,11 +17,13 @@ export declare function AppShell({ product, nav, navLabel, topBar, children, cla
|
|
|
16
17
|
children: React.ReactNode;
|
|
17
18
|
className?: string;
|
|
18
19
|
}): React.JSX.Element;
|
|
20
|
+
/** Navigation links (React Router `NavLink`); the current route is a neutral pill with `aria-current`. */
|
|
19
21
|
export declare function SideNav({ items, label, className }: {
|
|
20
22
|
items: NavItem[];
|
|
21
23
|
label: string;
|
|
22
24
|
className?: string;
|
|
23
25
|
}): React.JSX.Element;
|
|
26
|
+
/** Plain links separated by `/`; the last crumb is the current page. */
|
|
24
27
|
export declare function Breadcrumb({ items }: {
|
|
25
28
|
items: {
|
|
26
29
|
label: string;
|
|
@@ -9,6 +9,7 @@ export type AsyncLabels = {
|
|
|
9
9
|
done: string;
|
|
10
10
|
error?: string;
|
|
11
11
|
};
|
|
12
|
+
/** One button through idle, pending, done and error, with the label changing in place and its width fixed by the longest label. The outcome is announced politely. */
|
|
12
13
|
export declare function AsyncButton({ phase, labels, disabled, ...props }: Omit<ButtonProps, "children"> & {
|
|
13
14
|
phase: Phase;
|
|
14
15
|
labels: AsyncLabels;
|
|
@@ -8,9 +8,11 @@ export declare const buttonVariants: (props?: ({
|
|
|
8
8
|
export type ButtonProps = React.ComponentProps<"button"> & VariantProps<typeof buttonVariants> & {
|
|
9
9
|
asChild?: boolean;
|
|
10
10
|
};
|
|
11
|
+
/** A button with shadcn's variant names. The default variant is `outline`; ask for the filled accent (`variant="default"`) once per view. `type` defaults to `button`. */
|
|
11
12
|
export declare function Button({ className, variant, size, asChild, type, ...props }: ButtonProps): React.JSX.Element;
|
|
12
13
|
export type IconButtonProps = Omit<ButtonProps, "size" | "children" | "aria-label"> & {
|
|
13
14
|
icon: IconComponent;
|
|
14
15
|
label: string;
|
|
15
16
|
};
|
|
17
|
+
/** An icon-only button. `label` is required and becomes the accessible name and tooltip. */
|
|
16
18
|
export declare function IconButton({ icon: Icon, label, variant, ...props }: IconButtonProps): React.JSX.Element;
|
|
@@ -29,9 +29,13 @@ export type DataTableProps<T> = {
|
|
|
29
29
|
maxHeight?: number;
|
|
30
30
|
className?: string;
|
|
31
31
|
};
|
|
32
|
+
/** Reads a sort from its URL form (`pieces.desc`). */
|
|
32
33
|
export declare function parseSort(value: string | null): Sort | undefined;
|
|
34
|
+
/** Writes a sort in its URL form (`pieces.desc`). */
|
|
33
35
|
export declare function serialiseSort(sort: Sort): string;
|
|
36
|
+
/** A table on TanStack Table: muted headers, row rules, right-aligned tabular numbers, sortable headers with `aria-sort`, virtualised from `virtualizeFrom` rows, sort optionally kept in the URL (`sortParam`). */
|
|
34
37
|
export declare function DataTable<T>(props: DataTableProps<T>): React.JSX.Element;
|
|
38
|
+
/** The loading placeholder for a `DataTable`, shaped like its rows. */
|
|
35
39
|
export declare function TableSkeleton({ rows, columns }: {
|
|
36
40
|
rows?: number;
|
|
37
41
|
columns?: number;
|
|
@@ -9,22 +9,25 @@ export type Load<T> = {
|
|
|
9
9
|
status: "ready";
|
|
10
10
|
data: T;
|
|
11
11
|
};
|
|
12
|
+
/** Every view of remote data: renders `skeleton` while loading, an alert with Try again on error, `empty` when `isEmpty`, else `children(data)`. The skeleton and empty state are required. */
|
|
12
13
|
export declare function DataView<T>({ state, isEmpty, skeleton, empty, children, className, }: {
|
|
13
14
|
state: Load<T>;
|
|
14
15
|
/** When the loaded data counts as empty. For arrays, `(rows) => rows.length === 0`. */
|
|
15
16
|
isEmpty: (data: T) => boolean;
|
|
16
17
|
/** Mirrors the final layout, so nothing jumps when data arrives. Not a centred spinner. */
|
|
17
18
|
skeleton: React.ReactNode;
|
|
18
|
-
/** What goes here and how to add it. An
|
|
19
|
+
/** What goes here and how to add it. An `EmptyState`, not an illustration. */
|
|
19
20
|
empty: React.ReactNode;
|
|
20
21
|
children: (data: T) => React.ReactNode;
|
|
21
22
|
className?: string;
|
|
22
23
|
}): React.JSX.Element;
|
|
24
|
+
/** An empty state that teaches the next action: a title, a sentence and an action. No illustration slot, on purpose. */
|
|
23
25
|
export declare function EmptyState({ title, children, action }: {
|
|
24
26
|
title: string;
|
|
25
27
|
children?: React.ReactNode;
|
|
26
28
|
action?: React.ReactNode;
|
|
27
29
|
}): React.JSX.Element;
|
|
30
|
+
/** A placeholder block for a loading layout. Size it with layout classes so it mirrors the final content. */
|
|
28
31
|
export declare function Skeleton({ className }: {
|
|
29
32
|
className?: string;
|
|
30
33
|
}): React.JSX.Element;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
+
/** The completion moment for a rare, earned finish: what happened (past tense, with its object), what happens next, and optionally a figure drawn from the person's own data. Focus moves to the heading. */
|
|
2
3
|
export declare function Done({ title, next, figure, actions, focusOnMount, className, }: {
|
|
3
4
|
/** What happened, past tense, with its object. */
|
|
4
5
|
title: string;
|
|
@@ -10,15 +10,20 @@ export type FieldProps = {
|
|
|
10
10
|
children: React.ReactNode;
|
|
11
11
|
className?: string;
|
|
12
12
|
};
|
|
13
|
+
/** A labelled form field: wires the label, hint, error, `aria-describedby` and `aria-invalid` into the one control it wraps. */
|
|
13
14
|
export declare function Field({ label, id: idProp, hint, error, children, className }: FieldProps): React.JSX.Element;
|
|
15
|
+
/** A text input that picks up its id and descriptions from the surrounding `Field`. */
|
|
14
16
|
export declare function Input({ className, id, ...props }: React.ComponentProps<"input">): React.JSX.Element;
|
|
17
|
+
/** A multi-line input that picks up its id and descriptions from the surrounding `Field`. */
|
|
15
18
|
export declare function Textarea({ className, id, ...props }: React.ComponentProps<"textarea">): React.JSX.Element;
|
|
19
|
+
/** The platform select, styled. The default for short lists, especially on touch screens. */
|
|
16
20
|
export declare function NativeSelect({ className, id, ...props }: React.ComponentProps<"select">): React.JSX.Element;
|
|
17
21
|
export type Choice = {
|
|
18
22
|
value: string;
|
|
19
23
|
label: string;
|
|
20
24
|
detail?: string;
|
|
21
25
|
};
|
|
26
|
+
/** Radio choices in a fieldset with a legend; each whole row is the hit target. For 2 to 5 options that each need a sentence. */
|
|
22
27
|
export declare function ChoiceGroup({ legend, name, choices, value, onChange, error, }: {
|
|
23
28
|
legend: string;
|
|
24
29
|
name: string;
|
|
@@ -32,4 +32,5 @@ export type LineChartProps = {
|
|
|
32
32
|
};
|
|
33
33
|
/** Four gridline steps of a size people write on a scale (1, 2, 2.5, 4 or 5 × 10ⁿ), ending above the max. */
|
|
34
34
|
export declare function niceMax(max: number): number;
|
|
35
|
+
/** One measured series, drawn in real pixels, with the peak and current readings labelled on the chart and every reading in a screen-reader table. Optional plan, threshold band and `colourScale`. */
|
|
35
36
|
export declare function LineChart({ label, series, seriesLabel, planned, plannedLabel, band, formatX, formatY, xTicks, colour, colourScale, colourScaleLabel, height, className, }: LineChartProps): React.JSX.Element;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
+
/** Headline figures as one description list split by hairlines. Holds `Metric`s. */
|
|
2
3
|
export declare function MetricStrip({ label, children, className }: {
|
|
3
4
|
label: string;
|
|
4
5
|
children: React.ReactNode;
|
|
@@ -12,4 +13,5 @@ export type MetricProps = {
|
|
|
12
13
|
/** One sentence: the change in words with its window, or what the figure means right now. */
|
|
13
14
|
note?: React.ReactNode;
|
|
14
15
|
};
|
|
16
|
+
/** One figure: label over value, an optional muted unit and a note that states any change in words with its window. */
|
|
15
17
|
export declare function Metric({ label, value, unit, note }: MetricProps): React.JSX.Element;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
+
/** The top of an Operate screen: a noun title, its scope, the main action, and the finding (the one fact in today's data that most needs attention). */
|
|
2
3
|
export declare function PageHeader({ title, scope, actions, finding, as: Heading, }: {
|
|
3
4
|
title: string;
|
|
4
5
|
/** The title's element: h1 on a screen, h2 where the header is shown inside another page. */
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { IconComponent } from '../lib/icon';
|
|
2
2
|
export type Tone = "neutral" | "info" | "success" | "warning" | "danger";
|
|
3
|
+
/** A status as a word on a pale tint of its tone, with an optional icon. Never colour alone. */
|
|
3
4
|
export declare function Status({ tone, icon: Icon, children, className, }: {
|
|
4
5
|
tone: Tone;
|
|
5
6
|
icon?: IconComponent;
|
|
@@ -3,6 +3,7 @@ export type TabItem = {
|
|
|
3
3
|
value: string;
|
|
4
4
|
label: string;
|
|
5
5
|
};
|
|
6
|
+
/** Tabs whose selection lives in a URL search parameter (React Router), with the ARIA tabs keyboard model. */
|
|
6
7
|
export declare function Tabs({ param, items, label, children, className, }: {
|
|
7
8
|
/** The search parameter that holds the selected tab. */
|
|
8
9
|
param: string;
|
|
@@ -1,18 +1,20 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
-
|
|
2
|
+
/** A removal waiting out its undo window: what to say, and what to do when the window closes. */
|
|
3
|
+
export type PendingRemoval = {
|
|
3
4
|
id: string;
|
|
4
5
|
message: string;
|
|
5
6
|
commit: () => void;
|
|
6
7
|
};
|
|
8
|
+
/** Schedules a reversible removal to commit after `windowMs` unless undone. A second removal, or unmounting, commits the pending one. */
|
|
7
9
|
export declare function useUndoable(windowMs?: number): {
|
|
8
|
-
pending:
|
|
9
|
-
schedule: (next:
|
|
10
|
+
pending: PendingRemoval | null;
|
|
11
|
+
schedule: (next: PendingRemoval) => void;
|
|
10
12
|
undo: () => void;
|
|
11
13
|
};
|
|
14
|
+
/** The polite live region that shows a pending removal with an Undo button. */
|
|
12
15
|
export declare function UndoBar({ pending, onUndo, inline, }: {
|
|
13
|
-
pending:
|
|
16
|
+
pending: PendingRemoval | null;
|
|
14
17
|
onUndo: () => void;
|
|
15
18
|
/** Render in the flow instead of fixed to the viewport; for the state-matrix page. */
|
|
16
19
|
inline?: boolean;
|
|
17
20
|
}): React.JSX.Element;
|
|
18
|
-
export {};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
+
/** A list that renders only its visible rows (TanStack Virtual) from `virtualizeFrom` items, keeping list semantics for screen readers. */
|
|
2
3
|
export declare function VirtualList<T>({ label, items, itemKey, children, estimateSize, height, virtualizeFrom, className, }: {
|
|
3
4
|
label: string;
|
|
4
5
|
items: T[];
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* strata-react: an opinionated React + Tailwind v4 framework for building product frontends.
|
|
3
|
+
* Components ship structure, behaviour, states and accessibility; the look comes from the
|
|
4
|
+
* product's own theme. Every component has a Storybook story for each of its states.
|
|
5
|
+
*
|
|
6
|
+
* @module strata-react
|
|
7
|
+
*/
|
|
1
8
|
export { cn, typeSteps } from './lib/utils';
|
|
2
9
|
export { createFormat, format, NBSP, type Format } from './lib/format';
|
|
3
10
|
export type { IconComponent } from './lib/icon';
|
|
@@ -11,7 +18,7 @@ export { DataTable, TableSkeleton, parseSort, serialiseSort, type Column, type S
|
|
|
11
18
|
export { VirtualList } from './components/virtual-list';
|
|
12
19
|
export { LineChart, type LineChartProps, type Point } from './components/line-chart';
|
|
13
20
|
export { PageHeader } from './components/page-header';
|
|
14
|
-
export { UndoBar, useUndoable } from './components/undo';
|
|
21
|
+
export { UndoBar, useUndoable, type PendingRemoval } from './components/undo';
|
|
15
22
|
export { Done } from './components/done';
|
|
16
23
|
export { AppShell, SideNav, Breadcrumb, type NavItem } from './components/app-shell';
|
|
17
24
|
export { Tabs, type TabItem } from './components/tabs';
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as fetchJson, i as describeHttpError, n as StrataQueryProvider, o as toLoad, r as createQueryClient, s as useLoad, t as HttpError } from "./query-
|
|
1
|
+
import { a as fetchJson, i as describeHttpError, n as StrataQueryProvider, o as toLoad, r as createQueryClient, s as useLoad, t as HttpError } from "./query-DZVmYJSx.js";
|
|
2
2
|
import { clsx } from "clsx";
|
|
3
3
|
import { extendTailwindMerge } from "tailwind-merge";
|
|
4
4
|
import * as React from "react";
|
|
@@ -21,11 +21,13 @@ var typeSteps = [
|
|
|
21
21
|
"display"
|
|
22
22
|
];
|
|
23
23
|
var twMerge = extendTailwindMerge({ extend: { classGroups: { "font-size": [{ type: [...typeSteps] }] } } });
|
|
24
|
+
/** clsx + tailwind-merge, taught that `type-*` utilities are font sizes. */
|
|
24
25
|
function cn(...inputs) {
|
|
25
26
|
return twMerge(clsx(inputs));
|
|
26
27
|
}
|
|
27
28
|
// src/lib/format.ts
|
|
28
29
|
var NBSP = "\xA0";
|
|
30
|
+
/** Intl formatters for one locale: integers, decimals, percent, times, days, units with a non-breaking space, and counted nouns. */
|
|
29
31
|
function createFormat(locale) {
|
|
30
32
|
const integer = new Intl.NumberFormat(locale, { maximumFractionDigits: 0 });
|
|
31
33
|
const decimal = new Intl.NumberFormat(locale, { maximumFractionDigits: 1 });
|
|
@@ -62,6 +64,7 @@ function createFormat(locale) {
|
|
|
62
64
|
count: (value, one, many = `${one}s`) => `${integer.format(value)} ${new Intl.PluralRules(locale).select(value) === "one" ? one : many}`
|
|
63
65
|
};
|
|
64
66
|
}
|
|
67
|
+
/** The en-GB formatters. */
|
|
65
68
|
var format = createFormat("en-GB");
|
|
66
69
|
// src/components/button.tsx
|
|
67
70
|
var buttonVariants = cva("inline-flex shrink-0 cursor-pointer items-center justify-center gap-2 whitespace-nowrap rounded-control type-label transition-state press disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0", {
|
|
@@ -85,6 +88,7 @@ var buttonVariants = cva("inline-flex shrink-0 cursor-pointer items-center justi
|
|
|
85
88
|
size: "default"
|
|
86
89
|
}
|
|
87
90
|
});
|
|
91
|
+
/** A button with shadcn's variant names. The default variant is `outline`; ask for the filled accent (`variant="default"`) once per view. `type` defaults to `button`. */
|
|
88
92
|
function Button({ className, variant, size, asChild = false, type = "button", ...props }) {
|
|
89
93
|
return /* @__PURE__ */ jsx(asChild ? Slot : "button", {
|
|
90
94
|
type: asChild ? void 0 : type,
|
|
@@ -95,6 +99,7 @@ function Button({ className, variant, size, asChild = false, type = "button", ..
|
|
|
95
99
|
...props
|
|
96
100
|
});
|
|
97
101
|
}
|
|
102
|
+
/** An icon-only button. `label` is required and becomes the accessible name and tooltip. */
|
|
98
103
|
function IconButton({ icon: Icon, label, variant = "ghost", ...props }) {
|
|
99
104
|
return /* @__PURE__ */ jsx(Button, {
|
|
100
105
|
size: "icon",
|
|
@@ -106,6 +111,7 @@ function IconButton({ icon: Icon, label, variant = "ghost", ...props }) {
|
|
|
106
111
|
});
|
|
107
112
|
}
|
|
108
113
|
// src/components/async-button.tsx
|
|
114
|
+
/** One button through idle, pending, done and error, with the label changing in place and its width fixed by the longest label. The outcome is announced politely. */
|
|
109
115
|
function AsyncButton({ phase, labels, disabled, ...props }) {
|
|
110
116
|
const errorLabel = labels.error ?? "Try again";
|
|
111
117
|
const current = {
|
|
@@ -173,6 +179,7 @@ function useField(id) {
|
|
|
173
179
|
"aria-invalid": field?.invalid || void 0
|
|
174
180
|
};
|
|
175
181
|
}
|
|
182
|
+
/** A labelled form field: wires the label, hint, error, `aria-describedby` and `aria-invalid` into the one control it wraps. */
|
|
176
183
|
function Field({ label, id: idProp, hint, error, children, className }) {
|
|
177
184
|
const generated = React.useId();
|
|
178
185
|
const id = idProp ?? generated;
|
|
@@ -212,6 +219,7 @@ function Field({ label, id: idProp, hint, error, children, className }) {
|
|
|
212
219
|
});
|
|
213
220
|
}
|
|
214
221
|
var control = "w-full min-w-0 rounded-control border border-input bg-background px-3 type-body text-foreground transition-state placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:bg-muted disabled:opacity-70 aria-invalid:border-danger";
|
|
222
|
+
/** A text input that picks up its id and descriptions from the surrounding `Field`. */
|
|
215
223
|
function Input({ className, id, ...props }) {
|
|
216
224
|
return /* @__PURE__ */ jsx("input", {
|
|
217
225
|
...useField(id),
|
|
@@ -219,6 +227,7 @@ function Input({ className, id, ...props }) {
|
|
|
219
227
|
...props
|
|
220
228
|
});
|
|
221
229
|
}
|
|
230
|
+
/** A multi-line input that picks up its id and descriptions from the surrounding `Field`. */
|
|
222
231
|
function Textarea({ className, id, ...props }) {
|
|
223
232
|
return /* @__PURE__ */ jsx("textarea", {
|
|
224
233
|
...useField(id),
|
|
@@ -226,6 +235,7 @@ function Textarea({ className, id, ...props }) {
|
|
|
226
235
|
...props
|
|
227
236
|
});
|
|
228
237
|
}
|
|
238
|
+
/** The platform select, styled. The default for short lists, especially on touch screens. */
|
|
229
239
|
function NativeSelect({ className, id, ...props }) {
|
|
230
240
|
return /* @__PURE__ */ jsx("select", {
|
|
231
241
|
...useField(id),
|
|
@@ -233,6 +243,7 @@ function NativeSelect({ className, id, ...props }) {
|
|
|
233
243
|
...props
|
|
234
244
|
});
|
|
235
245
|
}
|
|
246
|
+
/** Radio choices in a fieldset with a legend; each whole row is the hit target. For 2 to 5 options that each need a sentence. */
|
|
236
247
|
function ChoiceGroup({ legend, name, choices, value, onChange, error }) {
|
|
237
248
|
const errorId = React.useId();
|
|
238
249
|
return /* @__PURE__ */ jsxs("fieldset", {
|
|
@@ -282,6 +293,7 @@ var tones = {
|
|
|
282
293
|
warning: "bg-warning-subtle text-warning",
|
|
283
294
|
danger: "bg-danger-subtle text-danger"
|
|
284
295
|
};
|
|
296
|
+
/** A status as a word on a pale tint of its tone, with an optional icon. Never colour alone. */
|
|
285
297
|
function Status({ tone, icon: Icon, children, className }) {
|
|
286
298
|
return /* @__PURE__ */ jsxs("span", {
|
|
287
299
|
className: cn("inline-flex items-center gap-1.5 whitespace-nowrap rounded-pill py-0.5 pr-2.5 pl-2 type-caption font-medium", tones[tone], className),
|
|
@@ -292,6 +304,7 @@ function Status({ tone, icon: Icon, children, className }) {
|
|
|
292
304
|
});
|
|
293
305
|
}
|
|
294
306
|
// src/components/metric.tsx
|
|
307
|
+
/** Headline figures as one description list split by hairlines. Holds `Metric`s. */
|
|
295
308
|
function MetricStrip({ label, children, className }) {
|
|
296
309
|
return /* @__PURE__ */ jsx("dl", {
|
|
297
310
|
"aria-label": label,
|
|
@@ -299,6 +312,7 @@ function MetricStrip({ label, children, className }) {
|
|
|
299
312
|
children
|
|
300
313
|
});
|
|
301
314
|
}
|
|
315
|
+
/** One figure: label over value, an optional muted unit and a note that states any change in words with its window. */
|
|
302
316
|
function Metric({ label, value, unit, note }) {
|
|
303
317
|
return /* @__PURE__ */ jsxs("div", {
|
|
304
318
|
className: "grid content-start gap-0.5 border-border py-3 not-first:border-t sm:px-5 sm:not-first:border-t-0 sm:first:pl-0",
|
|
@@ -322,6 +336,7 @@ function Metric({ label, value, unit, note }) {
|
|
|
322
336
|
});
|
|
323
337
|
}
|
|
324
338
|
// src/components/data-view.tsx
|
|
339
|
+
/** Every view of remote data: renders `skeleton` while loading, an alert with Try again on error, `empty` when `isEmpty`, else `children(data)`. The skeleton and empty state are required. */
|
|
325
340
|
function DataView({ state, isEmpty, skeleton, empty, children, className }) {
|
|
326
341
|
return /* @__PURE__ */ jsxs("div", {
|
|
327
342
|
className: cn("min-w-0", className),
|
|
@@ -351,6 +366,7 @@ function DataView({ state, isEmpty, skeleton, empty, children, className }) {
|
|
|
351
366
|
]
|
|
352
367
|
});
|
|
353
368
|
}
|
|
369
|
+
/** An empty state that teaches the next action: a title, a sentence and an action. No illustration slot, on purpose. */
|
|
354
370
|
function EmptyState({ title, children, action }) {
|
|
355
371
|
return /* @__PURE__ */ jsxs("div", {
|
|
356
372
|
className: "grid justify-items-start gap-2 border-y border-border py-6",
|
|
@@ -370,6 +386,7 @@ function EmptyState({ title, children, action }) {
|
|
|
370
386
|
]
|
|
371
387
|
});
|
|
372
388
|
}
|
|
389
|
+
/** A placeholder block for a loading layout. Size it with layout classes so it mirrors the final content. */
|
|
373
390
|
function Skeleton({ className }) {
|
|
374
391
|
return /* @__PURE__ */ jsx("div", {
|
|
375
392
|
"aria-hidden": true,
|
|
@@ -381,6 +398,7 @@ function useLocalSort(initial) {
|
|
|
381
398
|
const [sort, setSort] = React.useState(initial);
|
|
382
399
|
return [sort, setSort];
|
|
383
400
|
}
|
|
401
|
+
/** Reads a sort from its URL form (`pieces.desc`). */
|
|
384
402
|
function parseSort(value) {
|
|
385
403
|
if (!value) return void 0;
|
|
386
404
|
const [key, dir] = value.split(".");
|
|
@@ -390,6 +408,7 @@ function parseSort(value) {
|
|
|
390
408
|
dir: dir === "desc" ? "descending" : "ascending"
|
|
391
409
|
};
|
|
392
410
|
}
|
|
411
|
+
/** Writes a sort in its URL form (`pieces.desc`). */
|
|
393
412
|
function serialiseSort(sort) {
|
|
394
413
|
return `${sort.key}.${sort.dir === "descending" ? "desc" : "asc"}`;
|
|
395
414
|
}
|
|
@@ -401,6 +420,7 @@ function useUrlSort(param, initial) {
|
|
|
401
420
|
return out;
|
|
402
421
|
}, { replace: true }), [param, setParams])];
|
|
403
422
|
}
|
|
423
|
+
/** A table on TanStack Table: muted headers, row rules, right-aligned tabular numbers, sortable headers with `aria-sort`, virtualised from `virtualizeFrom` rows, sort optionally kept in the URL (`sortParam`). */
|
|
404
424
|
function DataTable(props) {
|
|
405
425
|
return props.sortParam ? /* @__PURE__ */ jsx(UrlSortedTable, {
|
|
406
426
|
...props,
|
|
@@ -529,6 +549,7 @@ function Table({ caption, columns, rows, rowKey, sortState: [sort, setSort], vir
|
|
|
529
549
|
})
|
|
530
550
|
});
|
|
531
551
|
}
|
|
552
|
+
/** The loading placeholder for a `DataTable`, shaped like its rows. */
|
|
532
553
|
function TableSkeleton({ rows = 4, columns = 5 }) {
|
|
533
554
|
return /* @__PURE__ */ jsxs("div", {
|
|
534
555
|
"aria-hidden": true,
|
|
@@ -543,6 +564,7 @@ function TableSkeleton({ rows = 4, columns = 5 }) {
|
|
|
543
564
|
});
|
|
544
565
|
}
|
|
545
566
|
// src/components/virtual-list.tsx
|
|
567
|
+
/** A list that renders only its visible rows (TanStack Virtual) from `virtualizeFrom` items, keeping list semantics for screen readers. */
|
|
546
568
|
function VirtualList({ label, items, itemKey, children, estimateSize = 48, height = 480, virtualizeFrom = 100, className }) {
|
|
547
569
|
const scroller = React.useRef(null);
|
|
548
570
|
const virtual = items.length >= virtualizeFrom;
|
|
@@ -618,6 +640,7 @@ function niceMax(max) {
|
|
|
618
640
|
10
|
|
619
641
|
].map((m) => m * mag).find((s) => s >= raw) ?? 10 * mag) * 4;
|
|
620
642
|
}
|
|
643
|
+
/** One measured series, drawn in real pixels, with the peak and current readings labelled on the chart and every reading in a screen-reader table. Optional plan, threshold band and `colourScale`. */
|
|
621
644
|
function LineChart({ label, series, seriesLabel, planned, plannedLabel, band, formatX, formatY, xTicks, colour = "var(--chart-1)", colourScale, colourScaleLabel, height = 280, className }) {
|
|
622
645
|
const [ref, width] = useWidth();
|
|
623
646
|
const gradientId = `heat-${React.useId().replace(/[^\w-]/g, "")}`;
|
|
@@ -832,6 +855,7 @@ function LineChart({ label, series, seriesLabel, planned, plannedLabel, band, fo
|
|
|
832
855
|
});
|
|
833
856
|
}
|
|
834
857
|
// src/components/page-header.tsx
|
|
858
|
+
/** The top of an Operate screen: a noun title, its scope, the main action, and the finding (the one fact in today's data that most needs attention). */
|
|
835
859
|
function PageHeader({ title, scope, actions, finding, as: Heading = "h1" }) {
|
|
836
860
|
return /* @__PURE__ */ jsxs("header", {
|
|
837
861
|
className: "grid gap-4",
|
|
@@ -860,6 +884,7 @@ function PageHeader({ title, scope, actions, finding, as: Heading = "h1" }) {
|
|
|
860
884
|
});
|
|
861
885
|
}
|
|
862
886
|
// src/components/undo.tsx
|
|
887
|
+
/** Schedules a reversible removal to commit after `windowMs` unless undone. A second removal, or unmounting, commits the pending one. */
|
|
863
888
|
function useUndoable(windowMs = 8e3) {
|
|
864
889
|
const [pending, setPending] = React.useState(null);
|
|
865
890
|
const current = React.useRef(null);
|
|
@@ -892,6 +917,7 @@ function useUndoable(windowMs = 8e3) {
|
|
|
892
917
|
undo
|
|
893
918
|
};
|
|
894
919
|
}
|
|
920
|
+
/** The polite live region that shows a pending removal with an Undo button. */
|
|
895
921
|
function UndoBar({ pending, onUndo, inline = false }) {
|
|
896
922
|
return /* @__PURE__ */ jsx("div", {
|
|
897
923
|
"aria-live": "polite",
|
|
@@ -912,6 +938,7 @@ function UndoBar({ pending, onUndo, inline = false }) {
|
|
|
912
938
|
});
|
|
913
939
|
}
|
|
914
940
|
// src/components/done.tsx
|
|
941
|
+
/** The completion moment for a rare, earned finish: what happened (past tense, with its object), what happens next, and optionally a figure drawn from the person's own data. Focus moves to the heading. */
|
|
915
942
|
function Done({ title, next, figure, actions, focusOnMount = true, className }) {
|
|
916
943
|
const heading = React.useRef(null);
|
|
917
944
|
const titleId = React.useId();
|
|
@@ -947,6 +974,7 @@ function Done({ title, next, figure, actions, focusOnMount = true, className })
|
|
|
947
974
|
});
|
|
948
975
|
}
|
|
949
976
|
// src/components/app-shell.tsx
|
|
977
|
+
/** The app frame: product name, side nav on the page ground (a scrollable row on phones), optional top bar, and the main region. */
|
|
950
978
|
function AppShell({ product, nav, navLabel = "Main", topBar, children, className }) {
|
|
951
979
|
return /* @__PURE__ */ jsxs("div", {
|
|
952
980
|
className: cn("min-h-dvh md:grid md:grid-cols-[14rem_minmax(0,1fr)]", className),
|
|
@@ -976,6 +1004,7 @@ function AppShell({ product, nav, navLabel = "Main", topBar, children, className
|
|
|
976
1004
|
})]
|
|
977
1005
|
});
|
|
978
1006
|
}
|
|
1007
|
+
/** Navigation links (React Router `NavLink`); the current route is a neutral pill with `aria-current`. */
|
|
979
1008
|
function SideNav({ items, label, className }) {
|
|
980
1009
|
return /* @__PURE__ */ jsx("nav", {
|
|
981
1010
|
"aria-label": label,
|
|
@@ -997,6 +1026,7 @@ function SideNav({ items, label, className }) {
|
|
|
997
1026
|
})
|
|
998
1027
|
});
|
|
999
1028
|
}
|
|
1029
|
+
/** Plain links separated by `/`; the last crumb is the current page. */
|
|
1000
1030
|
function Breadcrumb({ items }) {
|
|
1001
1031
|
return /* @__PURE__ */ jsx("nav", {
|
|
1002
1032
|
"aria-label": "Breadcrumb",
|
|
@@ -1024,6 +1054,7 @@ function Breadcrumb({ items }) {
|
|
|
1024
1054
|
});
|
|
1025
1055
|
}
|
|
1026
1056
|
// src/components/tabs.tsx
|
|
1057
|
+
/** Tabs whose selection lives in a URL search parameter (React Router), with the ARIA tabs keyboard model. */
|
|
1027
1058
|
function Tabs({ param, items, label, children, className }) {
|
|
1028
1059
|
const [params, setParams] = useSearchParams();
|
|
1029
1060
|
const fallback = items[0]?.value ?? "";
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["_Fragment"],"sources":["../src/lib/utils.ts","../src/lib/format.ts","../src/components/button.tsx","../src/components/async-button.tsx","../src/components/field.tsx","../src/components/status.tsx","../src/components/metric.tsx","../src/components/data-view.tsx","../src/components/data-table.tsx","../src/components/virtual-list.tsx","../src/components/line-chart.tsx","../src/components/page-header.tsx","../src/components/undo.tsx","../src/components/done.tsx","../src/components/app-shell.tsx","../src/components/tabs.tsx"],"sourcesContent":["import { clsx, type ClassValue } from \"clsx\";\nimport { extendTailwindMerge } from \"tailwind-merge\";\n\n// Teach tailwind-merge that `type-*` utilities are font sizes. Without this, cn() cannot tell\n// `type-label` from a colour and may drop one of two classes that both survive in CSS; with stock\n// shadcn the same bug drops `text-muted-foreground` next to a custom `text-*` size.\nexport const typeSteps = [\"caption\", \"label\", \"body-sm\", \"body\", \"heading\", \"title\", \"figure\", \"display\"] as const;\n\nconst twMerge = extendTailwindMerge({\n extend: {\n classGroups: {\n \"font-size\": [{ type: [...typeSteps] }],\n },\n },\n});\n\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs));\n}\n","// Every number and date on screen goes through Intl, once, here. Units are joined with a\n// non-breaking space so \"212 °C\" never wraps between figure and unit. A product that is not\n// en-GB calls createFormat with its locale once and exports the result from its own lib.\n\nexport const NBSP = \" \";\n\nexport function createFormat(locale: string) {\n const integer = new Intl.NumberFormat(locale, { maximumFractionDigits: 0 });\n const decimal = new Intl.NumberFormat(locale, { maximumFractionDigits: 1 });\n const percent = new Intl.NumberFormat(locale, { style: \"percent\", maximumFractionDigits: 0 });\n const clock = new Intl.DateTimeFormat(locale, { hour: \"2-digit\", minute: \"2-digit\" });\n const day = new Intl.DateTimeFormat(locale, { weekday: \"long\", day: \"numeric\", month: \"long\" });\n const shortDay = new Intl.DateTimeFormat(locale, { weekday: \"short\", hour: \"2-digit\", minute: \"2-digit\" });\n const withUnit = (value: number, unit: string, fmt: Intl.NumberFormat = integer) =>\n `${fmt.format(value)}${NBSP}${unit}`;\n return {\n locale,\n integer,\n decimal,\n percent,\n clock,\n day,\n shortDay,\n withUnit,\n celsius: (value: number) => withUnit(value, \"°C\"),\n /** \"1 piece\", \"6 pieces\": the count and its noun, so copy never reads \"1 pieces\". */\n count: (value: number, one: string, many = `${one}s`) =>\n `${integer.format(value)}${NBSP}${new Intl.PluralRules(locale).select(value) === \"one\" ? one : many}`,\n };\n}\n\nexport type Format = ReturnType<typeof createFormat>;\n\nexport const format = createFormat(\"en-GB\");\n","import * as React from \"react\";\nimport { Slot } from \"@radix-ui/react-slot\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport type { IconComponent } from \"../lib/icon\";\nimport { cn } from \"../lib/utils\";\n\n// Variant names are shadcn's, so copied code and agents' prior knowledge still work. Two things\n// differ on purpose:\n// 1. The default variant is `outline`, not the filled accent. A bare <Button> is the restrained\n// one; the accent (`variant=\"default\"`) has to be asked for, once per view. Generated app screens\n// put the accent on every button; 12 of 23 real product screens had no filled primary at all.\n// 2. Heights come from --control-h, shared with Input and NativeSelect, so density changes move\n// every control together.\n// 3. `press`: the button gives 2% under a click or a finger, over 100ms. Buttons are used tens of\n// times a day, so that is all the motion they get (character.md, the frequency budget).\nexport const buttonVariants = cva(\n \"inline-flex shrink-0 cursor-pointer items-center justify-center gap-2 whitespace-nowrap rounded-control type-label transition-state press disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0\",\n {\n variants: {\n variant: {\n default: \"bg-primary text-primary-foreground hover:bg-primary-10\",\n outline: \"border border-input bg-background text-foreground hover:bg-muted\",\n secondary: \"bg-muted text-foreground hover:bg-neutral-4\",\n ghost: \"text-foreground hover:bg-muted\",\n destructive: \"bg-destructive text-destructive-foreground hover:bg-danger\",\n link: \"h-auto px-0 text-primary-11 underline underline-offset-4 hover:text-primary-12\",\n },\n size: {\n default: \"h-(--control-h) px-4\",\n sm: \"h-(--control-h-sm) px-3\",\n icon: \"size-(--control-h)\",\n },\n },\n defaultVariants: { variant: \"outline\", size: \"default\" },\n },\n);\n\nexport type ButtonProps = React.ComponentProps<\"button\"> &\n VariantProps<typeof buttonVariants> & { asChild?: boolean };\n\nexport function Button({ className, variant, size, asChild = false, type = \"button\", ...props }: ButtonProps) {\n const Comp = asChild ? Slot : \"button\";\n return <Comp type={asChild ? undefined : type} className={cn(buttonVariants({ variant, size }), className)} {...props} />;\n}\n\n// An icon-only button cannot be written without a name: `label` is required by the type, becomes\n// the accessible name, and doubles as the native tooltip. This makes the most common generated\n// accessibility defect (an unlabelled icon button) a compile error instead of an audit finding.\nexport type IconButtonProps = Omit<ButtonProps, \"size\" | \"children\" | \"aria-label\"> & {\n icon: IconComponent;\n label: string;\n};\n\nexport function IconButton({ icon: Icon, label, variant = \"ghost\", ...props }: IconButtonProps) {\n return (\n <Button size=\"icon\" variant={variant} aria-label={label} title={label} {...props}>\n <Icon aria-hidden />\n </Button>\n );\n}\n","import * as React from \"react\";\nimport { Check, CircleAlert, LoaderCircle } from \"lucide-react\";\nimport { Button, type ButtonProps } from \"./button\";\n\n// One object through its states: \"Book firing\" becomes \"Booking…\" becomes \"Booked\", in the same\n// button, rather than the button being swapped for a spinner. The labels are stacked in one grid\n// cell so the button is always as wide as its longest label and nothing around it shifts. The\n// button stays enabled until the request starts (so a person can always try), and the outcome is\n// announced politely for screen readers.\n\nexport type Phase = \"idle\" | \"pending\" | \"done\" | \"error\";\n\nexport type AsyncLabels = {\n idle: string;\n /** Ends in an ellipsis: \"Saving…\", \"Booking…\". */\n pending: string;\n /** The same verb, finished: \"Saved\", \"Booked\". */\n done: string;\n error?: string;\n};\n\nexport function AsyncButton({\n phase,\n labels,\n disabled,\n ...props\n}: Omit<ButtonProps, \"children\"> & { phase: Phase; labels: AsyncLabels }) {\n const errorLabel = labels.error ?? \"Try again\";\n const current = { idle: labels.idle, pending: labels.pending, done: labels.done, error: errorLabel }[phase];\n const Icon = { idle: null, pending: LoaderCircle, done: Check, error: CircleAlert }[phase];\n\n return (\n <>\n <Button {...props} disabled={disabled || phase === \"pending\"} aria-disabled={phase === \"pending\" || undefined}>\n {Icon && <Icon aria-hidden className={phase === \"pending\" ? \"motion-safe:animate-spin\" : undefined} />}\n <span className=\"grid\">\n {[labels.idle, labels.pending, labels.done, errorLabel].map((l) => (\n <span key={l} aria-hidden={l !== current} className={l === current ? \"[grid-area:1/1]\" : \"invisible [grid-area:1/1]\"}>\n {l}\n </span>\n ))}\n </span>\n </Button>\n <span role=\"status\" className=\"sr-only\">\n {phase === \"done\" ? labels.done : phase === \"error\" ? errorLabel : \"\"}\n </span>\n </>\n );\n}\n\n/** Runs an async action and reports its phase. `done` returns to `idle` after `settleMs`. */\nexport function useAsyncAction<A extends unknown[]>(action: (...args: A) => Promise<void>, settleMs = 2000) {\n const [phase, setPhase] = React.useState<Phase>(\"idle\");\n const timer = React.useRef<number | undefined>(undefined);\n React.useEffect(() => () => window.clearTimeout(timer.current), []);\n const run = React.useCallback(\n async (...args: A) => {\n window.clearTimeout(timer.current);\n setPhase(\"pending\");\n try {\n await action(...args);\n setPhase(\"done\");\n timer.current = window.setTimeout(() => setPhase(\"idle\"), settleMs);\n } catch {\n setPhase(\"error\");\n }\n },\n [action, settleMs],\n );\n return [phase, run] as const;\n}\n","import * as React from \"react\";\nimport { CircleAlert } from \"lucide-react\";\nimport { cn } from \"../lib/utils\";\n\n// Field owns the wiring a generated form usually forgets: the label's htmlFor, aria-describedby\n// pointing at the hint and the error, and aria-invalid. Controls read it from context, so a control\n// inside a Field cannot end up unlabelled or silently invalid.\n\ntype FieldState = { id: string; describedBy?: string; invalid: boolean };\nconst FieldContext = React.createContext<FieldState | null>(null);\n\nfunction useField(id?: string) {\n const field = React.useContext(FieldContext);\n return {\n // Inside a Field the Field's id wins, so the label always points at the control. To choose the\n // id yourself, pass it to Field.\n id: field?.id ?? id,\n \"aria-describedby\": field?.describedBy,\n \"aria-invalid\": field?.invalid || undefined,\n };\n}\n\nexport type FieldProps = {\n label: string;\n /** The control's id, when the page needs a known one (an anchor, a test). Generated otherwise. */\n id?: string;\n /** What the person needs to know before typing, such as limits or units. */\n hint?: React.ReactNode;\n /** What went wrong and how to fix it, in one sentence. Shown under the control, never in a toast. */\n error?: string;\n children: React.ReactNode;\n className?: string;\n};\n\nexport function Field({ label, id: idProp, hint, error, children, className }: FieldProps) {\n const generated = React.useId();\n const id = idProp ?? generated;\n const hintId = hint ? `${id}-hint` : undefined;\n const errorId = error ? `${id}-error` : undefined;\n const describedBy = [errorId, hintId].filter(Boolean).join(\" \") || undefined;\n\n return (\n <FieldContext.Provider value={{ id, describedBy, invalid: Boolean(error) }}>\n <div className={cn(\"grid min-w-0 content-start gap-1.5\", className)}>\n <label htmlFor={id} className=\"type-label text-foreground\">\n {label}\n </label>\n {children}\n {error && (\n <p id={errorId} className=\"flex items-start gap-1.5 type-body-sm text-danger\">\n <CircleAlert aria-hidden className=\"mt-0.5 size-4 shrink-0\" />\n {error}\n </p>\n )}\n {hint && (\n <p id={hintId} className=\"type-body-sm text-muted-foreground\">\n {hint}\n </p>\n )}\n </div>\n </FieldContext.Provider>\n );\n}\n\nconst control =\n \"w-full min-w-0 rounded-control border border-input bg-background px-3 type-body text-foreground transition-state placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:bg-muted disabled:opacity-70 aria-invalid:border-danger\";\n\nexport function Input({ className, id, ...props }: React.ComponentProps<\"input\">) {\n return <input {...useField(id)} className={cn(control, \"h-(--control-h)\", className)} {...props} />;\n}\n\nexport function Textarea({ className, id, ...props }: React.ComponentProps<\"textarea\">) {\n return <textarea {...useField(id)} className={cn(control, \"min-h-24 py-2\", className)} {...props} />;\n}\n\n// A native select, styled. For short option lists on a touch screen it beats a custom listbox:\n// the platform picker, typeahead and form autofill all work with no code.\nexport function NativeSelect({ className, id, ...props }: React.ComponentProps<\"select\">) {\n return <select {...useField(id)} className={cn(control, \"h-(--control-h) pr-8\", className)} {...props} />;\n}\n\n// Radio choices: a fieldset with a legend, and each label wrapping its control so the whole row is\n// one hit target. accent-color carries the theme into the native control.\nexport type Choice = { value: string; label: string; detail?: string };\n\nexport function ChoiceGroup({\n legend,\n name,\n choices,\n value,\n onChange,\n error,\n}: {\n legend: string;\n name: string;\n choices: Choice[];\n value: string;\n onChange: (value: string) => void;\n error?: string;\n}) {\n const errorId = React.useId();\n return (\n <fieldset className=\"grid gap-2\" aria-describedby={error ? errorId : undefined}>\n <legend className=\"mb-1.5 type-label text-foreground\">{legend}</legend>\n {choices.map((c) => (\n <label key={c.value} className=\"flex min-h-(--control-h-sm) cursor-pointer items-start gap-3 py-1\">\n <input\n type=\"radio\"\n name={name}\n value={c.value}\n checked={value === c.value}\n onChange={() => onChange(c.value)}\n className=\"mt-1 size-4 shrink-0 accent-primary\"\n />\n <span className=\"grid\">\n <span className=\"type-body\">{c.label}</span>\n {c.detail && <span className=\"type-body-sm text-muted-foreground\">{c.detail}</span>}\n </span>\n </label>\n ))}\n {error && (\n <p id={errorId} className=\"flex items-start gap-1.5 type-body-sm text-danger\">\n <CircleAlert aria-hidden className=\"mt-0.5 size-4 shrink-0\" />\n {error}\n </p>\n )}\n </fieldset>\n );\n}\n","import type { IconComponent } from \"../lib/icon\";\nimport { cn } from \"../lib/utils\";\n\n// Status is a word first. `children` is required and must be a string, so a status can never be a\n// bare coloured dot. The tint and ink come from the fixed status roles, which are kept apart from\n// the brand accent: pale tint, dark ink of the same hue, never a saturated chip with white text.\n// No pulsing variant exists; nothing on a status mark moves unless the data behind it is live.\n\nexport type Tone = \"neutral\" | \"info\" | \"success\" | \"warning\" | \"danger\";\n\nconst tones: Record<Tone, string> = {\n neutral: \"bg-muted text-muted-foreground\",\n info: \"bg-info-subtle text-info\",\n success: \"bg-success-subtle text-success\",\n warning: \"bg-warning-subtle text-warning\",\n danger: \"bg-danger-subtle text-danger\",\n};\n\nexport function Status({\n tone,\n icon: Icon,\n children,\n className,\n}: {\n tone: Tone;\n icon?: IconComponent;\n children: string;\n className?: string;\n}) {\n return (\n <span\n className={cn(\n \"inline-flex items-center gap-1.5 whitespace-nowrap rounded-pill py-0.5 pr-2.5 pl-2 type-caption font-medium\",\n tones[tone],\n className,\n )}\n >\n {Icon && <Icon aria-hidden className=\"size-3.5 shrink-0\" />}\n {children}\n </span>\n );\n}\n","import * as React from \"react\";\nimport { cn } from \"../lib/utils\";\n\n// Headline figures as one container split by hairlines, label over value: what real product screens\n// do, instead of four cards each with an icon in a tinted circle. There is deliberately no `icon`,\n// `sparkline` or `trend` prop. A change is written in words with its window (\"4 more than last\n// Thursday\"), because a green \"+12.5%\" pill says neither what changed nor since when, and the trend\n// gets its own chart.\n\nexport function MetricStrip({ label, children, className }: { label: string; children: React.ReactNode; className?: string }) {\n return (\n <dl\n aria-label={label}\n className={cn(\n \"grid grid-cols-1 border-y border-border sm:auto-cols-fr sm:grid-flow-col sm:divide-x sm:divide-border\",\n className,\n )}\n >\n {children}\n </dl>\n );\n}\n\nexport type MetricProps = {\n label: string;\n /** Already formatted. Ink colour, 28px, tabular figures: never the brand colour, never 48px. */\n value: string;\n unit?: string;\n /** One sentence: the change in words with its window, or what the figure means right now. */\n note?: React.ReactNode;\n};\n\nexport function Metric({ label, value, unit, note }: MetricProps) {\n return (\n <div className=\"grid content-start gap-0.5 border-border py-3 not-first:border-t sm:px-5 sm:not-first:border-t-0 sm:first:pl-0\">\n <dt className=\"type-label text-muted-foreground\">{label}</dt>\n <dd className=\"type-figure text-foreground\">\n {value}\n {unit && <span className=\"type-body text-muted-foreground\">{\" \" + unit}</span>}\n </dd>\n {note && <dd className=\"type-body-sm text-muted-foreground\">{note}</dd>}\n </div>\n );\n}\n","import * as React from \"react\";\nimport { CircleAlert, RotateCcw } from \"lucide-react\";\nimport { Button } from \"./button\";\nimport { cn } from \"../lib/utils\";\n\n// Every view of remote data goes through DataView, and DataView will not type-check without a\n// skeleton and an empty state. Generated UI renders the happy path; this makes the other three\n// states part of the component's signature rather than a review comment.\n\nexport type Load<T> =\n | { status: \"loading\" }\n | { status: \"error\"; message: string; retry: () => void }\n | { status: \"ready\"; data: T };\n\nexport function DataView<T>({\n state,\n isEmpty,\n skeleton,\n empty,\n children,\n className,\n}: {\n state: Load<T>;\n /** When the loaded data counts as empty. For arrays, `(rows) => rows.length === 0`. */\n isEmpty: (data: T) => boolean;\n /** Mirrors the final layout, so nothing jumps when data arrives. Not a centred spinner. */\n skeleton: React.ReactNode;\n /** What goes here and how to add it. An <EmptyState>, not an illustration. */\n empty: React.ReactNode;\n children: (data: T) => React.ReactNode;\n className?: string;\n}) {\n return (\n <div className={cn(\"min-w-0\", className)} aria-busy={state.status === \"loading\" || undefined}>\n {state.status === \"loading\" && skeleton}\n {state.status === \"error\" && (\n <div role=\"alert\" className=\"flex flex-wrap items-start gap-3 rounded-surface bg-danger-subtle p-4 text-danger\">\n <CircleAlert aria-hidden className=\"mt-0.5 size-5 shrink-0\" />\n <p className=\"min-w-0 flex-1 type-body\">{state.message}</p>\n <Button size=\"sm\" onClick={state.retry}>\n <RotateCcw aria-hidden />\n Try again\n </Button>\n </div>\n )}\n {state.status === \"ready\" && (isEmpty(state.data) ? empty : children(state.data))}\n </div>\n );\n}\n\n// An empty state teaches the next action. No illustration slot exists, on purpose.\nexport function EmptyState({ title, children, action }: { title: string; children?: React.ReactNode; action?: React.ReactNode }) {\n return (\n <div className=\"grid justify-items-start gap-2 border-y border-border py-6\">\n <p className=\"type-heading\">{title}</p>\n {children && <p className=\"max-w-prose type-body text-muted-foreground\">{children}</p>}\n {action && <div className=\"mt-2\">{action}</div>}\n </div>\n );\n}\n\nexport function Skeleton({ className }: { className?: string }) {\n return <div aria-hidden className={cn(\"rounded-control bg-muted motion-safe:animate-pulse\", className)} />;\n}\n","import * as React from \"react\";\nimport { ArrowDown, ArrowUp, ArrowUpDown } from \"lucide-react\";\nimport {\n flexRender,\n getCoreRowModel,\n getSortedRowModel,\n useReactTable,\n type ColumnDef,\n type SortingState,\n} from \"@tanstack/react-table\";\nimport { useVirtualizer } from \"@tanstack/react-virtual\";\nimport { useSearchParams } from \"react-router\";\nimport { cn } from \"../lib/utils\";\n\n// The table real product screens use: 12px muted sentence-case headers, row rules (no zebra),\n// numbers right-aligned in tabular figures, one status mark and at most one action per row.\n// Sortable headers are buttons and look sortable; aria-sort says which way. The scroll container is\n// a labelled, focusable region so keyboard users can scroll a wide table.\n//\n// Underneath: TanStack Table for the row model and sorting, TanStack Virtual once a table passes\n// `virtualizeFrom` rows (only the visible rows are in the DOM; the header sticks). The column API\n// stays small on purpose (key, header, cell, numeric, sort), so a screen reads as a list of\n// decisions about columns, not as table plumbing. `sortParam` keeps the sort in the URL\n// (?sort=when.desc) so a sorted view can be linked and survives a reload.\n\nexport type Column<T> = {\n key: string;\n header: string;\n cell: (row: T) => React.ReactNode;\n /** Right-aligns and sets tabular figures. Use for every quantity. */\n numeric?: boolean;\n sort?: (a: T, b: T) => number;\n /** Cells that may hold long text get truncated rather than stretching the table. */\n truncate?: boolean;\n className?: string;\n};\n\nexport type Sort = { key: string; dir: \"ascending\" | \"descending\" };\n\nexport type DataTableProps<T> = {\n /** Names the table for screen readers and labels the scroll region. */\n caption: string;\n columns: Column<T>[];\n rows: T[];\n rowKey: (row: T) => string;\n initialSort?: Sort;\n /** Keep the sort in this URL search parameter. Needs a React Router context. */\n sortParam?: string;\n /** Row count from which only the visible rows render. Default 100. */\n virtualizeFrom?: number;\n /** Height of the scroll region once virtualised, in px. Default 480. */\n maxHeight?: number;\n className?: string;\n};\n\ntype SortState = [Sort | undefined, (next: Sort) => void];\n\nfunction useLocalSort(initial?: Sort): SortState {\n const [sort, setSort] = React.useState(initial);\n return [sort, setSort];\n}\n\nexport function parseSort(value: string | null): Sort | undefined {\n if (!value) return undefined;\n const [key, dir] = value.split(\".\");\n if (!key) return undefined;\n return { key, dir: dir === \"desc\" ? \"descending\" : \"ascending\" };\n}\n\nexport function serialiseSort(sort: Sort) {\n return `${sort.key}.${sort.dir === \"descending\" ? \"desc\" : \"asc\"}`;\n}\n\nfunction useUrlSort(param: string, initial?: Sort): SortState {\n const [params, setParams] = useSearchParams();\n const sort = parseSort(params.get(param)) ?? initial;\n const set = React.useCallback(\n (next: Sort) =>\n setParams(\n (p) => {\n const out = new URLSearchParams(p);\n out.set(param, serialiseSort(next));\n return out;\n },\n { replace: true },\n ),\n [param, setParams],\n );\n return [sort, set];\n}\n\nexport function DataTable<T>(props: DataTableProps<T>) {\n // Two thin wrappers so each calls its hooks unconditionally.\n return props.sortParam ? <UrlSortedTable {...props} sortParam={props.sortParam} /> : <LocalSortedTable {...props} />;\n}\n\nfunction LocalSortedTable<T>(props: DataTableProps<T>) {\n return <Table {...props} sortState={useLocalSort(props.initialSort)} />;\n}\n\nfunction UrlSortedTable<T>(props: DataTableProps<T> & { sortParam: string }) {\n return <Table {...props} sortState={useUrlSort(props.sortParam, props.initialSort)} />;\n}\n\ntype Meta = { numeric?: boolean; truncate?: boolean; className?: string };\n\nfunction Table<T>({\n caption,\n columns,\n rows,\n rowKey,\n sortState: [sort, setSort],\n virtualizeFrom = 100,\n maxHeight = 480,\n className,\n}: DataTableProps<T> & { sortState: SortState }) {\n const defs = React.useMemo<ColumnDef<T>[]>(\n () =>\n columns.map((c) => ({\n id: c.key,\n // TanStack only sorts columns with an accessor; the row itself is enough, since the\n // column's own comparator does the work.\n accessorFn: (row) => row,\n header: c.header,\n cell: (info) => c.cell(info.row.original),\n enableSorting: Boolean(c.sort),\n sortingFn: c.sort ? (a, b) => c.sort!(a.original, b.original) : undefined,\n meta: { numeric: c.numeric, truncate: c.truncate, className: c.className } satisfies Meta,\n })),\n [columns],\n );\n\n const sorting: SortingState = sort && columns.some((c) => c.key === sort.key && c.sort)\n ? [{ id: sort.key, desc: sort.dir === \"descending\" }]\n : [];\n\n const table = useReactTable({\n data: rows,\n columns: defs,\n state: { sorting },\n getRowId: (row) => rowKey(row),\n getCoreRowModel: getCoreRowModel(),\n getSortedRowModel: getSortedRowModel(),\n });\n\n const toggle = (key: string) =>\n setSort({ key, dir: sort?.key === key && sort.dir === \"ascending\" ? \"descending\" : \"ascending\" });\n\n const modelRows = table.getRowModel().rows;\n const virtual = modelRows.length >= virtualizeFrom;\n const scroller = React.useRef<HTMLDivElement>(null);\n const virtualizer = useVirtualizer({\n count: virtual ? modelRows.length : 0,\n getScrollElement: () => scroller.current,\n estimateSize: () => 41,\n overscan: 8,\n // Until the region is measured, assume it is full height: the first paint (and a test DOM\n // with no layout) shows the first page of rows instead of none.\n initialRect: { width: 0, height: maxHeight },\n });\n const items = virtual ? virtualizer.getVirtualItems() : [];\n const padTop = items.length ? items[0].start : 0;\n const padBottom = items.length ? virtualizer.getTotalSize() - items[items.length - 1].end : 0;\n const visible = virtual ? items.map((i) => modelRows[i.index]) : modelRows;\n\n return (\n <div\n ref={scroller}\n role=\"region\"\n aria-label={caption}\n tabIndex={0}\n className={cn(\"min-w-0 overflow-x-auto\", virtual && \"overflow-y-auto\", className)}\n style={virtual ? { maxHeight } : undefined}\n >\n <table className=\"w-full border-collapse type-body-sm\" aria-rowcount={virtual ? modelRows.length + 1 : undefined}>\n <caption className=\"sr-only\">{caption}</caption>\n <thead className={cn(virtual && \"sticky top-0 z-10 bg-background\")}>\n {table.getHeaderGroups().map((group) => (\n <tr key={group.id}>\n {group.headers.map((header) => {\n const meta = header.column.columnDef.meta as Meta | undefined;\n const active = sort?.key === header.id && header.column.getCanSort() ? sort.dir : undefined;\n const Icon = active === \"ascending\" ? ArrowUp : active === \"descending\" ? ArrowDown : ArrowUpDown;\n const label = flexRender(header.column.columnDef.header, header.getContext());\n return (\n <th\n key={header.id}\n scope=\"col\"\n aria-sort={active}\n className={cn(\n \"py-2 pr-4 text-left align-bottom type-caption text-muted-foreground last:pr-0\",\n meta?.numeric && \"text-right\",\n )}\n >\n {header.column.getCanSort() ? (\n <button\n type=\"button\"\n onClick={() => toggle(header.id)}\n className={cn(\n \"-mx-1 inline-flex cursor-pointer items-center gap-1 rounded-control px-1 transition-state hover:text-foreground\",\n meta?.numeric && \"flex-row-reverse\",\n active && \"text-foreground\",\n )}\n >\n {label}\n <Icon aria-hidden className={cn(\"size-3.5\", !active && \"opacity-50\")} />\n </button>\n ) : (\n label\n )}\n </th>\n );\n })}\n </tr>\n ))}\n </thead>\n <tbody>\n {padTop > 0 && <tr aria-hidden style={{ height: padTop }} />}\n {visible.map((row, i) => (\n <tr\n key={row.id}\n className=\"border-t border-border\"\n aria-rowindex={virtual ? (items[i]?.index ?? 0) + 2 : undefined}\n >\n {row.getVisibleCells().map((cell) => {\n const meta = cell.column.columnDef.meta as Meta | undefined;\n return (\n <td\n key={cell.id}\n className={cn(\n \"py-2.5 pr-4 align-middle last:pr-0\",\n meta?.numeric && \"text-right tabular-nums\",\n meta?.truncate && \"max-w-48 truncate\",\n meta?.className,\n )}\n >\n {flexRender(cell.column.columnDef.cell, cell.getContext())}\n </td>\n );\n })}\n </tr>\n ))}\n {padBottom > 0 && <tr aria-hidden style={{ height: padBottom }} />}\n </tbody>\n </table>\n </div>\n );\n}\n\nexport function TableSkeleton({ rows = 4, columns = 5 }: { rows?: number; columns?: number }) {\n return (\n <div aria-hidden className=\"grid gap-0\">\n <div className=\"flex gap-4 py-2\">\n {Array.from({ length: columns }, (_, i) => (\n <div key={i} className=\"h-3 flex-1 rounded-control bg-muted\" />\n ))}\n </div>\n {Array.from({ length: rows }, (_, r) => (\n <div key={r} className=\"flex gap-4 border-t border-border py-3.5\">\n {Array.from({ length: columns }, (_, i) => (\n <div key={i} className=\"h-4 flex-1 rounded-control bg-muted motion-safe:animate-pulse\" />\n ))}\n </div>\n ))}\n </div>\n );\n}\n","import * as React from \"react\";\nimport { useVirtualizer } from \"@tanstack/react-virtual\";\nimport { cn } from \"../lib/utils\";\n\n// A long list that keeps only its visible rows in the DOM (TanStack Virtual), for feeds, logs and\n// pickers with hundreds of items. Rows are measured after render, so variable heights work; the\n// estimate only sets the first paint. The list is a labelled, focusable region so it can be\n// scrolled from the keyboard, and keeps list semantics with aria-setsize/aria-posinset so screen\n// readers still hear \"12 of 480\". Under `virtualizeFrom` items it renders a plain list.\n\nexport function VirtualList<T>({\n label,\n items,\n itemKey,\n children,\n estimateSize = 48,\n height = 480,\n virtualizeFrom = 100,\n className,\n}: {\n label: string;\n items: T[];\n itemKey: (item: T) => string;\n children: (item: T, index: number) => React.ReactNode;\n estimateSize?: number;\n height?: number;\n virtualizeFrom?: number;\n className?: string;\n}) {\n const scroller = React.useRef<HTMLDivElement>(null);\n const virtual = items.length >= virtualizeFrom;\n const virtualizer = useVirtualizer({\n count: virtual ? items.length : 0,\n getScrollElement: () => scroller.current,\n estimateSize: () => estimateSize,\n overscan: 6,\n initialRect: { width: 0, height },\n });\n\n if (!virtual) {\n return (\n <ul aria-label={label} className={cn(\"grid min-w-0\", className)}>\n {items.map((item, i) => (\n <li key={itemKey(item)} className=\"border-t border-border first:border-t-0\">\n {children(item, i)}\n </li>\n ))}\n </ul>\n );\n }\n\n return (\n <div\n ref={scroller}\n role=\"region\"\n aria-label={label}\n tabIndex={0}\n className={cn(\"min-w-0 overflow-y-auto\", className)}\n style={{ height }}\n >\n <ul aria-label={label} className=\"relative w-full\" style={{ height: virtualizer.getTotalSize() }}>\n {virtualizer.getVirtualItems().map((v) => {\n const item = items[v.index];\n return (\n <li\n key={itemKey(item)}\n ref={virtualizer.measureElement}\n data-index={v.index}\n aria-setsize={items.length}\n aria-posinset={v.index + 1}\n className={cn(\"absolute inset-x-0 top-0\", v.index > 0 && \"border-t border-border\")}\n style={{ translate: `0 ${v.start}px` }}\n >\n {children(item, v.index)}\n </li>\n );\n })}\n </ul>\n </div>\n );\n}\n","import * as React from \"react\";\nimport { cn } from \"../lib/utils\";\n\n// A line chart with the defaults real product screens use: one measured series in one hue, a 2px\n// straight line with no fill and no markers, 3 to 5 faint gridlines, no y-axis line, no legend for a\n// single series (the series is labelled where it ends). Values never live only in a hover tooltip:\n// the latest and peak readings are labelled on the chart and every reading is in a table for screen\n// readers. Optional extras exist only where they answer a question: a dashed `planned` series, a\n// horizontal `band` (a threshold that matters), and a `now` marker.\n//\n//\n// `colourScale` colours the measured line by its own value, for subjects where that colour is a\n// fact rather than decoration: a kiln's glow by temperature, water by depth. The y position still\n// carries the value, so the colour is never the only cue. Without a reason in DESIGN.md, use one\n// hue.\n//\n// It draws in real pixels (measured with ResizeObserver) rather than scaling a fixed viewBox, so\n// the 12px labels stay 12px on a phone.\n\nexport type Point = { x: number; y: number };\n\nexport type LineChartProps = {\n /** What the chart shows, as a sentence. Used as the accessible name and the table caption. */\n label: string;\n series: Point[];\n seriesLabel: string;\n planned?: Point[];\n plannedLabel?: string;\n band?: { below: number; label: string };\n formatX: (x: number) => string;\n formatY: (y: number) => string;\n xTicks: number[];\n /** CSS colour for the measured series. Defaults to the first chart token. */\n colour?: string;\n /** Colour the measured series by value: each stop applies from `at` upwards, blending to the\n * next. Only where the colour means something in the subject; record the reason in DESIGN.md. */\n colourScale?: { at: number; colour: string }[];\n /** Says what the colours mean, shown beside the legend swatch when `colourScale` is set. */\n colourScaleLabel?: string;\n height?: number;\n className?: string;\n};\n\nfunction useWidth<T extends HTMLElement>() {\n const ref = React.useRef<T>(null);\n const [width, setWidth] = React.useState(0);\n React.useLayoutEffect(() => {\n const el = ref.current;\n if (!el) return;\n setWidth(el.getBoundingClientRect().width);\n const ro = new ResizeObserver(([entry]) => setWidth(entry.contentRect.width));\n ro.observe(el);\n return () => ro.disconnect();\n }, []);\n return [ref, width] as const;\n}\n\n/** Four gridline steps of a size people write on a scale (1, 2, 2.5, 4 or 5 × 10ⁿ), ending above the max. */\nexport function niceMax(max: number) {\n // All-zero (or empty) data still needs a positive extent, or every coordinate divides by zero.\n if (!(max > 0)) return 4;\n const raw = max / 4;\n const mag = 10 ** Math.floor(Math.log10(raw));\n const step = [1, 2, 2.5, 4, 5, 10].map((m) => m * mag).find((s) => s >= raw) ?? 10 * mag;\n return step * 4;\n}\n\nexport function LineChart({\n label,\n series,\n seriesLabel,\n planned,\n plannedLabel,\n band,\n formatX,\n formatY,\n xTicks,\n colour = \"var(--chart-1)\",\n colourScale,\n colourScaleLabel,\n height = 280,\n className,\n}: LineChartProps) {\n const [ref, width] = useWidth<HTMLDivElement>();\n const gradientId = `heat-${React.useId().replace(/[^\\w-]/g, \"\")}`;\n const all = [...series, ...(planned ?? [])];\n const xMin = Math.min(...all.map((p) => p.x));\n const xMax = Math.max(...all.map((p) => p.x));\n const yMax = niceMax(Math.max(...all.map((p) => p.y)));\n const yTicks = [0, 0.25, 0.5, 0.75, 1].map((f) => f * yMax);\n\n const pad = { top: 16, right: 16, bottom: 28, left: 56 };\n const w = Math.max(0, width - pad.left - pad.right);\n const h = height - pad.top - pad.bottom;\n const sx = (x: number) => pad.left + ((x - xMin) / (xMax - xMin || 1)) * w;\n const sy = (y: number) => pad.top + h - (y / yMax) * h;\n const path = (pts: Point[]) => pts.map((p, i) => `${i ? \"L\" : \"M\"}${sx(p.x).toFixed(1)},${sy(p.y).toFixed(1)}`).join(\"\");\n\n const last = series[series.length - 1];\n const peak = series.reduce((a, b) => (b.y > a.y ? b : a), series[0]);\n const stops = colourScale ? [...colourScale].sort((a, b) => a.at - b.at) : [];\n /** The colour a value lands on: the highest stop at or below it. Used for the labelled points. */\n const colourAt = (y: number) => (stops.length ? [...stops].reverse().find((s) => s.at <= y)?.colour ?? stops[0].colour : colour);\n const stroke = stops.length ? `url(#${gradientId})` : colour;\n\n return (\n <figure className={cn(\"grid min-w-0 gap-2\", className)}>\n <div ref={ref} className=\"min-w-0\">\n {width > 0 && (\n <svg width={width} height={height} role=\"img\" aria-label={label} className=\"block overflow-visible\">\n {stops.length > 0 && (\n // A vertical gradient in chart space, so a colour sits at a temperature, not at a\n // point along the line: the line is the same red wherever it crosses 600 °C.\n <defs>\n <linearGradient id={gradientId} gradientUnits=\"userSpaceOnUse\" x1={0} x2={0} y1={sy(0)} y2={sy(yMax)}>\n {stops.map((s) => (\n <stop key={s.at} offset={Math.min(1, Math.max(0, s.at / yMax))} stopColor={s.colour} />\n ))}\n </linearGradient>\n </defs>\n )}\n {band && (\n <g>\n <rect x={pad.left} y={sy(band.below)} width={w} height={sy(0) - sy(band.below)} fill=\"var(--success-subtle)\" />\n <text x={pad.left + 8} y={sy(band.below) + 16} className=\"fill-success type-caption\">\n {band.label}\n </text>\n </g>\n )}\n {yTicks.map((t) => (\n <g key={t}>\n <line x1={pad.left} x2={pad.left + w} y1={sy(t)} y2={sy(t)} stroke=\"var(--border)\" strokeWidth={1} />\n <text x={pad.left - 8} y={sy(t)} dy=\"0.32em\" textAnchor=\"end\" className=\"fill-muted-foreground type-caption tabular-nums\">\n {formatY(t)}\n </text>\n </g>\n ))}\n {xTicks.map((t) => (\n <text key={t} x={sx(t)} y={height - 8} textAnchor=\"middle\" className=\"fill-muted-foreground type-caption tabular-nums\">\n {formatX(t)}\n </text>\n ))}\n {planned && (\n <path d={path(planned)} fill=\"none\" stroke=\"var(--muted-foreground)\" strokeWidth={1.5} strokeDasharray=\"4 4\" />\n )}\n <path d={path(series)} fill=\"none\" stroke={stroke} strokeWidth={stops.length ? 2.5 : 2} strokeLinejoin=\"round\" strokeLinecap=\"round\" />\n {/* Direct labels instead of a legend and a tooltip. */}\n <circle cx={sx(peak.x)} cy={sy(peak.y)} r={3} fill={colourAt(peak.y)} />\n <text x={sx(peak.x) + 8} y={sy(peak.y) - 8} className=\"fill-foreground type-caption tabular-nums\">\n {formatY(peak.y)} peak\n </text>\n <line x1={sx(last.x)} x2={sx(last.x)} y1={pad.top} y2={pad.top + h} stroke=\"var(--foreground)\" strokeWidth={1} />\n <circle cx={sx(last.x)} cy={sy(last.y)} r={3.5} fill={colourAt(last.y)} />\n <text\n x={sx(last.x) - 8}\n y={sy(last.y) - 10}\n textAnchor=\"end\"\n className=\"fill-foreground type-label tabular-nums\"\n >\n {formatY(last.y)} now\n </text>\n </svg>\n )}\n </div>\n <figcaption className=\"flex flex-wrap gap-x-5 gap-y-1 type-caption text-muted-foreground\">\n <span className=\"inline-flex items-center gap-2\">\n {stops.length ? (\n <svg width={stops.length * 8} height=\"3\" aria-hidden>\n {stops.map((s, i) => (\n <line key={s.at} x1={i * 8} x2={i * 8 + 8} y1=\"1.5\" y2=\"1.5\" stroke={s.colour} strokeWidth={3} />\n ))}\n </svg>\n ) : (\n <svg width=\"16\" height=\"2\" aria-hidden>\n <line x1=\"0\" x2=\"16\" y1=\"1\" y2=\"1\" stroke={colour} strokeWidth={2} />\n </svg>\n )}\n {seriesLabel}\n {stops.length > 0 && colourScaleLabel && <span>({colourScaleLabel})</span>}\n </span>\n {planned && plannedLabel && (\n <span className=\"inline-flex items-center gap-2\">\n <svg width=\"16\" height=\"2\" aria-hidden>\n <line x1=\"0\" x2=\"16\" y1=\"1\" y2=\"1\" stroke=\"var(--muted-foreground)\" strokeWidth={1.5} strokeDasharray=\"4 4\" />\n </svg>\n {plannedLabel}\n </span>\n )}\n </figcaption>\n {/* sr-only on a wrapper: a table ignores width: 1px and would widen the page. */}\n <div className=\"sr-only\">\n <table>\n <caption>{label}</caption>\n <thead>\n <tr>\n <th scope=\"col\">Time</th>\n <th scope=\"col\">{seriesLabel}</th>\n </tr>\n </thead>\n <tbody>\n {series.map((p) => (\n <tr key={p.x}>\n <td>{formatX(p.x)}</td>\n <td>{formatY(p.y)}</td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n </figure>\n );\n}\n","import * as React from \"react\";\n\n// The top of every Operate screen: a noun title at 24px (never \"Welcome back\"), its scope, the\n// screen's main action, and the finding. The finding is the one fact in today's data that most\n// needs attention; it sits where it is read first, beside the action it drives.\n\nexport function PageHeader({\n title,\n scope,\n actions,\n finding,\n as: Heading = \"h1\",\n}: {\n title: string;\n /** The title's element: h1 on a screen, h2 where the header is shown inside another page. */\n as?: \"h1\" | \"h2\";\n /** What the screen covers: a date, a range, a filter. */\n scope?: React.ReactNode;\n actions?: React.ReactNode;\n finding?: { text: React.ReactNode; action?: React.ReactNode };\n}) {\n return (\n <header className=\"grid gap-4\">\n <div className=\"flex flex-wrap items-end justify-between gap-x-6 gap-y-3\">\n <div className=\"grid min-w-0 gap-0.5\">\n <Heading className=\"type-title text-balance\">{title}</Heading>\n {scope && <p className=\"type-body-sm text-muted-foreground\">{scope}</p>}\n </div>\n {actions && <div className=\"flex flex-wrap gap-2\">{actions}</div>}\n </div>\n {finding && (\n <div className=\"flex flex-wrap items-center justify-between gap-x-6 gap-y-3 border-y border-border py-3\">\n <p className=\"max-w-prose min-w-0 type-body text-pretty\">{finding.text}</p>\n {finding.action}\n </div>\n )}\n </header>\n );\n}\n","import * as React from \"react\";\nimport { Button } from \"./button\";\n\n// Destructive actions take effect visibly at once and commit after a window, with Undo in reach.\n// For reversible removals this beats a confirm dialog: the dialog interrupts every time to protect\n// against the rare mistake, and people learn to click through it. Keep a confirm dialog for\n// actions that cannot be undone (deleting an account, firing a kiln).\n\ntype Pending = { id: string; message: string; commit: () => void };\n\nexport function useUndoable(windowMs = 8000) {\n const [pending, setPending] = React.useState<Pending | null>(null);\n const current = React.useRef<Pending | null>(null);\n const timer = React.useRef<number | undefined>(undefined);\n\n const set = React.useCallback((p: Pending | null) => {\n current.current = p;\n setPending(p);\n }, []);\n\n const schedule = React.useCallback(\n (next: Pending) => {\n window.clearTimeout(timer.current);\n current.current?.commit(); // a second removal commits the first\n set(next);\n timer.current = window.setTimeout(() => {\n current.current?.commit();\n set(null);\n }, windowMs);\n },\n [set, windowMs],\n );\n\n const undo = React.useCallback(() => {\n window.clearTimeout(timer.current);\n set(null);\n }, [set]);\n\n // Leaving the screen is not Undo: if the owner unmounts inside the window (the person navigated\n // away), commit what was scheduled rather than silently dropping it.\n React.useEffect(\n () => () => {\n window.clearTimeout(timer.current);\n current.current?.commit();\n current.current = null;\n },\n [],\n );\n return { pending, schedule, undo };\n}\n\n// The bar lives in a polite live region that is always mounted, so screen readers hear the message\n// when it appears. It is fixed to the bottom of the viewport; a page that uses it keeps that much\n// bottom padding so the bar never covers the focused row.\nexport function UndoBar({\n pending,\n onUndo,\n inline = false,\n}: {\n pending: Pending | null;\n onUndo: () => void;\n /** Render in the flow instead of fixed to the viewport; for the state-matrix page. */\n inline?: boolean;\n}) {\n return (\n <div\n aria-live=\"polite\"\n className={\n inline ? \"flex\" : \"pointer-events-none fixed inset-x-0 bottom-4 z-50 flex justify-center px-4\"\n }\n >\n {pending && (\n <div className=\"pointer-events-auto flex max-w-lg items-center gap-4 rounded-overlay bg-foreground py-2 pr-2 pl-4 text-background shadow-overlay\">\n <p className=\"min-w-0 type-body-sm\">{pending.message}</p>\n <Button size=\"sm\" variant=\"ghost\" className=\"text-background hover:bg-neutral-11\" onClick={onUndo}>\n Undo\n </Button>\n </div>\n )}\n </div>\n );\n}\n","import * as React from \"react\";\nimport { Check } from \"lucide-react\";\nimport { cn } from \"../lib/utils\";\n\n// The completion moment: what a person sees after finishing something that took effort (a\n// booking, a submission, an import). It is the one place on an Operate product where the library\n// spends its delight budget (character.md §3), so it is a component with rules rather than a\n// confetti call:\n// - `title` says what happened, in the past tense, with the object: \"Booked: 6 pieces in kiln 2\".\n// - `next` is required. A finish that does not say what happens now is a dead end, however\n// pleasant.\n// - `figure` is optional and must be drawn from the person's own data in the subject's terms\n// (their pieces on a shelf, their route on a map), never a stock illustration.\n// - The part of the figure that is the person's result enters once, with the `enter-moment`\n// utility (500ms, hard ease-out, no overshoot): their pieces set down on the shelf. Reduced\n// motion makes it instant. Nothing loops and nothing plays a sound.\n// - Focus moves to the heading on mount, so keyboard and screen-reader users land on the result.\n\nexport function Done({\n title,\n next,\n figure,\n actions,\n focusOnMount = true,\n className,\n}: {\n /** What happened, past tense, with its object. */\n title: string;\n /** What the person does now, or what happens next and when. */\n next: React.ReactNode;\n /** A drawing of the result made from the person's data in the subject's own terms. */\n figure?: React.ReactNode;\n actions?: React.ReactNode;\n /** Off only where the result is not the page's subject (the state-matrix page). */\n focusOnMount?: boolean;\n className?: string;\n}) {\n const heading = React.useRef<HTMLHeadingElement>(null);\n const titleId = React.useId();\n React.useEffect(() => {\n if (focusOnMount) heading.current?.focus();\n }, [focusOnMount]);\n return (\n <section aria-labelledby={titleId} className={cn(\"grid gap-5\", className)}>\n <div className=\"grid gap-2\">\n <h1 id={titleId} ref={heading} tabIndex={-1} className=\"flex items-start gap-2 type-title\">\n <Check aria-hidden className=\"mt-1 size-6 shrink-0 text-success\" />\n {title}\n </h1>\n <div className=\"max-w-prose type-body text-muted-foreground\">{next}</div>\n </div>\n {figure}\n {actions && <div className=\"flex flex-wrap items-center gap-3\">{actions}</div>}\n </section>\n );\n}\n","import * as React from \"react\";\nimport { Link, NavLink } from \"react-router\";\nimport { cn } from \"../lib/utils\";\nimport type { IconComponent } from \"../lib/icon\";\n\n// The frame of an app: a side nav on the page ground (not a navy or tinted sidebar), the current\n// item as a neutral pill (no left bar, no accent), and the page beside it. A top bar exists only\n// when it holds something used on every screen, so it is a slot, not a default. Built on React\n// Router's NavLink, so \"current\" is the router's answer and aria-current is set for free. On\n// phones the nav becomes a horizontal, scrollable row above the page rather than a hamburger, so\n// every destination stays one tap away.\n\nexport type NavItem = {\n to: string;\n label: string;\n icon?: IconComponent;\n /** Match only this exact path, for an index route such as \"/\". */\n end?: boolean;\n};\n\nexport function AppShell({\n product,\n nav,\n navLabel = \"Main\",\n topBar,\n children,\n className,\n}: {\n /** The product's name or mark, linking home. */\n product: React.ReactNode;\n nav: NavItem[];\n navLabel?: string;\n topBar?: React.ReactNode;\n children: React.ReactNode;\n className?: string;\n}) {\n return (\n <div className={cn(\"min-h-dvh md:grid md:grid-cols-[14rem_minmax(0,1fr)]\", className)}>\n <aside className=\"border-b border-border md:border-r md:border-b-0\">\n <div className=\"flex items-center gap-4 px-4 py-3 md:block md:px-3 md:py-4\">\n <Link to=\"/\" className=\"shrink-0 rounded-control px-2 type-heading text-foreground no-underline\">\n {product}\n </Link>\n <SideNav items={nav} label={navLabel} className=\"min-w-0 flex-1 md:mt-4\" />\n </div>\n </aside>\n <div className=\"min-w-0\">\n {topBar && <div className=\"flex items-center gap-3 border-b border-border px-4 py-2 md:px-8\">{topBar}</div>}\n <main className=\"px-4 py-6 md:px-8 md:py-8\">{children}</main>\n </div>\n </div>\n );\n}\n\nexport function SideNav({ items, label, className }: { items: NavItem[]; label: string; className?: string }) {\n return (\n <nav aria-label={label} className={cn(\"min-w-0\", className)}>\n <ul className=\"flex gap-1 overflow-x-auto md:grid md:overflow-visible\">\n {items.map(({ to, label, icon: Icon, end }) => (\n <li key={to} className=\"shrink-0\">\n <NavLink\n to={to}\n end={end}\n className={({ isActive }) =>\n cn(\n \"flex h-(--control-h-sm) items-center gap-2 rounded-control px-2 type-label whitespace-nowrap no-underline transition-state\",\n isActive ? \"bg-muted text-foreground\" : \"text-muted-foreground hover:bg-muted hover:text-foreground\",\n )\n }\n >\n {Icon && <Icon aria-hidden className=\"size-4 shrink-0\" />}\n {label}\n </NavLink>\n </li>\n ))}\n </ul>\n </nav>\n );\n}\n\n// Plain links with a \"/\" separator in muted ink. The last crumb is the current page: text, not a\n// link, with aria-current.\nexport function Breadcrumb({ items }: { items: { label: string; to?: string }[] }) {\n return (\n <nav aria-label=\"Breadcrumb\">\n <ol className=\"flex flex-wrap items-center gap-x-2 type-body-sm text-muted-foreground\">\n {items.map((item, i) => {\n const last = i === items.length - 1;\n return (\n <li key={`${item.label}-${i}`} className=\"flex items-center gap-2\">\n {item.to && !last ? (\n <Link to={item.to} className=\"text-muted-foreground underline hover:text-foreground\">\n {item.label}\n </Link>\n ) : (\n <span aria-current={last ? \"page\" : undefined} className={cn(last && \"text-foreground\")}>\n {item.label}\n </span>\n )}\n {!last && <span aria-hidden>/</span>}\n </li>\n );\n })}\n </ol>\n </nav>\n );\n}\n","import * as React from \"react\";\nimport { useSearchParams } from \"react-router\";\nimport { cn } from \"../lib/utils\";\n\n// Tabs whose state lives in the URL (?view=readings), so a tab can be linked, survives reload and\n// works with Back. ARIA tabs pattern: arrow keys move between tabs and select them, Home and End\n// jump, only the selected tab is in the tab order. The selected tab is marked by weight and a\n// 2px rule in ink, not by colour alone. The first tab is the default and is not written to the URL.\n\nexport type TabItem = { value: string; label: string };\n\nexport function Tabs({\n param,\n items,\n label,\n children,\n className,\n}: {\n /** The search parameter that holds the selected tab. */\n param: string;\n items: TabItem[];\n /** Names the tab list for screen readers. */\n label: string;\n /** Renders the selected panel. */\n children: (value: string) => React.ReactNode;\n className?: string;\n}) {\n const [params, setParams] = useSearchParams();\n const fallback = items[0]?.value ?? \"\";\n const raw = params.get(param);\n const value = items.some((i) => i.value === raw) ? raw! : fallback;\n const baseId = React.useId();\n const refs = React.useRef<(HTMLButtonElement | null)[]>([]);\n\n const select = (next: string, focus = false) => {\n setParams(\n (p) => {\n const out = new URLSearchParams(p);\n if (next === fallback) out.delete(param);\n else out.set(param, next);\n return out;\n },\n { replace: true },\n );\n if (focus) refs.current[items.findIndex((i) => i.value === next)]?.focus();\n };\n\n const onKeyDown = (e: React.KeyboardEvent, index: number) => {\n const last = items.length - 1;\n const to =\n e.key === \"ArrowRight\" ? (index === last ? 0 : index + 1)\n : e.key === \"ArrowLeft\" ? (index === 0 ? last : index - 1)\n : e.key === \"Home\" ? 0\n : e.key === \"End\" ? last\n : undefined;\n if (to === undefined) return;\n e.preventDefault();\n select(items[to].value, true);\n };\n\n return (\n <div className={cn(\"grid gap-4\", className)}>\n <div role=\"tablist\" aria-label={label} className=\"flex gap-4 overflow-x-auto border-b border-border\">\n {items.map((item, i) => {\n const selected = item.value === value;\n return (\n <button\n key={item.value}\n ref={(el) => {\n refs.current[i] = el;\n }}\n type=\"button\"\n role=\"tab\"\n id={`${baseId}-tab-${item.value}`}\n aria-selected={selected}\n aria-controls={`${baseId}-panel`}\n tabIndex={selected ? 0 : -1}\n onClick={() => select(item.value)}\n onKeyDown={(e) => onKeyDown(e, i)}\n className={cn(\n \"-mb-px cursor-pointer border-b-2 py-2 type-label whitespace-nowrap transition-state\",\n selected\n ? \"border-foreground font-semibold text-foreground\"\n : \"border-transparent text-muted-foreground hover:text-foreground\",\n )}\n >\n {item.label}\n </button>\n );\n })}\n </div>\n <div role=\"tabpanel\" id={`${baseId}-panel`} aria-labelledby={`${baseId}-tab-${value}`} tabIndex={0}>\n {children(value)}\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":["_Fragment"],"sources":["../src/lib/utils.ts","../src/lib/format.ts","../src/components/button.tsx","../src/components/async-button.tsx","../src/components/field.tsx","../src/components/status.tsx","../src/components/metric.tsx","../src/components/data-view.tsx","../src/components/data-table.tsx","../src/components/virtual-list.tsx","../src/components/line-chart.tsx","../src/components/page-header.tsx","../src/components/undo.tsx","../src/components/done.tsx","../src/components/app-shell.tsx","../src/components/tabs.tsx"],"sourcesContent":["import { clsx, type ClassValue } from \"clsx\";\nimport { extendTailwindMerge } from \"tailwind-merge\";\n\n// Teach tailwind-merge that `type-*` utilities are font sizes. Without this, cn() cannot tell\n// `type-label` from a colour and may drop one of two classes that both survive in CSS; with stock\n// shadcn the same bug drops `text-muted-foreground` next to a custom `text-*` size.\nexport const typeSteps = [\"caption\", \"label\", \"body-sm\", \"body\", \"heading\", \"title\", \"figure\", \"display\"] as const;\n\nconst twMerge = extendTailwindMerge({\n extend: {\n classGroups: {\n \"font-size\": [{ type: [...typeSteps] }],\n },\n },\n});\n\n/** clsx + tailwind-merge, taught that `type-*` utilities are font sizes. */\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs));\n}\n","// Every number and date on screen goes through Intl, once, here. Units are joined with a\n// non-breaking space so \"212 °C\" never wraps between figure and unit. A product that is not\n// en-GB calls createFormat with its locale once and exports the result from its own lib.\n\nexport const NBSP = \" \";\n\n/** Intl formatters for one locale: integers, decimals, percent, times, days, units with a non-breaking space, and counted nouns. */\nexport function createFormat(locale: string) {\n const integer = new Intl.NumberFormat(locale, { maximumFractionDigits: 0 });\n const decimal = new Intl.NumberFormat(locale, { maximumFractionDigits: 1 });\n const percent = new Intl.NumberFormat(locale, { style: \"percent\", maximumFractionDigits: 0 });\n const clock = new Intl.DateTimeFormat(locale, { hour: \"2-digit\", minute: \"2-digit\" });\n const day = new Intl.DateTimeFormat(locale, { weekday: \"long\", day: \"numeric\", month: \"long\" });\n const shortDay = new Intl.DateTimeFormat(locale, { weekday: \"short\", hour: \"2-digit\", minute: \"2-digit\" });\n const withUnit = (value: number, unit: string, fmt: Intl.NumberFormat = integer) =>\n `${fmt.format(value)}${NBSP}${unit}`;\n return {\n locale,\n integer,\n decimal,\n percent,\n clock,\n day,\n shortDay,\n withUnit,\n celsius: (value: number) => withUnit(value, \"°C\"),\n /** \"1 piece\", \"6 pieces\": the count and its noun, so copy never reads \"1 pieces\". */\n count: (value: number, one: string, many = `${one}s`) =>\n `${integer.format(value)}${NBSP}${new Intl.PluralRules(locale).select(value) === \"one\" ? one : many}`,\n };\n}\n\nexport type Format = ReturnType<typeof createFormat>;\n\n/** The en-GB formatters. */\nexport const format = createFormat(\"en-GB\");\n","import * as React from \"react\";\nimport { Slot } from \"@radix-ui/react-slot\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport type { IconComponent } from \"../lib/icon\";\nimport { cn } from \"../lib/utils\";\n\n// Variant names are shadcn's, so copied code and agents' prior knowledge still work. Two things\n// differ on purpose:\n// 1. The default variant is `outline`, not the filled accent. A bare <Button> is the restrained\n// one; the accent (`variant=\"default\"`) has to be asked for, once per view. Generated app screens\n// put the accent on every button; 12 of 23 real product screens had no filled primary at all.\n// 2. Heights come from --control-h, shared with Input and NativeSelect, so density changes move\n// every control together.\n// 3. `press`: the button gives 2% under a click or a finger, over 100ms. Buttons are used tens of\n// times a day, so that is all the motion they get (character.md, the frequency budget).\nexport const buttonVariants = cva(\n \"inline-flex shrink-0 cursor-pointer items-center justify-center gap-2 whitespace-nowrap rounded-control type-label transition-state press disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0\",\n {\n variants: {\n variant: {\n default: \"bg-primary text-primary-foreground hover:bg-primary-10\",\n outline: \"border border-input bg-background text-foreground hover:bg-muted\",\n secondary: \"bg-muted text-foreground hover:bg-neutral-4\",\n ghost: \"text-foreground hover:bg-muted\",\n destructive: \"bg-destructive text-destructive-foreground hover:bg-danger\",\n link: \"h-auto px-0 text-primary-11 underline underline-offset-4 hover:text-primary-12\",\n },\n size: {\n default: \"h-(--control-h) px-4\",\n sm: \"h-(--control-h-sm) px-3\",\n icon: \"size-(--control-h)\",\n },\n },\n defaultVariants: { variant: \"outline\", size: \"default\" },\n },\n);\n\nexport type ButtonProps = React.ComponentProps<\"button\"> &\n VariantProps<typeof buttonVariants> & { asChild?: boolean };\n\n/** A button with shadcn's variant names. The default variant is `outline`; ask for the filled accent (`variant=\"default\"`) once per view. `type` defaults to `button`. */\nexport function Button({ className, variant, size, asChild = false, type = \"button\", ...props }: ButtonProps) {\n const Comp = asChild ? Slot : \"button\";\n return <Comp type={asChild ? undefined : type} className={cn(buttonVariants({ variant, size }), className)} {...props} />;\n}\n\n// An icon-only button cannot be written without a name: `label` is required by the type, becomes\n// the accessible name, and doubles as the native tooltip. This makes the most common generated\n// accessibility defect (an unlabelled icon button) a compile error instead of an audit finding.\nexport type IconButtonProps = Omit<ButtonProps, \"size\" | \"children\" | \"aria-label\"> & {\n icon: IconComponent;\n label: string;\n};\n\n/** An icon-only button. `label` is required and becomes the accessible name and tooltip. */\nexport function IconButton({ icon: Icon, label, variant = \"ghost\", ...props }: IconButtonProps) {\n return (\n <Button size=\"icon\" variant={variant} aria-label={label} title={label} {...props}>\n <Icon aria-hidden />\n </Button>\n );\n}\n","import * as React from \"react\";\nimport { Check, CircleAlert, LoaderCircle } from \"lucide-react\";\nimport { Button, type ButtonProps } from \"./button\";\n\n// One object through its states: \"Book firing\" becomes \"Booking…\" becomes \"Booked\", in the same\n// button, rather than the button being swapped for a spinner. The labels are stacked in one grid\n// cell so the button is always as wide as its longest label and nothing around it shifts. The\n// button stays enabled until the request starts (so a person can always try), and the outcome is\n// announced politely for screen readers.\n\nexport type Phase = \"idle\" | \"pending\" | \"done\" | \"error\";\n\nexport type AsyncLabels = {\n idle: string;\n /** Ends in an ellipsis: \"Saving…\", \"Booking…\". */\n pending: string;\n /** The same verb, finished: \"Saved\", \"Booked\". */\n done: string;\n error?: string;\n};\n\n/** One button through idle, pending, done and error, with the label changing in place and its width fixed by the longest label. The outcome is announced politely. */\nexport function AsyncButton({\n phase,\n labels,\n disabled,\n ...props\n}: Omit<ButtonProps, \"children\"> & { phase: Phase; labels: AsyncLabels }) {\n const errorLabel = labels.error ?? \"Try again\";\n const current = { idle: labels.idle, pending: labels.pending, done: labels.done, error: errorLabel }[phase];\n const Icon = { idle: null, pending: LoaderCircle, done: Check, error: CircleAlert }[phase];\n\n return (\n <>\n <Button {...props} disabled={disabled || phase === \"pending\"} aria-disabled={phase === \"pending\" || undefined}>\n {Icon && <Icon aria-hidden className={phase === \"pending\" ? \"motion-safe:animate-spin\" : undefined} />}\n <span className=\"grid\">\n {[labels.idle, labels.pending, labels.done, errorLabel].map((l) => (\n <span key={l} aria-hidden={l !== current} className={l === current ? \"[grid-area:1/1]\" : \"invisible [grid-area:1/1]\"}>\n {l}\n </span>\n ))}\n </span>\n </Button>\n <span role=\"status\" className=\"sr-only\">\n {phase === \"done\" ? labels.done : phase === \"error\" ? errorLabel : \"\"}\n </span>\n </>\n );\n}\n\n/** Runs an async action and reports its phase. `done` returns to `idle` after `settleMs`. */\nexport function useAsyncAction<A extends unknown[]>(action: (...args: A) => Promise<void>, settleMs = 2000) {\n const [phase, setPhase] = React.useState<Phase>(\"idle\");\n const timer = React.useRef<number | undefined>(undefined);\n React.useEffect(() => () => window.clearTimeout(timer.current), []);\n const run = React.useCallback(\n async (...args: A) => {\n window.clearTimeout(timer.current);\n setPhase(\"pending\");\n try {\n await action(...args);\n setPhase(\"done\");\n timer.current = window.setTimeout(() => setPhase(\"idle\"), settleMs);\n } catch {\n setPhase(\"error\");\n }\n },\n [action, settleMs],\n );\n return [phase, run] as const;\n}\n","import * as React from \"react\";\nimport { CircleAlert } from \"lucide-react\";\nimport { cn } from \"../lib/utils\";\n\n// Field owns the wiring a generated form usually forgets: the label's htmlFor, aria-describedby\n// pointing at the hint and the error, and aria-invalid. Controls read it from context, so a control\n// inside a Field cannot end up unlabelled or silently invalid.\n\ntype FieldState = { id: string; describedBy?: string; invalid: boolean };\nconst FieldContext = React.createContext<FieldState | null>(null);\n\nfunction useField(id?: string) {\n const field = React.useContext(FieldContext);\n return {\n // Inside a Field the Field's id wins, so the label always points at the control. To choose the\n // id yourself, pass it to Field.\n id: field?.id ?? id,\n \"aria-describedby\": field?.describedBy,\n \"aria-invalid\": field?.invalid || undefined,\n };\n}\n\nexport type FieldProps = {\n label: string;\n /** The control's id, when the page needs a known one (an anchor, a test). Generated otherwise. */\n id?: string;\n /** What the person needs to know before typing, such as limits or units. */\n hint?: React.ReactNode;\n /** What went wrong and how to fix it, in one sentence. Shown under the control, never in a toast. */\n error?: string;\n children: React.ReactNode;\n className?: string;\n};\n\n/** A labelled form field: wires the label, hint, error, `aria-describedby` and `aria-invalid` into the one control it wraps. */\nexport function Field({ label, id: idProp, hint, error, children, className }: FieldProps) {\n const generated = React.useId();\n const id = idProp ?? generated;\n const hintId = hint ? `${id}-hint` : undefined;\n const errorId = error ? `${id}-error` : undefined;\n const describedBy = [errorId, hintId].filter(Boolean).join(\" \") || undefined;\n\n return (\n <FieldContext.Provider value={{ id, describedBy, invalid: Boolean(error) }}>\n <div className={cn(\"grid min-w-0 content-start gap-1.5\", className)}>\n <label htmlFor={id} className=\"type-label text-foreground\">\n {label}\n </label>\n {children}\n {error && (\n <p id={errorId} className=\"flex items-start gap-1.5 type-body-sm text-danger\">\n <CircleAlert aria-hidden className=\"mt-0.5 size-4 shrink-0\" />\n {error}\n </p>\n )}\n {hint && (\n <p id={hintId} className=\"type-body-sm text-muted-foreground\">\n {hint}\n </p>\n )}\n </div>\n </FieldContext.Provider>\n );\n}\n\nconst control =\n \"w-full min-w-0 rounded-control border border-input bg-background px-3 type-body text-foreground transition-state placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:bg-muted disabled:opacity-70 aria-invalid:border-danger\";\n\n/** A text input that picks up its id and descriptions from the surrounding `Field`. */\nexport function Input({ className, id, ...props }: React.ComponentProps<\"input\">) {\n return <input {...useField(id)} className={cn(control, \"h-(--control-h)\", className)} {...props} />;\n}\n\n/** A multi-line input that picks up its id and descriptions from the surrounding `Field`. */\nexport function Textarea({ className, id, ...props }: React.ComponentProps<\"textarea\">) {\n return <textarea {...useField(id)} className={cn(control, \"min-h-24 py-2\", className)} {...props} />;\n}\n\n// A native select, styled. For short option lists on a touch screen it beats a custom listbox:\n// the platform picker, typeahead and form autofill all work with no code.\n/** The platform select, styled. The default for short lists, especially on touch screens. */\nexport function NativeSelect({ className, id, ...props }: React.ComponentProps<\"select\">) {\n return <select {...useField(id)} className={cn(control, \"h-(--control-h) pr-8\", className)} {...props} />;\n}\n\n// Radio choices: a fieldset with a legend, and each label wrapping its control so the whole row is\n// one hit target. accent-color carries the theme into the native control.\nexport type Choice = { value: string; label: string; detail?: string };\n\n/** Radio choices in a fieldset with a legend; each whole row is the hit target. For 2 to 5 options that each need a sentence. */\nexport function ChoiceGroup({\n legend,\n name,\n choices,\n value,\n onChange,\n error,\n}: {\n legend: string;\n name: string;\n choices: Choice[];\n value: string;\n onChange: (value: string) => void;\n error?: string;\n}) {\n const errorId = React.useId();\n return (\n <fieldset className=\"grid gap-2\" aria-describedby={error ? errorId : undefined}>\n <legend className=\"mb-1.5 type-label text-foreground\">{legend}</legend>\n {choices.map((c) => (\n <label key={c.value} className=\"flex min-h-(--control-h-sm) cursor-pointer items-start gap-3 py-1\">\n <input\n type=\"radio\"\n name={name}\n value={c.value}\n checked={value === c.value}\n onChange={() => onChange(c.value)}\n className=\"mt-1 size-4 shrink-0 accent-primary\"\n />\n <span className=\"grid\">\n <span className=\"type-body\">{c.label}</span>\n {c.detail && <span className=\"type-body-sm text-muted-foreground\">{c.detail}</span>}\n </span>\n </label>\n ))}\n {error && (\n <p id={errorId} className=\"flex items-start gap-1.5 type-body-sm text-danger\">\n <CircleAlert aria-hidden className=\"mt-0.5 size-4 shrink-0\" />\n {error}\n </p>\n )}\n </fieldset>\n );\n}\n","import type { IconComponent } from \"../lib/icon\";\nimport { cn } from \"../lib/utils\";\n\n// Status is a word first. `children` is required and must be a string, so a status can never be a\n// bare coloured dot. The tint and ink come from the fixed status roles, which are kept apart from\n// the brand accent: pale tint, dark ink of the same hue, never a saturated chip with white text.\n// No pulsing variant exists; nothing on a status mark moves unless the data behind it is live.\n\nexport type Tone = \"neutral\" | \"info\" | \"success\" | \"warning\" | \"danger\";\n\nconst tones: Record<Tone, string> = {\n neutral: \"bg-muted text-muted-foreground\",\n info: \"bg-info-subtle text-info\",\n success: \"bg-success-subtle text-success\",\n warning: \"bg-warning-subtle text-warning\",\n danger: \"bg-danger-subtle text-danger\",\n};\n\n/** A status as a word on a pale tint of its tone, with an optional icon. Never colour alone. */\nexport function Status({\n tone,\n icon: Icon,\n children,\n className,\n}: {\n tone: Tone;\n icon?: IconComponent;\n children: string;\n className?: string;\n}) {\n return (\n <span\n className={cn(\n \"inline-flex items-center gap-1.5 whitespace-nowrap rounded-pill py-0.5 pr-2.5 pl-2 type-caption font-medium\",\n tones[tone],\n className,\n )}\n >\n {Icon && <Icon aria-hidden className=\"size-3.5 shrink-0\" />}\n {children}\n </span>\n );\n}\n","import * as React from \"react\";\nimport { cn } from \"../lib/utils\";\n\n// Headline figures as one container split by hairlines, label over value: what real product screens\n// do, instead of four cards each with an icon in a tinted circle. There is deliberately no `icon`,\n// `sparkline` or `trend` prop. A change is written in words with its window (\"4 more than last\n// Thursday\"), because a green \"+12.5%\" pill says neither what changed nor since when, and the trend\n// gets its own chart.\n\n/** Headline figures as one description list split by hairlines. Holds `Metric`s. */\nexport function MetricStrip({ label, children, className }: { label: string; children: React.ReactNode; className?: string }) {\n return (\n <dl\n aria-label={label}\n className={cn(\n \"grid grid-cols-1 border-y border-border sm:auto-cols-fr sm:grid-flow-col sm:divide-x sm:divide-border\",\n className,\n )}\n >\n {children}\n </dl>\n );\n}\n\nexport type MetricProps = {\n label: string;\n /** Already formatted. Ink colour, 28px, tabular figures: never the brand colour, never 48px. */\n value: string;\n unit?: string;\n /** One sentence: the change in words with its window, or what the figure means right now. */\n note?: React.ReactNode;\n};\n\n/** One figure: label over value, an optional muted unit and a note that states any change in words with its window. */\nexport function Metric({ label, value, unit, note }: MetricProps) {\n return (\n <div className=\"grid content-start gap-0.5 border-border py-3 not-first:border-t sm:px-5 sm:not-first:border-t-0 sm:first:pl-0\">\n <dt className=\"type-label text-muted-foreground\">{label}</dt>\n <dd className=\"type-figure text-foreground\">\n {value}\n {unit && <span className=\"type-body text-muted-foreground\">{\" \" + unit}</span>}\n </dd>\n {note && <dd className=\"type-body-sm text-muted-foreground\">{note}</dd>}\n </div>\n );\n}\n","import * as React from \"react\";\nimport { CircleAlert, RotateCcw } from \"lucide-react\";\nimport { Button } from \"./button\";\nimport { cn } from \"../lib/utils\";\n\n// Every view of remote data goes through DataView, and DataView will not type-check without a\n// skeleton and an empty state. Generated UI renders the happy path; this makes the other three\n// states part of the component's signature rather than a review comment.\n\nexport type Load<T> =\n | { status: \"loading\" }\n | { status: \"error\"; message: string; retry: () => void }\n | { status: \"ready\"; data: T };\n\n/** Every view of remote data: renders `skeleton` while loading, an alert with Try again on error, `empty` when `isEmpty`, else `children(data)`. The skeleton and empty state are required. */\nexport function DataView<T>({\n state,\n isEmpty,\n skeleton,\n empty,\n children,\n className,\n}: {\n state: Load<T>;\n /** When the loaded data counts as empty. For arrays, `(rows) => rows.length === 0`. */\n isEmpty: (data: T) => boolean;\n /** Mirrors the final layout, so nothing jumps when data arrives. Not a centred spinner. */\n skeleton: React.ReactNode;\n /** What goes here and how to add it. An `EmptyState`, not an illustration. */\n empty: React.ReactNode;\n children: (data: T) => React.ReactNode;\n className?: string;\n}) {\n return (\n <div className={cn(\"min-w-0\", className)} aria-busy={state.status === \"loading\" || undefined}>\n {state.status === \"loading\" && skeleton}\n {state.status === \"error\" && (\n <div role=\"alert\" className=\"flex flex-wrap items-start gap-3 rounded-surface bg-danger-subtle p-4 text-danger\">\n <CircleAlert aria-hidden className=\"mt-0.5 size-5 shrink-0\" />\n <p className=\"min-w-0 flex-1 type-body\">{state.message}</p>\n <Button size=\"sm\" onClick={state.retry}>\n <RotateCcw aria-hidden />\n Try again\n </Button>\n </div>\n )}\n {state.status === \"ready\" && (isEmpty(state.data) ? empty : children(state.data))}\n </div>\n );\n}\n\n// An empty state teaches the next action. No illustration slot exists, on purpose.\n/** An empty state that teaches the next action: a title, a sentence and an action. No illustration slot, on purpose. */\nexport function EmptyState({ title, children, action }: { title: string; children?: React.ReactNode; action?: React.ReactNode }) {\n return (\n <div className=\"grid justify-items-start gap-2 border-y border-border py-6\">\n <p className=\"type-heading\">{title}</p>\n {children && <p className=\"max-w-prose type-body text-muted-foreground\">{children}</p>}\n {action && <div className=\"mt-2\">{action}</div>}\n </div>\n );\n}\n\n/** A placeholder block for a loading layout. Size it with layout classes so it mirrors the final content. */\nexport function Skeleton({ className }: { className?: string }) {\n return <div aria-hidden className={cn(\"rounded-control bg-muted motion-safe:animate-pulse\", className)} />;\n}\n","import * as React from \"react\";\nimport { ArrowDown, ArrowUp, ArrowUpDown } from \"lucide-react\";\nimport {\n flexRender,\n getCoreRowModel,\n getSortedRowModel,\n useReactTable,\n type ColumnDef,\n type SortingState,\n} from \"@tanstack/react-table\";\nimport { useVirtualizer } from \"@tanstack/react-virtual\";\nimport { useSearchParams } from \"react-router\";\nimport { cn } from \"../lib/utils\";\n\n// The table real product screens use: 12px muted sentence-case headers, row rules (no zebra),\n// numbers right-aligned in tabular figures, one status mark and at most one action per row.\n// Sortable headers are buttons and look sortable; aria-sort says which way. The scroll container is\n// a labelled, focusable region so keyboard users can scroll a wide table.\n//\n// Underneath: TanStack Table for the row model and sorting, TanStack Virtual once a table passes\n// `virtualizeFrom` rows (only the visible rows are in the DOM; the header sticks). The column API\n// stays small on purpose (key, header, cell, numeric, sort), so a screen reads as a list of\n// decisions about columns, not as table plumbing. `sortParam` keeps the sort in the URL\n// (?sort=when.desc) so a sorted view can be linked and survives a reload.\n\nexport type Column<T> = {\n key: string;\n header: string;\n cell: (row: T) => React.ReactNode;\n /** Right-aligns and sets tabular figures. Use for every quantity. */\n numeric?: boolean;\n sort?: (a: T, b: T) => number;\n /** Cells that may hold long text get truncated rather than stretching the table. */\n truncate?: boolean;\n className?: string;\n};\n\nexport type Sort = { key: string; dir: \"ascending\" | \"descending\" };\n\nexport type DataTableProps<T> = {\n /** Names the table for screen readers and labels the scroll region. */\n caption: string;\n columns: Column<T>[];\n rows: T[];\n rowKey: (row: T) => string;\n initialSort?: Sort;\n /** Keep the sort in this URL search parameter. Needs a React Router context. */\n sortParam?: string;\n /** Row count from which only the visible rows render. Default 100. */\n virtualizeFrom?: number;\n /** Height of the scroll region once virtualised, in px. Default 480. */\n maxHeight?: number;\n className?: string;\n};\n\ntype SortState = [Sort | undefined, (next: Sort) => void];\n\nfunction useLocalSort(initial?: Sort): SortState {\n const [sort, setSort] = React.useState(initial);\n return [sort, setSort];\n}\n\n/** Reads a sort from its URL form (`pieces.desc`). */\nexport function parseSort(value: string | null): Sort | undefined {\n if (!value) return undefined;\n const [key, dir] = value.split(\".\");\n if (!key) return undefined;\n return { key, dir: dir === \"desc\" ? \"descending\" : \"ascending\" };\n}\n\n/** Writes a sort in its URL form (`pieces.desc`). */\nexport function serialiseSort(sort: Sort) {\n return `${sort.key}.${sort.dir === \"descending\" ? \"desc\" : \"asc\"}`;\n}\n\nfunction useUrlSort(param: string, initial?: Sort): SortState {\n const [params, setParams] = useSearchParams();\n const sort = parseSort(params.get(param)) ?? initial;\n const set = React.useCallback(\n (next: Sort) =>\n setParams(\n (p) => {\n const out = new URLSearchParams(p);\n out.set(param, serialiseSort(next));\n return out;\n },\n { replace: true },\n ),\n [param, setParams],\n );\n return [sort, set];\n}\n\n/** A table on TanStack Table: muted headers, row rules, right-aligned tabular numbers, sortable headers with `aria-sort`, virtualised from `virtualizeFrom` rows, sort optionally kept in the URL (`sortParam`). */\nexport function DataTable<T>(props: DataTableProps<T>) {\n // Two thin wrappers so each calls its hooks unconditionally.\n return props.sortParam ? <UrlSortedTable {...props} sortParam={props.sortParam} /> : <LocalSortedTable {...props} />;\n}\n\nfunction LocalSortedTable<T>(props: DataTableProps<T>) {\n return <Table {...props} sortState={useLocalSort(props.initialSort)} />;\n}\n\nfunction UrlSortedTable<T>(props: DataTableProps<T> & { sortParam: string }) {\n return <Table {...props} sortState={useUrlSort(props.sortParam, props.initialSort)} />;\n}\n\ntype Meta = { numeric?: boolean; truncate?: boolean; className?: string };\n\nfunction Table<T>({\n caption,\n columns,\n rows,\n rowKey,\n sortState: [sort, setSort],\n virtualizeFrom = 100,\n maxHeight = 480,\n className,\n}: DataTableProps<T> & { sortState: SortState }) {\n const defs = React.useMemo<ColumnDef<T>[]>(\n () =>\n columns.map((c) => ({\n id: c.key,\n // TanStack only sorts columns with an accessor; the row itself is enough, since the\n // column's own comparator does the work.\n accessorFn: (row) => row,\n header: c.header,\n cell: (info) => c.cell(info.row.original),\n enableSorting: Boolean(c.sort),\n sortingFn: c.sort ? (a, b) => c.sort!(a.original, b.original) : undefined,\n meta: { numeric: c.numeric, truncate: c.truncate, className: c.className } satisfies Meta,\n })),\n [columns],\n );\n\n const sorting: SortingState = sort && columns.some((c) => c.key === sort.key && c.sort)\n ? [{ id: sort.key, desc: sort.dir === \"descending\" }]\n : [];\n\n const table = useReactTable({\n data: rows,\n columns: defs,\n state: { sorting },\n getRowId: (row) => rowKey(row),\n getCoreRowModel: getCoreRowModel(),\n getSortedRowModel: getSortedRowModel(),\n });\n\n const toggle = (key: string) =>\n setSort({ key, dir: sort?.key === key && sort.dir === \"ascending\" ? \"descending\" : \"ascending\" });\n\n const modelRows = table.getRowModel().rows;\n const virtual = modelRows.length >= virtualizeFrom;\n const scroller = React.useRef<HTMLDivElement>(null);\n const virtualizer = useVirtualizer({\n count: virtual ? modelRows.length : 0,\n getScrollElement: () => scroller.current,\n estimateSize: () => 41,\n overscan: 8,\n // Until the region is measured, assume it is full height: the first paint (and a test DOM\n // with no layout) shows the first page of rows instead of none.\n initialRect: { width: 0, height: maxHeight },\n });\n const items = virtual ? virtualizer.getVirtualItems() : [];\n const padTop = items.length ? items[0].start : 0;\n const padBottom = items.length ? virtualizer.getTotalSize() - items[items.length - 1].end : 0;\n const visible = virtual ? items.map((i) => modelRows[i.index]) : modelRows;\n\n return (\n <div\n ref={scroller}\n role=\"region\"\n aria-label={caption}\n tabIndex={0}\n className={cn(\"min-w-0 overflow-x-auto\", virtual && \"overflow-y-auto\", className)}\n style={virtual ? { maxHeight } : undefined}\n >\n <table className=\"w-full border-collapse type-body-sm\" aria-rowcount={virtual ? modelRows.length + 1 : undefined}>\n <caption className=\"sr-only\">{caption}</caption>\n <thead className={cn(virtual && \"sticky top-0 z-10 bg-background\")}>\n {table.getHeaderGroups().map((group) => (\n <tr key={group.id}>\n {group.headers.map((header) => {\n const meta = header.column.columnDef.meta as Meta | undefined;\n const active = sort?.key === header.id && header.column.getCanSort() ? sort.dir : undefined;\n const Icon = active === \"ascending\" ? ArrowUp : active === \"descending\" ? ArrowDown : ArrowUpDown;\n const label = flexRender(header.column.columnDef.header, header.getContext());\n return (\n <th\n key={header.id}\n scope=\"col\"\n aria-sort={active}\n className={cn(\n \"py-2 pr-4 text-left align-bottom type-caption text-muted-foreground last:pr-0\",\n meta?.numeric && \"text-right\",\n )}\n >\n {header.column.getCanSort() ? (\n <button\n type=\"button\"\n onClick={() => toggle(header.id)}\n className={cn(\n \"-mx-1 inline-flex cursor-pointer items-center gap-1 rounded-control px-1 transition-state hover:text-foreground\",\n meta?.numeric && \"flex-row-reverse\",\n active && \"text-foreground\",\n )}\n >\n {label}\n <Icon aria-hidden className={cn(\"size-3.5\", !active && \"opacity-50\")} />\n </button>\n ) : (\n label\n )}\n </th>\n );\n })}\n </tr>\n ))}\n </thead>\n <tbody>\n {padTop > 0 && <tr aria-hidden style={{ height: padTop }} />}\n {visible.map((row, i) => (\n <tr\n key={row.id}\n className=\"border-t border-border\"\n aria-rowindex={virtual ? (items[i]?.index ?? 0) + 2 : undefined}\n >\n {row.getVisibleCells().map((cell) => {\n const meta = cell.column.columnDef.meta as Meta | undefined;\n return (\n <td\n key={cell.id}\n className={cn(\n \"py-2.5 pr-4 align-middle last:pr-0\",\n meta?.numeric && \"text-right tabular-nums\",\n meta?.truncate && \"max-w-48 truncate\",\n meta?.className,\n )}\n >\n {flexRender(cell.column.columnDef.cell, cell.getContext())}\n </td>\n );\n })}\n </tr>\n ))}\n {padBottom > 0 && <tr aria-hidden style={{ height: padBottom }} />}\n </tbody>\n </table>\n </div>\n );\n}\n\n/** The loading placeholder for a `DataTable`, shaped like its rows. */\nexport function TableSkeleton({ rows = 4, columns = 5 }: { rows?: number; columns?: number }) {\n return (\n <div aria-hidden className=\"grid gap-0\">\n <div className=\"flex gap-4 py-2\">\n {Array.from({ length: columns }, (_, i) => (\n <div key={i} className=\"h-3 flex-1 rounded-control bg-muted\" />\n ))}\n </div>\n {Array.from({ length: rows }, (_, r) => (\n <div key={r} className=\"flex gap-4 border-t border-border py-3.5\">\n {Array.from({ length: columns }, (_, i) => (\n <div key={i} className=\"h-4 flex-1 rounded-control bg-muted motion-safe:animate-pulse\" />\n ))}\n </div>\n ))}\n </div>\n );\n}\n","import * as React from \"react\";\nimport { useVirtualizer } from \"@tanstack/react-virtual\";\nimport { cn } from \"../lib/utils\";\n\n// A long list that keeps only its visible rows in the DOM (TanStack Virtual), for feeds, logs and\n// pickers with hundreds of items. Rows are measured after render, so variable heights work; the\n// estimate only sets the first paint. The list is a labelled, focusable region so it can be\n// scrolled from the keyboard, and keeps list semantics with aria-setsize/aria-posinset so screen\n// readers still hear \"12 of 480\". Under `virtualizeFrom` items it renders a plain list.\n\n/** A list that renders only its visible rows (TanStack Virtual) from `virtualizeFrom` items, keeping list semantics for screen readers. */\nexport function VirtualList<T>({\n label,\n items,\n itemKey,\n children,\n estimateSize = 48,\n height = 480,\n virtualizeFrom = 100,\n className,\n}: {\n label: string;\n items: T[];\n itemKey: (item: T) => string;\n children: (item: T, index: number) => React.ReactNode;\n estimateSize?: number;\n height?: number;\n virtualizeFrom?: number;\n className?: string;\n}) {\n const scroller = React.useRef<HTMLDivElement>(null);\n const virtual = items.length >= virtualizeFrom;\n const virtualizer = useVirtualizer({\n count: virtual ? items.length : 0,\n getScrollElement: () => scroller.current,\n estimateSize: () => estimateSize,\n overscan: 6,\n initialRect: { width: 0, height },\n });\n\n if (!virtual) {\n return (\n <ul aria-label={label} className={cn(\"grid min-w-0\", className)}>\n {items.map((item, i) => (\n <li key={itemKey(item)} className=\"border-t border-border first:border-t-0\">\n {children(item, i)}\n </li>\n ))}\n </ul>\n );\n }\n\n return (\n <div\n ref={scroller}\n role=\"region\"\n aria-label={label}\n tabIndex={0}\n className={cn(\"min-w-0 overflow-y-auto\", className)}\n style={{ height }}\n >\n <ul aria-label={label} className=\"relative w-full\" style={{ height: virtualizer.getTotalSize() }}>\n {virtualizer.getVirtualItems().map((v) => {\n const item = items[v.index];\n return (\n <li\n key={itemKey(item)}\n ref={virtualizer.measureElement}\n data-index={v.index}\n aria-setsize={items.length}\n aria-posinset={v.index + 1}\n className={cn(\"absolute inset-x-0 top-0\", v.index > 0 && \"border-t border-border\")}\n style={{ translate: `0 ${v.start}px` }}\n >\n {children(item, v.index)}\n </li>\n );\n })}\n </ul>\n </div>\n );\n}\n","import * as React from \"react\";\nimport { cn } from \"../lib/utils\";\n\n// A line chart with the defaults real product screens use: one measured series in one hue, a 2px\n// straight line with no fill and no markers, 3 to 5 faint gridlines, no y-axis line, no legend for a\n// single series (the series is labelled where it ends). Values never live only in a hover tooltip:\n// the latest and peak readings are labelled on the chart and every reading is in a table for screen\n// readers. Optional extras exist only where they answer a question: a dashed `planned` series, a\n// horizontal `band` (a threshold that matters), and a `now` marker.\n//\n//\n// `colourScale` colours the measured line by its own value, for subjects where that colour is a\n// fact rather than decoration: a kiln's glow by temperature, water by depth. The y position still\n// carries the value, so the colour is never the only cue. Without a reason in DESIGN.md, use one\n// hue.\n//\n// It draws in real pixels (measured with ResizeObserver) rather than scaling a fixed viewBox, so\n// the 12px labels stay 12px on a phone.\n\nexport type Point = { x: number; y: number };\n\nexport type LineChartProps = {\n /** What the chart shows, as a sentence. Used as the accessible name and the table caption. */\n label: string;\n series: Point[];\n seriesLabel: string;\n planned?: Point[];\n plannedLabel?: string;\n band?: { below: number; label: string };\n formatX: (x: number) => string;\n formatY: (y: number) => string;\n xTicks: number[];\n /** CSS colour for the measured series. Defaults to the first chart token. */\n colour?: string;\n /** Colour the measured series by value: each stop applies from `at` upwards, blending to the\n * next. Only where the colour means something in the subject; record the reason in DESIGN.md. */\n colourScale?: { at: number; colour: string }[];\n /** Says what the colours mean, shown beside the legend swatch when `colourScale` is set. */\n colourScaleLabel?: string;\n height?: number;\n className?: string;\n};\n\nfunction useWidth<T extends HTMLElement>() {\n const ref = React.useRef<T>(null);\n const [width, setWidth] = React.useState(0);\n React.useLayoutEffect(() => {\n const el = ref.current;\n if (!el) return;\n setWidth(el.getBoundingClientRect().width);\n const ro = new ResizeObserver(([entry]) => setWidth(entry.contentRect.width));\n ro.observe(el);\n return () => ro.disconnect();\n }, []);\n return [ref, width] as const;\n}\n\n/** Four gridline steps of a size people write on a scale (1, 2, 2.5, 4 or 5 × 10ⁿ), ending above the max. */\nexport function niceMax(max: number) {\n // All-zero (or empty) data still needs a positive extent, or every coordinate divides by zero.\n if (!(max > 0)) return 4;\n const raw = max / 4;\n const mag = 10 ** Math.floor(Math.log10(raw));\n const step = [1, 2, 2.5, 4, 5, 10].map((m) => m * mag).find((s) => s >= raw) ?? 10 * mag;\n return step * 4;\n}\n\n/** One measured series, drawn in real pixels, with the peak and current readings labelled on the chart and every reading in a screen-reader table. Optional plan, threshold band and `colourScale`. */\nexport function LineChart({\n label,\n series,\n seriesLabel,\n planned,\n plannedLabel,\n band,\n formatX,\n formatY,\n xTicks,\n colour = \"var(--chart-1)\",\n colourScale,\n colourScaleLabel,\n height = 280,\n className,\n}: LineChartProps) {\n const [ref, width] = useWidth<HTMLDivElement>();\n const gradientId = `heat-${React.useId().replace(/[^\\w-]/g, \"\")}`;\n const all = [...series, ...(planned ?? [])];\n const xMin = Math.min(...all.map((p) => p.x));\n const xMax = Math.max(...all.map((p) => p.x));\n const yMax = niceMax(Math.max(...all.map((p) => p.y)));\n const yTicks = [0, 0.25, 0.5, 0.75, 1].map((f) => f * yMax);\n\n const pad = { top: 16, right: 16, bottom: 28, left: 56 };\n const w = Math.max(0, width - pad.left - pad.right);\n const h = height - pad.top - pad.bottom;\n const sx = (x: number) => pad.left + ((x - xMin) / (xMax - xMin || 1)) * w;\n const sy = (y: number) => pad.top + h - (y / yMax) * h;\n const path = (pts: Point[]) => pts.map((p, i) => `${i ? \"L\" : \"M\"}${sx(p.x).toFixed(1)},${sy(p.y).toFixed(1)}`).join(\"\");\n\n const last = series[series.length - 1];\n const peak = series.reduce((a, b) => (b.y > a.y ? b : a), series[0]);\n const stops = colourScale ? [...colourScale].sort((a, b) => a.at - b.at) : [];\n /** The colour a value lands on: the highest stop at or below it. Used for the labelled points. */\n const colourAt = (y: number) => (stops.length ? [...stops].reverse().find((s) => s.at <= y)?.colour ?? stops[0].colour : colour);\n const stroke = stops.length ? `url(#${gradientId})` : colour;\n\n return (\n <figure className={cn(\"grid min-w-0 gap-2\", className)}>\n <div ref={ref} className=\"min-w-0\">\n {width > 0 && (\n <svg width={width} height={height} role=\"img\" aria-label={label} className=\"block overflow-visible\">\n {stops.length > 0 && (\n // A vertical gradient in chart space, so a colour sits at a temperature, not at a\n // point along the line: the line is the same red wherever it crosses 600 °C.\n <defs>\n <linearGradient id={gradientId} gradientUnits=\"userSpaceOnUse\" x1={0} x2={0} y1={sy(0)} y2={sy(yMax)}>\n {stops.map((s) => (\n <stop key={s.at} offset={Math.min(1, Math.max(0, s.at / yMax))} stopColor={s.colour} />\n ))}\n </linearGradient>\n </defs>\n )}\n {band && (\n <g>\n <rect x={pad.left} y={sy(band.below)} width={w} height={sy(0) - sy(band.below)} fill=\"var(--success-subtle)\" />\n <text x={pad.left + 8} y={sy(band.below) + 16} className=\"fill-success type-caption\">\n {band.label}\n </text>\n </g>\n )}\n {yTicks.map((t) => (\n <g key={t}>\n <line x1={pad.left} x2={pad.left + w} y1={sy(t)} y2={sy(t)} stroke=\"var(--border)\" strokeWidth={1} />\n <text x={pad.left - 8} y={sy(t)} dy=\"0.32em\" textAnchor=\"end\" className=\"fill-muted-foreground type-caption tabular-nums\">\n {formatY(t)}\n </text>\n </g>\n ))}\n {xTicks.map((t) => (\n <text key={t} x={sx(t)} y={height - 8} textAnchor=\"middle\" className=\"fill-muted-foreground type-caption tabular-nums\">\n {formatX(t)}\n </text>\n ))}\n {planned && (\n <path d={path(planned)} fill=\"none\" stroke=\"var(--muted-foreground)\" strokeWidth={1.5} strokeDasharray=\"4 4\" />\n )}\n <path d={path(series)} fill=\"none\" stroke={stroke} strokeWidth={stops.length ? 2.5 : 2} strokeLinejoin=\"round\" strokeLinecap=\"round\" />\n {/* Direct labels instead of a legend and a tooltip. */}\n <circle cx={sx(peak.x)} cy={sy(peak.y)} r={3} fill={colourAt(peak.y)} />\n <text x={sx(peak.x) + 8} y={sy(peak.y) - 8} className=\"fill-foreground type-caption tabular-nums\">\n {formatY(peak.y)} peak\n </text>\n <line x1={sx(last.x)} x2={sx(last.x)} y1={pad.top} y2={pad.top + h} stroke=\"var(--foreground)\" strokeWidth={1} />\n <circle cx={sx(last.x)} cy={sy(last.y)} r={3.5} fill={colourAt(last.y)} />\n <text\n x={sx(last.x) - 8}\n y={sy(last.y) - 10}\n textAnchor=\"end\"\n className=\"fill-foreground type-label tabular-nums\"\n >\n {formatY(last.y)} now\n </text>\n </svg>\n )}\n </div>\n <figcaption className=\"flex flex-wrap gap-x-5 gap-y-1 type-caption text-muted-foreground\">\n <span className=\"inline-flex items-center gap-2\">\n {stops.length ? (\n <svg width={stops.length * 8} height=\"3\" aria-hidden>\n {stops.map((s, i) => (\n <line key={s.at} x1={i * 8} x2={i * 8 + 8} y1=\"1.5\" y2=\"1.5\" stroke={s.colour} strokeWidth={3} />\n ))}\n </svg>\n ) : (\n <svg width=\"16\" height=\"2\" aria-hidden>\n <line x1=\"0\" x2=\"16\" y1=\"1\" y2=\"1\" stroke={colour} strokeWidth={2} />\n </svg>\n )}\n {seriesLabel}\n {stops.length > 0 && colourScaleLabel && <span>({colourScaleLabel})</span>}\n </span>\n {planned && plannedLabel && (\n <span className=\"inline-flex items-center gap-2\">\n <svg width=\"16\" height=\"2\" aria-hidden>\n <line x1=\"0\" x2=\"16\" y1=\"1\" y2=\"1\" stroke=\"var(--muted-foreground)\" strokeWidth={1.5} strokeDasharray=\"4 4\" />\n </svg>\n {plannedLabel}\n </span>\n )}\n </figcaption>\n {/* sr-only on a wrapper: a table ignores width: 1px and would widen the page. */}\n <div className=\"sr-only\">\n <table>\n <caption>{label}</caption>\n <thead>\n <tr>\n <th scope=\"col\">Time</th>\n <th scope=\"col\">{seriesLabel}</th>\n </tr>\n </thead>\n <tbody>\n {series.map((p) => (\n <tr key={p.x}>\n <td>{formatX(p.x)}</td>\n <td>{formatY(p.y)}</td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n </figure>\n );\n}\n","import * as React from \"react\";\n\n// The top of every Operate screen: a noun title at 24px (never \"Welcome back\"), its scope, the\n// screen's main action, and the finding. The finding is the one fact in today's data that most\n// needs attention; it sits where it is read first, beside the action it drives.\n\n/** The top of an Operate screen: a noun title, its scope, the main action, and the finding (the one fact in today's data that most needs attention). */\nexport function PageHeader({\n title,\n scope,\n actions,\n finding,\n as: Heading = \"h1\",\n}: {\n title: string;\n /** The title's element: h1 on a screen, h2 where the header is shown inside another page. */\n as?: \"h1\" | \"h2\";\n /** What the screen covers: a date, a range, a filter. */\n scope?: React.ReactNode;\n actions?: React.ReactNode;\n finding?: { text: React.ReactNode; action?: React.ReactNode };\n}) {\n return (\n <header className=\"grid gap-4\">\n <div className=\"flex flex-wrap items-end justify-between gap-x-6 gap-y-3\">\n <div className=\"grid min-w-0 gap-0.5\">\n <Heading className=\"type-title text-balance\">{title}</Heading>\n {scope && <p className=\"type-body-sm text-muted-foreground\">{scope}</p>}\n </div>\n {actions && <div className=\"flex flex-wrap gap-2\">{actions}</div>}\n </div>\n {finding && (\n <div className=\"flex flex-wrap items-center justify-between gap-x-6 gap-y-3 border-y border-border py-3\">\n <p className=\"max-w-prose min-w-0 type-body text-pretty\">{finding.text}</p>\n {finding.action}\n </div>\n )}\n </header>\n );\n}\n","import * as React from \"react\";\nimport { Button } from \"./button\";\n\n// Destructive actions take effect visibly at once and commit after a window, with Undo in reach.\n// For reversible removals this beats a confirm dialog: the dialog interrupts every time to protect\n// against the rare mistake, and people learn to click through it. Keep a confirm dialog for\n// actions that cannot be undone (deleting an account, firing a kiln).\n\n/** A removal waiting out its undo window: what to say, and what to do when the window closes. */\nexport type PendingRemoval = { id: string; message: string; commit: () => void };\n\n/** Schedules a reversible removal to commit after `windowMs` unless undone. A second removal, or unmounting, commits the pending one. */\nexport function useUndoable(windowMs = 8000) {\n const [pending, setPending] = React.useState<PendingRemoval | null>(null);\n const current = React.useRef<PendingRemoval | null>(null);\n const timer = React.useRef<number | undefined>(undefined);\n\n const set = React.useCallback((p: PendingRemoval | null) => {\n current.current = p;\n setPending(p);\n }, []);\n\n const schedule = React.useCallback(\n (next: PendingRemoval) => {\n window.clearTimeout(timer.current);\n current.current?.commit(); // a second removal commits the first\n set(next);\n timer.current = window.setTimeout(() => {\n current.current?.commit();\n set(null);\n }, windowMs);\n },\n [set, windowMs],\n );\n\n const undo = React.useCallback(() => {\n window.clearTimeout(timer.current);\n set(null);\n }, [set]);\n\n // Leaving the screen is not Undo: if the owner unmounts inside the window (the person navigated\n // away), commit what was scheduled rather than silently dropping it.\n React.useEffect(\n () => () => {\n window.clearTimeout(timer.current);\n current.current?.commit();\n current.current = null;\n },\n [],\n );\n return { pending, schedule, undo };\n}\n\n// The bar lives in a polite live region that is always mounted, so screen readers hear the message\n// when it appears. It is fixed to the bottom of the viewport; a page that uses it keeps that much\n// bottom padding so the bar never covers the focused row.\n/** The polite live region that shows a pending removal with an Undo button. */\nexport function UndoBar({\n pending,\n onUndo,\n inline = false,\n}: {\n pending: PendingRemoval | null;\n onUndo: () => void;\n /** Render in the flow instead of fixed to the viewport; for the state-matrix page. */\n inline?: boolean;\n}) {\n return (\n <div\n aria-live=\"polite\"\n className={\n inline ? \"flex\" : \"pointer-events-none fixed inset-x-0 bottom-4 z-50 flex justify-center px-4\"\n }\n >\n {pending && (\n <div className=\"pointer-events-auto flex max-w-lg items-center gap-4 rounded-overlay bg-foreground py-2 pr-2 pl-4 text-background shadow-overlay\">\n <p className=\"min-w-0 type-body-sm\">{pending.message}</p>\n <Button size=\"sm\" variant=\"ghost\" className=\"text-background hover:bg-neutral-11\" onClick={onUndo}>\n Undo\n </Button>\n </div>\n )}\n </div>\n );\n}\n","import * as React from \"react\";\nimport { Check } from \"lucide-react\";\nimport { cn } from \"../lib/utils\";\n\n// The completion moment: what a person sees after finishing something that took effort (a\n// booking, a submission, an import). It is the one place on an Operate product where the library\n// spends its delight budget (character.md §3), so it is a component with rules rather than a\n// confetti call:\n// - `title` says what happened, in the past tense, with the object: \"Booked: 6 pieces in kiln 2\".\n// - `next` is required. A finish that does not say what happens now is a dead end, however\n// pleasant.\n// - `figure` is optional and must be drawn from the person's own data in the subject's terms\n// (their pieces on a shelf, their route on a map), never a stock illustration.\n// - The part of the figure that is the person's result enters once, with the `enter-moment`\n// utility (500ms, hard ease-out, no overshoot): their pieces set down on the shelf. Reduced\n// motion makes it instant. Nothing loops and nothing plays a sound.\n// - Focus moves to the heading on mount, so keyboard and screen-reader users land on the result.\n\n/** The completion moment for a rare, earned finish: what happened (past tense, with its object), what happens next, and optionally a figure drawn from the person's own data. Focus moves to the heading. */\nexport function Done({\n title,\n next,\n figure,\n actions,\n focusOnMount = true,\n className,\n}: {\n /** What happened, past tense, with its object. */\n title: string;\n /** What the person does now, or what happens next and when. */\n next: React.ReactNode;\n /** A drawing of the result made from the person's data in the subject's own terms. */\n figure?: React.ReactNode;\n actions?: React.ReactNode;\n /** Off only where the result is not the page's subject (the state-matrix page). */\n focusOnMount?: boolean;\n className?: string;\n}) {\n const heading = React.useRef<HTMLHeadingElement>(null);\n const titleId = React.useId();\n React.useEffect(() => {\n if (focusOnMount) heading.current?.focus();\n }, [focusOnMount]);\n return (\n <section aria-labelledby={titleId} className={cn(\"grid gap-5\", className)}>\n <div className=\"grid gap-2\">\n <h1 id={titleId} ref={heading} tabIndex={-1} className=\"flex items-start gap-2 type-title\">\n <Check aria-hidden className=\"mt-1 size-6 shrink-0 text-success\" />\n {title}\n </h1>\n <div className=\"max-w-prose type-body text-muted-foreground\">{next}</div>\n </div>\n {figure}\n {actions && <div className=\"flex flex-wrap items-center gap-3\">{actions}</div>}\n </section>\n );\n}\n","import * as React from \"react\";\nimport { Link, NavLink } from \"react-router\";\nimport { cn } from \"../lib/utils\";\nimport type { IconComponent } from \"../lib/icon\";\n\n// The frame of an app: a side nav on the page ground (not a navy or tinted sidebar), the current\n// item as a neutral pill (no left bar, no accent), and the page beside it. A top bar exists only\n// when it holds something used on every screen, so it is a slot, not a default. Built on React\n// Router's NavLink, so \"current\" is the router's answer and aria-current is set for free. On\n// phones the nav becomes a horizontal, scrollable row above the page rather than a hamburger, so\n// every destination stays one tap away.\n\nexport type NavItem = {\n to: string;\n label: string;\n icon?: IconComponent;\n /** Match only this exact path, for an index route such as \"/\". */\n end?: boolean;\n};\n\n/** The app frame: product name, side nav on the page ground (a scrollable row on phones), optional top bar, and the main region. */\nexport function AppShell({\n product,\n nav,\n navLabel = \"Main\",\n topBar,\n children,\n className,\n}: {\n /** The product's name or mark, linking home. */\n product: React.ReactNode;\n nav: NavItem[];\n navLabel?: string;\n topBar?: React.ReactNode;\n children: React.ReactNode;\n className?: string;\n}) {\n return (\n <div className={cn(\"min-h-dvh md:grid md:grid-cols-[14rem_minmax(0,1fr)]\", className)}>\n <aside className=\"border-b border-border md:border-r md:border-b-0\">\n <div className=\"flex items-center gap-4 px-4 py-3 md:block md:px-3 md:py-4\">\n <Link to=\"/\" className=\"shrink-0 rounded-control px-2 type-heading text-foreground no-underline\">\n {product}\n </Link>\n <SideNav items={nav} label={navLabel} className=\"min-w-0 flex-1 md:mt-4\" />\n </div>\n </aside>\n <div className=\"min-w-0\">\n {topBar && <div className=\"flex items-center gap-3 border-b border-border px-4 py-2 md:px-8\">{topBar}</div>}\n <main className=\"px-4 py-6 md:px-8 md:py-8\">{children}</main>\n </div>\n </div>\n );\n}\n\n/** Navigation links (React Router `NavLink`); the current route is a neutral pill with `aria-current`. */\nexport function SideNav({ items, label, className }: { items: NavItem[]; label: string; className?: string }) {\n return (\n <nav aria-label={label} className={cn(\"min-w-0\", className)}>\n <ul className=\"flex gap-1 overflow-x-auto md:grid md:overflow-visible\">\n {items.map(({ to, label, icon: Icon, end }) => (\n <li key={to} className=\"shrink-0\">\n <NavLink\n to={to}\n end={end}\n className={({ isActive }) =>\n cn(\n \"flex h-(--control-h-sm) items-center gap-2 rounded-control px-2 type-label whitespace-nowrap no-underline transition-state\",\n isActive ? \"bg-muted text-foreground\" : \"text-muted-foreground hover:bg-muted hover:text-foreground\",\n )\n }\n >\n {Icon && <Icon aria-hidden className=\"size-4 shrink-0\" />}\n {label}\n </NavLink>\n </li>\n ))}\n </ul>\n </nav>\n );\n}\n\n// Plain links with a \"/\" separator in muted ink. The last crumb is the current page: text, not a\n// link, with aria-current.\n/** Plain links separated by `/`; the last crumb is the current page. */\nexport function Breadcrumb({ items }: { items: { label: string; to?: string }[] }) {\n return (\n <nav aria-label=\"Breadcrumb\">\n <ol className=\"flex flex-wrap items-center gap-x-2 type-body-sm text-muted-foreground\">\n {items.map((item, i) => {\n const last = i === items.length - 1;\n return (\n <li key={`${item.label}-${i}`} className=\"flex items-center gap-2\">\n {item.to && !last ? (\n <Link to={item.to} className=\"text-muted-foreground underline hover:text-foreground\">\n {item.label}\n </Link>\n ) : (\n <span aria-current={last ? \"page\" : undefined} className={cn(last && \"text-foreground\")}>\n {item.label}\n </span>\n )}\n {!last && <span aria-hidden>/</span>}\n </li>\n );\n })}\n </ol>\n </nav>\n );\n}\n","import * as React from \"react\";\nimport { useSearchParams } from \"react-router\";\nimport { cn } from \"../lib/utils\";\n\n// Tabs whose state lives in the URL (?view=readings), so a tab can be linked, survives reload and\n// works with Back. ARIA tabs pattern: arrow keys move between tabs and select them, Home and End\n// jump, only the selected tab is in the tab order. The selected tab is marked by weight and a\n// 2px rule in ink, not by colour alone. The first tab is the default and is not written to the URL.\n\nexport type TabItem = { value: string; label: string };\n\n/** Tabs whose selection lives in a URL search parameter (React Router), with the ARIA tabs keyboard model. */\nexport function Tabs({\n param,\n items,\n label,\n children,\n className,\n}: {\n /** The search parameter that holds the selected tab. */\n param: string;\n items: TabItem[];\n /** Names the tab list for screen readers. */\n label: string;\n /** Renders the selected panel. */\n children: (value: string) => React.ReactNode;\n className?: string;\n}) {\n const [params, setParams] = useSearchParams();\n const fallback = items[0]?.value ?? \"\";\n const raw = params.get(param);\n const value = items.some((i) => i.value === raw) ? raw! : fallback;\n const baseId = React.useId();\n const refs = React.useRef<(HTMLButtonElement | null)[]>([]);\n\n const select = (next: string, focus = false) => {\n setParams(\n (p) => {\n const out = new URLSearchParams(p);\n if (next === fallback) out.delete(param);\n else out.set(param, next);\n return out;\n },\n { replace: true },\n );\n if (focus) refs.current[items.findIndex((i) => i.value === next)]?.focus();\n };\n\n const onKeyDown = (e: React.KeyboardEvent, index: number) => {\n const last = items.length - 1;\n const to =\n e.key === \"ArrowRight\" ? (index === last ? 0 : index + 1)\n : e.key === \"ArrowLeft\" ? (index === 0 ? last : index - 1)\n : e.key === \"Home\" ? 0\n : e.key === \"End\" ? last\n : undefined;\n if (to === undefined) return;\n e.preventDefault();\n select(items[to].value, true);\n };\n\n return (\n <div className={cn(\"grid gap-4\", className)}>\n <div role=\"tablist\" aria-label={label} className=\"flex gap-4 overflow-x-auto border-b border-border\">\n {items.map((item, i) => {\n const selected = item.value === value;\n return (\n <button\n key={item.value}\n ref={(el) => {\n refs.current[i] = el;\n }}\n type=\"button\"\n role=\"tab\"\n id={`${baseId}-tab-${item.value}`}\n aria-selected={selected}\n aria-controls={`${baseId}-panel`}\n tabIndex={selected ? 0 : -1}\n onClick={() => select(item.value)}\n onKeyDown={(e) => onKeyDown(e, i)}\n className={cn(\n \"-mb-px cursor-pointer border-b-2 py-2 type-label whitespace-nowrap transition-state\",\n selected\n ? \"border-foreground font-semibold text-foreground\"\n : \"border-transparent text-muted-foreground hover:text-foreground\",\n )}\n >\n {item.label}\n </button>\n );\n })}\n </div>\n <div role=\"tabpanel\" id={`${baseId}-panel`} aria-labelledby={`${baseId}-tab-${value}`} tabIndex={0}>\n {children(value)}\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
|
package/dist/lib/format.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export declare const NBSP = "\u00A0";
|
|
2
|
+
/** Intl formatters for one locale: integers, decimals, percent, times, days, units with a non-breaking space, and counted nouns. */
|
|
2
3
|
export declare function createFormat(locale: string): {
|
|
3
4
|
locale: string;
|
|
4
5
|
integer: Intl.NumberFormat;
|
|
@@ -13,6 +14,7 @@ export declare function createFormat(locale: string): {
|
|
|
13
14
|
count: (value: number, one: string, many?: string) => string;
|
|
14
15
|
};
|
|
15
16
|
export type Format = ReturnType<typeof createFormat>;
|
|
17
|
+
/** The en-GB formatters. */
|
|
16
18
|
export declare const format: {
|
|
17
19
|
locale: string;
|
|
18
20
|
integer: Intl.NumberFormat;
|
package/dist/lib/utils.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
import { ClassValue } from 'clsx';
|
|
2
2
|
export declare const typeSteps: readonly ["caption", "label", "body-sm", "body", "heading", "title", "figure", "display"];
|
|
3
|
+
/** clsx + tailwind-merge, taught that `type-*` utilities are font sizes. */
|
|
3
4
|
export declare function cn(...inputs: ClassValue[]): string;
|
package/dist/query/index.d.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { QueryClient, QueryKey, UseQueryOptions, UseQueryResult } from '@tanstack/react-query';
|
|
2
2
|
import { Load } from '../components/data-view';
|
|
3
3
|
import * as React from "react";
|
|
4
|
+
/** A QueryClient with Strata's defaults: one retry, 30 s stale time, no refetch on window focus. */
|
|
4
5
|
export declare function createQueryClient(overrides?: ConstructorParameters<typeof QueryClient>[0]): QueryClient;
|
|
6
|
+
/** Provides a query client (Strata's defaults unless one is passed) to the app. */
|
|
5
7
|
export declare function StrataQueryProvider({ client, children }: {
|
|
6
8
|
client?: QueryClient;
|
|
7
9
|
children: React.ReactNode;
|
|
@@ -9,8 +11,9 @@ export declare function StrataQueryProvider({ client, children }: {
|
|
|
9
11
|
/** Writes an error for a person: what happened, in a sentence. A product passes its own words. */
|
|
10
12
|
export type DescribeError = (error: unknown) => string;
|
|
11
13
|
export declare const describeHttpError: DescribeError;
|
|
14
|
+
/** Turns a TanStack query result into the `Load<T>` that `DataView` takes; errors are described for a person by `describe`. */
|
|
12
15
|
export declare function toLoad<T>(query: UseQueryResult<T>, describe?: DescribeError): Load<T>;
|
|
13
|
-
/** useQuery
|
|
16
|
+
/** `useQuery`, returned as a `Load<T>` for `DataView`. */
|
|
14
17
|
export declare function useLoad<T, K extends QueryKey = QueryKey>(options: UseQueryOptions<T, Error, T, K> & {
|
|
15
18
|
describeError?: DescribeError;
|
|
16
19
|
}): Load<T>;
|
|
@@ -2,6 +2,7 @@ import * as React from "react";
|
|
|
2
2
|
import { jsx } from "react/jsx-runtime";
|
|
3
3
|
import { QueryClient, QueryClientProvider, useQuery } from "@tanstack/react-query";
|
|
4
4
|
// src/query/index.tsx
|
|
5
|
+
/** A QueryClient with Strata's defaults: one retry, 30 s stale time, no refetch on window focus. */
|
|
5
6
|
function createQueryClient(overrides) {
|
|
6
7
|
return new QueryClient({
|
|
7
8
|
...overrides,
|
|
@@ -16,6 +17,7 @@ function createQueryClient(overrides) {
|
|
|
16
17
|
}
|
|
17
18
|
});
|
|
18
19
|
}
|
|
20
|
+
/** Provides a query client (Strata's defaults unless one is passed) to the app. */
|
|
19
21
|
function StrataQueryProvider({ client, children }) {
|
|
20
22
|
const [fallback] = React.useState(() => client ?? createQueryClient());
|
|
21
23
|
return /* @__PURE__ */ jsx(QueryClientProvider, {
|
|
@@ -24,6 +26,7 @@ function StrataQueryProvider({ client, children }) {
|
|
|
24
26
|
});
|
|
25
27
|
}
|
|
26
28
|
var describeHttpError = (error) => error instanceof HttpError ? error.status >= 500 ? `The server could not answer (${error.status}). Nothing was changed.` : `The request was refused (${error.status}): ${error.statusText || "no reason given"}.` : error instanceof Error && error.message ? error.message : "The data could not be loaded. Nothing was changed.";
|
|
29
|
+
/** Turns a TanStack query result into the `Load<T>` that `DataView` takes; errors are described for a person by `describe`. */
|
|
27
30
|
function toLoad(query, describe = describeHttpError) {
|
|
28
31
|
if (query.status === "success") return {
|
|
29
32
|
status: "ready",
|
|
@@ -36,7 +39,7 @@ function toLoad(query, describe = describeHttpError) {
|
|
|
36
39
|
};
|
|
37
40
|
return { status: "loading" };
|
|
38
41
|
}
|
|
39
|
-
/** useQuery
|
|
42
|
+
/** `useQuery`, returned as a `Load<T>` for `DataView`. */
|
|
40
43
|
function useLoad(options) {
|
|
41
44
|
const { describeError, ...rest } = options;
|
|
42
45
|
return toLoad(useQuery(rest), describeError);
|
|
@@ -66,4 +69,4 @@ async function fetchJson(input, init) {
|
|
|
66
69
|
}
|
|
67
70
|
export { fetchJson as a, describeHttpError as i, StrataQueryProvider as n, toLoad as o, createQueryClient as r, useLoad as s, HttpError as t };
|
|
68
71
|
|
|
69
|
-
//# sourceMappingURL=query-
|
|
72
|
+
//# sourceMappingURL=query-DZVmYJSx.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"query-DZVmYJSx.js","names":[],"sources":["../src/query/index.tsx"],"sourcesContent":["import * as React from \"react\";\nimport {\n QueryClient,\n QueryClientProvider,\n useQuery,\n type QueryKey,\n type UseQueryOptions,\n type UseQueryResult,\n} from \"@tanstack/react-query\";\nimport type { Load } from \"../components/data-view\";\n\n// Data loading, the Strata way: TanStack Query fetches and caches, and every view of the result\n// goes through DataView, which takes a Load<T>. `toLoad` is the one bridge between them, so a\n// screen never re-derives loading/error/ready from a query's flags and never forgets one.\n//\n// Opinions in createQueryClient: one retry (a second failure is real and the person should see\n// it with a \"Try again\"), 30 s stale time (dashboards are read many times a minute but change\n// over minutes), and no refetch on window focus (a table that reorders itself while someone reads\n// it is worse than one that is 30 s old). Override per query when the data says otherwise.\n\n/** A QueryClient with Strata's defaults: one retry, 30 s stale time, no refetch on window focus. */\nexport function createQueryClient(overrides?: ConstructorParameters<typeof QueryClient>[0]) {\n return new QueryClient({\n ...overrides,\n defaultOptions: {\n ...overrides?.defaultOptions,\n queries: { retry: 1, staleTime: 30_000, refetchOnWindowFocus: false, ...overrides?.defaultOptions?.queries },\n },\n });\n}\n\n/** Provides a query client (Strata's defaults unless one is passed) to the app. */\nexport function StrataQueryProvider({ client, children }: { client?: QueryClient; children: React.ReactNode }) {\n const [fallback] = React.useState(() => client ?? createQueryClient());\n return <QueryClientProvider client={client ?? fallback}>{children}</QueryClientProvider>;\n}\n\n/** Writes an error for a person: what happened, in a sentence. A product passes its own words. */\nexport type DescribeError = (error: unknown) => string;\n\nexport const describeHttpError: DescribeError = (error) =>\n error instanceof HttpError\n ? error.status >= 500\n ? `The server could not answer (${error.status}). Nothing was changed.`\n : `The request was refused (${error.status}): ${error.statusText || \"no reason given\"}.`\n : error instanceof Error && error.message\n ? error.message\n : \"The data could not be loaded. Nothing was changed.\";\n\n/** Turns a TanStack query result into the `Load<T>` that `DataView` takes; errors are described for a person by `describe`. */\nexport function toLoad<T>(query: UseQueryResult<T>, describe: DescribeError = describeHttpError): Load<T> {\n if (query.status === \"success\") return { status: \"ready\", data: query.data };\n if (query.status === \"error\") return { status: \"error\", message: describe(query.error), retry: () => void query.refetch() };\n return { status: \"loading\" };\n}\n\n/** `useQuery`, returned as a `Load<T>` for `DataView`. */\nexport function useLoad<T, K extends QueryKey = QueryKey>(\n options: UseQueryOptions<T, Error, T, K> & { describeError?: DescribeError },\n): Load<T> {\n const { describeError, ...rest } = options;\n return toLoad(useQuery(rest), describeError);\n}\n\nexport class HttpError extends Error {\n constructor(\n readonly status: number,\n readonly statusText: string,\n readonly url: string,\n ) {\n super(`${status} ${statusText} from ${url}`);\n this.name = \"HttpError\";\n }\n}\n\n/** fetch() that throws an HttpError on a non-2xx response and parses JSON: the queryFn default. */\nexport async function fetchJson<T>(input: string | URL, init?: RequestInit): Promise<T> {\n // Headers, tuple arrays and plain objects are all valid HeadersInit; normalise before adding the\n // default so a caller's Authorization (or their own Accept) is never dropped.\n const headers = new Headers(init?.headers);\n if (!headers.has(\"Accept\")) headers.set(\"Accept\", \"application/json\");\n const res = await fetch(input, { ...init, headers });\n if (!res.ok) throw new HttpError(res.status, res.statusText, String(input));\n return (await res.json()) as T;\n}\n"],"mappings":";;;;;AAqBA,SAAgB,kBAAkB,WAA0D;CAC1F,OAAO,IAAI,YAAY;EACrB,GAAG;EACH,gBAAgB;GACd,GAAG,WAAW;GACd,SAAS;IAAE,OAAO;IAAG,WAAW;IAAQ,sBAAsB;IAAO,GAAG,WAAW,gBAAgB;GAAQ;EAC7G;CACF,CAAC;AACH;;AAGA,SAAgB,oBAAoB,EAAE,QAAQ,YAAiE;CAC7G,MAAM,CAAC,YAAY,MAAM,eAAe,UAAU,kBAAkB,CAAC;CACrE,OAAO,oBAAC,qBAAD;EAAqB,QAAQ,UAAU;EAAW;CAA8B,CAAA;AACzF;AAKA,IAAa,qBAAoC,UAC/C,iBAAiB,YACb,MAAM,UAAU,MACd,gCAAgC,MAAM,OAAO,2BAC7C,4BAA4B,MAAM,OAAO,KAAK,MAAM,cAAc,kBAAkB,KACtF,iBAAiB,SAAS,MAAM,UAC9B,MAAM,UACN;;AAGR,SAAgB,OAAU,OAA0B,WAA0B,mBAA4B;CACxG,IAAI,MAAM,WAAW,WAAW,OAAO;EAAE,QAAQ;EAAS,MAAM,MAAM;CAAK;CAC3E,IAAI,MAAM,WAAW,SAAS,OAAO;EAAE,QAAQ;EAAS,SAAS,SAAS,MAAM,KAAK;EAAG,aAAa,KAAK,MAAM,QAAQ;CAAE;CAC1H,OAAO,EAAE,QAAQ,UAAU;AAC7B;;AAGA,SAAgB,QACd,SACS;CACT,MAAM,EAAE,eAAe,GAAG,SAAS;CACnC,OAAO,OAAO,SAAS,IAAI,GAAG,aAAa;AAC7C;AAEA,IAAa,YAAb,cAA+B,MAAM;CAExB;CACA;CACA;CAHX,YACE,QACA,YACA,KACA;EACA,MAAM,GAAG,OAAO,GAAG,WAAW,QAAQ,KAAK;EAJlC,KAAA,SAAA;EACA,KAAA,aAAA;EACA,KAAA,MAAA;EAGT,KAAK,OAAO;CACd;AACF;;AAGA,eAAsB,UAAa,OAAqB,MAAgC;CAGtF,MAAM,UAAU,IAAI,QAAQ,MAAM,OAAO;CACzC,IAAI,CAAC,QAAQ,IAAI,QAAQ,GAAG,QAAQ,IAAI,UAAU,kBAAkB;CACpE,MAAM,MAAM,MAAM,MAAM,OAAO;EAAE,GAAG;EAAM;CAAQ,CAAC;CACnD,IAAI,CAAC,IAAI,IAAI,MAAM,IAAI,UAAU,IAAI,QAAQ,IAAI,YAAY,OAAO,KAAK,CAAC;CAC1E,OAAQ,MAAM,IAAI,KAAK;AACzB"}
|
package/dist/testing/index.d.ts
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import { RenderOptions } from '@testing-library/react';
|
|
2
2
|
import { QueryClient } from '@tanstack/react-query';
|
|
3
|
+
/**
|
|
4
|
+
* Test helpers: render a component inside a router and a query client, as a Strata app runs it.
|
|
5
|
+
* Use with MSW for the network, never module mocks.
|
|
6
|
+
*
|
|
7
|
+
* @module strata-react/testing
|
|
8
|
+
*/
|
|
3
9
|
import * as React from "react";
|
|
4
10
|
export declare function createTestQueryClient(): QueryClient;
|
|
5
11
|
export declare function renderWithStrata(ui: React.ReactElement, { route, client, ...options }?: RenderOptions & {
|
package/dist/testing.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { n as StrataQueryProvider, r as createQueryClient } from "./query-
|
|
1
|
+
import { n as StrataQueryProvider, r as createQueryClient } from "./query-DZVmYJSx.js";
|
|
2
2
|
import "react";
|
|
3
3
|
import { jsx } from "react/jsx-runtime";
|
|
4
4
|
import { MemoryRouter } from "react-router";
|
package/dist/testing.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"testing.js","names":[],"sources":["../src/testing/index.tsx"],"sourcesContent":["
|
|
1
|
+
{"version":3,"file":"testing.js","names":[],"sources":["../src/testing/index.tsx"],"sourcesContent":["/**\n * Test helpers: render a component inside a router and a query client, as a Strata app runs it.\n * Use with MSW for the network, never module mocks.\n *\n * @module strata-react/testing\n */\nimport * as React from \"react\";\nimport { render, type RenderOptions } from \"@testing-library/react\";\nimport { MemoryRouter } from \"react-router\";\nimport { QueryClient } from \"@tanstack/react-query\";\nimport { StrataQueryProvider, createQueryClient } from \"../query\";\n\n// Render a component the way a Strata app runs it: inside a router and a query client. Tests use\n// MSW for the network (see CLAUDE.md), never module mocks, so the real fetch/query/DataView path\n// runs. Each call gets a fresh QueryClient with retries off, so a test that serves a 500 sees the\n// error at once instead of after the retry delay.\n\nexport function createTestQueryClient() {\n return createQueryClient({ defaultOptions: { queries: { retry: false, staleTime: Infinity, gcTime: Infinity } } });\n}\n\nexport function renderWithStrata(\n ui: React.ReactElement,\n { route = \"/\", client = createTestQueryClient(), ...options }: RenderOptions & { route?: string; client?: QueryClient } = {},\n) {\n const Wrapper = ({ children }: { children: React.ReactNode }) => (\n <StrataQueryProvider client={client}>\n <MemoryRouter initialEntries={[route]}>{children}</MemoryRouter>\n </StrataQueryProvider>\n );\n return { client, ...render(ui, { wrapper: Wrapper, ...options }) };\n}\n"],"mappings":";;;;;;AAiBA,SAAgB,wBAAwB;CACtC,OAAO,kBAAkB,EAAE,gBAAgB,EAAE,SAAS;EAAE,OAAO;EAAO,WAAW;EAAU,QAAQ;CAAS,EAAE,EAAE,CAAC;AACnH;AAEA,SAAgB,iBACd,IACA,EAAE,QAAQ,KAAK,SAAS,sBAAsB,GAAG,GAAG,YAAsE,CAAC,GAC3H;CACA,MAAM,WAAW,EAAE,eACjB,oBAAC,qBAAD;EAA6B;EAC3B,UAAA,oBAAC,cAAD;GAAc,gBAAgB,CAAC,KAAK;GAAI;EAAuB,CAAA;CAC5C,CAAA;CAEvB,OAAO;EAAE;EAAQ,GAAG,OAAO,IAAI;GAAE,SAAS;GAAS,GAAG;EAAQ,CAAC;CAAE;AACnE"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "strata-react",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "React + Tailwind v4 components that ship structure, behaviour, states and a token contract, and no look: each product brings a theme generated from its own subject.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -48,7 +48,9 @@
|
|
|
48
48
|
"test:visual": "npm run build-storybook && node scripts/visual.mjs",
|
|
49
49
|
"test:visual:update": "npm run build-storybook && node scripts/visual.mjs --update",
|
|
50
50
|
"prepare": "npm run build",
|
|
51
|
-
"prepublishOnly": "npm run test:all"
|
|
51
|
+
"prepublishOnly": "npm run test:all",
|
|
52
|
+
"docs:api": "typedoc",
|
|
53
|
+
"site": "npm run build-storybook && typedoc && node scripts/build-site.mjs"
|
|
52
54
|
},
|
|
53
55
|
"keywords": [
|
|
54
56
|
"react",
|
|
@@ -110,6 +112,7 @@
|
|
|
110
112
|
"react-router": "^8.5.1",
|
|
111
113
|
"storybook": "^10.6.6",
|
|
112
114
|
"tailwindcss": "^4.3.5",
|
|
115
|
+
"typedoc": "^0.28.20",
|
|
113
116
|
"typescript": "~5.9",
|
|
114
117
|
"vite": "^8.3.6",
|
|
115
118
|
"vite-plugin-dts": "^5.1.2",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"query-BXH-Gfj7.js","names":[],"sources":["../src/query/index.tsx"],"sourcesContent":["import * as React from \"react\";\nimport {\n QueryClient,\n QueryClientProvider,\n useQuery,\n type QueryKey,\n type UseQueryOptions,\n type UseQueryResult,\n} from \"@tanstack/react-query\";\nimport type { Load } from \"../components/data-view\";\n\n// Data loading, the Strata way: TanStack Query fetches and caches, and every view of the result\n// goes through DataView, which takes a Load<T>. `toLoad` is the one bridge between them, so a\n// screen never re-derives loading/error/ready from a query's flags and never forgets one.\n//\n// Opinions in createQueryClient: one retry (a second failure is real and the person should see\n// it with a \"Try again\"), 30 s stale time (dashboards are read many times a minute but change\n// over minutes), and no refetch on window focus (a table that reorders itself while someone reads\n// it is worse than one that is 30 s old). Override per query when the data says otherwise.\n\nexport function createQueryClient(overrides?: ConstructorParameters<typeof QueryClient>[0]) {\n return new QueryClient({\n ...overrides,\n defaultOptions: {\n ...overrides?.defaultOptions,\n queries: { retry: 1, staleTime: 30_000, refetchOnWindowFocus: false, ...overrides?.defaultOptions?.queries },\n },\n });\n}\n\nexport function StrataQueryProvider({ client, children }: { client?: QueryClient; children: React.ReactNode }) {\n const [fallback] = React.useState(() => client ?? createQueryClient());\n return <QueryClientProvider client={client ?? fallback}>{children}</QueryClientProvider>;\n}\n\n/** Writes an error for a person: what happened, in a sentence. A product passes its own words. */\nexport type DescribeError = (error: unknown) => string;\n\nexport const describeHttpError: DescribeError = (error) =>\n error instanceof HttpError\n ? error.status >= 500\n ? `The server could not answer (${error.status}). Nothing was changed.`\n : `The request was refused (${error.status}): ${error.statusText || \"no reason given\"}.`\n : error instanceof Error && error.message\n ? error.message\n : \"The data could not be loaded. Nothing was changed.\";\n\nexport function toLoad<T>(query: UseQueryResult<T>, describe: DescribeError = describeHttpError): Load<T> {\n if (query.status === \"success\") return { status: \"ready\", data: query.data };\n if (query.status === \"error\") return { status: \"error\", message: describe(query.error), retry: () => void query.refetch() };\n return { status: \"loading\" };\n}\n\n/** useQuery, returned as a Load<T> for DataView. */\nexport function useLoad<T, K extends QueryKey = QueryKey>(\n options: UseQueryOptions<T, Error, T, K> & { describeError?: DescribeError },\n): Load<T> {\n const { describeError, ...rest } = options;\n return toLoad(useQuery(rest), describeError);\n}\n\nexport class HttpError extends Error {\n constructor(\n readonly status: number,\n readonly statusText: string,\n readonly url: string,\n ) {\n super(`${status} ${statusText} from ${url}`);\n this.name = \"HttpError\";\n }\n}\n\n/** fetch() that throws an HttpError on a non-2xx response and parses JSON: the queryFn default. */\nexport async function fetchJson<T>(input: string | URL, init?: RequestInit): Promise<T> {\n // Headers, tuple arrays and plain objects are all valid HeadersInit; normalise before adding the\n // default so a caller's Authorization (or their own Accept) is never dropped.\n const headers = new Headers(init?.headers);\n if (!headers.has(\"Accept\")) headers.set(\"Accept\", \"application/json\");\n const res = await fetch(input, { ...init, headers });\n if (!res.ok) throw new HttpError(res.status, res.statusText, String(input));\n return (await res.json()) as T;\n}\n"],"mappings":";;;;AAoBA,SAAgB,kBAAkB,WAA0D;CAC1F,OAAO,IAAI,YAAY;EACrB,GAAG;EACH,gBAAgB;GACd,GAAG,WAAW;GACd,SAAS;IAAE,OAAO;IAAG,WAAW;IAAQ,sBAAsB;IAAO,GAAG,WAAW,gBAAgB;GAAQ;EAC7G;CACF,CAAC;AACH;AAEA,SAAgB,oBAAoB,EAAE,QAAQ,YAAiE;CAC7G,MAAM,CAAC,YAAY,MAAM,eAAe,UAAU,kBAAkB,CAAC;CACrE,OAAO,oBAAC,qBAAD;EAAqB,QAAQ,UAAU;EAAW;CAA8B,CAAA;AACzF;AAKA,IAAa,qBAAoC,UAC/C,iBAAiB,YACb,MAAM,UAAU,MACd,gCAAgC,MAAM,OAAO,2BAC7C,4BAA4B,MAAM,OAAO,KAAK,MAAM,cAAc,kBAAkB,KACtF,iBAAiB,SAAS,MAAM,UAC9B,MAAM,UACN;AAER,SAAgB,OAAU,OAA0B,WAA0B,mBAA4B;CACxG,IAAI,MAAM,WAAW,WAAW,OAAO;EAAE,QAAQ;EAAS,MAAM,MAAM;CAAK;CAC3E,IAAI,MAAM,WAAW,SAAS,OAAO;EAAE,QAAQ;EAAS,SAAS,SAAS,MAAM,KAAK;EAAG,aAAa,KAAK,MAAM,QAAQ;CAAE;CAC1H,OAAO,EAAE,QAAQ,UAAU;AAC7B;;AAGA,SAAgB,QACd,SACS;CACT,MAAM,EAAE,eAAe,GAAG,SAAS;CACnC,OAAO,OAAO,SAAS,IAAI,GAAG,aAAa;AAC7C;AAEA,IAAa,YAAb,cAA+B,MAAM;CAExB;CACA;CACA;CAHX,YACE,QACA,YACA,KACA;EACA,MAAM,GAAG,OAAO,GAAG,WAAW,QAAQ,KAAK;EAJlC,KAAA,SAAA;EACA,KAAA,aAAA;EACA,KAAA,MAAA;EAGT,KAAK,OAAO;CACd;AACF;;AAGA,eAAsB,UAAa,OAAqB,MAAgC;CAGtF,MAAM,UAAU,IAAI,QAAQ,MAAM,OAAO;CACzC,IAAI,CAAC,QAAQ,IAAI,QAAQ,GAAG,QAAQ,IAAI,UAAU,kBAAkB;CACpE,MAAM,MAAM,MAAM,MAAM,OAAO;EAAE,GAAG;EAAM;CAAQ,CAAC;CACnD,IAAI,CAAC,IAAI,IAAI,MAAM,IAAI,UAAU,IAAI,QAAQ,IAAI,YAAY,OAAO,KAAK,CAAC;CAC1E,OAAQ,MAAM,IAAI,KAAK;AACzB"}
|