@jirawatpyk/aura-react 5.3.0 → 5.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +25 -3
- package/dist/aura.bundle.js +11 -2
- package/dist/cjs/index.cjs +11 -2
- package/dist/esm/DataTable.js +3 -2
- package/dist/esm/Select.js +9 -1
- package/dist/index.d.cts +43 -16
- package/dist/index.d.ts +43 -16
- package/package.json +14 -14
- package/scripts/aura-theme.mjs +20 -8
package/README.md
CHANGED
|
@@ -41,7 +41,7 @@ npm i @jirawatpyk/aura-react @jirawatpyk/aura-tokens # React 18 or 19; import
|
|
|
41
41
|
npm i @aura/react@npm:@jirawatpyk/aura-react @aura/tokens@npm:@jirawatpyk/aura-tokens
|
|
42
42
|
```
|
|
43
43
|
|
|
44
|
-
React 18.3 and 19 are both tested: the dev dependencies pin 18, and CI switches the whole workspace to 19 (`node scripts/use-react.
|
|
44
|
+
React 18.3 and 19 are both tested: the dev dependencies pin 18, and CI switches the whole workspace to 19 (`node scripts/use-react.mts 19 && npm install`) and runs every suite again — contrast, token lint, SSR, types, build, hydration with 0 warnings, layout before hydration, the three pilot pages and Storybook (axe + behaviour).
|
|
45
45
|
|
|
46
46
|
Public on npmjs; internal projects can use GitHub Packages instead ([repository README](https://github.com/Jirawatpyk/Aura-design#use-it-in-a-project)).
|
|
47
47
|
|
|
@@ -186,6 +186,26 @@ DataTable with sort and page in the URL (4.17): `onStateChange={({ sort, page })
|
|
|
186
186
|
- **Time zone**: `timeZone` on AuraProvider, DatePicker, DateRangePicker and Calendar decides "today" (the marker, `min`/`max="today"`, the first month shown); or pass `today` as an ISO date. `todayIn('Asia/Bangkok')` is exported from the root and `/server`.
|
|
187
187
|
- **Toasts** queue past three instead of dropping: six in a row all show, in order, three at a time.
|
|
188
188
|
|
|
189
|
+
## 5.5 — DataTable is generic over its rows
|
|
190
|
+
|
|
191
|
+
- The type of `rows` is the table's row type, and every `render`, `sortValue`, `getRowHref` and `onRowActivate` gets it — no annotations, and a typo inside a callback (`r.amuont`) or a callback written for another type is a compile error (column `key`s stay plain strings):
|
|
192
|
+
|
|
193
|
+
```tsx
|
|
194
|
+
const columns: DataTableColumn<Order>[] = [{ key: 'amount', label: 'AMOUNT', align: 'end', render: (r) => baht(r.amount) }];
|
|
195
|
+
<DataTable label="Orders" rows={orders} columns={columns} onRowActivate={setDetail} getRowHref={(r) => '/orders/' + r.id} />
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
- `DataTableColumn<Row>` types a column list declared on its own; `<DataTable<Order> …>` names the type explicitly. `rows={[]}` (nothing loaded yet) keeps the untyped default, and so do `any[]`, `Record<string, any>[]` and anything reading `typeof DataTable` (`ComponentProps`, `memo`, wrappers).
|
|
199
|
+
- Two type-level changes: rows that are a union of arrays (`cond ? orders : invoices`) need the type named, `<DataTable<Order | Invoice> …>`; rows typed `Record<string, unknown>[]` give `render` an `unknown` value, so convert it (`String(r.n)`).
|
|
200
|
+
- Types only; nothing changes at run time. `typeof DataTable` is now a generic function type rather than `ForwardRefExoticComponent` (it still takes `ref`).
|
|
201
|
+
|
|
202
|
+
## 5.4 — types, and TypeScript across the repo
|
|
203
|
+
|
|
204
|
+
- **DataTable** takes rows of your own interface (`rows={orders}` with `interface Order {…}`; readonly arrays too), and `render`, `sortValue`, `getRowHref` and `onRowActivate` can be written with it: `render: (r: Order) => baht(r.amount)` — no cast from `Record<string, any>`.
|
|
205
|
+
- **FormErrorSummary** `errors` accepts a nested object written by hand (`{ address: { street: { message } } }`, arrays for field arrays), not only react-hook-form's `formState.errors`. New type `FormErrorTree`.
|
|
206
|
+
- **Select** `label` is optional when `aria-label` or `aria-labelledby` names the field (a toolbar, a table cell); development builds warn when it has no name at all. Every other field still requires `label`.
|
|
207
|
+
- The example pages (`examples/*`) are TypeScript and type-checked in CI, as are the stories and the repo's scripts; the pilot checks run on Playwright Test instead of Python.
|
|
208
|
+
|
|
189
209
|
## 5.3 — Select opens AURA's own list
|
|
190
210
|
|
|
191
211
|
- **Select** no longer hands its list to the operating system (a white list in dark mode on Windows, a different look on every OS). The field is a button that opens an AURA list like Combobox: tokens in light and dark, a check on the chosen item, `optgroup` headings, disabled options skipped, type to jump (Thai too), Home/End/PageUp/PageDown, Escape closes the list before a dialog. The same list is used on phones (as in shadcn), not the native picker.
|
|
@@ -311,11 +331,13 @@ Every optional prop takes `undefined` (4.16), so apps on `exactOptionalPropertyT
|
|
|
311
331
|
```bash
|
|
312
332
|
npm run build # dist/ (ESM, CJS, IIFE, CSS, types)
|
|
313
333
|
npm run test:ssr # server-render every component
|
|
314
|
-
npm run typecheck # tsc --strict over src/,
|
|
334
|
+
npm run typecheck # tsc --strict over src/, types-test/ against the published API, and the example pages
|
|
315
335
|
npm run size # gzip size of what projects import, against size-budgets.json
|
|
316
|
-
npm run test:pilots # builds the pilots + 4 brand themes, then
|
|
336
|
+
npm run test:pilots # builds the pilots + 4 brand themes, then 44 behaviour/axe checks at 1440/820/390px (Playwright, axe-core)
|
|
317
337
|
```
|
|
318
338
|
|
|
339
|
+
From the repository root, `npm run typecheck` also checks the Storybook stories (strict) and every build, test and release script. The scripts are TypeScript that Node runs as is (type stripping), so working on the repo needs **Node 22.18 or later**; the published packages still run on Node 18+ (their CLI and checkers stay JavaScript, type-checked through `// @ts-check`).
|
|
340
|
+
|
|
319
341
|
## Source
|
|
320
342
|
|
|
321
343
|
`src/` is TypeScript (strict) and JSX, formatted with Prettier (`npm run format`), **one file per component** (`Badge.tsx`, `Tooltip.tsx`, …) so a project that imports one component gets only that component's code. Public prop types live in `src/types.ts` with their docs; each component imports its props from there and `forwardRef`s with them, so the published `dist/index.d.ts` is generated from the code and can't drift from it. The build compiles JSX to `React.createElement` (classic runtime), so the `window.Aura` script needs only `window.React`.
|
package/dist/aura.bundle.js
CHANGED
|
@@ -68,7 +68,7 @@ window.Aura = (() => {
|
|
|
68
68
|
Combobox: () => Combobox,
|
|
69
69
|
Command: () => Command,
|
|
70
70
|
Container: () => Container,
|
|
71
|
-
DataTable: () =>
|
|
71
|
+
DataTable: () => DataTable2,
|
|
72
72
|
DatePicker: () => DatePicker,
|
|
73
73
|
DateRangePicker: () => DateRangePicker,
|
|
74
74
|
Dialog: () => Dialog,
|
|
@@ -1286,6 +1286,14 @@ window.Aura = (() => {
|
|
|
1286
1286
|
const mounted = useMounted();
|
|
1287
1287
|
const live = !native && mounted;
|
|
1288
1288
|
const density = useDensity();
|
|
1289
|
+
React13.useEffect(function() {
|
|
1290
|
+
if (props.label || props["aria-label"] || props["aria-labelledby"] || props.title) return;
|
|
1291
|
+
if (document.querySelector('label[for="' + id.replace(/["\\]/g, "\\$&") + '"]')) return;
|
|
1292
|
+
devWarnOnce(
|
|
1293
|
+
"select-name",
|
|
1294
|
+
"Select needs a label, aria-label or aria-labelledby: screen readers announce it with no name."
|
|
1295
|
+
);
|
|
1296
|
+
}, []);
|
|
1289
1297
|
const selRef = React13.useRef(null), trigRef = React13.useRef(null), listRef = React13.useRef(null);
|
|
1290
1298
|
const shown = React13.useState(function() {
|
|
1291
1299
|
return initialLabel(props);
|
|
@@ -3974,7 +3982,7 @@ window.Aura = (() => {
|
|
|
3974
3982
|
var ROW_H_DEFAULT = 48;
|
|
3975
3983
|
var OVERSCAN = 8;
|
|
3976
3984
|
var FLEX_MIN = 160;
|
|
3977
|
-
var
|
|
3985
|
+
var DataTableImpl = React28.forwardRef(function DataTable(props, ref) {
|
|
3978
3986
|
const t = useStrings();
|
|
3979
3987
|
const columns = props.columns || DEFAULT_COLUMNS;
|
|
3980
3988
|
const byKey = {};
|
|
@@ -5209,6 +5217,7 @@ window.Aura = (() => {
|
|
|
5209
5217
|
}
|
|
5210
5218
|
return /* @__PURE__ */ React28.createElement("div", { ref, className: cx("aura-table-box", props.className) }, out);
|
|
5211
5219
|
});
|
|
5220
|
+
var DataTable2 = DataTableImpl;
|
|
5212
5221
|
|
|
5213
5222
|
// src/Card.tsx
|
|
5214
5223
|
var React29 = __toESM(require_react(), 1);
|
package/dist/cjs/index.cjs
CHANGED
|
@@ -49,7 +49,7 @@ __export(index_exports, {
|
|
|
49
49
|
Combobox: () => Combobox,
|
|
50
50
|
Command: () => Command,
|
|
51
51
|
Container: () => Container,
|
|
52
|
-
DataTable: () =>
|
|
52
|
+
DataTable: () => DataTable2,
|
|
53
53
|
DatePicker: () => DatePicker,
|
|
54
54
|
DateRangePicker: () => DateRangePicker,
|
|
55
55
|
Dialog: () => Dialog,
|
|
@@ -1268,6 +1268,14 @@ var Select = React13.forwardRef(function Select2(props, ref) {
|
|
|
1268
1268
|
const mounted = useMounted();
|
|
1269
1269
|
const live = !native && mounted;
|
|
1270
1270
|
const density = useDensity();
|
|
1271
|
+
React13.useEffect(function() {
|
|
1272
|
+
if (props.label || props["aria-label"] || props["aria-labelledby"] || props.title) return;
|
|
1273
|
+
if (document.querySelector('label[for="' + id.replace(/["\\]/g, "\\$&") + '"]')) return;
|
|
1274
|
+
devWarnOnce(
|
|
1275
|
+
"select-name",
|
|
1276
|
+
"Select needs a label, aria-label or aria-labelledby: screen readers announce it with no name."
|
|
1277
|
+
);
|
|
1278
|
+
}, []);
|
|
1271
1279
|
const selRef = React13.useRef(null), trigRef = React13.useRef(null), listRef = React13.useRef(null);
|
|
1272
1280
|
const shown = React13.useState(function() {
|
|
1273
1281
|
return initialLabel(props);
|
|
@@ -3956,7 +3964,7 @@ function hidePx(c) {
|
|
|
3956
3964
|
var ROW_H_DEFAULT = 48;
|
|
3957
3965
|
var OVERSCAN = 8;
|
|
3958
3966
|
var FLEX_MIN = 160;
|
|
3959
|
-
var
|
|
3967
|
+
var DataTableImpl = React28.forwardRef(function DataTable(props, ref) {
|
|
3960
3968
|
const t = useStrings();
|
|
3961
3969
|
const columns = props.columns || DEFAULT_COLUMNS;
|
|
3962
3970
|
const byKey = {};
|
|
@@ -5191,6 +5199,7 @@ var DataTable = React28.forwardRef(function DataTable2(props, ref) {
|
|
|
5191
5199
|
}
|
|
5192
5200
|
return /* @__PURE__ */ React28.createElement("div", { ref, className: cx("aura-table-box", props.className) }, out);
|
|
5193
5201
|
});
|
|
5202
|
+
var DataTable2 = DataTableImpl;
|
|
5194
5203
|
|
|
5195
5204
|
// src/Card.tsx
|
|
5196
5205
|
var React29 = __toESM(require("react"), 1);
|
package/dist/esm/DataTable.js
CHANGED
|
@@ -27,7 +27,7 @@ function hidePx(c) {
|
|
|
27
27
|
return px != null && px > 0 ? px : void 0;
|
|
28
28
|
}
|
|
29
29
|
const ROW_H_DEFAULT = 48, OVERSCAN = 8, FLEX_MIN = 160;
|
|
30
|
-
const
|
|
30
|
+
const DataTableImpl = React.forwardRef(function DataTable(props, ref) {
|
|
31
31
|
const t = useStrings();
|
|
32
32
|
const columns = props.columns || DEFAULT_COLUMNS;
|
|
33
33
|
const byKey = {};
|
|
@@ -1262,6 +1262,7 @@ const DataTable = React.forwardRef(function DataTable2(props, ref) {
|
|
|
1262
1262
|
}
|
|
1263
1263
|
return /* @__PURE__ */ React.createElement("div", { ref, className: cx("aura-table-box", props.className) }, out);
|
|
1264
1264
|
});
|
|
1265
|
+
const DataTable2 = DataTableImpl;
|
|
1265
1266
|
export {
|
|
1266
|
-
DataTable
|
|
1267
|
+
DataTable2 as DataTable
|
|
1267
1268
|
};
|
package/dist/esm/Select.js
CHANGED
|
@@ -4,7 +4,7 @@ import { createPortal } from "react-dom";
|
|
|
4
4
|
import { FIELD_KEYS, Field, describedBy } from "./Field.js";
|
|
5
5
|
import { Icon } from "./Icon.js";
|
|
6
6
|
import { useDensity } from "./locale.js";
|
|
7
|
-
import { cx, omit, uid, useIsoLayoutEffect, useMergedRef, useMounted } from "./internal.js";
|
|
7
|
+
import { cx, devWarnOnce, omit, uid, useIsoLayoutEffect, useMergedRef, useMounted } from "./internal.js";
|
|
8
8
|
const h = React.createElement;
|
|
9
9
|
const FORM_KEYS = ["name", "form", "value", "defaultValue", "disabled", "autoComplete", "onInput", "onInvalid"];
|
|
10
10
|
const HANDLED_KEYS = FORM_KEYS.concat([
|
|
@@ -66,6 +66,14 @@ const Select = React.forwardRef(function Select2(props, ref) {
|
|
|
66
66
|
const mounted = useMounted();
|
|
67
67
|
const live = !native && mounted;
|
|
68
68
|
const density = useDensity();
|
|
69
|
+
React.useEffect(function() {
|
|
70
|
+
if (props.label || props["aria-label"] || props["aria-labelledby"] || props.title) return;
|
|
71
|
+
if (document.querySelector('label[for="' + id.replace(/["\\]/g, "\\$&") + '"]')) return;
|
|
72
|
+
devWarnOnce(
|
|
73
|
+
"select-name",
|
|
74
|
+
"Select needs a label, aria-label or aria-labelledby: screen readers announce it with no name."
|
|
75
|
+
);
|
|
76
|
+
}, []);
|
|
69
77
|
const selRef = React.useRef(null), trigRef = React.useRef(null), listRef = React.useRef(null);
|
|
70
78
|
const shown = React.useState(function() {
|
|
71
79
|
return initialLabel(props);
|
package/dist/index.d.cts
CHANGED
|
@@ -46,7 +46,18 @@ export interface DataTableState {
|
|
|
46
46
|
sort: DataTableSort | null;
|
|
47
47
|
page: number;
|
|
48
48
|
}
|
|
49
|
-
|
|
49
|
+
/** Internal helper (exported only because the declarations reference it): `T` as written, but never inferred from
|
|
50
|
+
* this position (works before TypeScript 5.4's own `NoInfer`), so a table's row type comes from its `rows`. */
|
|
51
|
+
export type NoInferRow<T> = [
|
|
52
|
+
T
|
|
53
|
+
][T extends any ? 0 : never];
|
|
54
|
+
/** A callback that gets one row of type `Row` and returns `T`. Checked the way method parameters are, so a callback
|
|
55
|
+
* written for a narrower or wider row type still fits; one written for an unrelated type is an error (5.5). */
|
|
56
|
+
export type RowCallback<T, Row = Record<string, any>> = {
|
|
57
|
+
bivarianceHack(row: NoInferRow<Row>): T;
|
|
58
|
+
}["bivarianceHack"];
|
|
59
|
+
/** One column. `Row` is the table's row type (5.5): `DataTableColumn<Order>` types `render` and `sortValue`. */
|
|
60
|
+
export interface DataTableColumn<Row extends Record<string, any> = Record<string, any>> {
|
|
50
61
|
/** Key into each row object. */
|
|
51
62
|
key: string;
|
|
52
63
|
/** Header label (source style: UPPERCASE). */
|
|
@@ -62,9 +73,9 @@ export interface DataTableColumn {
|
|
|
62
73
|
/** Header becomes a button cycling asc → desc → unsorted. */
|
|
63
74
|
sortable?: boolean | undefined;
|
|
64
75
|
/** Value to sort by, when not the cell value (dates, amounts). */
|
|
65
|
-
sortValue?:
|
|
76
|
+
sortValue?: RowCallback<string | number | null, Row> | undefined;
|
|
66
77
|
/** Custom cell content. */
|
|
67
|
-
render?:
|
|
78
|
+
render?: RowCallback<React$1.ReactNode, Row> | undefined;
|
|
68
79
|
/** Set false to keep a sized column fixed when the table is `resizable`. */
|
|
69
80
|
resizable?: boolean | undefined;
|
|
70
81
|
/** Resize limits in px. Defaults 64 / 480. For the flexible column, minWidth defaults to 160. */
|
|
@@ -94,11 +105,14 @@ export interface DataTableEmpty {
|
|
|
94
105
|
/** One action, usually a secondary Button. */
|
|
95
106
|
action?: React$1.ReactNode | undefined;
|
|
96
107
|
}
|
|
97
|
-
/** Enterprise data table: 48px rows, hairline dividers, mono header band.
|
|
98
|
-
|
|
108
|
+
/** Enterprise data table: 48px rows, hairline dividers, mono header band. Generic over the row type (5.5): with
|
|
109
|
+
* `rows={orders}` every `render`, `sortValue`, `getRowHref` and `onRowActivate` gets an `Order`. */
|
|
110
|
+
export interface DataTableProps<Row extends Record<string, any> = Record<string, any>> {
|
|
99
111
|
/** Defaults to ID · NAME · STATUS · OWNER (96 / 160 / 112 / auto px). */
|
|
100
|
-
columns?: DataTableColumn[] | undefined;
|
|
101
|
-
|
|
112
|
+
columns?: DataTableColumn<Row>[] | undefined;
|
|
113
|
+
/** Row objects, of any interface; their type is the table's row type. Cells show the value at each column's key
|
|
114
|
+
* unless the column has `render`. */
|
|
115
|
+
rows: ReadonlyArray<Row>;
|
|
102
116
|
/** Column key giving each row a unique React key. Default: the first column's key. */
|
|
103
117
|
rowKey?: string | undefined;
|
|
104
118
|
/** Accessible name for the table. */
|
|
@@ -145,7 +159,7 @@ export interface DataTableProps {
|
|
|
145
159
|
totalRows?: number | undefined;
|
|
146
160
|
/** Makes each row a link: the first column's content renders as the provider's linkComponent (or `<a>`), and a click
|
|
147
161
|
* or Enter anywhere on the row follows it. Ctrl/⌘-click opens a new tab as usual. */
|
|
148
|
-
getRowHref?:
|
|
162
|
+
getRowHref?: RowCallback<string, Row> | undefined;
|
|
149
163
|
/** Pager arrows become links to these URLs (search-param paging). Without onPageChange the link navigates. */
|
|
150
164
|
getPageHref?: ((page: number) => string) | undefined;
|
|
151
165
|
/** Router link for getRowHref / getPageHref. Default: AuraProvider's linkComponent, else `<a>`. */
|
|
@@ -153,7 +167,7 @@ export interface DataTableProps {
|
|
|
153
167
|
/** Skeleton row count when there is no pageSize. Default 5. */
|
|
154
168
|
skeletonRows?: number | undefined;
|
|
155
169
|
/** Called on row click or Enter. */
|
|
156
|
-
onRowActivate?:
|
|
170
|
+
onRowActivate?: RowCallback<void, Row> | undefined;
|
|
157
171
|
/** Fixed height in px: sticky header, and only the rows in view are rendered. */
|
|
158
172
|
height?: number | undefined;
|
|
159
173
|
/** Column menu on every header, drag-to-reorder, and the show/hide columns button. */
|
|
@@ -321,11 +335,14 @@ export interface FormErrorItem {
|
|
|
321
335
|
message: React$1.ReactNode;
|
|
322
336
|
}
|
|
323
337
|
/** The list of problems at the top of a form after a failed submit; each links to its field (GOV.UK pattern). */
|
|
338
|
+
/** react-hook-form's `formState.errors` (FieldErrors) or any object shaped like it: a `message` at any depth. */
|
|
339
|
+
export type FormErrorTree = {
|
|
340
|
+
readonly [field: string]: object | undefined;
|
|
341
|
+
};
|
|
324
342
|
export interface FormErrorSummaryProps {
|
|
325
|
-
/** A list, or react-hook-form's `formState.errors` as is
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
} | undefined>;
|
|
343
|
+
/** A list, or react-hook-form's `formState.errors` as is: `{ name: { message } }`, nested for `address.street`
|
|
344
|
+
* and field arrays (`items.0.name`). Empty → renders nothing. */
|
|
345
|
+
errors: FormErrorItem[] | FormErrorTree;
|
|
329
346
|
/** Default "Fix N fields to continue" in the provider's language. */
|
|
330
347
|
title?: React$1.ReactNode | undefined;
|
|
331
348
|
/** Called with the field when a link is followed, e.g. react-hook-form's `setFocus`. Default: focus the element whose id (or name) is the field. */
|
|
@@ -419,7 +436,10 @@ export type SelectOption = string | {
|
|
|
419
436
|
};
|
|
420
437
|
/** 5.3: opens AURA's own list (light and dark alike) over a real `<select>`, which keeps `name`, `required`, the ref,
|
|
421
438
|
* `onChange`, react-hook-form `register` and form posts. `multiple` or `size > 1` keeps the native list box. */
|
|
422
|
-
export interface SelectProps extends FieldProps, Omit<React$1.SelectHTMLAttributes<HTMLSelectElement>, "required"> {
|
|
439
|
+
export interface SelectProps extends Omit<FieldProps, "label">, Omit<React$1.SelectHTMLAttributes<HTMLSelectElement>, "required"> {
|
|
440
|
+
/** Visible label; also the accessible name. Omit only with `aria-label` or `aria-labelledby` (a toolbar, a table
|
|
441
|
+
* cell); development builds warn when the field has no name at all. */
|
|
442
|
+
label?: string | undefined;
|
|
423
443
|
/** The choices; or pass `<option>` / `<optgroup>` children instead (5.3: optional). */
|
|
424
444
|
options?: SelectOption[] | undefined;
|
|
425
445
|
/** Shown in fg-tertiary on the closed field until something is chosen; not listed as a choice. */
|
|
@@ -1277,8 +1297,15 @@ export declare const Tooltip: React$1.ForwardRefExoticComponent<TooltipProps & R
|
|
|
1277
1297
|
export declare const Dialog: React$1.ForwardRefExoticComponent<DialogProps & React$1.RefAttributes<HTMLDivElement>>;
|
|
1278
1298
|
/** Side panel over the page: record detail, filters, mobile navigation. Modal (focus trap, scroll lock, focus restore). */
|
|
1279
1299
|
export declare const Drawer: React$1.ForwardRefExoticComponent<DrawerProps & React$1.RefAttributes<HTMLDivElement>>;
|
|
1280
|
-
/** Enterprise data table: 48px rows, hairline dividers, mono header band.
|
|
1281
|
-
|
|
1300
|
+
/** Enterprise data table: 48px rows, hairline dividers, mono header band. Generic over the row type (5.5): the
|
|
1301
|
+
* type of `rows` types every `render`, `sortValue`, `getRowHref` and `onRowActivate`. */
|
|
1302
|
+
export declare const DataTable: {
|
|
1303
|
+
(props: DataTableProps & {
|
|
1304
|
+
rows: readonly never[];
|
|
1305
|
+
} & React$1.RefAttributes<HTMLDivElement>): React$1.ReactElement | null;
|
|
1306
|
+
<Row extends Record<string, any> = Record<string, any>>(props: DataTableProps<Row> & React$1.RefAttributes<HTMLDivElement>): React$1.ReactElement | null;
|
|
1307
|
+
displayName?: string | undefined;
|
|
1308
|
+
};
|
|
1282
1309
|
export declare const Card: React$1.ForwardRefExoticComponent<CardProps & React$1.RefAttributes<HTMLElement>>;
|
|
1283
1310
|
export declare const Tabs: React$1.ForwardRefExoticComponent<TabsProps & React$1.RefAttributes<HTMLDivElement>>;
|
|
1284
1311
|
/** Side navigation: sections of links or buttons, collapsible groups, counts and badges. Arrow keys move between items. */
|
package/dist/index.d.ts
CHANGED
|
@@ -46,7 +46,18 @@ export interface DataTableState {
|
|
|
46
46
|
sort: DataTableSort | null;
|
|
47
47
|
page: number;
|
|
48
48
|
}
|
|
49
|
-
|
|
49
|
+
/** Internal helper (exported only because the declarations reference it): `T` as written, but never inferred from
|
|
50
|
+
* this position (works before TypeScript 5.4's own `NoInfer`), so a table's row type comes from its `rows`. */
|
|
51
|
+
export type NoInferRow<T> = [
|
|
52
|
+
T
|
|
53
|
+
][T extends any ? 0 : never];
|
|
54
|
+
/** A callback that gets one row of type `Row` and returns `T`. Checked the way method parameters are, so a callback
|
|
55
|
+
* written for a narrower or wider row type still fits; one written for an unrelated type is an error (5.5). */
|
|
56
|
+
export type RowCallback<T, Row = Record<string, any>> = {
|
|
57
|
+
bivarianceHack(row: NoInferRow<Row>): T;
|
|
58
|
+
}["bivarianceHack"];
|
|
59
|
+
/** One column. `Row` is the table's row type (5.5): `DataTableColumn<Order>` types `render` and `sortValue`. */
|
|
60
|
+
export interface DataTableColumn<Row extends Record<string, any> = Record<string, any>> {
|
|
50
61
|
/** Key into each row object. */
|
|
51
62
|
key: string;
|
|
52
63
|
/** Header label (source style: UPPERCASE). */
|
|
@@ -62,9 +73,9 @@ export interface DataTableColumn {
|
|
|
62
73
|
/** Header becomes a button cycling asc → desc → unsorted. */
|
|
63
74
|
sortable?: boolean | undefined;
|
|
64
75
|
/** Value to sort by, when not the cell value (dates, amounts). */
|
|
65
|
-
sortValue?:
|
|
76
|
+
sortValue?: RowCallback<string | number | null, Row> | undefined;
|
|
66
77
|
/** Custom cell content. */
|
|
67
|
-
render?:
|
|
78
|
+
render?: RowCallback<React$1.ReactNode, Row> | undefined;
|
|
68
79
|
/** Set false to keep a sized column fixed when the table is `resizable`. */
|
|
69
80
|
resizable?: boolean | undefined;
|
|
70
81
|
/** Resize limits in px. Defaults 64 / 480. For the flexible column, minWidth defaults to 160. */
|
|
@@ -94,11 +105,14 @@ export interface DataTableEmpty {
|
|
|
94
105
|
/** One action, usually a secondary Button. */
|
|
95
106
|
action?: React$1.ReactNode | undefined;
|
|
96
107
|
}
|
|
97
|
-
/** Enterprise data table: 48px rows, hairline dividers, mono header band.
|
|
98
|
-
|
|
108
|
+
/** Enterprise data table: 48px rows, hairline dividers, mono header band. Generic over the row type (5.5): with
|
|
109
|
+
* `rows={orders}` every `render`, `sortValue`, `getRowHref` and `onRowActivate` gets an `Order`. */
|
|
110
|
+
export interface DataTableProps<Row extends Record<string, any> = Record<string, any>> {
|
|
99
111
|
/** Defaults to ID · NAME · STATUS · OWNER (96 / 160 / 112 / auto px). */
|
|
100
|
-
columns?: DataTableColumn[] | undefined;
|
|
101
|
-
|
|
112
|
+
columns?: DataTableColumn<Row>[] | undefined;
|
|
113
|
+
/** Row objects, of any interface; their type is the table's row type. Cells show the value at each column's key
|
|
114
|
+
* unless the column has `render`. */
|
|
115
|
+
rows: ReadonlyArray<Row>;
|
|
102
116
|
/** Column key giving each row a unique React key. Default: the first column's key. */
|
|
103
117
|
rowKey?: string | undefined;
|
|
104
118
|
/** Accessible name for the table. */
|
|
@@ -145,7 +159,7 @@ export interface DataTableProps {
|
|
|
145
159
|
totalRows?: number | undefined;
|
|
146
160
|
/** Makes each row a link: the first column's content renders as the provider's linkComponent (or `<a>`), and a click
|
|
147
161
|
* or Enter anywhere on the row follows it. Ctrl/⌘-click opens a new tab as usual. */
|
|
148
|
-
getRowHref?:
|
|
162
|
+
getRowHref?: RowCallback<string, Row> | undefined;
|
|
149
163
|
/** Pager arrows become links to these URLs (search-param paging). Without onPageChange the link navigates. */
|
|
150
164
|
getPageHref?: ((page: number) => string) | undefined;
|
|
151
165
|
/** Router link for getRowHref / getPageHref. Default: AuraProvider's linkComponent, else `<a>`. */
|
|
@@ -153,7 +167,7 @@ export interface DataTableProps {
|
|
|
153
167
|
/** Skeleton row count when there is no pageSize. Default 5. */
|
|
154
168
|
skeletonRows?: number | undefined;
|
|
155
169
|
/** Called on row click or Enter. */
|
|
156
|
-
onRowActivate?:
|
|
170
|
+
onRowActivate?: RowCallback<void, Row> | undefined;
|
|
157
171
|
/** Fixed height in px: sticky header, and only the rows in view are rendered. */
|
|
158
172
|
height?: number | undefined;
|
|
159
173
|
/** Column menu on every header, drag-to-reorder, and the show/hide columns button. */
|
|
@@ -321,11 +335,14 @@ export interface FormErrorItem {
|
|
|
321
335
|
message: React$1.ReactNode;
|
|
322
336
|
}
|
|
323
337
|
/** The list of problems at the top of a form after a failed submit; each links to its field (GOV.UK pattern). */
|
|
338
|
+
/** react-hook-form's `formState.errors` (FieldErrors) or any object shaped like it: a `message` at any depth. */
|
|
339
|
+
export type FormErrorTree = {
|
|
340
|
+
readonly [field: string]: object | undefined;
|
|
341
|
+
};
|
|
324
342
|
export interface FormErrorSummaryProps {
|
|
325
|
-
/** A list, or react-hook-form's `formState.errors` as is
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
} | undefined>;
|
|
343
|
+
/** A list, or react-hook-form's `formState.errors` as is: `{ name: { message } }`, nested for `address.street`
|
|
344
|
+
* and field arrays (`items.0.name`). Empty → renders nothing. */
|
|
345
|
+
errors: FormErrorItem[] | FormErrorTree;
|
|
329
346
|
/** Default "Fix N fields to continue" in the provider's language. */
|
|
330
347
|
title?: React$1.ReactNode | undefined;
|
|
331
348
|
/** Called with the field when a link is followed, e.g. react-hook-form's `setFocus`. Default: focus the element whose id (or name) is the field. */
|
|
@@ -419,7 +436,10 @@ export type SelectOption = string | {
|
|
|
419
436
|
};
|
|
420
437
|
/** 5.3: opens AURA's own list (light and dark alike) over a real `<select>`, which keeps `name`, `required`, the ref,
|
|
421
438
|
* `onChange`, react-hook-form `register` and form posts. `multiple` or `size > 1` keeps the native list box. */
|
|
422
|
-
export interface SelectProps extends FieldProps, Omit<React$1.SelectHTMLAttributes<HTMLSelectElement>, "required"> {
|
|
439
|
+
export interface SelectProps extends Omit<FieldProps, "label">, Omit<React$1.SelectHTMLAttributes<HTMLSelectElement>, "required"> {
|
|
440
|
+
/** Visible label; also the accessible name. Omit only with `aria-label` or `aria-labelledby` (a toolbar, a table
|
|
441
|
+
* cell); development builds warn when the field has no name at all. */
|
|
442
|
+
label?: string | undefined;
|
|
423
443
|
/** The choices; or pass `<option>` / `<optgroup>` children instead (5.3: optional). */
|
|
424
444
|
options?: SelectOption[] | undefined;
|
|
425
445
|
/** Shown in fg-tertiary on the closed field until something is chosen; not listed as a choice. */
|
|
@@ -1277,8 +1297,15 @@ export declare const Tooltip: React$1.ForwardRefExoticComponent<TooltipProps & R
|
|
|
1277
1297
|
export declare const Dialog: React$1.ForwardRefExoticComponent<DialogProps & React$1.RefAttributes<HTMLDivElement>>;
|
|
1278
1298
|
/** Side panel over the page: record detail, filters, mobile navigation. Modal (focus trap, scroll lock, focus restore). */
|
|
1279
1299
|
export declare const Drawer: React$1.ForwardRefExoticComponent<DrawerProps & React$1.RefAttributes<HTMLDivElement>>;
|
|
1280
|
-
/** Enterprise data table: 48px rows, hairline dividers, mono header band.
|
|
1281
|
-
|
|
1300
|
+
/** Enterprise data table: 48px rows, hairline dividers, mono header band. Generic over the row type (5.5): the
|
|
1301
|
+
* type of `rows` types every `render`, `sortValue`, `getRowHref` and `onRowActivate`. */
|
|
1302
|
+
export declare const DataTable: {
|
|
1303
|
+
(props: DataTableProps & {
|
|
1304
|
+
rows: readonly never[];
|
|
1305
|
+
} & React$1.RefAttributes<HTMLDivElement>): React$1.ReactElement | null;
|
|
1306
|
+
<Row extends Record<string, any> = Record<string, any>>(props: DataTableProps<Row> & React$1.RefAttributes<HTMLDivElement>): React$1.ReactElement | null;
|
|
1307
|
+
displayName?: string | undefined;
|
|
1308
|
+
};
|
|
1282
1309
|
export declare const Card: React$1.ForwardRefExoticComponent<CardProps & React$1.RefAttributes<HTMLElement>>;
|
|
1283
1310
|
export declare const Tabs: React$1.ForwardRefExoticComponent<TabsProps & React$1.RefAttributes<HTMLDivElement>>;
|
|
1284
1311
|
/** Side navigation: sections of links or buttons, collapsible groups, counts and badges. Arrow keys move between items. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jirawatpyk/aura-react",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.5.0",
|
|
4
4
|
"description": "AURA Design System — React components (Enterprise Standard, Human Creative). Thai-first: Buddhist-era dates, Thai collation.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -44,20 +44,20 @@
|
|
|
44
44
|
"./dist/aura.bundle.js"
|
|
45
45
|
],
|
|
46
46
|
"scripts": {
|
|
47
|
-
"build": "node scripts/build.
|
|
48
|
-
"test:ssr": "node scripts/ssr-test.
|
|
49
|
-
"typecheck": "tsc -p tsconfig.src.json && tsc -p tsconfig.json && tsc -p tsconfig.exact.json",
|
|
50
|
-
"examples": "node examples/build.
|
|
51
|
-
"themes": "node scripts/test-themes.
|
|
52
|
-
"test:pilots": "npm run examples && npm run themes &&
|
|
47
|
+
"build": "node scripts/build.ts && node scripts/sync-bundle.ts",
|
|
48
|
+
"test:ssr": "node scripts/ssr-test.ts && node --conditions=react-server scripts/server-entry-test.ts",
|
|
49
|
+
"typecheck": "tsc -p tsconfig.src.json && tsc -p tsconfig.json && tsc -p tsconfig.exact.json && tsc -p examples/tsconfig.json",
|
|
50
|
+
"examples": "node examples/build.ts",
|
|
51
|
+
"themes": "node scripts/test-themes.ts",
|
|
52
|
+
"test:pilots": "npm run examples && npm run themes && playwright test -c tests/playwright.config.ts",
|
|
53
53
|
"theme": "node scripts/aura-theme.mjs",
|
|
54
54
|
"prepack": "npm run build",
|
|
55
|
-
"pretest:ssr": "node scripts/build.
|
|
56
|
-
"prethemes": "node scripts/build.
|
|
57
|
-
"test:hydrate": "node scripts/hydrate-test.
|
|
58
|
-
"size": "node scripts/size-check.
|
|
59
|
-
"preexamples": "node scripts/build.
|
|
60
|
-
"test:layout": "node scripts/layout-test.
|
|
55
|
+
"pretest:ssr": "node scripts/build.ts",
|
|
56
|
+
"prethemes": "node scripts/build.ts",
|
|
57
|
+
"test:hydrate": "node scripts/hydrate-test.ts",
|
|
58
|
+
"size": "node scripts/size-check.ts",
|
|
59
|
+
"preexamples": "node scripts/build.ts",
|
|
60
|
+
"test:layout": "node scripts/layout-test.ts"
|
|
61
61
|
},
|
|
62
62
|
"peerDependencies": {
|
|
63
63
|
"react": ">=18",
|
|
@@ -72,7 +72,7 @@
|
|
|
72
72
|
"react": "^18.3.1",
|
|
73
73
|
"react-dom": "^18.3.1",
|
|
74
74
|
"react-hook-form": "^7.53.0",
|
|
75
|
-
"typescript": "^5.
|
|
75
|
+
"typescript": "^5.8.0",
|
|
76
76
|
"@playwright/test": "^1.45.0"
|
|
77
77
|
},
|
|
78
78
|
"bin": {
|
package/scripts/aura-theme.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
// @ts-check
|
|
2
3
|
/* aura-theme — write a project theme CSS file from a brand colour.
|
|
3
4
|
* npx aura-theme --brand "#0ea5e9" [--signal "#facc15"] [--primary brand] [--name acme] [--selector .tenant-acme] [--out src/aura-theme.css]
|
|
4
5
|
* Prints every contrast check; exits 1 if any fails. */
|
|
@@ -7,39 +8,50 @@ import { createTheme } from '../dist/esm/theme.js';
|
|
|
7
8
|
|
|
8
9
|
const USAGE = 'Usage: aura-theme --brand "#0ea5e9" [--signal "#facc15"] [--primary ink|brand] [--name acme] [--selector .tenant] [--out theme.css]';
|
|
9
10
|
const KNOWN = ['brand', 'signal', 'primary', 'name', 'selector', 'out', 'help'];
|
|
11
|
+
/**
|
|
12
|
+
* @param {string} msg
|
|
13
|
+
* @returns {never}
|
|
14
|
+
*/
|
|
10
15
|
function fail(msg) {
|
|
11
16
|
console.error('aura-theme: ' + msg + '\n' + USAGE);
|
|
12
17
|
process.exit(2);
|
|
13
18
|
}
|
|
14
19
|
/* --key value and --key=value (5.2); a value-taking option with no value is an error, not `true`. */
|
|
20
|
+
/** @type {Record<string, string>} */
|
|
15
21
|
const args = {};
|
|
22
|
+
let help = false;
|
|
16
23
|
for (let i = 2; i < process.argv.length; i++) {
|
|
17
|
-
const a = process.argv[i];
|
|
24
|
+
const a = process.argv[i] || '';
|
|
18
25
|
if (!a.startsWith('--')) fail('unexpected "' + a + '"');
|
|
19
26
|
const eq = a.indexOf('=');
|
|
20
27
|
const k = eq > 0 ? a.slice(2, eq) : a.slice(2);
|
|
21
28
|
if (KNOWN.indexOf(k) < 0) fail('unknown option --' + k);
|
|
22
|
-
if (k === 'help') {
|
|
23
|
-
const
|
|
29
|
+
if (k === 'help') { help = true; continue; }
|
|
30
|
+
const next = process.argv[i + 1];
|
|
31
|
+
const v = eq > 0 ? a.slice(eq + 1) : next !== undefined && !next.startsWith('--') ? process.argv[++i] : undefined;
|
|
24
32
|
if (v === undefined || v === '') fail('--' + k + ' needs a value');
|
|
25
33
|
args[k] = v;
|
|
26
34
|
}
|
|
27
|
-
if (
|
|
35
|
+
if (help) {
|
|
28
36
|
console.log(USAGE);
|
|
29
37
|
process.exit(0);
|
|
30
38
|
}
|
|
31
39
|
if (!args.brand) fail('--brand is required');
|
|
32
40
|
for (const k of ['brand', 'signal']) {
|
|
33
|
-
|
|
34
|
-
if (
|
|
41
|
+
const c = args[k];
|
|
42
|
+
if (c !== undefined && /^(?:[0-9a-f]{3}|[0-9a-f]{6})$/i.test(c)) args[k] = '#' + c; /* a shell ate the # */
|
|
43
|
+
const hex = args[k];
|
|
44
|
+
if (hex !== undefined && !/^#(?:[0-9a-f]{3}|[0-9a-f]{6})$/i.test(hex)) fail('--' + k + ' must be a #rgb or #rrggbb colour (got "' + args[k] + '"; quote it in the shell: --' + k + ' "#0ea5e9")');
|
|
35
45
|
}
|
|
36
46
|
if (args.primary !== undefined && args.primary !== 'ink' && args.primary !== 'brand') fail('--primary is ink or brand');
|
|
37
|
-
|
|
47
|
+
/** @type {ReturnType<typeof createTheme>} */
|
|
48
|
+
let t;
|
|
49
|
+
let css = '';
|
|
38
50
|
try {
|
|
39
51
|
t = createTheme({ brand: args.brand, signal: args.signal, primary: args.primary, name: args.name });
|
|
40
52
|
css = t.css(args.selector);
|
|
41
53
|
} catch (e) {
|
|
42
|
-
fail(e.message);
|
|
54
|
+
fail(e instanceof Error ? e.message : String(e));
|
|
43
55
|
}
|
|
44
56
|
if (args.out) fs.writeFileSync(args.out, css); else process.stdout.write(css);
|
|
45
57
|
const log = args.out ? console.log : console.error;
|