@apliteni/apliteni-ui 0.25.3 → 0.27.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 +2 -2
- package/package.json +1 -1
- package/react/README.md +92 -2
- package/react/dist/index.css +0 -12
- package/react/dist/index.d.ts +73 -6
- package/react/dist/index.js +380 -114
- package/src/components/pagination.js +344 -0
- package/src/index.css +1 -0
- package/src/index.js +1 -0
- package/src/inline.js +2 -0
- package/src/styles/button.css +14 -0
- package/src/styles/input.css +6 -0
- package/src/styles/pagination.css +124 -0
- package/src/tokens/tokens.css +6 -0
package/README.md
CHANGED
|
@@ -107,7 +107,7 @@ import { tokensCss, topbarCss, cssText } from '@apliteni/apliteni-ui/inline';
|
|
|
107
107
|
|
|
108
108
|
## React components (stateful surfaces)
|
|
109
109
|
|
|
110
|
-
`DataTable`, `Modal`, `Button`, `Badge`, `Card` and `Icon` — same `.ui-*` classes,
|
|
110
|
+
`DataTable`, `Pagination`, `Modal`, `Button`, `Badge`, `Card` and `Icon` — same `.ui-*` classes,
|
|
111
111
|
same tokens, TypeScript types included. They ship as a **subpath of this package**,
|
|
112
112
|
not as a package of their own: one install, one version, one pin.
|
|
113
113
|
|
|
@@ -126,7 +126,7 @@ its tree.
|
|
|
126
126
|
|
|
127
127
|
```tsx
|
|
128
128
|
import '@apliteni/apliteni-ui/css'; // kit tokens + .ui-* classes
|
|
129
|
-
import '@apliteni/apliteni-ui/react/css'; // React components' shell styles (modal,
|
|
129
|
+
import '@apliteni/apliteni-ui/react/css'; // React components' shell styles (modal, sort control)
|
|
130
130
|
import { DataTable, Modal } from '@apliteni/apliteni-ui/react';
|
|
131
131
|
```
|
|
132
132
|
|
package/package.json
CHANGED
package/react/README.md
CHANGED
|
@@ -25,11 +25,19 @@ The kit declares no dependency on `react` or `react-dom`, so install them yourse
|
|
|
25
25
|
|
|
26
26
|
```tsx
|
|
27
27
|
import '@apliteni/apliteni-ui/css'; // kit tokens + .ui-* classes
|
|
28
|
-
import '@apliteni/apliteni-ui/react/css'; // React components' shell styles (modal,
|
|
28
|
+
import '@apliteni/apliteni-ui/react/css'; // React components' shell styles (modal, sort control)
|
|
29
29
|
import { DataTable, Modal, Button } from '@apliteni/apliteni-ui/react';
|
|
30
30
|
```
|
|
31
31
|
|
|
32
|
-
Components: `DataTable`, `Modal`, `Button`, `Badge`, `Card`, `Icon`.
|
|
32
|
+
Components: `DataTable`, `Pagination`, `Modal`, `Button`, `Badge`, `Card`, `Icon`.
|
|
33
|
+
|
|
34
|
+
`Pagination` renders the kit's `pagination()` markup, class for class, so its styles come from
|
|
35
|
+
`@apliteni/apliteni-ui/css` rather than from this bundle. One deliberate difference: it takes no
|
|
36
|
+
`href`, because a React pager reports through `onPageChange` rather than navigating. Use the
|
|
37
|
+
vanilla factory where the steps have to be real links. `PAGE_SIZES` and
|
|
38
|
+
`DEFAULT_PAGE_SIZE` are exported here too — the scale is the kit's, so no call site writes
|
|
39
|
+
either number. They are declared in this package's own types, and `PAGE_SIZES` is a
|
|
40
|
+
`readonly number[]`: pass it to `pageSizes`, but do not add sizes to it.
|
|
33
41
|
|
|
34
42
|
## What the Modal does with focus
|
|
35
43
|
|
|
@@ -64,3 +72,85 @@ The bare `@apliteni/apliteni-ui` specifier in this source resolves to the kit it
|
|
|
64
72
|
once installed. In the repo there is no copy to resolve to, so `kit-alias.ts` points
|
|
65
73
|
vitest and Storybook straight at `../src/` — which is why the class-name parity tests
|
|
66
74
|
now compare against the working tree rather than the last published release.
|
|
75
|
+
|
|
76
|
+
### Tables with another presentation of the same rows
|
|
77
|
+
|
|
78
|
+
Pass `selectable={false}` to omit selection controls and their callbacks. Existing callers
|
|
79
|
+
that supply selection callbacks retain the checkbox column by default.
|
|
80
|
+
|
|
81
|
+
`sort` and `onSortChange` make sorting controlled. Both presentations can use the exported
|
|
82
|
+
`sortTableRows` helper, so initial order, stable ties and later changes agree:
|
|
83
|
+
|
|
84
|
+
```tsx
|
|
85
|
+
const [sort, setSort] = useState<TableSort<Row>>({ key: 'name', dir: -1 });
|
|
86
|
+
const ordered = sortTableRows(rows, sort);
|
|
87
|
+
|
|
88
|
+
<DataTable columns={columns} rows={rows} selectable={false}
|
|
89
|
+
sort={sort} onSortChange={setSort} />
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Render the sibling list from `ordered`. Omit `sort` to keep the table's own state;
|
|
93
|
+
`onSortChange` can also observe that uncontrolled state. Set `key: undefined` to preserve
|
|
94
|
+
input order. Import `TableSort` and `sortTableRows` from `@apliteni/apliteni-ui/react`.
|
|
95
|
+
|
|
96
|
+
Changing the sort returns the table to its first page. The comparator uses JavaScript
|
|
97
|
+
`<` and `>`; use consistently typed, comparable values in sortable columns. Ordering of
|
|
98
|
+
mixed types, missing values and `NaN` is not guaranteed. `sortTableRows` always returns
|
|
99
|
+
a new array, including when `key` is `undefined`.
|
|
100
|
+
|
|
101
|
+
Choose controlled or uncontrolled once per table. Passing `sort` for a while and then
|
|
102
|
+
dropping it is not supported: the table falls back to the sort state it started with, not to
|
|
103
|
+
the one it was last given.
|
|
104
|
+
|
|
105
|
+
### Tables paged by a server
|
|
106
|
+
|
|
107
|
+
`page` and `onPageChange` make pagination controlled, the same way `sort` does — and the same
|
|
108
|
+
rule applies: choose one mode per table and keep it. Given a `page`, the table renders `rows`
|
|
109
|
+
exactly as handed to it and never slices or re-orders them; the range comes from `page`,
|
|
110
|
+
`pageSize` and `total`:
|
|
111
|
+
|
|
112
|
+
```tsx
|
|
113
|
+
<DataTable columns={columns} rows={pageOfRows} selectable={false}
|
|
114
|
+
page={page} total={total} pageSize={size} onPageChange={fetchPage}
|
|
115
|
+
pageSizes={PAGE_SIZES} onPageSizeChange={setSize} loading={loading} />
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
`total` is required with `page`: it is the row count of the whole result, not of `rows`. Pass
|
|
119
|
+
`total={null}` for a result whose size is not known, and then `hasMore` is required too — the
|
|
120
|
+
pager offers Prev and Next alone, because no other control can be computed without a last
|
|
121
|
+
page. Leave both out and the call does not type-check: a pager told nothing can only draw two
|
|
122
|
+
dead buttons.
|
|
123
|
+
|
|
124
|
+
Without `pageSize`, a controlled table takes the page size from `rows.length`, the page it was
|
|
125
|
+
handed. Pass `pageSize` whenever the last page can be shorter than the rest.
|
|
126
|
+
|
|
127
|
+
A controlled table never sorts the rows it is handed. Changing the sort asks its owner for page
|
|
128
|
+
1 through `onPageChange`, which is the most a controlled table can do about it. Keep `sort`
|
|
129
|
+
controlled too, so the headers can say which column the server ordered by. Without it the
|
|
130
|
+
headers still report a press through `onSortChange`, but no column announces `aria-sort` or
|
|
131
|
+
draws a direction, because the table does not know the server's order.
|
|
132
|
+
|
|
133
|
+
Omit `page` to keep the table's own paging: it slices `rows` in memory and the total is
|
|
134
|
+
`rows.length`. `pageSizes` offers a size control in either mode — without a `pageSize` prop the
|
|
135
|
+
table remembers the size the reader picked, with one it reports the choice through
|
|
136
|
+
`onPageSizeChange` and shows what it is given. A table that owns its page returns the reader to
|
|
137
|
+
page 1 when the size changes. A controlled table only calls `onPageSizeChange`, once, and
|
|
138
|
+
leaves the page to its owner: a new size means page 1, so fetch page 1 at that size. It does
|
|
139
|
+
not also call `onPageChange(1)`, because that second call would carry the old size.
|
|
140
|
+
|
|
141
|
+
`pageSize` is read the way the pager reads it, so the rows and the range always agree: a
|
|
142
|
+
fraction is truncated, and `NaN`, zero or a negative number falls back to the default. A value
|
|
143
|
+
taken from a URL, such as `Number(params.get('size'))`, is safe to pass as it is.
|
|
144
|
+
|
|
145
|
+
`pager={false}` renders no pager at all, for a surface that supplies its own. One page of
|
|
146
|
+
content renders none either: the pager keeps GOV.UK's rule that pagination for a single page is
|
|
147
|
+
not shown, and with a size control on offer it keeps the row count and that control alone.
|
|
148
|
+
|
|
149
|
+
Focus stays on the step the reader pressed, so they can press it again. At an end that step is
|
|
150
|
+
disabled, and a browser drops focus from a disabled control to the page body. So once the new
|
|
151
|
+
page has arrived, the pager moves focus to the nearest step that can still move, never into
|
|
152
|
+
the rows. It moves focus only after a press in the pager, and never while `loading`.
|
|
153
|
+
Clearing the page-jump box, or typing into it and then pressing a step, does not change the
|
|
154
|
+
page.
|
|
155
|
+
|
|
156
|
+
`pageSize` defaults to `DEFAULT_PAGE_SIZE` (100). **Breaking:** it used to default to 4.
|
package/react/dist/index.css
CHANGED
|
@@ -80,15 +80,3 @@
|
|
|
80
80
|
.rx-caret {
|
|
81
81
|
margin-left: 2px;
|
|
82
82
|
}
|
|
83
|
-
.rx-pager {
|
|
84
|
-
display: flex;
|
|
85
|
-
align-items: center;
|
|
86
|
-
gap: 8px;
|
|
87
|
-
justify-content: flex-end;
|
|
88
|
-
margin-top: 16px;
|
|
89
|
-
}
|
|
90
|
-
.rx-pager__info {
|
|
91
|
-
color: var(--muted);
|
|
92
|
-
font-size: var(--text-sm);
|
|
93
|
-
margin-right: auto;
|
|
94
|
-
}
|
package/react/dist/index.d.ts
CHANGED
|
@@ -45,17 +45,81 @@ type Column<T> = {
|
|
|
45
45
|
sortable?: boolean;
|
|
46
46
|
render?: (row: T) => ReactNode;
|
|
47
47
|
};
|
|
48
|
-
type
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
48
|
+
type TableSort<T> = {
|
|
49
|
+
key: (keyof T & string) | undefined;
|
|
50
|
+
dir: 1 | -1;
|
|
51
|
+
};
|
|
52
|
+
type SelectionProps = {
|
|
53
|
+
selectable: false;
|
|
54
|
+
selected?: Set<string>;
|
|
55
|
+
onToggle?: (name: string) => void;
|
|
56
|
+
onTogglePage?: (names: string[]) => void;
|
|
57
|
+
} | {
|
|
58
|
+
selectable?: true;
|
|
52
59
|
selected: Set<string>;
|
|
53
60
|
onToggle: (name: string) => void;
|
|
54
61
|
onTogglePage: (names: string[]) => void;
|
|
55
62
|
};
|
|
63
|
+
type PagerProps = {
|
|
64
|
+
page?: never;
|
|
65
|
+
onPageChange?: (page: number) => void;
|
|
66
|
+
total?: never;
|
|
67
|
+
hasMore?: never;
|
|
68
|
+
} | {
|
|
69
|
+
page: number;
|
|
70
|
+
onPageChange: (page: number) => void;
|
|
71
|
+
total: number;
|
|
72
|
+
hasMore?: boolean;
|
|
73
|
+
} | {
|
|
74
|
+
page: number;
|
|
75
|
+
onPageChange: (page: number) => void;
|
|
76
|
+
total: null;
|
|
77
|
+
hasMore: boolean;
|
|
78
|
+
};
|
|
79
|
+
type DataTableProps<T> = {
|
|
80
|
+
columns: Column<T>[];
|
|
81
|
+
rows: T[];
|
|
82
|
+
pageSize?: number;
|
|
83
|
+
pageSizes?: readonly number[] | null;
|
|
84
|
+
onPageSizeChange?: (size: number) => void;
|
|
85
|
+
/** `false` renders no pager at all — for a surface that supplies its own. */
|
|
86
|
+
pager?: boolean;
|
|
87
|
+
/**
|
|
88
|
+
* The pager's accessible name. Two tables on one page otherwise publish two
|
|
89
|
+
* landmarks called "Pagination", and a reader listing the landmarks cannot tell
|
|
90
|
+
* which one moves which table. Axe will not catch it: `landmark-unique` is a
|
|
91
|
+
* best-practice rule and the kit's gate runs only the WCAG A/AA tags.
|
|
92
|
+
*/
|
|
93
|
+
pagerLabel?: string;
|
|
94
|
+
loading?: boolean;
|
|
95
|
+
} & SelectionProps & PagerProps & ({
|
|
96
|
+
sort?: never;
|
|
97
|
+
onSortChange?: (sort: TableSort<T>) => void;
|
|
98
|
+
} | {
|
|
99
|
+
sort: TableSort<T>;
|
|
100
|
+
onSortChange: (sort: TableSort<T>) => void;
|
|
101
|
+
});
|
|
102
|
+
declare function sortTableRows<T>(rows: T[], sort: TableSort<T>): T[];
|
|
56
103
|
declare function DataTable<T extends {
|
|
57
104
|
name: string;
|
|
58
|
-
}>({ columns, rows, pageSize, selected, onToggle, onTogglePage, }: DataTableProps<T>): react.JSX.Element;
|
|
105
|
+
}>({ columns, rows, pageSize, pageSizes, onPageSizeChange, pager, pagerLabel, loading, selectable, selected, onToggle, onTogglePage, sort: controlledSort, onSortChange, page: controlledPage, onPageChange, total, hasMore, }: DataTableProps<T>): react.JSX.Element;
|
|
106
|
+
|
|
107
|
+
type PaginationProps = {
|
|
108
|
+
page?: number;
|
|
109
|
+
pageSize?: number;
|
|
110
|
+
/** Rows in the whole result. `null` — the honest answer for a caller who cannot count. */
|
|
111
|
+
total?: number | null;
|
|
112
|
+
/** Read only when `total` is null: whether a page exists after this one. */
|
|
113
|
+
hasMore?: boolean;
|
|
114
|
+
pageSizes?: readonly number[] | null;
|
|
115
|
+
variant?: 'steps' | 'numbered' | 'jump';
|
|
116
|
+
label?: string;
|
|
117
|
+
loading?: boolean;
|
|
118
|
+
id?: string;
|
|
119
|
+
onPageChange?: (page: number) => void;
|
|
120
|
+
onPageSizeChange?: (size: number) => void;
|
|
121
|
+
};
|
|
122
|
+
declare function Pagination({ page, pageSize, total, hasMore, pageSizes, variant, label, loading, id, onPageChange, onPageSizeChange, }: PaginationProps): react.JSX.Element | null;
|
|
59
123
|
|
|
60
124
|
type SkeletonProps = {
|
|
61
125
|
/** A count of bars, or explicit widths when a ragged prose edge matters. */
|
|
@@ -96,4 +160,7 @@ type DeniedProps = {
|
|
|
96
160
|
};
|
|
97
161
|
declare function Denied({ title, sub, need, icon, className, children, }: DeniedProps): react.JSX.Element;
|
|
98
162
|
|
|
99
|
-
|
|
163
|
+
declare const PAGE_SIZES: readonly number[];
|
|
164
|
+
declare const DEFAULT_PAGE_SIZE: number;
|
|
165
|
+
|
|
166
|
+
export { Badge, BusyRegion, type BusyRegionProps, Button, type ButtonProps, Card, type Column, DEFAULT_PAGE_SIZE, DataTable, type DataTableProps, Denied, type DeniedProps, Icon, Modal, type ModalProps, PAGE_SIZES, Pagination, type PaginationProps, Skeleton, type SkeletonProps, SkeletonTable, type SkeletonTableProps, type TableSort, sortTableRows };
|