strata-react 0.0.0-stage → 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/LICENSE +21 -0
- package/README.md +116 -2
- package/dist/components/app-shell.d.ts +32 -0
- package/dist/components/async-button.d.ts +18 -0
- package/dist/components/button.d.ts +18 -0
- package/dist/components/data-table.d.ts +42 -0
- package/dist/components/data-view.d.ts +33 -0
- package/dist/components/done.d.ts +14 -0
- package/dist/components/field.d.ts +34 -0
- package/dist/components/line-chart.d.ts +36 -0
- package/dist/components/metric.d.ts +17 -0
- package/dist/components/page-header.d.ts +14 -0
- package/dist/components/status.d.ts +9 -0
- package/dist/components/tabs.d.ts +16 -0
- package/dist/components/undo.d.ts +20 -0
- package/dist/components/virtual-list.d.ts +12 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.js +1116 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/format.d.ts +30 -0
- package/dist/lib/icon.d.ts +6 -0
- package/dist/lib/utils.d.ts +4 -0
- package/dist/query/index.d.ts +27 -0
- package/dist/query-DZVmYJSx.js +72 -0
- package/dist/query-DZVmYJSx.js.map +1 -0
- package/dist/strata.css +181 -0
- package/dist/testing/index.d.ts +70 -0
- package/dist/testing.js +32 -0
- package/dist/testing.js.map +1 -0
- package/package.json +127 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Jonathan Burnhams
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,117 @@
|
|
|
1
|
-
#
|
|
1
|
+
# strata-react
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
An opinionated React + Tailwind v4 framework for building product frontends: components,
|
|
4
|
+
UX patterns and styles, with the testing and review tooling that keeps them honest. Built
|
|
5
|
+
from the research in [jburnhams/ux-research](https://github.com/jburnhams/ux-research)
|
|
6
|
+
(`ui-library/`), which explains every decision.
|
|
7
|
+
|
|
8
|
+
It is opinionated about **structure, behaviour, states, accessibility and the stack**, and
|
|
9
|
+
deliberately silent about **the look**: there is no default theme. Each product generates its
|
|
10
|
+
own palette from its subject, and Tailwind's stock colours, sizes, radii and shadows are
|
|
11
|
+
removed so they cannot be typed by accident.
|
|
12
|
+
|
|
13
|
+
Rules for working in this repo, for people and coding agents: [`CLAUDE.md`](./CLAUDE.md).
|
|
14
|
+
|
|
15
|
+
## The stack
|
|
16
|
+
|
|
17
|
+
Node 26 · React 19 · TypeScript · Vite 8 · Tailwind v4 · class-variance-authority · lucide-react ·
|
|
18
|
+
TanStack Query, Table and Virtual · React Router · Vitest + Testing Library + MSW ·
|
|
19
|
+
Storybook 10 · Playwright Chromium + pixelmatch visual regression · GitHub Actions.
|
|
20
|
+
|
|
21
|
+
## Install
|
|
22
|
+
|
|
23
|
+
Not on npm yet. Install from GitHub (the `prepare` script builds it):
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npm install github:jburnhams/strata-react react react-dom react-router @tanstack/react-query tailwindcss @tailwindcss/vite
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Set up an app
|
|
30
|
+
|
|
31
|
+
```css
|
|
32
|
+
/* src/app.css */
|
|
33
|
+
@import "tailwindcss";
|
|
34
|
+
@import "strata-react/strata.css"; /* token contract, namespace reset, type steps, base layer */
|
|
35
|
+
@import "./theme.generated.css"; /* your palette: ux-research ux-design/skill/scripts/palette.mjs */
|
|
36
|
+
@import "./theme.css"; /* your status roles, faces and subject colours, with reasons */
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
`theme.css` supplies what Strata names but does not choose: `--success`, `--success-subtle`,
|
|
40
|
+
`--warning…`, `--danger…`, `--info…` (pale tint + dark ink of one hue, ink at least 6:1),
|
|
41
|
+
`--font-sans` / `--font-display` in an `@theme` block, and optionally `--title-weight`,
|
|
42
|
+
`--title-stretch`, `--title-tracking`. See `.storybook/theme.css` for a complete example (the
|
|
43
|
+
kiln-room fixture).
|
|
44
|
+
|
|
45
|
+
```tsx
|
|
46
|
+
import { createBrowserRouter, RouterProvider } from "react-router";
|
|
47
|
+
import { StrataQueryProvider } from "strata-react";
|
|
48
|
+
import "./app.css";
|
|
49
|
+
|
|
50
|
+
const router = createBrowserRouter([{ path: "/", element: <KilnRoom /> }]);
|
|
51
|
+
|
|
52
|
+
createRoot(document.getElementById("root")!).render(
|
|
53
|
+
<StrataQueryProvider>
|
|
54
|
+
<RouterProvider router={router} />
|
|
55
|
+
</StrataQueryProvider>,
|
|
56
|
+
);
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
```tsx
|
|
60
|
+
// One question: "Is my work out of the kiln yet?"
|
|
61
|
+
function KilnRoom() {
|
|
62
|
+
const load = useLoad({ queryKey: ["bookings"], queryFn: () => fetchJson<Booking[]>("/api/bookings") });
|
|
63
|
+
return (
|
|
64
|
+
<AppShell product="Back Lane Clay" nav={nav}>
|
|
65
|
+
<PageHeader title="Kiln room" finding={{ text: "Kiln 2 is safe to open at about 14:50." }} />
|
|
66
|
+
<DataView
|
|
67
|
+
state={load}
|
|
68
|
+
isEmpty={(rows) => rows.length === 0}
|
|
69
|
+
skeleton={<TableSkeleton />}
|
|
70
|
+
empty={<EmptyState title="All three kilns are cold" />}
|
|
71
|
+
>
|
|
72
|
+
{(rows) => <DataTable caption="Work in the kilns" columns={columns} rows={rows} rowKey={(b) => b.id} sortParam="sort" />}
|
|
73
|
+
</DataView>
|
|
74
|
+
</AppShell>
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Components
|
|
80
|
+
|
|
81
|
+
| Group | Exports | Notes |
|
|
82
|
+
|---|---|---|
|
|
83
|
+
| Actions | `Button`, `IconButton`, `AsyncButton`, `useAsyncAction` | Default is `outline`; one filled button per view; `IconButton` requires `label`; `AsyncButton` keeps one width through idle, pending, done and error |
|
|
84
|
+
| Form | `Field`, `Input`, `Textarea`, `NativeSelect`, `ChoiceGroup` | `Field` wires label, hint, error, `aria-describedby`, `aria-invalid` |
|
|
85
|
+
| Status and figures | `Status`, `Metric`, `MetricStrip` | A status is always a word; figures have no icons or sparklines |
|
|
86
|
+
| Data | `DataView`, `Load<T>`, `EmptyState`, `Skeleton`, `DataTable`, `TableSkeleton`, `VirtualList`, `LineChart` | `DataView` requires skeleton and empty; tables and lists virtualise from 100 rows; charts label values directly |
|
|
87
|
+
| Data loading | `StrataQueryProvider`, `createQueryClient`, `useLoad`, `toLoad`, `fetchJson`, `HttpError` | TanStack Query in, `Load<T>` out |
|
|
88
|
+
| Page and navigation | `AppShell`, `SideNav`, `Breadcrumb`, `Tabs`, `PageHeader` | React Router; tab and sort state in the URL |
|
|
89
|
+
| Feedback | `UndoBar`, `useUndoable`, `Done` | Undo before confirm; `Done` is the one celebration, and it must say what happens next |
|
|
90
|
+
| Helpers | `cn`, `format`, `createFormat` | `cn` knows `type-*`; all numbers and dates through `Intl` |
|
|
91
|
+
| Testing | `strata-react/testing`: `renderWithStrata`, `createTestQueryClient` | Router + query client around Testing Library's `render` |
|
|
92
|
+
|
|
93
|
+
Specs for the components not built yet (dialogs, sheets, menus, toasts, command menu, prose)
|
|
94
|
+
are in ux-research `ui-library/components.md`.
|
|
95
|
+
|
|
96
|
+
## Develop
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
npm install
|
|
100
|
+
npm run storybook # http://localhost:6006
|
|
101
|
+
npm test # typecheck + unit tests
|
|
102
|
+
npm run test:integration # build, then check the package and its CSS as an app sees them
|
|
103
|
+
npm run test:visual # screenshot every story (light, dark) and compare with baselines
|
|
104
|
+
npm run test:visual:update # accept the current screenshots as baselines
|
|
105
|
+
npm run site # build the docs site into site/
|
|
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
|
+
|
|
115
|
+
## Licence
|
|
116
|
+
|
|
117
|
+
MIT
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { IconComponent } from '../lib/icon';
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
export type NavItem = {
|
|
4
|
+
to: string;
|
|
5
|
+
label: string;
|
|
6
|
+
icon?: IconComponent;
|
|
7
|
+
/** Match only this exact path, for an index route such as "/". */
|
|
8
|
+
end?: boolean;
|
|
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. */
|
|
11
|
+
export declare function AppShell({ product, nav, navLabel, topBar, children, className, }: {
|
|
12
|
+
/** The product's name or mark, linking home. */
|
|
13
|
+
product: React.ReactNode;
|
|
14
|
+
nav: NavItem[];
|
|
15
|
+
navLabel?: string;
|
|
16
|
+
topBar?: React.ReactNode;
|
|
17
|
+
children: React.ReactNode;
|
|
18
|
+
className?: string;
|
|
19
|
+
}): React.JSX.Element;
|
|
20
|
+
/** Navigation links (React Router `NavLink`); the current route is a neutral pill with `aria-current`. */
|
|
21
|
+
export declare function SideNav({ items, label, className }: {
|
|
22
|
+
items: NavItem[];
|
|
23
|
+
label: string;
|
|
24
|
+
className?: string;
|
|
25
|
+
}): React.JSX.Element;
|
|
26
|
+
/** Plain links separated by `/`; the last crumb is the current page. */
|
|
27
|
+
export declare function Breadcrumb({ items }: {
|
|
28
|
+
items: {
|
|
29
|
+
label: string;
|
|
30
|
+
to?: string;
|
|
31
|
+
}[];
|
|
32
|
+
}): React.JSX.Element;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { ButtonProps } from './button';
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
export type Phase = "idle" | "pending" | "done" | "error";
|
|
4
|
+
export type AsyncLabels = {
|
|
5
|
+
idle: string;
|
|
6
|
+
/** Ends in an ellipsis: "Saving…", "Booking…". */
|
|
7
|
+
pending: string;
|
|
8
|
+
/** The same verb, finished: "Saved", "Booked". */
|
|
9
|
+
done: string;
|
|
10
|
+
error?: string;
|
|
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. */
|
|
13
|
+
export declare function AsyncButton({ phase, labels, disabled, ...props }: Omit<ButtonProps, "children"> & {
|
|
14
|
+
phase: Phase;
|
|
15
|
+
labels: AsyncLabels;
|
|
16
|
+
}): React.JSX.Element;
|
|
17
|
+
/** Runs an async action and reports its phase. `done` returns to `idle` after `settleMs`. */
|
|
18
|
+
export declare function useAsyncAction<A extends unknown[]>(action: (...args: A) => Promise<void>, settleMs?: number): readonly [Phase, (...args: A) => Promise<void>];
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { VariantProps } from 'class-variance-authority';
|
|
2
|
+
import { IconComponent } from '../lib/icon';
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
export declare const buttonVariants: (props?: ({
|
|
5
|
+
variant?: "link" | "default" | "outline" | "secondary" | "ghost" | "destructive" | null | undefined;
|
|
6
|
+
size?: "default" | "sm" | "icon" | null | undefined;
|
|
7
|
+
} & import('class-variance-authority/types').ClassProp) | undefined) => string;
|
|
8
|
+
export type ButtonProps = React.ComponentProps<"button"> & VariantProps<typeof buttonVariants> & {
|
|
9
|
+
asChild?: boolean;
|
|
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`. */
|
|
12
|
+
export declare function Button({ className, variant, size, asChild, type, ...props }: ButtonProps): React.JSX.Element;
|
|
13
|
+
export type IconButtonProps = Omit<ButtonProps, "size" | "children" | "aria-label"> & {
|
|
14
|
+
icon: IconComponent;
|
|
15
|
+
label: string;
|
|
16
|
+
};
|
|
17
|
+
/** An icon-only button. `label` is required and becomes the accessible name and tooltip. */
|
|
18
|
+
export declare function IconButton({ icon: Icon, label, variant, ...props }: IconButtonProps): React.JSX.Element;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
export type Column<T> = {
|
|
3
|
+
key: string;
|
|
4
|
+
header: string;
|
|
5
|
+
cell: (row: T) => React.ReactNode;
|
|
6
|
+
/** Right-aligns and sets tabular figures. Use for every quantity. */
|
|
7
|
+
numeric?: boolean;
|
|
8
|
+
sort?: (a: T, b: T) => number;
|
|
9
|
+
/** Cells that may hold long text get truncated rather than stretching the table. */
|
|
10
|
+
truncate?: boolean;
|
|
11
|
+
className?: string;
|
|
12
|
+
};
|
|
13
|
+
export type Sort = {
|
|
14
|
+
key: string;
|
|
15
|
+
dir: "ascending" | "descending";
|
|
16
|
+
};
|
|
17
|
+
export type DataTableProps<T> = {
|
|
18
|
+
/** Names the table for screen readers and labels the scroll region. */
|
|
19
|
+
caption: string;
|
|
20
|
+
columns: Column<T>[];
|
|
21
|
+
rows: T[];
|
|
22
|
+
rowKey: (row: T) => string;
|
|
23
|
+
initialSort?: Sort;
|
|
24
|
+
/** Keep the sort in this URL search parameter. Needs a React Router context. */
|
|
25
|
+
sortParam?: string;
|
|
26
|
+
/** Row count from which only the visible rows render. Default 100. */
|
|
27
|
+
virtualizeFrom?: number;
|
|
28
|
+
/** Height of the scroll region once virtualised, in px. Default 480. */
|
|
29
|
+
maxHeight?: number;
|
|
30
|
+
className?: string;
|
|
31
|
+
};
|
|
32
|
+
/** Reads a sort from its URL form (`pieces.desc`). */
|
|
33
|
+
export declare function parseSort(value: string | null): Sort | undefined;
|
|
34
|
+
/** Writes a sort in its URL form (`pieces.desc`). */
|
|
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`). */
|
|
37
|
+
export declare function DataTable<T>(props: DataTableProps<T>): React.JSX.Element;
|
|
38
|
+
/** The loading placeholder for a `DataTable`, shaped like its rows. */
|
|
39
|
+
export declare function TableSkeleton({ rows, columns }: {
|
|
40
|
+
rows?: number;
|
|
41
|
+
columns?: number;
|
|
42
|
+
}): React.JSX.Element;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
export type Load<T> = {
|
|
3
|
+
status: "loading";
|
|
4
|
+
} | {
|
|
5
|
+
status: "error";
|
|
6
|
+
message: string;
|
|
7
|
+
retry: () => void;
|
|
8
|
+
} | {
|
|
9
|
+
status: "ready";
|
|
10
|
+
data: T;
|
|
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. */
|
|
13
|
+
export declare function DataView<T>({ state, isEmpty, skeleton, empty, children, className, }: {
|
|
14
|
+
state: Load<T>;
|
|
15
|
+
/** When the loaded data counts as empty. For arrays, `(rows) => rows.length === 0`. */
|
|
16
|
+
isEmpty: (data: T) => boolean;
|
|
17
|
+
/** Mirrors the final layout, so nothing jumps when data arrives. Not a centred spinner. */
|
|
18
|
+
skeleton: React.ReactNode;
|
|
19
|
+
/** What goes here and how to add it. An `EmptyState`, not an illustration. */
|
|
20
|
+
empty: React.ReactNode;
|
|
21
|
+
children: (data: T) => React.ReactNode;
|
|
22
|
+
className?: string;
|
|
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. */
|
|
25
|
+
export declare function EmptyState({ title, children, action }: {
|
|
26
|
+
title: string;
|
|
27
|
+
children?: React.ReactNode;
|
|
28
|
+
action?: React.ReactNode;
|
|
29
|
+
}): React.JSX.Element;
|
|
30
|
+
/** A placeholder block for a loading layout. Size it with layout classes so it mirrors the final content. */
|
|
31
|
+
export declare function Skeleton({ className }: {
|
|
32
|
+
className?: string;
|
|
33
|
+
}): React.JSX.Element;
|
|
@@ -0,0 +1,14 @@
|
|
|
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. */
|
|
3
|
+
export declare function Done({ title, next, figure, actions, focusOnMount, className, }: {
|
|
4
|
+
/** What happened, past tense, with its object. */
|
|
5
|
+
title: string;
|
|
6
|
+
/** What the person does now, or what happens next and when. */
|
|
7
|
+
next: React.ReactNode;
|
|
8
|
+
/** A drawing of the result made from the person's data in the subject's own terms. */
|
|
9
|
+
figure?: React.ReactNode;
|
|
10
|
+
actions?: React.ReactNode;
|
|
11
|
+
/** Off only where the result is not the page's subject (the state-matrix page). */
|
|
12
|
+
focusOnMount?: boolean;
|
|
13
|
+
className?: string;
|
|
14
|
+
}): React.JSX.Element;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
export type FieldProps = {
|
|
3
|
+
label: string;
|
|
4
|
+
/** The control's id, when the page needs a known one (an anchor, a test). Generated otherwise. */
|
|
5
|
+
id?: string;
|
|
6
|
+
/** What the person needs to know before typing, such as limits or units. */
|
|
7
|
+
hint?: React.ReactNode;
|
|
8
|
+
/** What went wrong and how to fix it, in one sentence. Shown under the control, never in a toast. */
|
|
9
|
+
error?: string;
|
|
10
|
+
children: React.ReactNode;
|
|
11
|
+
className?: string;
|
|
12
|
+
};
|
|
13
|
+
/** A labelled form field: wires the label, hint, error, `aria-describedby` and `aria-invalid` into the one control it wraps. */
|
|
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`. */
|
|
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`. */
|
|
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. */
|
|
20
|
+
export declare function NativeSelect({ className, id, ...props }: React.ComponentProps<"select">): React.JSX.Element;
|
|
21
|
+
export type Choice = {
|
|
22
|
+
value: string;
|
|
23
|
+
label: string;
|
|
24
|
+
detail?: string;
|
|
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. */
|
|
27
|
+
export declare function ChoiceGroup({ legend, name, choices, value, onChange, error, }: {
|
|
28
|
+
legend: string;
|
|
29
|
+
name: string;
|
|
30
|
+
choices: Choice[];
|
|
31
|
+
value: string;
|
|
32
|
+
onChange: (value: string) => void;
|
|
33
|
+
error?: string;
|
|
34
|
+
}): React.JSX.Element;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
export type Point = {
|
|
3
|
+
x: number;
|
|
4
|
+
y: number;
|
|
5
|
+
};
|
|
6
|
+
export type LineChartProps = {
|
|
7
|
+
/** What the chart shows, as a sentence. Used as the accessible name and the table caption. */
|
|
8
|
+
label: string;
|
|
9
|
+
series: Point[];
|
|
10
|
+
seriesLabel: string;
|
|
11
|
+
planned?: Point[];
|
|
12
|
+
plannedLabel?: string;
|
|
13
|
+
band?: {
|
|
14
|
+
below: number;
|
|
15
|
+
label: string;
|
|
16
|
+
};
|
|
17
|
+
formatX: (x: number) => string;
|
|
18
|
+
formatY: (y: number) => string;
|
|
19
|
+
xTicks: number[];
|
|
20
|
+
/** CSS colour for the measured series. Defaults to the first chart token. */
|
|
21
|
+
colour?: string;
|
|
22
|
+
/** Colour the measured series by value: each stop applies from `at` upwards, blending to the
|
|
23
|
+
* next. Only where the colour means something in the subject; record the reason in DESIGN.md. */
|
|
24
|
+
colourScale?: {
|
|
25
|
+
at: number;
|
|
26
|
+
colour: string;
|
|
27
|
+
}[];
|
|
28
|
+
/** Says what the colours mean, shown beside the legend swatch when `colourScale` is set. */
|
|
29
|
+
colourScaleLabel?: string;
|
|
30
|
+
height?: number;
|
|
31
|
+
className?: string;
|
|
32
|
+
};
|
|
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
|
+
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`. */
|
|
36
|
+
export declare function LineChart({ label, series, seriesLabel, planned, plannedLabel, band, formatX, formatY, xTicks, colour, colourScale, colourScaleLabel, height, className, }: LineChartProps): React.JSX.Element;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
/** Headline figures as one description list split by hairlines. Holds `Metric`s. */
|
|
3
|
+
export declare function MetricStrip({ label, children, className }: {
|
|
4
|
+
label: string;
|
|
5
|
+
children: React.ReactNode;
|
|
6
|
+
className?: string;
|
|
7
|
+
}): React.JSX.Element;
|
|
8
|
+
export type MetricProps = {
|
|
9
|
+
label: string;
|
|
10
|
+
/** Already formatted. Ink colour, 28px, tabular figures: never the brand colour, never 48px. */
|
|
11
|
+
value: string;
|
|
12
|
+
unit?: string;
|
|
13
|
+
/** One sentence: the change in words with its window, or what the figure means right now. */
|
|
14
|
+
note?: React.ReactNode;
|
|
15
|
+
};
|
|
16
|
+
/** One figure: label over value, an optional muted unit and a note that states any change in words with its window. */
|
|
17
|
+
export declare function Metric({ label, value, unit, note }: MetricProps): React.JSX.Element;
|
|
@@ -0,0 +1,14 @@
|
|
|
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). */
|
|
3
|
+
export declare function PageHeader({ title, scope, actions, finding, as: Heading, }: {
|
|
4
|
+
title: string;
|
|
5
|
+
/** The title's element: h1 on a screen, h2 where the header is shown inside another page. */
|
|
6
|
+
as?: "h1" | "h2";
|
|
7
|
+
/** What the screen covers: a date, a range, a filter. */
|
|
8
|
+
scope?: React.ReactNode;
|
|
9
|
+
actions?: React.ReactNode;
|
|
10
|
+
finding?: {
|
|
11
|
+
text: React.ReactNode;
|
|
12
|
+
action?: React.ReactNode;
|
|
13
|
+
};
|
|
14
|
+
}): React.JSX.Element;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { IconComponent } from '../lib/icon';
|
|
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. */
|
|
4
|
+
export declare function Status({ tone, icon: Icon, children, className, }: {
|
|
5
|
+
tone: Tone;
|
|
6
|
+
icon?: IconComponent;
|
|
7
|
+
children: string;
|
|
8
|
+
className?: string;
|
|
9
|
+
}): import('react').JSX.Element;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
export type TabItem = {
|
|
3
|
+
value: string;
|
|
4
|
+
label: string;
|
|
5
|
+
};
|
|
6
|
+
/** Tabs whose selection lives in a URL search parameter (React Router), with the ARIA tabs keyboard model. */
|
|
7
|
+
export declare function Tabs({ param, items, label, children, className, }: {
|
|
8
|
+
/** The search parameter that holds the selected tab. */
|
|
9
|
+
param: string;
|
|
10
|
+
items: TabItem[];
|
|
11
|
+
/** Names the tab list for screen readers. */
|
|
12
|
+
label: string;
|
|
13
|
+
/** Renders the selected panel. */
|
|
14
|
+
children: (value: string) => React.ReactNode;
|
|
15
|
+
className?: string;
|
|
16
|
+
}): React.JSX.Element;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
/** A removal waiting out its undo window: what to say, and what to do when the window closes. */
|
|
3
|
+
export type PendingRemoval = {
|
|
4
|
+
id: string;
|
|
5
|
+
message: string;
|
|
6
|
+
commit: () => void;
|
|
7
|
+
};
|
|
8
|
+
/** Schedules a reversible removal to commit after `windowMs` unless undone. A second removal, or unmounting, commits the pending one. */
|
|
9
|
+
export declare function useUndoable(windowMs?: number): {
|
|
10
|
+
pending: PendingRemoval | null;
|
|
11
|
+
schedule: (next: PendingRemoval) => void;
|
|
12
|
+
undo: () => void;
|
|
13
|
+
};
|
|
14
|
+
/** The polite live region that shows a pending removal with an Undo button. */
|
|
15
|
+
export declare function UndoBar({ pending, onUndo, inline, }: {
|
|
16
|
+
pending: PendingRemoval | null;
|
|
17
|
+
onUndo: () => void;
|
|
18
|
+
/** Render in the flow instead of fixed to the viewport; for the state-matrix page. */
|
|
19
|
+
inline?: boolean;
|
|
20
|
+
}): React.JSX.Element;
|
|
@@ -0,0 +1,12 @@
|
|
|
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. */
|
|
3
|
+
export declare function VirtualList<T>({ label, items, itemKey, children, estimateSize, height, virtualizeFrom, className, }: {
|
|
4
|
+
label: string;
|
|
5
|
+
items: T[];
|
|
6
|
+
itemKey: (item: T) => string;
|
|
7
|
+
children: (item: T, index: number) => React.ReactNode;
|
|
8
|
+
estimateSize?: number;
|
|
9
|
+
height?: number;
|
|
10
|
+
virtualizeFrom?: number;
|
|
11
|
+
className?: string;
|
|
12
|
+
}): React.JSX.Element;
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
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
|
+
*/
|
|
8
|
+
export { cn, typeSteps } from './lib/utils';
|
|
9
|
+
export { createFormat, format, NBSP, type Format } from './lib/format';
|
|
10
|
+
export type { IconComponent } from './lib/icon';
|
|
11
|
+
export { Button, IconButton, buttonVariants, type ButtonProps, type IconButtonProps } from './components/button';
|
|
12
|
+
export { AsyncButton, useAsyncAction, type Phase, type AsyncLabels } from './components/async-button';
|
|
13
|
+
export { Field, Input, Textarea, NativeSelect, ChoiceGroup, type FieldProps, type Choice } from './components/field';
|
|
14
|
+
export { Status, type Tone } from './components/status';
|
|
15
|
+
export { Metric, MetricStrip, type MetricProps } from './components/metric';
|
|
16
|
+
export { DataView, EmptyState, Skeleton, type Load } from './components/data-view';
|
|
17
|
+
export { DataTable, TableSkeleton, parseSort, serialiseSort, type Column, type Sort, type DataTableProps } from './components/data-table';
|
|
18
|
+
export { VirtualList } from './components/virtual-list';
|
|
19
|
+
export { LineChart, type LineChartProps, type Point } from './components/line-chart';
|
|
20
|
+
export { PageHeader } from './components/page-header';
|
|
21
|
+
export { UndoBar, useUndoable, type PendingRemoval } from './components/undo';
|
|
22
|
+
export { Done } from './components/done';
|
|
23
|
+
export { AppShell, SideNav, Breadcrumb, type NavItem } from './components/app-shell';
|
|
24
|
+
export { Tabs, type TabItem } from './components/tabs';
|
|
25
|
+
export { StrataQueryProvider, createQueryClient, toLoad, useLoad, fetchJson, HttpError, describeHttpError, type DescribeError, } from './query';
|