@nxgt/material 3.10.2 → 3.12.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 +96 -0
- package/README.md +10 -11
- package/dist/{async-slice-DDjaJY_F.js → async-slice-4qbER86I.js} +1 -1
- package/dist/button-BNjw-pnE.js +327 -0
- package/dist/button-group-context-CaP0X5p_.js +5 -0
- package/dist/{button-tones-BgZJ1tkD.js → button-tones-BwuVvAmd.js} +9 -1
- package/dist/{callout-view-DdJPdFrP.js → callout-view-C6voDS00.js} +1 -1
- package/dist/charts.js +5 -5
- package/dist/{chip-DpySRqCy.js → chip-DLgQTJBZ.js} +4 -4
- package/dist/{code-editor-BiA14xua.js → code-editor-BZbaYLvo.js} +8 -8
- package/dist/code-editor.js +7 -7
- package/dist/{code-language-items-C_dJ6GVs.js → code-language-items-BT5BO5w6.js} +2 -2
- package/dist/components/base.css +21 -6
- package/dist/components/components.css +6 -2
- package/dist/components/type-scale.css +194 -0
- package/dist/components/ui/actions/button/button.css +156 -0
- package/dist/components/ui/actions/button-group/button-group-standard.css +131 -0
- package/dist/components/ui/actions/button-group/button-group.css +4 -1
- package/dist/components/ui/actions/icon-button/icon-button.css +115 -0
- package/dist/components/ui/actions/morph-fill.css +62 -0
- package/dist/components/ui/feedback/toast/toast-expressive.css +17 -0
- package/dist/components/ui/layout/app-shell/activity-backdrop-shapes.css +49 -0
- package/dist/components/ui/layout/app-shell/activity-backdrop.css +0 -17
- package/dist/components/ui/layout/sidebar/sidebar-tokens.css +7 -2
- package/dist/{components-BkF9N-Yx.js → components-yx1t_5_P.js} +13740 -13422
- package/dist/components.js +24 -24
- package/dist/{dialog-CkIYpJXk.js → dialog-BQp5IdFl.js} +147 -147
- package/dist/dnd.js +1 -1
- package/dist/{dropdown-menu-BC5Tbhb9.js → dropdown-menu-BsH9W_3C.js} +3 -3
- package/dist/editor-viewer.js +1 -1
- package/dist/editor.js +13 -13
- package/dist/{empty-state-SeAg2TOX.js → empty-state-WaU4-T2c.js} +5 -5
- package/dist/escape-owner-C4W_sOAU.js +116 -0
- package/dist/{field-blur-DPLsMufs.js → field-blur-CJHzVSh9.js} +1 -1
- package/dist/{field-blur-context-1ZwJgsHE.js → field-blur-context-Cft9s7Hg.js} +2 -2
- package/dist/{hover-card-Cox-tPDz.js → hover-card-C0fw2kLK.js} +72 -71
- package/dist/i18n.js +12 -12
- package/dist/{icon-badge-q3Ve8gra.js → icon-badge-DUAq9f7H.js} +4 -4
- package/dist/icon-button-ifTpXt0A.js +355 -0
- package/dist/{illustration-BfHRpVzM.js → illustration-BmQmvW_4.js} +1 -1
- package/dist/illustrations.js +2 -2
- package/dist/lib/components/ui/actions/button/button-tones.d.ts +16 -0
- package/dist/lib/components/ui/actions/button/button.d.ts +13 -0
- package/dist/lib/components/ui/actions/button/button.variants.d.ts +4 -3
- package/dist/lib/components/ui/actions/button-group/button-group-context.d.ts +16 -0
- package/dist/lib/components/ui/actions/button-group/button-group-reset.d.ts +13 -0
- package/dist/lib/components/ui/actions/button-group/button-group.d.ts +19 -2
- package/dist/lib/components/ui/actions/icon-button/icon-button-shape.d.ts +3 -2
- package/dist/lib/components/ui/actions/icon-button/icon-button.d.ts +18 -4
- package/dist/lib/components/ui/actions/icon-button/icon-button.variants.d.ts +34 -4
- package/dist/lib/components/ui/display/badge/badge.d.ts +7 -2
- package/dist/lib/components/ui/display/entity-header/entity-header.d.ts +9 -1
- package/dist/lib/components/ui/display/entity-header/entity-stats.d.ts +16 -0
- package/dist/lib/components/ui/display/entity-header/partials/entity-header-actions.d.ts +6 -0
- package/dist/lib/components/ui/display/entity-header/partials/entity-header-body.d.ts +14 -0
- package/dist/lib/components/ui/display/entity-header/partials/entity-header-icon.d.ts +6 -0
- package/dist/lib/components/ui/display/entity-header/partials/shown.d.ts +2 -0
- package/dist/lib/components/ui/display/list-tile/list-tile.d.ts +8 -1
- package/dist/lib/components/ui/display/list-tile/partials/list-tile-body.d.ts +2 -1
- package/dist/lib/components/ui/display/stat-card/index.d.ts +1 -1
- package/dist/lib/components/ui/display/stat-card/partials/stat-card-compact.d.ts +12 -0
- package/dist/lib/components/ui/display/stat-card/stat-card-classes.d.ts +3 -2
- package/dist/lib/components/ui/display/stat-card/stat-card.d.ts +2 -2
- package/dist/lib/components/ui/display/stat-card/types.d.ts +9 -0
- package/dist/lib/components/ui/feedback/alert/alert-variants.d.ts +18 -0
- package/dist/lib/components/ui/feedback/alert/alert.d.ts +2 -6
- package/dist/lib/components/ui/feedback/typing-indicator/typing-indicator.d.ts +1 -1
- package/dist/lib/components/ui/fields/slider/split-slider-props.d.ts +1 -1
- package/dist/lib/components/ui/fields/slider/use-slider-control.d.ts +1 -1
- package/dist/lib/components/ui/layout/app-shell/activity-column.d.ts +17 -0
- package/dist/lib/components/ui/layout/app-shell/activity-layout.d.ts +3 -106
- package/dist/lib/components/ui/layout/app-shell/activity-layout.types.d.ts +113 -0
- package/dist/lib/components/ui/layout/app-shell/app-breadcrumb.d.ts +2 -1
- package/dist/lib/components/ui/layout/app-shell/app-sidebar-icon-item.d.ts +13 -0
- package/dist/lib/components/ui/layout/app-shell/app-sidebar-label-item.d.ts +11 -0
- package/dist/lib/components/ui/layout/app-shell/keep-shapes-inside.d.ts +6 -0
- package/dist/lib/components/ui/layout/app-shell/observe-layout.d.ts +11 -0
- package/dist/lib/components/ui/layout/app-shell/shape-units.d.ts +16 -0
- package/dist/lib/components/ui/layout/app-shell/shapes-clear-content.d.ts +13 -0
- package/dist/lib/components/ui/layout/app-shell/types.d.ts +10 -0
- package/dist/lib/components/ui/layout/app-shell/use-keep-shapes-inside.d.ts +14 -0
- package/dist/lib/components/ui/layout/app-shell/watch-content.d.ts +19 -0
- package/dist/lib/components/ui/overlays/dialog/dialog-variants.d.ts +1 -1
- package/dist/lib/components/ui/primitives/toggle/toggle.d.ts +1 -1
- package/dist/lib/components/ui/primitives/typography/typography.d.ts +17 -7
- package/dist/lib/components/ui/primitives/typography/typography.variants.d.ts +55 -0
- package/dist/lib/components/ui/selection/choice-group/use-choice-group-field.d.ts +2 -2
- package/dist/lib/components/ui/tones.d.ts +1 -1
- package/dist/lib/i18n/translator.d.ts +1 -1
- package/dist/lib.js +2 -2
- package/dist/{line-path-BQeLQZ-P.js → line-path-C5pZGC49.js} +1 -1
- package/dist/main.js +29 -29
- package/dist/map.js +9 -9
- package/dist/material.css +1 -1
- package/dist/pdf-reader.js +1 -1
- package/dist/print.js +1 -1
- package/dist/{public-CtpsxV4I.js → public-B0Nq_yYV.js} +524 -524
- package/dist/{render-json-DyFYFIkZ.js → render-json-BOhvPnSv.js} +1 -1
- package/dist/{rich-code-block-C2Fp_FDU.js → rich-code-block-v7WmeZp9.js} +5 -5
- package/dist/{rich-text-viewer-LO1IZCWs.js → rich-text-viewer-W15XIwah.js} +2 -2
- package/dist/{skeleton-Bmp4gWAH.js → skeleton-DSdtr2qc.js} +1 -1
- package/dist/{sortable-provider-pVaEZzty.js → sortable-provider-CgDTolQR.js} +3 -3
- package/dist/{spinner-CsHcmcpF.js → spinner-BtSIYVrC.js} +3 -3
- package/dist/{state-scenes-SGNyZjTz.js → state-scenes-BX03WmSu.js} +1 -1
- package/dist/styles.css +2 -0
- package/dist/theme-picker.js +3 -3
- package/dist/{theme-toggle-oU-fh6WE.js → theme-toggle-B2Pehdhn.js} +2 -2
- package/dist/{toggle-group-j8zaxEg1.js → toggle-group-BMJNcFAC.js} +1 -1
- package/dist/{toolbar-items-CqHvlTU8.js → toolbar-items-rUHdVqAq.js} +399 -398
- package/dist/{tooltip-rhbqNmr3.js → tooltip-CkttDIab.js} +1 -1
- package/dist/{translate-material-CGZljjp1.js → translate-material-Bd_KgfB8.js} +1 -1
- package/dist/{translator-DCYO4Ij3.js → translator-Dje8HrNz.js} +5 -5
- package/dist/{use-chat-composer-nWWfVyrK.js → use-chat-composer-B0_STpQN.js} +4173 -4173
- package/dist/{use-material-translation-DkxcLbLM.js → use-material-translation-B3V_bz-5.js} +1 -1
- package/dist/{utils-Bcd2Z3ea.js → utils-BLfntITJ.js} +14 -1
- package/docs/components/buttons-and-actions.md +64 -15
- package/docs/components/data-display.md +40 -9
- package/docs/components/feedback-and-status.md +12 -1
- package/docs/components/navigation-and-layout.md +4 -3
- package/docs/components/primitives.md +52 -10
- package/docs/guides/app-shell.md +52 -8
- package/docs/guides/backgrounds.md +2 -0
- package/docs/guides/theming.md +55 -3
- package/docs/previews/buttons-actions-button.webp +0 -0
- package/docs/previews/buttons-actions-iconbutton.webp +0 -0
- package/docs/previews/data-display-badge.webp +0 -0
- package/docs/previews/data-display-entityheader.webp +0 -0
- package/docs/previews/data-display-listtile.webp +0 -0
- package/docs/previews/data-display-statcard.webp +0 -0
- package/docs/previews/feedback-status-banner.webp +0 -0
- package/docs/previews/navigation-layout-appshell-footer-icon.webp +0 -0
- package/docs/previews/navigation-layout-appshell-menu-badge.webp +0 -0
- package/docs/previews/navigation-layout-appshell.webp +0 -0
- package/docs/previews/navigation-layout-sidebar.webp +0 -0
- package/docs/previews/primitives-typography.webp +0 -0
- package/docs/roadmap.md +53 -4
- package/docs/troubleshooting.md +75 -4
- package/lib/components/base.css +21 -6
- package/lib/components/components.css +6 -2
- package/lib/components/type-scale.css +194 -0
- package/lib/components/ui/actions/button/button.css +156 -0
- package/lib/components/ui/actions/button-group/button-group-standard.css +131 -0
- package/lib/components/ui/actions/button-group/button-group.css +4 -1
- package/lib/components/ui/actions/icon-button/icon-button.css +115 -0
- package/lib/components/ui/actions/morph-fill.css +62 -0
- package/lib/components/ui/feedback/toast/toast-expressive.css +17 -0
- package/lib/components/ui/layout/app-shell/activity-backdrop-shapes.css +49 -0
- package/lib/components/ui/layout/app-shell/activity-backdrop.css +0 -17
- package/lib/components/ui/layout/sidebar/sidebar-tokens.css +7 -2
- package/lib/styles.css +2 -0
- package/package.json +2 -1
- package/dist/button-DbfdITBB.js +0 -322
- package/dist/components/ui/actions/icon-button/icon-button-morph-media.css +0 -65
- package/dist/components/ui/actions/icon-button/icon-button-morph.css +0 -152
- package/dist/escape-owner-CripBce_.js +0 -57
- package/dist/icon-button-DJcWpIhu.js +0 -319
- package/lib/components/ui/actions/icon-button/icon-button-morph-media.css +0 -65
- package/lib/components/ui/actions/icon-button/icon-button-morph.css +0 -152
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { r as e } from "./i18n-provider-C6HhBw34.js";
|
|
2
|
-
import { t } from "./translate-material-
|
|
2
|
+
import { t } from "./translate-material-Bd_KgfB8.js";
|
|
3
3
|
import { useCallback as n } from "react";
|
|
4
4
|
//#region lib/i18n/use-material-translation.ts
|
|
5
5
|
function r() {
|
|
@@ -1733,7 +1733,20 @@ var n = (e, t) => {
|
|
|
1733
1733
|
}, Pe = (e, t, n) => {
|
|
1734
1734
|
let r = t[n];
|
|
1735
1735
|
r !== void 0 && (e[n] = e[n] ? e[n].concat(r) : r);
|
|
1736
|
-
}, Fe = ((e, ...t) => typeof e == "function" ? k(Ae, e, ...t) : k(() => je(Ae(), e), ...t))({ extend: { theme: {
|
|
1736
|
+
}, Fe = ((e, ...t) => typeof e == "function" ? k(Ae, e, ...t) : k(() => je(Ae(), e), ...t))({ extend: { theme: {
|
|
1737
|
+
radius: ["field", "field-inner"],
|
|
1738
|
+
text: [
|
|
1739
|
+
"display",
|
|
1740
|
+
"headline",
|
|
1741
|
+
"title",
|
|
1742
|
+
"body",
|
|
1743
|
+
"label"
|
|
1744
|
+
].flatMap((e) => [
|
|
1745
|
+
"lg",
|
|
1746
|
+
"md",
|
|
1747
|
+
"sm"
|
|
1748
|
+
].flatMap((t) => [`${e}-${t}`, `${e}-${t}-em`]))
|
|
1749
|
+
} } });
|
|
1737
1750
|
function Ie(...e) {
|
|
1738
1751
|
return Fe(t(e));
|
|
1739
1752
|
}
|
|
@@ -6,9 +6,9 @@ and language switches. Everything is imported from `@nxgt/material/components`.
|
|
|
6
6
|
|
|
7
7
|
| | Component | What it does |
|
|
8
8
|
| --- | --- | --- |
|
|
9
|
-
| <img src="../previews/buttons-actions-button.webp" width="96" alt=""> | [Button](#button) | Five variants in seven colours and
|
|
10
|
-
| <img src="../previews/buttons-actions-iconbutton.webp" width="96" alt=""> | [IconButton](#iconbutton) | Icon-only button with an optional tooltip |
|
|
11
|
-
| <img src="../previews/buttons-actions-buttongroup.webp" width="96" alt=""> | [ButtonGroup](#buttongroup) | Joins adjacent buttons into one control |
|
|
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 |
|
|
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 |
|
|
14
14
|
| <img src="../previews/buttons-actions-fabmenu.webp" width="96" alt=""> | [FabMenu](#fabmenu) | A FAB that opens a few related actions as pills |
|
|
@@ -28,7 +28,7 @@ and language switches. Everything is imported from `@nxgt/material/components`.
|
|
|
28
28
|
|
|
29
29
|

|
|
30
30
|
|
|
31
|
-
Filled, tonal, outlined, ghost or link button in seven colours,
|
|
31
|
+
Filled, tonal, outlined, ghost or link button in seven colours, six sizes (24 to 96 px) and four square icon sizes, round or square, with a selected state and a loading state.
|
|
32
32
|
|
|
33
33
|
```tsx
|
|
34
34
|
import { Button } from '@nxgt/material/components';
|
|
@@ -43,6 +43,7 @@ import { Mail } from 'lucide-react';
|
|
|
43
43
|
</Button>
|
|
44
44
|
<Button loading>Saving</Button>
|
|
45
45
|
<Button size="sm">Small</Button>
|
|
46
|
+
<Button size="xl" shape="square">Big and square</Button>
|
|
46
47
|
<Button size="icon" aria-label="Email">
|
|
47
48
|
<Mail />
|
|
48
49
|
</Button>
|
|
@@ -52,8 +53,10 @@ import { Mail } from 'lucide-react';
|
|
|
52
53
|
| Prop | Type | Default | Effect |
|
|
53
54
|
| --- | --- | --- | --- |
|
|
54
55
|
| `variant` | `'filled' \| 'tonal' \| 'outlined' \| 'ghost' \| 'link'` | `'filled'` | the style |
|
|
55
|
-
| `color` | `'primary' \| 'secondary' \| 'success' \| 'info' \| 'warning' \| 'error' \| 'default'` | `'primary'` | the colour; `default` is the foreground colour. A `ghost` button in `secondary`, `success`, `info`, `warning` or `error` takes that colour's text and a tint of it on hover, at 4.5:1 on the page and on a card in both themes;
|
|
56
|
-
| `size` | `'xs' \| 'sm' \| 'default' \| 'lg' \| 'icon-xs' \| 'icon-sm' \| 'icon' \| 'icon-lg'` | — | height: 24, 32, 36 and
|
|
56
|
+
| `color` | `'primary' \| 'secondary' \| 'success' \| 'info' \| 'warning' \| 'error' \| 'default'` | `'primary'` | the colour; `default` is the foreground colour. A `ghost` button in `secondary`, `success`, `info`, `warning` or `error` takes that colour's text and a tint of it on hover, at 4.5:1 on the page and on a card in both themes; without a `color` (or with `default`) it keeps the foreground text, neutral; `color="primary"` set on a ghost button is tinted and takes the primary text |
|
|
57
|
+
| `size` | `'xs' \| 'sm' \| 'default' \| 'lg' \| 'xl' \| '2xl' \| 'icon-xs' \| 'icon-sm' \| 'icon' \| 'icon-lg'` | — | height: 24, 32, 36, 40, 56 and 96 px, with a label from the type scale (`label-md` to `headline-lg`, heavier as the size grows) and an icon that follows (14, 16, 18, 20, 28 and 44 px); the `icon*` sizes are square, for a button holding only an icon (give it an `aria-label`). Unset, the button keeps its content's height and its 16 px padding, as before sizes existed |
|
|
58
|
+
| `shape` | `'round' \| 'square'` | `'round'` | `round` is a pill (radius half the height); `square` has a corner of 6 px at `xs` to 28 px at `2xl`. A press springs to the other shape and scales to .97 (.985 at `2xl`); reduced motion keeps the colour change only |
|
|
59
|
+
| `selected` | `boolean` | — | makes it a toggle: `aria-pressed` and `data-selected`, the other shape and a solid fill of its `color` (a `ghost` toggle without a `color` is a neutral tint of the text colour). Unset: a plain button. Ignored with `asChild` (a warning in development): set `aria-pressed` on the child |
|
|
57
60
|
| `loading` | `boolean` | — | busy: a spinner, `aria-busy="true"` and `aria-disabled="true"`, and every click cancelled — a submit button submits neither on a click nor on Enter in a field. It stays focusable (no native `disabled`), so focus is not lost |
|
|
58
61
|
| `lockOnClick` | `boolean` | — | a click makes the button busy at once, until `loading` has turned true and back (or for one second when it never does): for a `loading` that turns true only after an `await` |
|
|
59
62
|
| `spinnerVariant` | `SpinnerProps['variant']` | `'2'` | which spinner `loading` shows |
|
|
@@ -62,7 +65,19 @@ import { Mail } from 'lucide-react';
|
|
|
62
65
|
| `asChild` | `boolean` | `false` | styles its only child instead of rendering a `<button>` |
|
|
63
66
|
| `type` | `'button' \| 'submit' \| 'reset'` | `'button'` | not `submit` by default — pass it inside a form |
|
|
64
67
|
|
|
65
|
-
The classes are exported as `buttonVariants` for styling your own element.
|
|
68
|
+
The classes are exported as `buttonVariants` for styling your own element (Pagination, AlertDialog and the calendar's date picker use them).
|
|
69
|
+
|
|
70
|
+
**The look.** The label is set in the type scale and is heavier than before (weights 560 at `xs` to 700 at `2xl`). The focus is a 2 px outline in the button's colour, 2 px off the button (the error colour with `aria-invalid`). A disabled button has neutral text (40 % of the text colour) and a neutral 12 % fill, none on `ghost` and `link`. `outlined` is a 1.5 px line inside the box at 55 % of the colour, and keeps a `ButtonText` outline in forced colours, where the inset shadow is removed. A `ghost` button is neutral without a `color`, and takes the tone of one it is given. On a coarse pointer every size has a 44 px hit area.
|
|
71
|
+
|
|
72
|
+
```tsx
|
|
73
|
+
const [on, setOn] = useState(false);
|
|
74
|
+
|
|
75
|
+
<Button selected={on} onClick={() => setOn(!on)}>
|
|
76
|
+
<Star />
|
|
77
|
+
Favourite
|
|
78
|
+
</Button>;
|
|
79
|
+
<Button size="2xl" shape="square" variant="tonal">Start</Button>;
|
|
80
|
+
```
|
|
66
81
|
|
|
67
82
|
**On a phone.** A button is never wider than its container, and keeps its
|
|
68
83
|
content's width in a column: a label too
|
|
@@ -71,7 +86,7 @@ accessible name — and a `title` you give it — keep the whole text. Give a
|
|
|
71
86
|
label that must be read in full `wrap`, or a width of your own (`w-full`).
|
|
72
87
|
The text is put in a `<span data-slot="button-label">` (one per run of
|
|
73
88
|
text) that shrinks and ends in the ellipsis; it needs the text to be the button's own child; with `asChild`,
|
|
74
|
-
put the label in a `<span>` inside the child element.
|
|
89
|
+
put the label in a `<span>` inside the child element. On a touch screen in Safari (no `overflow-clip-margin`) an `asChild` host does not clip, to keep its 44 px hit area, so it has no ellipsis at all: a label that needs one goes in a `<span>` with `truncate`.
|
|
75
90
|
|
|
76
91
|
```tsx
|
|
77
92
|
<Button title="Continue to the payment details">Continue to the payment details</Button>;
|
|
@@ -110,7 +125,7 @@ A test finds a busy button by its state, not by `disabled`:
|
|
|
110
125
|
`expect(button).toHaveAttribute('aria-busy', 'true')` (`toBeDisabled()` is
|
|
111
126
|
false: it is still focusable).
|
|
112
127
|
|
|
113
|
-
**Storybook:** `Buttons & actions/Button` (`Submits Once`, `Lock On Click`), `Buttons & actions/Button/Phone` · **Vue:** `Button`
|
|
128
|
+
**Storybook:** `Buttons & actions/Button` (`Submits Once`, `Lock On Click`), `Buttons & actions/Button/Expressive` (`SizesLadder`, `Shape`, `Selected`), `Buttons & actions/Button/Press and motion` (`PressMorph`, `ReducedMotion`), `Buttons & actions/Button/States` (`FocusOutline`, `Disabled`, `GhostSelectedNeutral`, `SelectedForcedColours`, `OutlinedForcedColours`, `PhoneBigSizes`), `Buttons & actions/Button/Phone`, `Guides/Buttons expressive` · **Vue:** `Button` (no `xl`, `2xl`, `shape` or `selected`)
|
|
114
129
|
|
|
115
130
|
---
|
|
116
131
|
|
|
@@ -136,14 +151,16 @@ import { Pencil, Trash2 } from 'lucide-react';
|
|
|
136
151
|
|
|
137
152
|
| Prop | Type | Default | Effect |
|
|
138
153
|
| --- | --- | --- | --- |
|
|
139
|
-
| `variant` | `'filled' \| 'tonal' \| 'outlined' \| 'ghost' \| 'link'` | `'filled'` | the style; a round
|
|
154
|
+
| `variant` | `'filled' \| 'tonal' \| 'outlined' \| 'ghost' \| 'link'` | `'filled'` | the style; a round button |
|
|
140
155
|
| `color` | as `Button` | `'primary'` | the colour; a `ghost` one in a colour other than `primary` or `default` draws its icon in it, as `Button` does |
|
|
156
|
+
| `size` | `'xs' \| 'sm' \| 'default' \| 'lg' \| 'xl'` | — | 24, 32, 36, 40 or 56 px, icons of 14, 16, 18, 20 or 28 px (for a glyph without its own `size-*` class; an `Icon` is 16 px unless sized). Unset: the 36 px button with a 16 px icon it always was, so an explicit `default` has an 18 px icon |
|
|
157
|
+
| `width` | `'narrow' \| 'default' \| 'wide'` | `'default'` | 0.78, 1 or 1.45 times the height, always a pill. Ignored with `shape` (a warning in development) |
|
|
141
158
|
| `tooltip` | `ReactNode` | — | wraps it in a `Tooltip`; a string tooltip also becomes the `aria-label` |
|
|
142
159
|
| `aria-label` | `string` | the tooltip, if a string | give one when there is no string tooltip |
|
|
143
160
|
| `shape` | `ShapeName` | — | draws the button as one of the expressive shapes (`cookie-9`, `sunny`, `squircle`…) instead of a circle; changing it morphs. Unset: the round button, unchanged |
|
|
144
161
|
| `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) |
|
|
145
162
|
|
|
146
|
-
**A toggle button**
|
|
163
|
+
**A toggle button** fills with the button's `color` (`primary` by default) through `data-selected` and the fill `Button` shares, and a pointer down squeezes the corners; `pill` widens the button itself by 14 px (its neighbours move by the 14 px), at any `size` (give the button a `size`, not a `size-*` class: the pressed width follows the `size`, see [troubleshooting](../troubleshooting.md#a-pill-iconbutton-toggle-is-the-wrong-width-with-a-size--class)). Below 600 px wide only the colour changes, so a phone row never reflows; `prefers-reduced-motion` keeps the colour change only; in forced colours the pressed fill is `SelectedItem`. The pressed fill and corners are the button's own (a `bg-*` or `rounded-*` class on a pressed toggle does not apply); a ghost toggle without a `color` is neutral when selected (a tint of the text colour), and primary only with a `color`. The focus is a 2 px outline 2 px off the button, a disabled button has a neutral 12 % fill (none on `ghost` and `link`), and on a coarse pointer every size has a 44 px hit area. The morph works with a `tooltip` too (the styles match `data-icon-button`, which the tooltip trigger does not replace). The date picker's input-mode switch uses `morph="round"`; `PasswordField`'s eye, which sits inside its field, and `RichChatComposer`'s format toggle, which draws its own pressed look, use `morph="none"`; the mini player's move button and the Gallery viewer's repeat are pills too, white with a black icon on their dark chrome. `CodeEditor`'s word-wrap pill takes the editor theme's tint.
|
|
147
164
|
|
|
148
165
|
```tsx
|
|
149
166
|
import { IconButton } from '@nxgt/material/components';
|
|
@@ -214,7 +231,9 @@ built this way. A `Contacts` badge is `role="img"`, named by the contact type.
|
|
|
214
231
|
|
|
215
232
|
The classes are exported as `iconButtonVariants`.
|
|
216
233
|
|
|
217
|
-
**
|
|
234
|
+
**Forced colours.** An `outlined` button keeps a `ButtonText` outline and a pressed toggle, `Button` or `IconButton`, fills with `SelectedItem` (its text and icon in `SelectedItemText`); the focus outline follows the system colours.
|
|
235
|
+
|
|
236
|
+
**Storybook:** `Buttons & actions/IconButton` (`Shapes`, `ShapeToggle`), `Buttons & actions/IconButton/Sizes and widths` (`SizesLadder`, `Widths`, `Colours`), `Buttons & actions/IconButton/States` (`FocusOutline`, `Disabled`, `GhostToggleNeutral`, `SelectedForcedColours`), `Buttons & actions/IconButton/Toggle and motion` (`SizedToggle`, `ReducedMotion`, `PressMorph`), `Buttons & actions/IconButton/Morph` (`Frozen`, `WidensOrStaysRound`, `Contrast`), `Buttons & actions/IconButton/Morph unaffected` (`Unaffected`, `WithTooltip`), `Buttons & actions/IconButton/Shapes` (`InsideAGroup`) · **Vue:** `IconButton` (no `shape`)
|
|
218
237
|
|
|
219
238
|
---
|
|
220
239
|
|
|
@@ -222,7 +241,7 @@ The classes are exported as `iconButtonVariants`.
|
|
|
222
241
|
|
|
223
242
|

|
|
224
243
|
|
|
225
|
-
Joins adjacent buttons into one control.
|
|
244
|
+
Joins adjacent buttons into one control; `look="standard"` is the Material 3 Expressive action group.
|
|
226
245
|
|
|
227
246
|
```tsx
|
|
228
247
|
import { Button, ButtonGroup } from '@nxgt/material/components';
|
|
@@ -236,12 +255,42 @@ import { Button, ButtonGroup } from '@nxgt/material/components';
|
|
|
236
255
|
</ButtonGroup>;
|
|
237
256
|
```
|
|
238
257
|
|
|
239
|
-
|
|
258
|
+
| Prop | Type | Default | Effect |
|
|
259
|
+
| --- | --- | --- | --- |
|
|
260
|
+
| `look` | `'standard'` | — | the Material 3 Expressive group (see below). Unset: the original group, which becomes the `standard` one by default in the next major, with `look="legacy"` to keep it |
|
|
261
|
+
| `size` | `'xs' \| 'sm' \| 'default' \| 'lg' \| 'xl'` | — | the `size` every `Button` and `IconButton` in the group takes through a context; a child's own `size` wins. Works with either look |
|
|
262
|
+
| `...div` | `ComponentProps<'div'>` | — | the host's props; `data-slot="button-group"`, `data-look` and `data-size` are set on it |
|
|
263
|
+
|
|
264
|
+
A `<div>` taking its props. Without `look` it stretches to its container (`w-full`); mark the
|
|
240
265
|
current button with `data-state="active"`: it fills with the primary colour and
|
|
241
266
|
its label and icons switch to the primary foreground, so every button can stay
|
|
242
267
|
`tonal` and only the attribute moves.
|
|
243
268
|
|
|
244
|
-
|
|
269
|
+
`look="standard"` is the Material 3 Expressive group: the buttons keep their own
|
|
270
|
+
look, 8px apart, and a pressed one widens by 14px on each side while its
|
|
271
|
+
neighbours give the width back, so the group's width never changes (7, 10 and
|
|
272
|
+
11px at `xs`, `sm` and `default`, which have less padding to give; none with
|
|
273
|
+
reduced motion; an icon-only neighbour at the end of the group keeps its icon and may give back less). `size` is shared by every `Button` and `IconButton` in the group
|
|
274
|
+
(a child's own `size` wins) and works with either look. It sets
|
|
275
|
+
`data-look` and `data-size` on the host; mark the group with `role="group"` and a
|
|
276
|
+
name when it is one control.
|
|
277
|
+
|
|
278
|
+
```tsx
|
|
279
|
+
<ButtonGroup look="standard" size="lg" role="group" aria-label="Actions">
|
|
280
|
+
<Button variant="tonal">Save</Button>
|
|
281
|
+
<Button variant="tonal">Share</Button>
|
|
282
|
+
<IconButton variant="tonal" aria-label="More">
|
|
283
|
+
<MoreHorizontal />
|
|
284
|
+
</IconButton>
|
|
285
|
+
</ButtonGroup>
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
On a coarse pointer the standard group's gap is `max(8px, 44px - the height of its size)`
|
|
289
|
+
(20px at `xs`, 12px at `sm`, 8px from `default`), so every button, in a row or
|
|
290
|
+
across wrapped rows, keeps a 44px hit area that touches no neighbour's. A child with its own smaller `size` in a standard group keeps the group's gap, so its coarse 44px hit area can overlap a neighbour's: give the group the smallest `size` instead. Without
|
|
291
|
+
`look` the group is unchanged.
|
|
292
|
+
|
|
293
|
+
**Storybook:** `Buttons & actions/ButtonGroup`, `Buttons & actions/ButtonGroup/Standard`, `Buttons & actions/ButtonGroup/Standard toggles`, `Buttons & actions/ButtonGroup/Standard phone and motion`, `Guides/Buttons expressive` · **Vue:** `ButtonGroup` (no `look` or `size`)
|
|
245
294
|
|
|
246
295
|
---
|
|
247
296
|
|
|
@@ -9,7 +9,7 @@ from `@nxgt/material/components`.
|
|
|
9
9
|
| <img src="../previews/data-display-avatar.webp" width="96" alt=""> | [Avatar](#avatar) | Image with a fallback and an optional online / offline dot |
|
|
10
10
|
| <img src="../previews/data-display-avatargroup.webp" width="96" alt=""> | [AvatarGroup](#avatargroup) | Overlapping avatars with a `+N` overflow |
|
|
11
11
|
| <img src="../previews/data-display-shape.webp" width="96" alt=""> | [Shape](#shape) | Material 3 Expressive's shapes as a backdrop, an icon container or a mask for a photo |
|
|
12
|
-
| <img src="../previews/data-display-badge.webp" width="96" alt=""> | [Badge](#badge) |
|
|
12
|
+
| <img src="../previews/data-display-badge.webp" width="96" alt=""> | [Badge](#badge) | Status pill, 20 px, in seven tones, soft or outlined |
|
|
13
13
|
| <img src="../previews/data-display-countbadge.webp" width="96" alt=""> | [CountBadge](#countbadge) | Count in the corner of its children |
|
|
14
14
|
| <img src="../previews/data-display-dayseparator.webp" width="96" alt=""> | [DaySeparator](#dayseparator) | "Today", "Yesterday" or the date between two hairlines |
|
|
15
15
|
| <img src="../previews/data-display-periodcards.webp" width="96" alt=""> | [PeriodCards](#periodcards) | Consecutive date ranges (terms, sprints) as cards, with a current marker and an optional link per card |
|
|
@@ -210,20 +210,25 @@ and no lazy loading: give `src` the size you need.
|
|
|
210
210
|
|
|
211
211
|

|
|
212
212
|
|
|
213
|
-
|
|
213
|
+
Status badge: a 20 px pill with the `label-sm` text, compact padding and no hover or pointer affordance. Use `soft` (a tonal fill from the tone tokens) for a state; `outlined` is a thin 1 px line at 40 % of the tone. Every tone stays at 4.5:1 or more in the light and dark themes and follows the theme presets.
|
|
214
214
|
|
|
215
215
|
```tsx
|
|
216
216
|
import { Badge } from '@nxgt/material/components';
|
|
217
217
|
|
|
218
|
-
<Badge variant="success">Paid</Badge>
|
|
218
|
+
<Badge variant="soft" tone="success">Paid</Badge>
|
|
219
|
+
<Badge variant="outlined" tone="error">Late</Badge>
|
|
220
|
+
<Badge variant="success">New</Badge>
|
|
219
221
|
```
|
|
220
222
|
|
|
221
223
|
| Prop | Type | Default | Effect |
|
|
222
224
|
| --- | --- | --- | --- |
|
|
223
|
-
| `variant` | `'default' \| 'secondary' \| 'error' \| 'success' \| 'info' \| 'warning' \| 'outlined'` | `'default'` |
|
|
225
|
+
| `variant` | `'default' \| 'secondary' \| 'error' \| 'success' \| 'info' \| 'warning' \| 'soft' \| 'outlined'` | `'default'` | the filled tones, `soft` (recommended for a state) or `outlined` |
|
|
226
|
+
| `tone` | the eight named tones, or a tone of your own declared in `AppTones` | `'neutral'` | the colour of a `soft` or `outlined` badge; ignored by the filled variants |
|
|
224
227
|
| `asChild` | `boolean` | `false` | renders its child (a link) with the badge styles |
|
|
225
228
|
|
|
226
|
-
|
|
229
|
+
A `Badge` link (`asChild`) no longer fades on hover: a badge is a status, not a button. Put a `Chip` or a `Button` where the user acts.
|
|
230
|
+
|
|
231
|
+
**Storybook:** `Data display/Badge` (Soft, SoftContrast) · **Vue:** `Badge` (without `soft` and `tone` yet)
|
|
227
232
|
|
|
228
233
|
---
|
|
229
234
|
|
|
@@ -428,12 +433,22 @@ import { StatCard } from '@nxgt/material/components';
|
|
|
428
433
|
| `tone` | the eight named tones, or a tone of your own declared in `AppTones` | — | the value is drawn in the tone's text colour and the icon sits on a chip of its container. Without one the card is as before |
|
|
429
434
|
| `variant` | `'outlined' \| 'tonal' \| 'filled'` | `'outlined'` | `tonal` paints the card with the tone's container and a fine outline in its text colour (3:1 against the page); `filled` paints it with the tone and draws everything on it in the on-colour. Both use `neutral` when there is no `tone`. Every text is 4.5:1 or more. In a [calm zone](../guides/theming.md#calm-zone), a `tonal` or `filled` card is the card's own surface with the tone's ring, and its icon chip a ring |
|
|
430
435
|
| `valueSize` | `'default' \| 'display'` | `'default'` | `display` draws the value larger (`headline-large`) |
|
|
436
|
+
| `size` | `'default' \| 'sm'` | `'default'` | `sm` is a compact tile for a band or a header (see below); it ignores `hint`, `delta` and `valueSize` |
|
|
431
437
|
|
|
432
438
|
```tsx
|
|
433
439
|
<StatCard tone="success" variant="tonal" valueSize="display" label="Revenue" value="€48,200" delta={12.4} icon={<CreditCard />} />
|
|
434
440
|
```
|
|
435
441
|
|
|
436
|
-
|
|
442
|
+
**Compact tile (`size="sm"`).** For figures in a header band: a tile 40 to 56 px tall that hugs its content, the optional icon chip, then the value (tabular figures) over its label. It takes the same `tone` and `variant` (`tonal`, `outlined`, `filled`); `hint`, `delta` and `valueSize` are ignored (nothing is drawn for them, and `loading` shows a skeleton in place of the value only). Tiles in a `flex flex-wrap gap-2` row sit side by side and wrap on a phone.
|
|
443
|
+
|
|
444
|
+
```tsx
|
|
445
|
+
<div className="flex flex-wrap gap-2">
|
|
446
|
+
<StatCard size="sm" variant="tonal" tone="primary" label="Students" value={22} />
|
|
447
|
+
<StatCard size="sm" variant="tonal" tone="tertiary" label="Classes" value={6} />
|
|
448
|
+
</div>
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
Parts: the root carries `data-slot="stat-card"`, `data-variant` and, when a tone applies, `data-tone` (the tone, or `neutral` on a `tonal` or `filled` card without one); the icon wrapper carries `data-slot="stat-card-icon"`. The compact tile adds `data-size="sm"`. The types `StatCardProps`, `StatCardVariant`, `StatCardValueSize` and `StatCardSize` are exported.
|
|
437
452
|
|
|
438
453
|
In `filled`, the delta and its trend arrow take the on-colour too (the arrow and the sign still say up or down). The outline's share is `--stat-card-ring-share` (`68%`, or `--nxgt-tone-ring-share` for every ring); it draws the tonal card's border, the filled card's edge in a calm zone and the hairline round the icon chip. Known limit: the dark theme's primary `filled` card has an edge of about 2.3:1 on a `Card` and 2.5:1 on the page (its text is 7:1); a `tonal` card avoids it. See [App tones](../guides/theming.md#app-tones-on-shape-fab-avatar-statcard-and-the-metric-cards).
|
|
439
454
|
|
|
@@ -618,10 +633,24 @@ import { Badge, EntityHeader } from '@nxgt/material/components';
|
|
|
618
633
|
| `title` | `ReactNode` | — | the `h1`; optional: without it no heading is rendered (see below) |
|
|
619
634
|
| `icon`, `status` | `ReactNode` | — | before and after the title; `icon` is decoration, shown in an `aria-hidden` badge, so put nothing meaningful or focusable there |
|
|
620
635
|
| `metadata` | `{ label: string; value: ReactNode }[]` | `[]` | facts under the title, a description list: a muted label beside its value, no colon; a link in a value is drawn in the primary text colour (`text-primary-text`), not the prose link's info blue, and `nxgt-plain-link` still opts out |
|
|
636
|
+
| `stats` | `EntityStat[]` | `[]` | figures (counts, a mean) as compact `size="sm"` [StatCard](#statcard) tiles in a wrapping row under the metadata; a `tone` paints a tile, `0` is a figure, an empty value drops the tile (see Stats below) |
|
|
621
637
|
| `actions` | `ResponsiveButtonProps[] \| ReactNode` | `[]` | an array becomes responsive buttons (icon-only on phones) |
|
|
622
638
|
|
|
623
639
|
The root carries `data-slot="entity-header"`; the metadata is a `dl` with `data-slot="entity-metadata"` (a muted `dt` label beside its `dd` value, no colon), and the actions sit in `data-slot="entity-header-actions"` (absent when there are none). Inside an EntityHeader that replaces the old `summary-data` slot and its `Label:` text, so update a selector or a test that read "Label: value".
|
|
624
640
|
|
|
641
|
+
**Stats.** Figures (counts, a mean) go in `stats`, shown as compact [StatCard](#statcard) tiles (`size="sm"`) in a wrapping row under the metadata; facts (dates, names) stay in `metadata`. A `tone` paints a tile (tonal), none leaves it outlined; `0` is a figure, an empty value drops the tile. The row is `data-slot="entity-stats"`, and the type `EntityStat` is exported.
|
|
642
|
+
|
|
643
|
+
```tsx
|
|
644
|
+
<EntityHeader
|
|
645
|
+
title="Class 6A"
|
|
646
|
+
metadata={[{ label: 'Year', value: '2026-2027' }]}
|
|
647
|
+
stats={[
|
|
648
|
+
{ label: 'Students', value: 24, tone: 'primary' },
|
|
649
|
+
{ label: 'Coefficient', value: 2 },
|
|
650
|
+
]}
|
|
651
|
+
/>
|
|
652
|
+
```
|
|
653
|
+
|
|
625
654
|
`title` is optional. A record page whose `h1` is the app bar's title (`handle.title`, `AppShellHandle.title`, see the [app shell guide](../guides/app-shell.md#the-pages-title-in-the-app-bar)) uses EntityHeader without it, so the page keeps a single heading:
|
|
626
655
|
|
|
627
656
|
```tsx
|
|
@@ -653,15 +682,17 @@ With a title, in a narrow container (under `@sm`) the order is title, metadata,
|
|
|
653
682
|
Row with leading / trailing slots, title and subtitle; becomes a link or button when interactive. `size="sm"` is the dense row.
|
|
654
683
|
|
|
655
684
|
```tsx
|
|
656
|
-
import { ChevronRight } from 'lucide-react';
|
|
657
685
|
import { ListTile } from '@nxgt/material/components';
|
|
658
686
|
|
|
659
|
-
<ListTile title="Notifications" subtitle="Email and push"
|
|
687
|
+
<ListTile title="Notifications" subtitle="Email and push" href="/settings/notifications" />
|
|
660
688
|
```
|
|
661
689
|
|
|
690
|
+
A tile with an `href` ends with a discreet chevron after `trailing`: decorative (`aria-hidden`, so the link's name stays its text) and flipped in right-to-left. `chevron={false}` opts out; `chevron` on a button tile (an `onClick` that navigates) opts in.
|
|
691
|
+
|
|
662
692
|
| Prop | Type | Default | Effect |
|
|
663
693
|
| --- | --- | --- | --- |
|
|
664
|
-
| `href` | `string` | — | renders an `<a
|
|
694
|
+
| `href` | `string` | — | renders an `<a>`, with a chevron by default |
|
|
695
|
+
| `chevron` | `boolean` | `true` with an `href`, else `false` | the decorative chevron after `trailing` |
|
|
665
696
|
| `linkComponent` | `AppShellLinkComponent` | plain `<a>` | the link element, as in the app shell: takes `href`, spreads every other prop, forwards `ref` |
|
|
666
697
|
| `onClick` | `MouseEventHandler` | — | renders a `<button>` |
|
|
667
698
|
| `selected` | `boolean` | `false` | highlighted row |
|
|
@@ -7,7 +7,7 @@ centre.
|
|
|
7
7
|
| | Component | What it does |
|
|
8
8
|
| --- | --- | --- |
|
|
9
9
|
| <img src="../previews/feedback-status-alert.webp" width="96" alt=""> | [Alert](#alert) | Callout with icon, title and description in seven tones |
|
|
10
|
-
| <img src="../previews/feedback-status-banner.webp" width="96" alt=""> | [Banner](#banner) | Dismissible banner with an action |
|
|
10
|
+
| <img src="../previews/feedback-status-banner.webp" width="96" alt=""> | [Banner](#banner) | Dismissible banner with an action, in the look of Alert |
|
|
11
11
|
| <img src="../previews/feedback-status-alertdialog.webp" width="96" alt=""> | [AlertDialog](#alertdialog) | Parts for a blocking confirmation dialog |
|
|
12
12
|
| <img src="../previews/feedback-status-confirmdialog.webp" width="96" alt=""> | [ConfirmDialog](#confirmdialog) | Confirm / cancel dialog, `ConfirmationDialog`, `CustomAlertDialog` |
|
|
13
13
|
| <img src="../previews/feedback-status-customdialog.webp" width="96" alt=""> | [CustomDialog](#customdialog) | Ready-made dialog with trigger, icon, title, body and footer |
|
|
@@ -63,6 +63,13 @@ The root carries `data-slot="alert"`, the title `data-slot="alert-title"` and th
|
|
|
63
63
|
|
|
64
64
|
Dismissible info / success / warning / error banner with an action.
|
|
65
65
|
|
|
66
|
+
A Banner looks like an [Alert](#alert): the same light tone surface, start
|
|
67
|
+
accent bar, tone-coloured icon, title and description typography and padding.
|
|
68
|
+
They are built on one shared variants module, so they cannot drift. What a
|
|
69
|
+
Banner adds is its role: page level (full width), an `action` and a dismiss
|
|
70
|
+
button. Before 3.12 it was a solid tinted fill with a full tone border and
|
|
71
|
+
rounded corners.
|
|
72
|
+
|
|
66
73
|
```tsx
|
|
67
74
|
import { Banner, Button } from '@nxgt/material/components';
|
|
68
75
|
|
|
@@ -754,6 +761,10 @@ longer than the toast (a URL) breaks at its edge instead of pushing the
|
|
|
754
761
|
action out. With a long message and an action, the action and the X go under
|
|
755
762
|
the text; the icon stays 40px and never squeezes the text.
|
|
756
763
|
|
|
764
|
+
In a collapsed stack only the front toast's action and X are reachable; the
|
|
765
|
+
ones behind it appear when the stack opens on hover or when focus is inside
|
|
766
|
+
the toaster.
|
|
767
|
+
|
|
757
768
|
The toast id defaults to the title, so two toasts with the same title
|
|
758
769
|
replace each other; pass `options: { id }` to show both. `Toaster` wraps
|
|
759
770
|
Sonner's and takes its props (`position`, `expand`, `richColors`…), all
|
|
@@ -7,7 +7,7 @@ documentation-site shell. Everything is imported from
|
|
|
7
7
|
|
|
8
8
|
| | Component | What it does |
|
|
9
9
|
| --- | --- | --- |
|
|
10
|
-
| <img src="../previews/navigation-layout-appshell.webp" width="96" alt=""> | [ActivityLayout](#activitylayout) | App shell: sidebar, header, breadcrumbs, titled page; a route's `handle.title` is the page's `h1` in the app bar (it labels `main`; `usePageTitleFocus()` focuses it after a navigation), `breadcrumbRoot` a text root crumb, `context` the tenant line atop the phone's menu sheet; `menuOpen` / `onMenuOpenChange` control the phone's menu sheet, which closes itself on a followed link or any location change |
|
|
10
|
+
| <img src="../previews/navigation-layout-appshell.webp" width="96" alt=""> | [ActivityLayout](#activitylayout) | App shell: sidebar, header, breadcrumbs, titled page; a route's `handle.title` is the page's `h1` in the app bar (it labels `main`; `usePageTitleFocus()` focuses it after a navigation), `breadcrumbRoot` a text root crumb, `breadcrumb={false}` no trail, `context` the tenant line atop the phone's menu sheet; `menuOpen` / `onMenuOpenChange` control the phone's menu sheet, which closes itself on a followed link or any location change |
|
|
11
11
|
| <img src="../previews/navigation-layout-accountmenu.webp" width="96" alt=""> | [AccountMenu](#accountmenu) | The app bar's avatar menu: the account, the app's links, Sign out |
|
|
12
12
|
| <img src="../previews/navigation-layout-authcard.webp" width="96" alt=""> | [AuthCard](#authcard) | Signed-out page frame: one `main`, a centred card, its title the `h1` |
|
|
13
13
|
| <img src="../previews/navigation-layout-sidebar.webp" width="96" alt=""> | [Sidebar](#sidebar) | Collapsible navigation sidebar |
|
|
@@ -64,7 +64,7 @@ phone's menu sheet pad clear of `env(safe-area-inset-*)`, as classes; see the
|
|
|
64
64
|
|
|
65
65
|
| Prop | Type | Default | Effect |
|
|
66
66
|
| --- | --- | --- | --- |
|
|
67
|
-
| `items` | `MenuItem[]` | — | `label` (already translated), `url`, `icon`, `footer`, `badge` (`true` a dot, a number a count up to "99+"), `badgeLabel` (see [Badges](../guides/app-shell.md#badges)) |
|
|
67
|
+
| `items` | `MenuItem[]` | — | `label` (already translated), `url`, `icon`, `footer`, `display` (`'icon'`: a round icon-only button, the label as tooltip and name; see the [app shell guide](../guides/app-shell.md)), `badge` (`true` a dot, a number a count up to "99+"), `badgeLabel` (see [Badges](../guides/app-shell.md#badges)) |
|
|
68
68
|
| `linkComponent` | `AppShellLinkComponent` | React Router `Link` | The link each menu entry renders, given `href`; see the [app shell guide](../guides/app-shell.md) |
|
|
69
69
|
| `logo`, `name` | `AppLogo` (`ReactNode \| string \| AppLogoImage`, `AppLogoImage` = `{ src, alt? }`), `string` | — | the app's logo alone in the collapsed rail, named by `name` (also its tooltip); logo and name side by side in the phone's menu sheet |
|
|
70
70
|
| `context` | `ReactNode` | — | a line at the top of the phone's menu sheet, above the brand: the current tenant's name; `name` stays the app's brand; `AppHeader` and `AppSidebar` take it too (see the [app shell guide](../guides/app-shell.md#the-brand-logo-and-name)) |
|
|
@@ -72,6 +72,7 @@ phone's menu sheet pad clear of `env(safe-area-inset-*)`, as classes; see the
|
|
|
72
72
|
| `avatar` | `ReactNode` | — | an image URL or a node, in the header |
|
|
73
73
|
| `headerTitle` | `ReactNode` | — | the header's leading title (the context: an organisation's name), after the phone's menu button, truncated when long; when it is cut off, its full text is the `title` tooltip (#629) |
|
|
74
74
|
| `handle.title` | `ReactNode \| (match: UIMatch) => ReactNode` | — | on a route: the page's title in the app bar, its one `h1`, in the first server HTML; `ActivityContent` leaves its own title out while it is set (see the [app shell guide](../guides/app-shell.md#the-pages-title-in-the-app-bar)) |
|
|
75
|
+
| `breadcrumb` | `boolean` | `true` | `false` hides the breadcrumb trail (an error page); it is also hidden when every matched `handle.breadcrumb` renders `null`; see the [app shell guide](../guides/app-shell.md#breadcrumbs) |
|
|
75
76
|
| `breadcrumbRoot` | `{ label: ReactNode; to: string }` | — | the breadcrumb's first crumb, a text link in place of the home icon (`AppBreadcrumb`'s `root`); see the [app shell guide](../guides/app-shell.md#breadcrumbs) |
|
|
76
77
|
| `headerActions` | `ReactNode` | — | header, before the avatar |
|
|
77
78
|
| `title`, `subtitle`, `actions` | `ReactNode` | — | `ActivityContent`'s header card; `title` is left out while a route's `handle.title` puts the page's title in the app bar |
|
|
@@ -295,7 +296,7 @@ import { BottomAppBar, IconButton, TopAppBar, Typography } from '@nxgt/material/
|
|
|
295
296
|
|
|
296
297
|
<TopAppBar>
|
|
297
298
|
<IconButton variant="ghost" aria-label="Open menu"><Menu /></IconButton>
|
|
298
|
-
<Typography variant="title-
|
|
299
|
+
<Typography variant="title-md" emphasized className="flex-1">Inbox</Typography>
|
|
299
300
|
<IconButton variant="ghost" aria-label="Search"><Search /></IconButton>
|
|
300
301
|
</TopAppBar>
|
|
301
302
|
|
|
@@ -10,7 +10,7 @@ other pages are built from these.
|
|
|
10
10
|
| <img src="../previews/primitives-accordion.webp" width="96" alt=""> | [Accordion](#accordion) | Collapsible sections with a rotating chevron and animated content |
|
|
11
11
|
| <img src="../previews/primitives-collapsible.webp" width="96" alt=""> | [Collapsible](#collapsible) | A single show / hide region and its trigger |
|
|
12
12
|
| <img src="../previews/primitives-card.webp" width="96" alt=""> | [Card](#card) | Card surface with header, action slot, content and footer |
|
|
13
|
-
| <img src="../previews/primitives-typography.webp" width="96" alt=""> | [Typography](#typography) | Text in the Material type scale |
|
|
13
|
+
| <img src="../previews/primitives-typography.webp" width="96" alt=""> | [Typography](#typography) | Text in the Material 3 type scale, with an emphasized weight |
|
|
14
14
|
| <img src="../previews/primitives-anchorheading.webp" width="96" alt=""> | [AnchorHeading](#anchorheading) | Heading with a hover copy-link |
|
|
15
15
|
| <img src="../previews/primitives-label.webp" width="96" alt=""> | [Label](#label) | Form label with an optional required asterisk |
|
|
16
16
|
| <img src="../previews/primitives-helpertext.webp" width="96" alt=""> | [HelperText](#helpertext) | Hint or error message under a field |
|
|
@@ -171,36 +171,78 @@ with `as` (`<CardTitle as="h2">`), so `getByRole('heading', { name })` finds it.
|
|
|
171
171
|
|
|
172
172
|

|
|
173
173
|
|
|
174
|
-
Text in the Material type scale
|
|
174
|
+
Text in the Material 3 type scale: five roles, three sizes each, and an emphasized weight.
|
|
175
175
|
|
|
176
176
|
```tsx
|
|
177
177
|
import { Typography } from '@nxgt/material/components';
|
|
178
178
|
|
|
179
179
|
<>
|
|
180
|
-
<Typography variant="headline-
|
|
181
|
-
<Typography
|
|
182
|
-
<Typography variant="
|
|
180
|
+
<Typography variant="headline-sm">Orders</Typography>
|
|
181
|
+
<Typography>12 orders are waiting to ship.</Typography>
|
|
182
|
+
<Typography variant="label-sm" tone="muted">Updated a minute ago</Typography>
|
|
183
|
+
<Typography variant="title-md" emphasized>Next delivery</Typography>
|
|
183
184
|
</>;
|
|
184
185
|
```
|
|
185
186
|
|
|
186
187
|
| Prop | Type | Default | Effect |
|
|
187
188
|
| --- | --- | --- | --- |
|
|
188
|
-
| `variant` |
|
|
189
|
+
| `variant` | `` `${'display' \| 'headline' \| 'title' \| 'body' \| 'label'}-${'lg' \| 'md' \| 'sm'}` `` | `'body-md'` | the step of the scale: size, line height, tracking and weight together. The fifteen former names still work, deprecated (see below) |
|
|
190
|
+
| `emphasized` | `boolean` | `false` | the heavier weight of the same step (`text-<step>-em`); same size, line height and tracking. No effect on a deprecated alias that is emphasized already |
|
|
189
191
|
| `tone` | `'default' \| 'muted' \| 'primary' \| 'error'` | `'default'` | text colour: `muted` is `text-muted-foreground`, `primary` is `text-primary-text` (primary lightened in dark), `error` is `text-error-text`; `default` inherits (a `className` colour still wins) |
|
|
190
192
|
| `as` | an intrinsic tag name (`keyof JSX.IntrinsicElements`) | `'div'` | the element to render, keeping the variant, e.g. `as="h2"` or `as="span"` |
|
|
191
193
|
| `asChild` | `boolean` | `false` | merge the classes onto the single child instead of rendering an element |
|
|
192
194
|
|
|
195
|
+
**The scale.** Sizes are in px, then line height; the weight is 400 except `title-md`, `title-sm` and the three `label` steps (500).
|
|
196
|
+
|
|
197
|
+
| Role | `lg` | `md` | `sm` |
|
|
198
|
+
| --- | --- | --- | --- |
|
|
199
|
+
| `display` | 57 / 64 | 45 / 52 | 36 / 44 |
|
|
200
|
+
| `headline` | 32 / 40 | 28 / 36 | 24 / 32 |
|
|
201
|
+
| `title` | 22 / 28 | 16 / 24 | 14 / 20 |
|
|
202
|
+
| `body` | 16 / 24 | 14 / 20 | 12 / 16 |
|
|
203
|
+
| `label` | 14 / 20 | 12 / 16 | 11 / 16 |
|
|
204
|
+
|
|
205
|
+
`emphasized` weights: `display` 540, `headline` 560, `title-lg` 580, `title-md` and `title-sm` 680, `body` 520, `label` 700. A variable font such as Inter reads them on its `wght` axis; any other font rounds to the nearest weight it has.
|
|
206
|
+
|
|
207
|
+
**The tokens.** The scale is Tailwind 4 tokens in `styles.css` (`--text-<role>-<size>` with `--line-height`, `--letter-spacing`, `--font-weight` and `--font-weight-em`), so `text-title-md` and `text-title-md-em` are utilities for your own markup, and an app or a zone retunes a step with CSS variables alone (no `defineTheme` key). The [theming guide](../guides/theming.md#type-scale) has the details.
|
|
208
|
+
|
|
209
|
+
```tsx
|
|
210
|
+
<Typography variant="title-md" emphasized className="[--text-title-md:1.5rem] [--text-title-md--line-height:2rem]">
|
|
211
|
+
Larger inside this element
|
|
212
|
+
</Typography>
|
|
213
|
+
```
|
|
214
|
+
|
|
193
215
|
It renders a `<div>` unless `as` or `asChild` says otherwise. Use `as="span"` inside a `<button>` or a link, where a `<div>` is invalid HTML. It sets no `overflow`, so it is never a scroll region: text with break points wraps, while an unbreakable string can overflow its box (add `wrap-anywhere` or `min-w-0`; put your own wrapper with `overflow-auto` and `tabIndex={0}` around it to scroll):
|
|
194
216
|
|
|
195
217
|
```tsx
|
|
196
|
-
<Typography as="span" variant="title-
|
|
197
|
-
<Typography asChild
|
|
198
|
-
<Typography tone="muted"
|
|
218
|
+
<Typography as="span" variant="title-sm" emphasized>Inline</Typography>
|
|
219
|
+
<Typography asChild><a href="/docs">Docs</a></Typography>
|
|
220
|
+
<Typography tone="muted">Jan 3 to Jan 9</Typography>
|
|
199
221
|
```
|
|
200
222
|
|
|
223
|
+
**Deprecated names.** The fifteen former variants are aliases of the scale by name, removed in 4.0. Their **sizes changed** in 3.11 (the weights and line heights follow the scale too):
|
|
224
|
+
|
|
225
|
+
| Former variant | Use | Size before → after |
|
|
226
|
+
| --- | --- | --- |
|
|
227
|
+
| `normal`, `body-medium` | `body-md` | 14 → 14 px (weight 400, which `Typography` now sets itself instead of inheriting its parent's) |
|
|
228
|
+
| `body-small` | `body-sm` | 12 → 12 px |
|
|
229
|
+
| `caption` | `label-sm` with `tone="muted"` | 10 → 11 px |
|
|
230
|
+
| `title-small` | `title-sm` emphasized | 16 → 14 px |
|
|
231
|
+
| `title-medium` | `title-md` emphasized | 18 → 16 px |
|
|
232
|
+
| `title-large` | `title-lg` emphasized | 20 → 22 px |
|
|
233
|
+
| `headline-small` | `headline-sm` emphasized | 24 → 24 px |
|
|
234
|
+
| `headline-medium` | `headline-md` emphasized | 30 → 28 px |
|
|
235
|
+
| `headline-large` | `headline-lg` emphasized | 36 → 32 px |
|
|
236
|
+
| `headline-larger` | `display-sm` emphasized | 48 → 36 px |
|
|
237
|
+
| `huge-small` | `display-sm` emphasized | 60 → 36 px |
|
|
238
|
+
| `huge-medium` | `display-md` emphasized | 72 → 45 px |
|
|
239
|
+
| `huge-large`, `huge-larger` | `display-lg` emphasized | 96 → 57 px |
|
|
240
|
+
|
|
241
|
+
The bare `h1` to `h6` elements follow the scale too: `headline-md`, `headline-sm`, `title-lg`, `title-md`, `title-sm` and `label-md`, at weight 500.
|
|
242
|
+
|
|
201
243
|
The variants are also exported as `typographyVariants` for use on your own elements.
|
|
202
244
|
|
|
203
|
-
**Storybook:** `Primitives/Typography` · **Vue:** `Typography`
|
|
245
|
+
**Storybook:** `Primitives/Typography` (`Scale`), `Primitives/Typography/Type scale` (`Emphasized`, `Aliases`, `ZoneOverride`) · **Vue:** `Typography` (no scale)
|
|
204
246
|
|
|
205
247
|
---
|
|
206
248
|
|
package/docs/guides/app-shell.md
CHANGED
|
@@ -82,6 +82,7 @@ type MenuItem = {
|
|
|
82
82
|
url: string;
|
|
83
83
|
icon: IconName; // a symbol of the duotone sprite, see Getting started › Icons
|
|
84
84
|
footer?: boolean; // pin to the sidebar footer
|
|
85
|
+
display?: 'icon'; // a round, icon-only button; see below
|
|
85
86
|
badge?: number | boolean | ReactNode; // see Badges
|
|
86
87
|
badgeLabel?: string; // the badge's text in the link's name, already translated
|
|
87
88
|
};
|
|
@@ -103,6 +104,22 @@ type MenuItem = {
|
|
|
103
104
|
|
|
104
105
|
- **`footer: true` pins an entry to the footer.** Being last in the list does
|
|
105
106
|
not: appending an entry never moves another one out of the footer.
|
|
107
|
+
- **`display: 'icon'` draws the entry as a round, icon-only button.** Meant
|
|
108
|
+
for the footer's "Settings": in the collapsed rail, the expanded sidebar and
|
|
109
|
+
the phone's menu sheet alike, it is an `IconButton` (the Button family's
|
|
110
|
+
look, ghost, tonal when it is the active entry) with the label as its
|
|
111
|
+
tooltip and, visually hidden, its accessible name, `aria-current="page"`
|
|
112
|
+
when active, and a 44px target on a coarse pointer. The entries without it
|
|
113
|
+
keep their icon and label. An icon entry draws no `badge`.
|
|
114
|
+
|
|
115
|
+
```tsx
|
|
116
|
+
{ label: t('menu.settings'), url: '/settings', icon: 'gear', footer: true, display: 'icon' }
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
- The active entry's icon and label are in primary on the rail: a darker
|
|
120
|
+
primary in light (85 % of its lightness, so the label reads 4.5:1 on its
|
|
121
|
+
pill for every theme preset) and a lavender of its hue in dark; see
|
|
122
|
+
[the sidebar tokens](./theming.md).
|
|
106
123
|
- The icons are drawn with `Icon type="duotone"`, so the duotone sprite must
|
|
107
124
|
be served ([getting started](../getting-started.md#icons)).
|
|
108
125
|
|
|
@@ -183,6 +200,7 @@ partitionMenu(items); // { content, footer }
|
|
|
183
200
|
| `avatar` | `ReactNode` | — | End of the header. A `string` is used as the image URL of an `Avatar`; anything else is rendered as-is |
|
|
184
201
|
| `linkComponent` | `AppShellLinkComponent` | React Router `Link` | The link each menu entry renders, given `href` (see below) |
|
|
185
202
|
| `headerTitle` | `ReactNode` | — | The header's leading title: the context the app is in (the active organisation's name), right after the phone's menu button, truncated when long; when it is cut off, its full text is the `title` tooltip (#629) |
|
|
203
|
+
| `breadcrumb` | `boolean` | `true` | `false` hides the breadcrumb trail, for an error page whose matched routes would show a misleading one (see [Breadcrumbs](#breadcrumbs)) |
|
|
186
204
|
| `breadcrumbRoot` | `AppBreadcrumbRoot` (`{ label: ReactNode; to: string }`) | — | The breadcrumb's first crumb, in place of the home icon: a text link to `to` (see [Breadcrumbs](#breadcrumbs)) |
|
|
187
205
|
| `headerActions` | `ReactNode` | — | In the header, before the avatar: notifications, help, `ThemeToggle` |
|
|
188
206
|
| `children` | `ReactNode` | — | The routed content, usually `<Outlet />` |
|
|
@@ -498,11 +516,23 @@ or an `NxgtTheme` zone around it; see [Themes](./theming.md#the-provider-and-zon
|
|
|
498
516
|
| `backdrop={<Background … />}` | that node, for this screen |
|
|
499
517
|
| `backdrop={false}` | no background and no blobs: a plain page (`data-plain` on the root) |
|
|
500
518
|
|
|
501
|
-
The theme's shapes (the motif and illustrated presets)
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
519
|
+
**Where the shapes go.** The theme's shapes (the motif and illustrated presets)
|
|
520
|
+
are drawn whole or not at all, and only in empty space. A shape that would cross the layer's edge,
|
|
521
|
+
or reach the app bar, the breadcrumb, the header card or an `EntityHeader`, is
|
|
522
|
+
not drawn (a permanent exclusion). A shape that lies over (or within 8px of) the page's text or
|
|
523
|
+
controls (inputs, buttons, links, images, and every line of text) is hidden: it
|
|
524
|
+
fades out in 150ms (none under `prefers-reduced-motion`) and fades back once
|
|
525
|
+
the content moves away, for instance when the page scrolls or a list changes.
|
|
526
|
+
Shapes are hidden, never moved. The check reads the content when it changes or
|
|
527
|
+
resizes and keeps the boxes, so a scroll only shifts them (one `requestAnimationFrame`-throttled
|
|
528
|
+
pass, well under a millisecond on a typical page; text past the first 6000 boxes is not collected, so a shape may be drawn over it). A moon, a planet or an eclipse counts as one shape: it is hidden whole, with its halo. Limits: a sticky element is measured where it is laid out, not where it sticks, and a late font or image, or an inner scroller, triggers a new check a moment later. Nothing shows until the first
|
|
529
|
+
check has run, so no shape flashes over the content. On a phone, where the
|
|
530
|
+
content fills the width, the shapes you see are the ones in the empty part of
|
|
531
|
+
the page, usually below the content. Full-width textures such as `waves` and the
|
|
532
|
+
gradient are not shapes and are not filtered. A `Background` passed as
|
|
533
|
+
`backdrop` follows the same rule; any other node is left as it is. The layer is
|
|
534
|
+
below the content in z-order (`-z-10` in the column's own stacking context), so
|
|
535
|
+
a dialog, a sheet or a sticky header is always above it.
|
|
506
536
|
|
|
507
537
|
`backdrop={false}` used to draw the built-in gradient, as if it were left out;
|
|
508
538
|
it now removes it. To keep the gradient, leave `backdrop` out. A theme's
|
|
@@ -522,7 +552,7 @@ to know the theme on the server (a cookie, passed to `defaultTheme`).
|
|
|
522
552
|
|
|
523
553
|
The shell draws the gradient with three blurred blobs behind its frame. To use
|
|
524
554
|
one of the 50 presets of `@nxgt/material/backgrounds` instead, pass it as
|
|
525
|
-
`backdrop
|
|
555
|
+
`backdrop` (behind the shell its shapes show only in empty space, see [Where the shapes go](#the-themes-background)):
|
|
526
556
|
|
|
527
557
|
```tsx
|
|
528
558
|
import { Outlet } from 'react-router';
|
|
@@ -559,7 +589,7 @@ export function Shell({ items }: { items: MenuItem[] }) {
|
|
|
559
589
|
`scroll="container"` alike.
|
|
560
590
|
- **The chrome is lightly translucent and frosted.** Under a `backdrop`, the
|
|
561
591
|
app bar, the breadcrumb (a pill as wide as its crumbs) and the tabs strip
|
|
562
|
-
show
|
|
592
|
+
show the backdrop faintly through their fill, over a 6px blur that keeps it from
|
|
563
593
|
competing with a text. The lists and tables (band and rows), the segmented
|
|
564
594
|
`ToggleGroup`, the list bar, the phone tiles and the cards stay opaque, so no
|
|
565
595
|
shape runs behind a row. The sidebar is frosted too (87%), but the layer is
|
|
@@ -797,7 +827,21 @@ to the router's root, then adds one crumb per matched route that has a
|
|
|
797
827
|
the trail. The last crumb is text (`aria-current="page"`); the others are
|
|
798
828
|
links, each inside its `<li>`. The home icon is itself the link, named "Home"
|
|
799
829
|
(`material.app-shell.home`, translated): `getByRole('link', { name: 'Home' })`.
|
|
800
|
-
The trail is hidden when no route has a breadcrumb.
|
|
830
|
+
The trail is hidden when no route has a breadcrumb. A crumb whose
|
|
831
|
+
`handle.breadcrumb` returns `null` (or `undefined`, `false`) is left out with
|
|
832
|
+
its separator, as `handle.title` accepts `null`, so the trail is hidden too
|
|
833
|
+
when every matched route returns `null`.
|
|
834
|
+
|
|
835
|
+
An error page rendered by `ActivityLayout` still matches the failed route, so
|
|
836
|
+
it would show its trail ("Home > Section" on a 404). Pass `breadcrumb={false}`
|
|
837
|
+
to render the layout without the trail:
|
|
838
|
+
|
|
839
|
+
```tsx
|
|
840
|
+
// the root route's errorElement
|
|
841
|
+
<ActivityLayout items={items} breadcrumb={false}>
|
|
842
|
+
<ActivityContent title="Page not found" />
|
|
843
|
+
</ActivityLayout>
|
|
844
|
+
```
|
|
801
845
|
|
|
802
846
|
A multi-tenant app whose root is the current tenant passes `breadcrumbRoot`
|
|
803
847
|
(`AppBreadcrumb`'s `root`) to replace the home icon with a text link,
|