@nxgt/material 3.49.0 → 3.51.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/CHANGELOG.md +34 -0
- package/README.md +1 -1
- package/dist/{async-slice-DlvKCKAa.js → async-slice-BGtmF3K5.js} +2 -2
- package/dist/{button-DJgaQbBD.js → button-BWcr2b49.js} +45 -76
- package/dist/charts.js +3 -3
- package/dist/{chip-DN44R1vW.js → chip-CmLW6ZrT.js} +3 -3
- package/dist/{code-editor-BJ9ewxSC.js → code-editor-BKMaeIF6.js} +6 -6
- package/dist/code-editor.js +5 -5
- package/dist/{code-language-items-BJE3VuQg.js → code-language-items-Cs1Pwd1h.js} +2 -2
- package/dist/components/ui/layout/app-shell/activity-backdrop-shapes.css +14 -9
- package/dist/components/ui/layout/auth-card/auth-card-backdrop.css +2 -2
- package/dist/{components-AtBccCir.js → components-CuwU9Sux.js} +11878 -11614
- package/dist/components.js +26 -27
- package/dist/{dialog-ODql4vH7.js → dialog-TdT_5iUc.js} +7 -7
- package/dist/dnd.js +1 -1
- package/dist/{dropdown-menu-DRXYP9ta.js → dropdown-menu-BozV6Ikw.js} +25 -26
- package/dist/editor.js +9 -9
- package/dist/{empty-state-C9QqvBU1.js → empty-state-gzBPimAo.js} +2 -2
- package/dist/{escape-owner-DCrlERLM.js → escape-owner-Bu-aDEEO.js} +1 -1
- package/dist/{field-blur-DYGyIdfD.js → field-blur-47zmuDkH.js} +1 -1
- package/dist/{field-blur-context-B-fSpl1H.js → field-blur-context-6McYOnM8.js} +1 -1
- package/dist/hooks.js +2 -2
- package/dist/{hover-card-EO_CckIx.js → hover-card-zHdZJpBG.js} +14 -14
- package/dist/{i18n-provider-C0fB0sEL.js → i18n-provider-Ang2f31m.js} +1 -1
- package/dist/i18n.js +5 -5
- package/dist/{icon-badge-BQYj_LE6.js → icon-badge-CRFGIQPX.js} +3 -3
- package/dist/{icon-button-B481L2TP.js → icon-button-CtG3Snfm.js} +119 -87
- package/dist/lib/components/ui/actions/icon-button/icon-button-busy.d.ts +22 -0
- package/dist/lib/components/ui/actions/icon-button/icon-button-shape.d.ts +2 -0
- package/dist/lib/components/ui/actions/icon-button/icon-button.d.ts +7 -0
- package/dist/lib/components/ui/collections/data-table/data-table-selection-host.d.ts +7 -0
- package/dist/lib/components/ui/collections/data-table/internal.d.ts +1 -0
- package/dist/lib/components/ui/collections/data-table/partials/layouts/data-table-list-bar.d.ts +2 -1
- package/dist/lib/components/ui/collections/filter/bar/partials/filter-quick-toggle.d.ts +3 -2
- package/dist/lib/components/ui/collections/filter/sections/filter-sections-classes.d.ts +7 -1
- package/dist/lib/components/ui/collections/page-footer/page-footer.d.ts +6 -1
- package/dist/lib/components/ui/collections/row-menu/row-menu-trigger.d.ts +5 -2
- package/dist/lib/components/ui/collections/server-table/index.d.ts +1 -1
- package/dist/lib/components/ui/collections/server-table/partials/filtered-server-table.d.ts +8 -0
- package/dist/lib/components/ui/collections/server-table/partials/server-table-band.d.ts +10 -1
- package/dist/lib/components/ui/collections/server-table/partials/server-table-bulk-buttons.d.ts +3 -2
- package/dist/lib/components/ui/collections/server-table/partials/server-table-bulk-more.d.ts +4 -2
- package/dist/lib/components/ui/collections/server-table/partials/server-table-bulk.d.ts +2 -1
- package/dist/lib/components/ui/collections/server-table/partials/server-table-controls.d.ts +16 -0
- package/dist/lib/components/ui/collections/server-table/partials/server-table-empty-chips.d.ts +5 -3
- package/dist/lib/components/ui/collections/server-table/partials/server-table-grid.d.ts +3 -1
- package/dist/lib/components/ui/collections/server-table/partials/server-table-selection-bar.d.ts +17 -0
- package/dist/lib/components/ui/collections/server-table/partials/server-table-view.d.ts +15 -0
- package/dist/lib/components/ui/collections/server-table/partials/use-server-table-phone-row.d.ts +14 -0
- package/dist/lib/components/ui/collections/server-table/partials/use-server-table-selection-slots.d.ts +24 -0
- package/dist/lib/components/ui/collections/server-table/types-paging.d.ts +17 -0
- package/dist/lib/components/ui/collections/server-table/types.d.ts +3 -10
- package/dist/lib/components/ui/collections/server-table/use-selection-band.d.ts +14 -0
- package/dist/lib/components/ui/feedback/step-up-dialog/partials/step-up-body.d.ts +5 -3
- package/dist/lib/components/ui/feedback/step-up-dialog/partials/step-up-request-error.d.ts +15 -0
- package/dist/lib/components/ui/feedback/step-up-dialog/step-up-dialog.d.ts +6 -3
- package/dist/lib/components/ui/feedback/step-up-dialog/types.d.ts +12 -1
- package/dist/lib/components/ui/feedback/step-up-dialog/use-retry-focus.d.ts +27 -0
- package/dist/lib/components/ui/feedback/step-up-dialog/use-retry.d.ts +11 -0
- package/dist/lib/components/ui/fields/contacts/placeholders.d.ts +1 -1
- package/dist/lib/components/ui/fields/verification-code/use-settled-focus.d.ts +10 -0
- package/dist/lib/components/ui/layout/app-shell/use-keep-shapes-inside.d.ts +12 -1
- package/dist/lib/i18n/resources/en/en.d.ts +4 -0
- package/dist/lib/i18n/resources/en/material.json.d.ts +6 -2
- package/dist/lib/i18n/resources/fr/fr.d.ts +4 -0
- package/dist/lib/i18n/resources/fr/material.json.d.ts +6 -2
- package/dist/lib/i18n/resources/resources.d.ts +8 -0
- package/dist/lib/i18n/translator.d.ts +1 -1
- package/dist/lib.js +1 -1
- package/dist/main.js +43 -44
- package/dist/map.js +8 -8
- package/dist/material.css +1 -1
- package/dist/pdf-reader.js +1 -1
- package/dist/print.js +2 -2
- package/dist/{public-CZBz4JVu.js → public-CTZ5RK_Y.js} +20 -20
- package/dist/{rich-code-block-597e0_Qm.js → rich-code-block-oTgL-s4W.js} +5 -5
- package/dist/{sortable-provider-CK_xAk2H.js → sortable-provider-CpA5NYgD.js} +2 -2
- package/dist/{dist-B61_o9Mg.js → spinner-UvqlDsV8.js} +478 -256
- package/dist/{stored-language-IaL1Ftn_.js → stored-language-Cb9FMEwk.js} +12 -4
- package/dist/theme-picker.js +2 -2
- package/dist/{theme-toggle-Dnjn83TJ.js → theme-toggle-BZVz6O5q.js} +1 -1
- package/dist/{toolbar-items-CCwIu7fE.js → toolbar-items-C09bJphe.js} +106 -106
- package/dist/{translate-material-v9u-e7Kd.js → translate-material-D6KCK21i.js} +2 -2
- package/dist/{translator-Dk4_qE6U.js → translator-BnQXtpSZ.js} +1 -1
- package/dist/{use-chat-composer-Ot5XUbdn.js → use-chat-composer-AZAj4tFJ.js} +4881 -4882
- package/dist/{button-tones-BwuVvAmd.js → use-click-lock-BZYtTmVt.js} +37 -7
- package/dist/{use-heading-spy-DjKixjLm.js → use-heading-spy-C9-A2md2.js} +1 -1
- package/dist/{use-material-translation-DOkTaWK_.js → use-material-translation-pLgSqqLw.js} +2 -2
- package/docs/components/buttons-and-actions.md +2 -1
- package/docs/components/data-table-filter-and-lists.md +67 -14
- package/docs/components/feedback-and-status.md +22 -3
- package/docs/components/form-fields.md +7 -0
- package/docs/guides/backgrounds.md +3 -1
- package/docs/guides/cards-and-tiles.md +3 -0
- package/docs/guides/server-lists-url.md +3 -2
- package/lib/components/ui/layout/app-shell/activity-backdrop-shapes.css +14 -9
- package/lib/components/ui/layout/auth-card/auth-card-backdrop.css +2 -2
- package/package.json +1 -1
- package/dist/spinner-BhuZuHK1.js +0 -229
|
@@ -1,6 +1,20 @@
|
|
|
1
|
-
import { createContext as e } from "react";
|
|
1
|
+
import { createContext as e, useCallback as t, useEffect as n, useRef as r, useState as i } from "react";
|
|
2
2
|
//#region lib/components/ui/primitives/icon/icon-context.ts
|
|
3
|
-
var
|
|
3
|
+
var a = e(!1);
|
|
4
|
+
//#endregion
|
|
5
|
+
//#region lib/components/ui/actions/button/busy-click-capture.ts
|
|
6
|
+
function o(e, t, n) {
|
|
7
|
+
return (r) => {
|
|
8
|
+
if (e || t.isLocked()) {
|
|
9
|
+
r.preventDefault(), r.stopPropagation();
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
t.lock(), n?.(r);
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
//#endregion
|
|
16
|
+
//#region lib/components/ui/actions/button/button-tones.ts
|
|
17
|
+
var s = {
|
|
4
18
|
primary: "text-primary-text",
|
|
5
19
|
secondary: "text-secondary",
|
|
6
20
|
success: "text-success dark:text-[color-mix(in_oklab,var(--color-success)_85%,white)]",
|
|
@@ -8,7 +22,7 @@ var t = e(!1), n = {
|
|
|
8
22
|
warning: "text-[color-mix(in_oklab,var(--color-warning)_85%,black)] dark:text-[color-mix(in_oklab,var(--color-warning)_85%,white)]",
|
|
9
23
|
error: "text-[color-mix(in_oklab,var(--color-error)_85%,black)] dark:text-[color-mix(in_oklab,var(--color-error)_70%,white)]",
|
|
10
24
|
default: "text-foreground"
|
|
11
|
-
},
|
|
25
|
+
}, c = {
|
|
12
26
|
primary: "[&_svg]:data-[slot=icon]:text-primary-text",
|
|
13
27
|
secondary: "[&_svg]:data-[slot=icon]:text-secondary",
|
|
14
28
|
success: "[&_svg]:data-[slot=icon]:text-success dark:[&_svg]:data-[slot=icon]:text-[color-mix(in_oklab,var(--color-success)_85%,white)]",
|
|
@@ -16,7 +30,7 @@ var t = e(!1), n = {
|
|
|
16
30
|
warning: "[&_svg]:data-[slot=icon]:text-[color-mix(in_oklab,var(--color-warning)_85%,black)] dark:[&_svg]:data-[slot=icon]:text-[color-mix(in_oklab,var(--color-warning)_85%,white)]",
|
|
17
31
|
error: "[&_svg]:data-[slot=icon]:text-[color-mix(in_oklab,var(--color-error)_85%,black)] dark:[&_svg]:data-[slot=icon]:text-[color-mix(in_oklab,var(--color-error)_70%,white)]",
|
|
18
32
|
default: "[&_svg]:data-[slot=icon]:text-foreground"
|
|
19
|
-
},
|
|
33
|
+
}, l = {
|
|
20
34
|
primary: "[&_svg]:stroke-primary-text",
|
|
21
35
|
secondary: "[&_svg]:stroke-secondary",
|
|
22
36
|
success: "[&_svg]:stroke-success dark:[&_svg]:stroke-[color-mix(in_oklab,var(--color-success)_85%,white)]",
|
|
@@ -24,7 +38,7 @@ var t = e(!1), n = {
|
|
|
24
38
|
warning: "[&_svg]:stroke-[color-mix(in_oklab,var(--color-warning)_85%,black)] dark:[&_svg]:stroke-[color-mix(in_oklab,var(--color-warning)_85%,white)]",
|
|
25
39
|
error: "[&_svg]:stroke-[color-mix(in_oklab,var(--color-error)_85%,black)] dark:[&_svg]:stroke-[color-mix(in_oklab,var(--color-error)_70%,white)]",
|
|
26
40
|
default: "[&_svg]:stroke-foreground"
|
|
27
|
-
},
|
|
41
|
+
}, u = {
|
|
28
42
|
primary: "[--morph-on:var(--color-primary)] [--morph-on-fg:var(--color-primary-foreground)] [--btn-focus:var(--color-primary-text)] [--btn-line:var(--color-primary)]",
|
|
29
43
|
secondary: "[--morph-on:var(--color-secondary)] dark:[--morph-on:color-mix(in_oklab,var(--color-secondary)_70%,var(--color-background))] [--morph-on-fg:var(--color-secondary-foreground)] [--btn-focus:var(--color-secondary)] [--btn-line:var(--color-secondary)]",
|
|
30
44
|
success: "[--morph-on:var(--color-success)] dark:[--morph-on:color-mix(in_oklab,var(--color-success)_60%,var(--color-background))] [--morph-on-fg:var(--color-success-foreground)] [--btn-focus:var(--color-success)] dark:[--btn-focus:color-mix(in_oklab,var(--color-success)_85%,white)] [--btn-line:var(--color-success)]",
|
|
@@ -32,6 +46,22 @@ var t = e(!1), n = {
|
|
|
32
46
|
warning: "[--morph-on:var(--color-warning)] dark:[--morph-on:color-mix(in_oklab,var(--color-warning)_60%,var(--color-background))] [--morph-on-fg:var(--color-warning-foreground)] [--btn-focus:color-mix(in_oklab,var(--color-warning)_85%,black)] dark:[--btn-focus:color-mix(in_oklab,var(--color-warning)_85%,white)] [--btn-line:var(--color-warning)]",
|
|
33
47
|
error: "[--morph-on:var(--color-error)] [--morph-on-fg:var(--color-error-foreground)] [--btn-focus:color-mix(in_oklab,var(--color-error)_85%,black)] dark:[--btn-focus:color-mix(in_oklab,var(--color-error)_70%,white)] [--btn-line:var(--color-error)]",
|
|
34
48
|
default: "[--morph-on:var(--color-foreground)] [--morph-on-fg:var(--color-background)] [--btn-focus:var(--color-foreground)] [--btn-line:var(--color-foreground)]"
|
|
35
|
-
};
|
|
49
|
+
}, d = 1e3;
|
|
50
|
+
function f(e, a) {
|
|
51
|
+
let o = r(!1), s = r(!1), c = r(void 0), [l, u] = i(!1), f = t(() => {
|
|
52
|
+
o.current = !1, s.current = !1, clearTimeout(c.current), u(!1);
|
|
53
|
+
}, []), p = t(() => {
|
|
54
|
+
e && (o.current = !0, u(!0), clearTimeout(c.current), c.current = setTimeout(() => {
|
|
55
|
+
s.current || f();
|
|
56
|
+
}, d));
|
|
57
|
+
}, [e, f]);
|
|
58
|
+
return n(() => {
|
|
59
|
+
o.current && (a ? s.current = !0 : s.current && f());
|
|
60
|
+
}, [a, f]), n(() => () => clearTimeout(c.current), []), {
|
|
61
|
+
locked: l,
|
|
62
|
+
lock: p,
|
|
63
|
+
isLocked: t(() => o.current, [])
|
|
64
|
+
};
|
|
65
|
+
}
|
|
36
66
|
//#endregion
|
|
37
|
-
export {
|
|
67
|
+
export { u as a, s as i, c as n, o, l as r, a as s, f as t };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { r as e } from "./i18n-provider-
|
|
1
|
+
import { r as e } from "./i18n-provider-Ang2f31m.js";
|
|
2
2
|
import { _ as t, g as n, h as r, n as i, v as a, y as o } from "./number.utils-BG_eEt5g.js";
|
|
3
3
|
import { useCallback as s, useEffect as c, useMemo as l, useRef as u, useState as d } from "react";
|
|
4
4
|
import { busySettler as f, busySettler as p, createSubmitGuard as m, createSubmitGuard as h } from "@nxgt/material-core/submit-guard";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { r as e } from "./i18n-provider-
|
|
2
|
-
import { t } from "./translate-material-
|
|
1
|
+
import { r as e } from "./i18n-provider-Ang2f31m.js";
|
|
2
|
+
import { t } from "./translate-material-D6KCK21i.js";
|
|
3
3
|
import { useCallback as n } from "react";
|
|
4
4
|
//#region lib/i18n/use-material-translation.ts
|
|
5
5
|
function r() {
|
|
@@ -7,7 +7,7 @@ and language switches. Everything is imported from `@nxgt/material/components`.
|
|
|
7
7
|
| | Component | What it does |
|
|
8
8
|
| --- | --- | --- |
|
|
9
9
|
| <img src="../previews/buttons-actions-button.webp" width="96" alt=""> | [Button](#button) | Five variants in seven colours, six sizes, a round or square shape and a selected state, with a loading state |
|
|
10
|
-
| <img src="../previews/buttons-actions-iconbutton.webp" width="96" alt=""> | [IconButton](#iconbutton) | Icon-only button in five sizes and three widths, with an optional tooltip |
|
|
10
|
+
| <img src="../previews/buttons-actions-iconbutton.webp" width="96" alt=""> | [IconButton](#iconbutton) | Icon-only button in five sizes and three widths, with an optional tooltip and a loading state |
|
|
11
11
|
| <img src="../previews/buttons-actions-buttongroup.webp" width="96" alt=""> | [ButtonGroup](#buttongroup) | Joins adjacent buttons into one control; the expressive look widens the pressed one |
|
|
12
12
|
| <img src="../previews/buttons-actions-splitbutton.webp" width="96" alt=""> | [SplitButton](#splitbutton) | Main action plus a menu of related ones, Material 3 Expressive |
|
|
13
13
|
| <img src="../previews/buttons-actions-fab.webp" width="96" alt=""> | [Fab](#fab) | The screen's main action, floating; extended with its label, folds on scroll |
|
|
@@ -180,6 +180,7 @@ import { Pencil, Trash2 } from 'lucide-react';
|
|
|
180
180
|
| `morph` | `'round' \| 'pill' \| 'none'` | `'pill'` | what a **toggle** (`aria-pressed` set) does when pressed: `pill` fills with its colour and widens by 14 px into a pill, `round` fills and stays a circle, `none` keeps the look your classes give it. Not set on a button without `aria-pressed`, one with a `shape`, or a Toolbar item (the Toolbar's own look) |
|
|
181
181
|
| `badge` | `number` | — | a count on the top end corner (top left in a right-to-left page): "99+" past 99, nothing at 0 or unset. It takes no room, so the box and the 44 px hit area are unchanged. It is **decorative** (`aria-hidden`): put the number in the accessible name yourself, e.g. `aria-label="Publish 2 items"` |
|
|
182
182
|
| `badgeTone` | `'primary' \| 'error' \| 'neutral'` | `'primary'` | the badge's colour: a count is not an alert, so `primary`; `error` for a notification; `neutral` is the page's text colour on its background |
|
|
183
|
+
| `loading` | `boolean` | `false` | busy, as `Button`'s: `aria-busy` and `aria-disabled`, a spinner in place of the icon (the button keeps its size) and every click blocked. It stays focusable (no native `disabled`), so the focus is not lost while the work runs |
|
|
183
184
|
|
|
184
185
|
**A count badge** (`badge`) is a primary pill (`badgeTone="error"` is the menu-item badge's red), ringed in the page background so it reads over any variant, and sits half outside the corner, over the neighbours' gap (leave 4 px; a `Toolbar` clips 4 px outside its items, so leave room there too). It does not dim when the button is disabled. It never names itself: `<IconButton badge={2} aria-label="Publish 2 items">`. `Button` has no `badge`: its label can be cut with an ellipsis and an `asChild` one clips, so compose a `CountBadge` around it.
|
|
185
186
|
|
|
@@ -15,9 +15,9 @@ either can be used alone.
|
|
|
15
15
|
| --- | --- | --- |
|
|
16
16
|
| <img src="../previews/data-table-filter-lists-datatable.webp" width="96" alt=""> | [DataTable](#datatable) | TanStack Table v9 with sorting, selection, expansion and pagination, client-side or one server page at a time |
|
|
17
17
|
| <img src="../previews/data-table-filter-lists-servertable.webp" width="96" alt=""> | [ServerTable](#servertable) | A server-mode `DataTable` driven by the URL, with the Filter in its top band (a title and a description, or neither): sort, cursor paging, the empty page with a scene, an action and removable chips, one count announced |
|
|
18
|
-
| <img src="../previews/data-table-filter-lists-rowmenu.webp" width="96" alt=""> | [RowMenu](#rowmenu) | A row's "..." button named after its row ("Actions for Ada"), its menu aligned to the end with the destructive item last, and `rowMenuColumn` to drop both in a table |
|
|
18
|
+
| <img src="../previews/data-table-filter-lists-rowmenu.webp" width="96" alt=""> | [RowMenu](#rowmenu) | A row's "..." button named after its row ("Actions for Ada"), its menu aligned to the end with the destructive item last, a loading state while an action runs, and `rowMenuColumn` to drop both in a table |
|
|
19
19
|
| <img src="../previews/data-table-filter-lists-listempty.webp" width="96" alt=""> | [ListEmpty](#listempty) | A list's empty page: nothing yet, with a scene and an action, or nothing matches and the way out |
|
|
20
|
-
| <img src="../previews/data-table-filter-lists-pagefooter.webp" width="96" alt=""> | [PageFooter](#pagefooter) | The page footer of a feed that is not a table (cards, a news feed) under a FilterBar: the range and Previous / Next as links from the URL's cursor trail, `pageControls="icon"`; no `totalCount` for a feed with no total (no range, Previous / Next only) |
|
|
20
|
+
| <img src="../previews/data-table-filter-lists-pagefooter.webp" width="96" alt=""> | [PageFooter](#pagefooter) | The page footer of a feed that is not a table (cards, a news feed) under a FilterBar: the range and Previous / Next as links from the URL's cursor trail, `pageControls="icon"`; no `totalCount` for a feed with no total (no range, Previous / Next only); `hideOnSinglePage` hides it on a single page |
|
|
21
21
|
| <img src="../previews/data-table-filter-lists-datagrid.webp" width="96" alt=""> | [DataGrid](#datagrid) | An editable grid: spreadsheet keys, paste, a fill handle with series, undo, totals, saving with versions and conflicts (lazy ones fetch theirs on "Keep mine"), `onRefuse` for a refused paste or fill, row selection (`enableRowSelection`), and resizable, hideable and reorderable columns (`columnControls`, `columnLayout`, `DataGridColumnsMenu`) |
|
|
22
22
|
| <img src="../previews/data-table-filter-lists-daygroupedlist.webp" width="96" alt=""> | [DayGroupedList](#daygroupedlist) | A list split by day under sticky Today / Yesterday / Earlier headings |
|
|
23
23
|
| <img src="../previews/data-table-filter-lists-filter.webp" width="96" alt=""> | [Filter](#filter) | Schema-driven filters with chips, presets and persistence |
|
|
@@ -244,8 +244,10 @@ with a `select` column and `actions`, the toolbar keeps its band while
|
|
|
244
244
|
nothing is selected, so the first selection does not move the rows (on a
|
|
245
245
|
wide screen; on a phone the extra lines still push them down). Below 768px
|
|
246
246
|
the declarative `bulkActions` fold past the third into a "More" menu in the
|
|
247
|
-
toolbar
|
|
248
|
-
|
|
247
|
+
toolbar (a `ServerTable` without `filter`, or with the facets panel) and in a
|
|
248
|
+
phone's second row (#1719), as the cards and tiles do (#1797). The
|
|
249
|
+
[selection band](#the-selection-band) keeps one action and puts the rest in
|
|
250
|
+
"More".
|
|
249
251
|
|
|
250
252
|
```tsx
|
|
251
253
|
<DataTable
|
|
@@ -293,6 +295,33 @@ press elsewhere is never pulled back. The footer's selected count is a polite
|
|
|
293
295
|
status, so the first selection is announced ("1 row selected") when its bar
|
|
294
296
|
shows up.
|
|
295
297
|
|
|
298
|
+
### The selection band
|
|
299
|
+
|
|
300
|
+
With `bulkActions` and a `filter` (not `variant: 'facets'`), the first
|
|
301
|
+
selected row turns the band that holds the filter bar into a selection bar, in
|
|
302
|
+
place and on the same row: "N selected", the bulk actions, and a tonal
|
|
303
|
+
"Cancel" that clears the selection (#1718). The search and the page's
|
|
304
|
+
`actions` stay mounted, hidden (`invisible`, `inert`) and keep their size, so
|
|
305
|
+
the table does not move; clearing the selection brings them back. The bar's
|
|
306
|
+
height is the row's (40px, 44px on a coarse pointer). From 768px every
|
|
307
|
+
declarative action keeps its button; below it the bar is one row: the first
|
|
308
|
+
action keeps its button, the others go into "More" (an icon, still named),
|
|
309
|
+
and "Cancel" is an icon (its name and tooltip stay). Bulk actions written as a
|
|
310
|
+
function are rendered as they are, in the same place.
|
|
311
|
+
|
|
312
|
+
After "Cancel" the focus goes back to the control that started the selection
|
|
313
|
+
(the row's or the header's checkbox) when it is still on the page, else to the
|
|
314
|
+
list; a selection cleared another way (a bulk action's `selection.clear()`, a
|
|
315
|
+
new query) keeps its own focus rule. While rows are selected the search is
|
|
316
|
+
`inert`, so `filterRef.focus()` does nothing: cancel the selection first. The count is told once, politely, by the
|
|
317
|
+
footer's status ("N rows selected"); the bar's own count is plain text. The
|
|
318
|
+
cards' and tiles' list bar leaves its count and "Clear selection" to the bar.
|
|
319
|
+
A phone's second row of a list in cards or tiles with `actions` (#1719) is the
|
|
320
|
+
selection bar there, as before. Without `filter` (or with the facets panel) the
|
|
321
|
+
band has no filter bar to replace, and the bulk actions stay in the table's
|
|
322
|
+
toolbar. Text keys: `material.server-table.bulk-selected`, `bulk-cancel`,
|
|
323
|
+
`bulk-bar` (English and French).
|
|
324
|
+
|
|
296
325
|
### Focus after a bulk action
|
|
297
326
|
|
|
298
327
|
When a bulk action clears the selection, its buttons unmount and the focus
|
|
@@ -410,7 +439,9 @@ grow the toolbar. Without the `select` column, or without `actions`, no band
|
|
|
410
439
|
is kept above the table. `reserveActions={false}` keeps none either: the bar
|
|
411
440
|
arrives on the first selection with a short grow (none under reduced motion),
|
|
412
441
|
and the rows move down by its height. `ServerTable` passes `false`, so its
|
|
413
|
-
bulk bar leaves no empty row under the band while nothing is selected.
|
|
442
|
+
bulk bar leaves no empty row under the band while nothing is selected. With
|
|
443
|
+
`filter` it has no toolbar row at all: the band's selection bar holds the bulk
|
|
444
|
+
actions ([the selection band](#the-selection-band)).
|
|
414
445
|
|
|
415
446
|
```tsx
|
|
416
447
|
<DataTable
|
|
@@ -818,8 +849,8 @@ export default function Students() {
|
|
|
818
849
|
- **Nothing matches.** The empty page is [`ListEmpty`](#listempty), with no
|
|
819
850
|
header row and no footer. With `filter`, it lists the user's filters as
|
|
820
851
|
removable chips (the band's row keeps the locked ones only, so none is
|
|
821
|
-
listed twice), then "Clear all"
|
|
822
|
-
removing one moves the focus to the next chip, then "Clear all", then the
|
|
852
|
+
listed twice), then "Clear all" (worded by `empty.clear`) whenever a chip or a search
|
|
853
|
+
remains, a single chip included, the locked conditions staying; removing one moves the focus to the next chip, then "Clear all", then the
|
|
823
854
|
search. When rows come back while the focus was on the empty page, it
|
|
824
855
|
goes to the search; rows that come back on their own leave the focus
|
|
825
856
|
alone. The empty page stays while the next answer loads (`data-busy`).
|
|
@@ -843,7 +874,8 @@ export default function Students() {
|
|
|
843
874
|
onRetry={() => fetcher.reload()}
|
|
844
875
|
/>
|
|
845
876
|
```
|
|
846
|
-
- **Selection.** With `bulkActions(ids, selection)`, rows are selectable
|
|
877
|
+
- **Selection.** With `bulkActions(ids, selection)`, rows are selectable
|
|
878
|
+
(see [the selection band](#the-selection-band) for where the bulk actions show);
|
|
847
879
|
the selection is kept across pages and drops on a new query.
|
|
848
880
|
`selection.clear()` empties it and moves the focus to the table
|
|
849
881
|
(`tabindex="-1"`, by script only; the list of cards or tiles in those layouts); `selection.deselect(ids)` drops rows
|
|
@@ -995,6 +1027,7 @@ Its props are `ServerTableProps<T>`; `filter` is a `ServerTableFilter`,
|
|
|
995
1027
|
| `actions` | `ReactNode` | — | the page's actions, at the band's end |
|
|
996
1028
|
| `bandDensity` | `'comfortable' \| 'compact'` | `'comfortable'` | (the same prop as [`Filter`](#a-compact-band-for-a-list-inside-a-page-section)'s bar) `'compact'` halves the room above the band (16px to 8px) and under it (12px to 6px), for a list under a sticky app bar and a breadcrumb; the controls keep their size (the Filter's own `density` is the controls') |
|
|
997
1029
|
| `pageControls` | `'labelled' \| 'icon'` | `'labelled'` | the footer's Previous / Next: `'icon'` is icon-only, with a tooltip and the names "Previous page" / "Next page" (`DataTable`'s default is unchanged); on a coarse pointer the icon controls are 44x44 boxes (still painted 32px) |
|
|
1030
|
+
| `hideOnSinglePage` | `boolean` | `false` | hides the footer's range and Previous / Next when the result is one page (no previous, no next), in the table, cards and tiles layouts; the count is still announced by the live region but no longer shown, so put it in `description` or keep the footer. With `bulkActions` the footer stays for "N rows selected", without its pager |
|
|
998
1031
|
| `bandActions` | `'stacked' \| 'inline'` | `'stacked'` | with `filter`, where `actions` sit below 768px: `'stacked'` gives them a row above the search; `'inline'` keeps them on the search's row (the search takes the room left), for a lone icon action such as a "+". An action wider than 3.5rem wraps under the search. From 768px both are the same. With a cards or tiles list on a phone, neither applies: [the two-row header](#the-phone-header-of-a-list-in-cards-or-tiles) puts the actions on its second row |
|
|
999
1032
|
| `filterRef` | `Ref<ServerTableFilterHandle>` | — | with `filter`: `filterRef.current.focus()` focuses the Filter's first control, the search, else the first quick filter, "Add filter" or "Filters (N)" the width shows |
|
|
1000
1033
|
| `sorting` | `createSorting(…)` | — | headers sorted from the URL |
|
|
@@ -1035,7 +1068,8 @@ rows (the bulk bar is not drawn under it). With the declarative list form of
|
|
|
1035
1068
|
(2)", named "Publish 2 rows"), hidden at 0 rows; the buttons wrap onto a second
|
|
1036
1069
|
line rather than scroll, and past the third action the rest go into a "More"
|
|
1037
1070
|
menu (`material.server-table.bulk-more`), each item keeping its count (the same
|
|
1038
|
-
fold applies below 768px to every layout
|
|
1071
|
+
fold applies below 768px to every layout in the toolbar and in this row; the
|
|
1072
|
+
[selection band](#the-selection-band) of a list with `filter` keeps one action). From
|
|
1039
1073
|
768px the buttons read "Label (n)" as before. When the focused button
|
|
1040
1074
|
disappears the focus goes to the next one, else to "More", else to "Select all"; an action past `max` shows its notice there (an inline text, or a disabled More item). The function
|
|
1041
1075
|
form (`bulkActions={(ids) => node}`) is drawn whole in the row, so keep it to
|
|
@@ -1168,6 +1202,7 @@ and `ref`, so it is the child of a `DropdownMenuTrigger asChild`.
|
|
|
1168
1202
|
| Prop | Type | Default | Effect |
|
|
1169
1203
|
| --- | --- | --- | --- |
|
|
1170
1204
|
| `label` (`RowMenu`, `RowMenuTrigger`) | `string` | — | the row's name; the button's name is "Actions for {label}" (translated). An `aria-label` of your own wins |
|
|
1205
|
+
| `loading` (`RowMenuTrigger`, `triggerProps.loading` of `RowMenu`) | `boolean` | `false` | an action chosen from this row's menu is running: `aria-busy`, a spinner in place of the dots (the button keeps its size), and the menu does not open again until it is over. The button stays focusable and keeps the focus. As `Button`'s `loading` |
|
|
1171
1206
|
| `items` (`RowMenu`) | `{ label, icon?, onSelect?, disabled?, destructive?, id? }[]` | `[]` | the actions; `destructive` ones are drawn in the error colour and moved last, after a separator |
|
|
1172
1207
|
| `children` (`RowMenu`) | `ReactNode` | — | menu content of your own, above `items` (put a destructive item last yourself); its menu items are 44px high on a coarse pointer too |
|
|
1173
1208
|
| `triggerProps` (`RowMenu`) | button props | — | `disabled`, `className`, `data-*` on the button |
|
|
@@ -1175,6 +1210,17 @@ and `ref`, so it is the child of a `DropdownMenuTrigger asChild`.
|
|
|
1175
1210
|
| `label`, `items`, `menu` (`rowMenuColumn`) | `(row) => string` / `(row) => RowMenuItem[]` / `(row) => ReactNode` | — | the row's name, its actions as data, or its menu content as nodes |
|
|
1176
1211
|
| `id`, `className` (`rowMenuColumn`) | `string` | `'actions'`, `'w-12'` | the column's id and its cell classes |
|
|
1177
1212
|
|
|
1213
|
+
While an action runs, set `loading` on its row's button. The button keeps its size and
|
|
1214
|
+
the focus, so the focus does not fall to the page when the menu closes:
|
|
1215
|
+
|
|
1216
|
+
```tsx
|
|
1217
|
+
<RowMenu
|
|
1218
|
+
label={person.name}
|
|
1219
|
+
items={[{ label: 'Archive', onSelect: () => archive.mutate(person.id) }]}
|
|
1220
|
+
triggerProps={{ loading: archive.isPending && archive.variables === person.id }}
|
|
1221
|
+
/>
|
|
1222
|
+
```
|
|
1223
|
+
|
|
1178
1224
|
Focus returns to the button when the menu closes, and when a dialog opened from
|
|
1179
1225
|
an item (a `ConfirmDialog`, a `Dialog`, a `Sheet`) closes: the item is gone by
|
|
1180
1226
|
then, so the dialog hands the focus back to the menu's button.
|
|
@@ -1249,6 +1295,12 @@ A cursor feed whose API answers `pageInfo` and the items but no total (a news or
|
|
|
1249
1295
|
<PageFooter pageInfo={pageInfo} params={params} pageSize={20} count={items.length} pageControls="icon" />
|
|
1250
1296
|
```
|
|
1251
1297
|
|
|
1298
|
+
A list that fits one page shows "1–5 of 5" by default. `hideOnSinglePage` renders nothing when there is no previous and no next page (`ServerTable` takes the same prop, in every layout). The range is gone from the screen then: tell the count elsewhere, a heading or a status line, or keep the footer.
|
|
1299
|
+
|
|
1300
|
+
```tsx
|
|
1301
|
+
<PageFooter pageInfo={pageInfo} totalCount={5} params={params} pageSize={20} count={5} hideOnSinglePage />
|
|
1302
|
+
```
|
|
1303
|
+
|
|
1252
1304
|
| Prop | Type | Default | Effect |
|
|
1253
1305
|
| --- | --- | --- | --- |
|
|
1254
1306
|
| `params` | `URLSearchParams` | — | the page's search params: the trail is read from them and every other param is kept in the hrefs |
|
|
@@ -1259,6 +1311,7 @@ A cursor feed whose API answers `pageInfo` and the items but no total (a news or
|
|
|
1259
1311
|
| `pageHref` | `(params) => string` | ``?${params}`` | a page's href |
|
|
1260
1312
|
| `pagingNames` | `{ after?, trail? }` | `after`, `trail` | the cursor's search param names |
|
|
1261
1313
|
| `pageControls` | `'labelled' \| 'icon'` | `'labelled'` | icon-only controls with a tooltip; 44x44 boxes on a coarse pointer |
|
|
1314
|
+
| `hideOnSinglePage` | `boolean` | `false` | renders nothing when there is no previous and no next page; the range is then not shown, so tell the count elsewhere (a heading, a status line) or keep the footer |
|
|
1262
1315
|
| `live` | `boolean` | `true` | `false` renders the range as plain text instead of a polite live region |
|
|
1263
1316
|
| `disabled` | `boolean` | `false` | makes Previous and Next inert while the feed loads |
|
|
1264
1317
|
| `linkComponent` | `DataTableLinkComponent` | `<a>` | your router's link; it must forward `className` and `ref` |
|
|
@@ -1484,7 +1537,7 @@ filter values become one form value. See [forms](../guides/forms.md).
|
|
|
1484
1537
|
`variant="bar"` is a query-driven bar over
|
|
1485
1538
|
`@nxgt/material-core/filter`. So far: the search; the quick filters, a group
|
|
1486
1539
|
named "Quick filters" (a dashed "+ Status" pill that opens the field's
|
|
1487
|
-
editor, or a toggle for a switch or checkbox field); "Add filter", a menu
|
|
1540
|
+
editor, or a toggle chip for a switch or checkbox field: its label alone while off, the chip's check mark once on); "Add filter", a menu
|
|
1488
1541
|
that picks a field, then an operator (skipped when the field has one), then
|
|
1489
1542
|
its value, applied as it changes, Done (`aria-disabled`, with its reason described, while the value cannot be read) returning the focus to "Add filter"
|
|
1490
1543
|
and announcing the filter ("Level is CE1, 3 results"), Escape or a click
|
|
@@ -1492,7 +1545,7 @@ elsewhere dropping it; the active filters as chips in a list named "Active
|
|
|
1492
1545
|
filters" (each opens its editor, titled with its field and operator: select,
|
|
1493
1546
|
multi-select, radio group, chips, text, switch, checkbox, autocomplete, date, date range, time, time range, a custom field the page renders; "is empty" takes
|
|
1494
1547
|
no value); the page's locked conditions (a lock, no ✕); "Clear all" from two
|
|
1495
|
-
filters; live apply and one polite live region telling the settled count.
|
|
1548
|
+
filters (on the empty page, from one); live apply and one polite live region telling the settled count.
|
|
1496
1549
|
`/` focuses the search and `F` opens "Add filter" (never while typing, with
|
|
1497
1550
|
a modifier, or while a menu or dialog is open; on a phone, `F` opens the
|
|
1498
1551
|
sheet). Below 768px, a "Filters (N)" button opens the phone sheet (below). That button is `FilterSheetTrigger`, exported: to place it elsewhere, render `<FilterSheetTrigger />` inside `ServerTable` (in its `actions`, or anywhere else in its tree) and hide the bar's own with `filter.sheetTrigger: false`; `Filter` takes no children, and outside a filter scope the trigger throws (see the troubleshooting page). `filter.variant: 'facets'` keeps its own. It opens the one sheet the bar mounts, `F` still opens it, and the focus comes back to the button that opened it.
|
|
@@ -1884,7 +1937,7 @@ a word, never colour alone): a chip, a quick pill, a scope pill (on a phone
|
|
|
1884
1937
|
too), a quick switch or checkbox toggle, and the default chip. It is described "Invalid: <message>"; its
|
|
1885
1938
|
editor and its facets section show the message under the title; and the
|
|
1886
1939
|
polite live region tells it ("Period: The period cannot exceed 366 days."),
|
|
1887
|
-
once, after the count, also when the page opens with it; the next count replaces it. A dashed control (
|
|
1940
|
+
once, after the count, also when the page opens with it; the next count replaces it. A dashed control (the default chip) keeps its dashes
|
|
1888
1941
|
and takes the error tone on its line and text. Only a field of the model that the page does not lock shows
|
|
1889
1942
|
one. The page clears it when the condition changes, by passing no error. The
|
|
1890
1943
|
words are `material.filter.error.*`; `fieldErrors(errors, model, locked)` in
|
|
@@ -1948,7 +2001,7 @@ field's, once and after the count: "Search: Not a valid item." and the
|
|
|
1948
2001
|
search or the query changes, by passing nothing, as it does for `errors`.
|
|
1949
2002
|
|
|
1950
2003
|
When the loader answers one flat record, `splitFilterErrors(refusal, model)`
|
|
1951
|
-
from `@nxgt/material
|
|
2004
|
+
(from `@nxgt/material/components`, usable in a loader) splits it: the key equal to the model's
|
|
1952
2005
|
search param becomes `searchError`, the key `FILTER_FORM_ERROR_KEY`
|
|
1953
2006
|
(`'$form'`, never a field id) becomes `formError`, and the rest stays in
|
|
1954
2007
|
`errors`. Messages are trimmed and an empty one is dropped:
|
|
@@ -1971,7 +2024,7 @@ search param, so `q` is free for a field. `model.search.enabled` tells.
|
|
|
1971
2024
|
|
|
1972
2025
|
Fields declared `quick: true` in the model (or listed in `quick`) show as
|
|
1973
2026
|
pills beside the search, before any chip: a dashed "+ Level" pill opens the
|
|
1974
|
-
field's editor, and a switch or checkbox field is a toggle.
|
|
2027
|
+
field's editor, and a switch or checkbox field is a toggle chip (`aria-pressed`): its label alone, not dashed and with no plus while off, the chip's check mark once on.
|
|
1975
2028
|
|
|
1976
2029
|
```tsx
|
|
1977
2030
|
<Filter
|
|
@@ -418,20 +418,39 @@ export function ConfirmItsYou({
|
|
|
418
418
|
| `via` | `'email' \| 'secondFactor'` | — | how the code was issued; left out, the code is still being requested: no field, a status line. Changing it starts the form again (typed value, recovery switch) |
|
|
419
419
|
| `destination` | `ReactNode` | — | where an e-mailed code went; named in the description |
|
|
420
420
|
| `requestError` | `ReactNode` | — | the request for a code failed: an alert in place of the field |
|
|
421
|
+
| `onRetry` | `() => unknown` | — | with `requestError`, the alert holds a "Retry" button (`labels.retry`, "Réessayer" in French) that calls it. While the returned promise is pending the button is busy and keeps the focus. Clear `requestError` and set `via` and the code field takes the focus; set a new `requestError` and the focus stays on "Retry" and the new text is announced. Left out, there is no button |
|
|
421
422
|
| `attemptsLeft`, `locked`, `error`, `retryAfter`, `pending` | as `VerificationCode` | — | the answer to the last code (`error="throttled"` with `retryAfter` seconds disables "Resend the code" for that long and never locks; the footer is unchanged). No tries left: an e-mailed code reads "expired, ask for a new code"; an app or recovery code shows the locked alert and writes the page-wide lock |
|
|
422
423
|
| `length`, `allowRecovery` | `number`, `boolean` | `6`, per `via` | the slots in the code field, and "Use a recovery code" (on for `via="secondFactor"`), passed to the `VerificationCode` |
|
|
424
|
+
| `autoFocus` | `boolean` | — | the code field, not the title, takes the focus on open (passed to the `VerificationCode`) |
|
|
423
425
|
| `onSubmit`, `onResend`, `resent` | as `VerificationCode` | — | your calls; "Resend the code" sits alone on its own line |
|
|
424
426
|
| `confirming`, `confirmedLabel` | `boolean`, `ReactNode` | — | the code was accepted and you re-run the action: the field goes, a status line shows, Cancel is disabled and Escape does nothing until you close it |
|
|
425
427
|
| `title`, `description` | `ReactNode` | built-in | the title ("Confirm it's you") and the line under it, which follows `via` |
|
|
426
|
-
| `labels`, `icons` | partial objects | built-in | every string of the dialog (`title`, `description`, `descriptionApp`, `descriptionEmail`, `descriptionEmailTo`, `requesting`, `cancel`, `confirmed`) and of `VerificationCode` (`incomplete`, `incompleteRecovery` among them) |
|
|
428
|
+
| `labels`, `icons` | partial objects | built-in | every string of the dialog (`title`, `description`, `descriptionApp`, `descriptionEmail`, `descriptionEmailTo`, `requesting`, `cancel`, `confirmed`, `retry`) and of `VerificationCode` (`incomplete`, `incompleteRecovery` among them) |
|
|
429
|
+
|
|
430
|
+
**Retrying a failed request.** Give `onRetry` and the alert offers a button
|
|
431
|
+
instead of leaving the user to cancel and start over:
|
|
432
|
+
|
|
433
|
+
```tsx
|
|
434
|
+
<StepUpDialog
|
|
435
|
+
open
|
|
436
|
+
onCancel={close}
|
|
437
|
+
requestError={step.requestError} // "We could not send a code."
|
|
438
|
+
onRetry={async () => {
|
|
439
|
+
// clear requestError and set via on success; set a new requestError on failure
|
|
440
|
+
await requestCode();
|
|
441
|
+
}}
|
|
442
|
+
onSubmit={(code, { kind }) => check(code, kind)}
|
|
443
|
+
/>
|
|
444
|
+
```
|
|
427
445
|
|
|
428
446
|
Confirm is a footer button that submits the form. Pressed with too few digits
|
|
429
447
|
(or an empty recovery code) it focuses the field and says "Enter all 6 digits."
|
|
430
448
|
instead of calling `onSubmit`.
|
|
431
449
|
|
|
432
450
|
**Focus.** The title takes the focus on open and whenever the field goes away
|
|
433
|
-
(a code being requested, a code accepted)
|
|
434
|
-
on
|
|
451
|
+
(a code being requested, a code accepted), unless `autoFocus` sends it to the
|
|
452
|
+
field on open; "Retry" keeps it while a retry runs and when the request fails again, and the code field takes it once the request succeeds; the field takes it after a refusal and once a resend settles
|
|
453
|
+
(answered or throttled), when "Resend" held the focus as it started; on close it goes back to the button that opened the dialog. A locked code
|
|
435
454
|
leaves Cancel usable.
|
|
436
455
|
|
|
437
456
|
**On a phone** the footer stays one row, Cancel on the left and Confirm on the
|
|
@@ -792,6 +792,7 @@ export function SecondStep({ answer, pending, send }: Props) {
|
|
|
792
792
|
| `onResend`, `resent` | `() => unknown`, `boolean` | — | "Resend the code" alone on its own line (an e-mailed code), "New code" / "Try again" once a code expired; `resent` says a new code was sent |
|
|
793
793
|
| `allowRecovery` | `boolean` | `via === 'secondFactor'` | "Use a recovery code", which swaps the field for a text field and back |
|
|
794
794
|
| `length` | `number` | `6` | slots in the code field |
|
|
795
|
+
| `autoFocus` | `boolean` | — | puts the focus in the code field on mount |
|
|
795
796
|
| `id` | `string` | generated | the form's id; the buttons of `actions="none"` point at it with `form={id}`. Other `<form>` props pass through |
|
|
796
797
|
| `actions` | `'inline' \| 'none'` | `'inline'` | `'none'` leaves the buttons to you; point them at the form with `form={id}` |
|
|
797
798
|
| `submitLabel`, `secondaryAction` | `ReactNode` | "Confirm" | the button, and what sits beside it (Cancel), which stays usable when locked |
|
|
@@ -836,6 +837,12 @@ is described by the line, and the live region leaves it out. The words are
|
|
|
836
837
|
`labels.throttled`, `throttledMinutes(n)` and `throttledSeconds(n)` (`throttledMinutesApp(n)` and `throttledSecondsApp(n)` for `via="secondFactor"`). On the
|
|
837
838
|
server nothing waits and no timer runs.
|
|
838
839
|
|
|
840
|
+
**After a resend settles** (answered with or without a throttle), the focus
|
|
841
|
+
returns to the code field when "Resend" held it, or the page did, as the resend
|
|
842
|
+
started (a host that sets `pending` disables the button, which drops the focus
|
|
843
|
+
to the page): WCAG 2.4.3. A focus the user moved elsewhere in the meantime is
|
|
844
|
+
left alone.
|
|
845
|
+
|
|
839
846
|
**With no tries left** (`attemptsLeft === 0`) the form reads the answer by what
|
|
840
847
|
the code was: an e-mailed code shows "expired, ask for a new code" (nothing is
|
|
841
848
|
held against it), and an app or recovery code shows the locked alert, because
|
|
@@ -545,7 +545,9 @@ good.
|
|
|
545
545
|
- The server renders every glyph. The drop happens after the first measure, on the client, in one frame (boxes read
|
|
546
546
|
before any write), so the markup never differs between server and client.
|
|
547
547
|
- A dropped glyph fades out in 150 ms (none under `prefers-reduced-motion`) and fades back when the content moves
|
|
548
|
-
away. It runs again on resize and when the content changes.
|
|
548
|
+
away. It runs again on resize and when the content changes. A glyph the first pass drops is hidden at once: the fade
|
|
549
|
+
starts a frame later (`data-settled` on the scene), so no glyph is seen over a paragraph for the length of a fade on
|
|
550
|
+
a reload.
|
|
549
551
|
- The open areas keep their glyphs; the space behind and between cards is empty. `keepClear={false}` on an `AuthCard`
|
|
550
552
|
lets the glyphs run under the card.
|
|
551
553
|
- **`keepClear="gaps"`** (on `ActivityLayout` and `AuthCard`, opt-in; `true` stays the default) lets a glyph stay between
|
|
@@ -144,6 +144,9 @@ is not `focusable`). On a narrow list (under 30rem) the bar stays on one row and
|
|
|
144
144
|
Clear shrinks to an icon button; its label stays its accessible name, and it has
|
|
145
145
|
a tooltip. Do not add your own in `actions`: that one is the bulk bar's.
|
|
146
146
|
Once a bulk action clears the selection and the focus is lost, the list takes it.
|
|
147
|
+
In a `ServerTable` with `filter` and `bulkActions`, the band's own selection bar
|
|
148
|
+
tells the count and has "Cancel" (#1718), so the list bar leaves its count and
|
|
149
|
+
"Clear selection" out ([the selection band](../components/data-table-filter-and-lists.md#the-selection-band)).
|
|
147
150
|
|
|
148
151
|
Inside a `ServerTable` with `filter` and `actions`, below 768px, the bar has no
|
|
149
152
|
row of its own: its "Select all" (with a short visible label, "Select all"),
|
|
@@ -6,7 +6,8 @@ and an edit in the bar writes the URL back. A shared link, a reload and the
|
|
|
6
6
|
browser's Back button all land on the same list.
|
|
7
7
|
|
|
8
8
|
It uses the URL codec (`defineFilterModel`, `parseFilterQuery`,
|
|
9
|
-
`serializeFilterQuery
|
|
9
|
+
`serializeFilterQuery`, plus `effectiveConditions` and `splitFilterErrors` for
|
|
10
|
+
a loader), which `@nxgt/material/components` re-exports (it is
|
|
10
11
|
also in `@nxgt/material-core/filter`, so the examples below work with either
|
|
11
12
|
import) and React Router's
|
|
12
13
|
framework mode. Any router works the same way: the codec takes and returns
|
|
@@ -71,7 +72,7 @@ a condition with no readable value left is still dropped.
|
|
|
71
72
|
|
|
72
73
|
```ts
|
|
73
74
|
// app/routes/students.tsx
|
|
74
|
-
import { effectiveConditions, parseFilterQuery } from '@nxgt/material
|
|
75
|
+
import { effectiveConditions, parseFilterQuery } from '@nxgt/material/components';
|
|
75
76
|
import type { Route } from './+types/students';
|
|
76
77
|
import { STUDENT_FILTERS } from './students.filters';
|
|
77
78
|
import { LOCKED } from './students.locked';
|
|
@@ -18,15 +18,20 @@
|
|
|
18
18
|
visibility: hidden;
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
/* A shape over the page's text, controls or surfaces (cards, tables, lists)
|
|
22
|
-
* clear (watch-content.ts): it is hidden,
|
|
23
|
-
* moon, the planet, the eclipse: `.pt.mg`)
|
|
24
|
-
* (`.pt.mh`). The scene stays invisible until
|
|
25
|
-
* flashes over the content.
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
[data-
|
|
29
|
-
[data-
|
|
21
|
+
/* A shape over the page's text, controls or surfaces (cards, tables, lists)
|
|
22
|
+
* fades out and comes back once clear (watch-content.ts): it is hidden,
|
|
23
|
+
* never moved. A compound body (the moon, the planet, the eclipse: `.pt.mg`)
|
|
24
|
+
* is hidden whole, with its halo (`.pt.mh`). The scene stays invisible until
|
|
25
|
+
* the first check, so nothing flashes over the content. The fade waits for
|
|
26
|
+
* `data-settled`, set a frame after the first pass: a shape that pass drops
|
|
27
|
+
* is hidden at once, never seen fading out over the text on a reload. */
|
|
28
|
+
[data-keep-inside][data-settled] .mo > *,
|
|
29
|
+
[data-keep-inside][data-settled] .pt,
|
|
30
|
+
[data-slot="activity-layout-backdrop"]
|
|
31
|
+
[data-background][data-settled]
|
|
32
|
+
.mo
|
|
33
|
+
> *,
|
|
34
|
+
[data-slot="activity-layout-backdrop"] [data-background][data-settled] .pt {
|
|
30
35
|
transition: opacity 150ms ease;
|
|
31
36
|
}
|
|
32
37
|
/* Important: a preset sets its own opacity on a body (`.md`, the dark
|
|
@@ -13,8 +13,8 @@
|
|
|
13
13
|
visibility: hidden;
|
|
14
14
|
}
|
|
15
15
|
/* A shape over the card or the brand row fades out (watch-content.ts). */
|
|
16
|
-
[data-slot="auth-card-backdrop"] [data-background] .mo > *,
|
|
17
|
-
[data-slot="auth-card-backdrop"] [data-background] .pt {
|
|
16
|
+
[data-slot="auth-card-backdrop"] [data-background][data-settled] .mo > *,
|
|
17
|
+
[data-slot="auth-card-backdrop"] [data-background][data-settled] .pt {
|
|
18
18
|
transition: opacity 150ms ease;
|
|
19
19
|
}
|
|
20
20
|
[data-slot="auth-card-backdrop"] [data-background] .mo > [data-hidden],
|