@nxgt/material 3.12.1 → 3.13.1
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 +45 -0
- package/README.md +15 -13
- package/dist/{async-slice-4qbER86I.js → async-slice-ChtlTP8W.js} +2 -2
- package/dist/{background-cEqcZQ3v.js → background-Ce7M5V42.js} +2 -1
- package/dist/backgrounds/css-react/icons-quiet.css +43 -0
- package/dist/backgrounds/css-react/icons.css +2 -0
- package/dist/backgrounds.js +1 -1
- package/dist/{button-BNjw-pnE.js → button-BrgyWw7T.js} +1 -1
- package/dist/charts.js +3 -3
- package/dist/{chip-DLgQTJBZ.js → chip-BZu-bvAJ.js} +1 -1
- package/dist/{code-editor-BZbaYLvo.js → code-editor-DjY7_8TH.js} +4 -4
- package/dist/code-editor.js +4 -4
- package/dist/{code-language-items-BT5BO5w6.js → code-language-items-D1lpNQ6J.js} +2 -2
- package/dist/components/components.css +2 -0
- package/dist/components/ui/display/avatar/avatar.css +17 -0
- package/dist/components/ui/feedback/alert/alert.css +16 -0
- package/dist/{components-yx1t_5_P.js → components-BhGc4Bj7.js} +15147 -14990
- package/dist/components.js +26 -26
- package/dist/{dialog-BQp5IdFl.js → dialog-rOMjlKJ7.js} +1 -1
- package/dist/dnd.js +1 -1
- package/dist/draw-animate.js +1 -1
- package/dist/draw.js +1 -1
- package/dist/{drawing-B8VuG7nx.js → drawing-iP1SDjdL.js} +60 -54
- package/dist/{dropdown-menu-BsH9W_3C.js → dropdown-menu-BEyP-A0J.js} +1 -1
- package/dist/editor.js +527 -527
- package/dist/{empty-state-WaU4-T2c.js → empty-state-ZtqqpX0S.js} +1 -1
- package/dist/hooks.js +2 -2
- package/dist/{i18n-provider-C6HhBw34.js → i18n-provider-GV4Ukoe2.js} +1 -1
- package/dist/i18n.js +5 -5
- package/dist/{icon-badge-DUAq9f7H.js → icon-badge-_tY1MwU9.js} +1 -1
- package/dist/lib/backgrounds/background/types.d.ts +2 -1
- package/dist/lib/backgrounds/index.d.ts +1 -1
- package/dist/lib/components/ui/collections/data-table/numeric-class.d.ts +2 -0
- package/dist/lib/components/ui/collections/data-table/table-meta.d.ts +8 -3
- package/dist/lib/components/ui/display/entity-header/entity-header.d.ts +8 -2
- package/dist/lib/components/ui/display/entity-header/entity-stats.d.ts +1 -1
- package/dist/lib/components/ui/display/entity-header/partials/entity-header-body.d.ts +2 -1
- package/dist/lib/components/ui/display/list-tile/list-tile.d.ts +43 -8
- package/dist/lib/components/ui/display/list-tile/partials/list-tile-body.d.ts +4 -1
- package/dist/lib/components/ui/display/list-tile/partials/list-tile-line.d.ts +3 -0
- package/dist/lib/components/ui/display/list-tile/partials/list-tile-toggle.d.ts +20 -0
- package/dist/lib/components/ui/display/stat-card/index.d.ts +2 -0
- package/dist/lib/components/ui/display/stat-card/stat-card-group.d.ts +12 -0
- package/dist/lib/components/ui/feedback/alert/alert-tone-icon.d.ts +9 -0
- package/dist/lib/components/ui/feedback/alert/alert.d.ts +4 -0
- package/dist/lib/components/ui/fields/form-section/form-section-heading-classes.d.ts +4 -0
- package/dist/lib/components/ui/fields/form-section/form-section.d.ts +9 -1
- package/dist/lib/components/ui/layout/app-shell/use-truncated-title.d.ts +11 -1
- package/dist/lib/components/ui/layout/list-details-layout/list-details-layout.d.ts +12 -2
- package/dist/lib/components/ui/layout/list-details-layout/partials/list-details-back.d.ts +16 -0
- package/dist/lib/components/ui/layout/list-details-layout/partials/list-details-details.d.ts +11 -3
- package/dist/lib/components/ui/layout/list-details-layout/partials/pane-opener.d.ts +15 -0
- package/dist/lib/components/ui/layout/list-details-layout/partials/use-container-narrow.d.ts +7 -0
- package/dist/lib/components/ui/layout/list-details-layout/partials/use-pane-focus.d.ts +13 -0
- package/dist/lib/components/ui/widgets/drawing/drawing-types.d.ts +6 -0
- package/dist/lib/components/ui/widgets/drawing/partials/drawing-root.d.ts +5 -1
- package/dist/lib/draw-animate/morph-drawing.d.ts +2 -8
- package/dist/lib/i18n/resources/en/en.d.ts +3 -0
- package/dist/lib/i18n/resources/en/material.json.d.ts +3 -0
- package/dist/lib/i18n/resources/fr/fr.d.ts +3 -0
- package/dist/lib/i18n/resources/fr/material.json.d.ts +3 -0
- package/dist/lib/i18n/resources/resources.d.ts +6 -0
- package/dist/lib/i18n/translator.d.ts +1 -1
- package/dist/lib/print/components/print-document/print-document.d.ts +12 -2
- package/dist/lib/print/index.d.ts +2 -1
- package/dist/lib.js +1 -1
- package/dist/main.js +43 -43
- package/dist/map.js +4 -4
- package/dist/material.css +1 -1
- package/dist/pdf-reader.js +1 -1
- package/dist/print.js +101 -98
- package/dist/{public-B0Nq_yYV.js → public-D_Gz7wgr.js} +6 -6
- package/dist/{rich-code-block-v7WmeZp9.js → rich-code-block-RN-EpeTA.js} +4 -4
- package/dist/{sortable-provider-CgDTolQR.js → sortable-provider-Iej7IS4n.js} +1 -1
- package/dist/{spinner-BtSIYVrC.js → spinner-BwWDXIbg.js} +1 -1
- package/dist/{stored-language-BeIKw3-K.js → stored-language-BAAP7qS3.js} +4 -2
- package/dist/theme-picker.js +2 -2
- package/dist/{theme-toggle-B2Pehdhn.js → theme-toggle-8zHeXSD1.js} +1 -1
- package/dist/{toolbar-items-rUHdVqAq.js → toolbar-items-CVQBYp4K.js} +2 -2
- package/dist/{translate-material-Bd_KgfB8.js → translate-material-C0aOnHpZ.js} +2 -2
- package/dist/{translator-Dje8HrNz.js → translator-BU07gNva.js} +1 -1
- package/dist/{use-chat-composer-B0_STpQN.js → use-chat-composer-P4GxmCHa.js} +14 -14
- package/dist/{use-heading-spy-DXo6IOX9.js → use-heading-spy-DETdqnB6.js} +1 -1
- package/dist/{use-material-translation-B3V_bz-5.js → use-material-translation-DGeVp-qP.js} +2 -2
- package/docs/components/data-display.md +60 -8
- package/docs/components/data-table-filter-and-lists.md +1 -1
- package/docs/components/feedback-and-status.md +22 -4
- package/docs/components/form-fields.md +14 -3
- package/docs/components/navigation-and-layout.md +8 -3
- package/docs/guides/backgrounds.md +35 -2
- package/docs/guides/drawing.md +15 -0
- package/docs/guides/printing.md +113 -2
- package/docs/guides/server-lists-url.md +3 -0
- package/docs/previews/feedback-status-alert.webp +0 -0
- package/docs/roadmap.md +5 -3
- package/docs/troubleshooting.md +59 -0
- package/lib/backgrounds/css-react/icons-quiet.css +43 -0
- package/lib/backgrounds/css-react/icons.css +2 -0
- package/lib/components/components.css +2 -0
- package/lib/components/ui/display/avatar/avatar.css +17 -0
- package/lib/components/ui/feedback/alert/alert.css +16 -0
- package/package.json +6 -2
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { r as e } from "./i18n-provider-
|
|
1
|
+
import { r as e } from "./i18n-provider-GV4Ukoe2.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 } from "react";
|
|
4
4
|
//#region ../../node_modules/date-fns/locale/fr/_lib/formatDistance.js
|
|
@@ -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-GV4Ukoe2.js";
|
|
2
|
+
import { t } from "./translate-material-C0aOnHpZ.js";
|
|
3
3
|
import { useCallback as n } from "react";
|
|
4
4
|
//#region lib/i18n/use-material-translation.ts
|
|
5
5
|
function r() {
|
|
@@ -66,6 +66,12 @@ The hairline's strength is `--avatar-ring-share`, the share of the tone's text c
|
|
|
66
66
|
:root { --avatar-ring-share: 80%; }
|
|
67
67
|
```
|
|
68
68
|
|
|
69
|
+
**Initials scale with the size.** The fallback's text is 40 % of the avatar's diameter (never under 10 px), whatever the size class (`size-6` to `size-16` and beyond): two letters keep clear of the edge at 32 px and grow on a larger avatar. The colours are the tone's, so the text stays 4.5:1. The avatar is a size container (`container-type: inline-size`) and the share is `--avatar-text-share` (a unitless number, `40` by default):
|
|
70
|
+
|
|
71
|
+
```css
|
|
72
|
+
:root { --avatar-text-share: 36; }
|
|
73
|
+
```
|
|
74
|
+
|
|
69
75
|
A fallback you restyle with `className` keeps the hairline: pass `shadow-none` (or a `tone`) for a filled look, for instance `<AvatarFallback className="bg-primary text-primary-foreground shadow-none">`. A toneless `AvatarFallback` inside `<AvatarBase tone="success">` inherits the root's tone. `AvatarFallback` needs an `Avatar` or `AvatarBase` ancestor (or its own `tone`) to be coloured: on its own it renders transparent, with no hairline.
|
|
70
76
|
|
|
71
77
|
A shaped avatar:
|
|
@@ -83,7 +89,7 @@ The fallback with no `tone` is neutral: the neutral tone's container (it was the
|
|
|
83
89
|
<Avatar shape="cookie-9" fallback="MK" tone="maths" /> {/* a tone the app declared in AppTones */}
|
|
84
90
|
```
|
|
85
91
|
|
|
86
|
-
**Storybook:** `Data display/Avatar` (`Shapes`, `ShapedFallbacks`), `Data display/Avatar/Tones` · **Vue:** `Avatar` (`status`, `status-pulse`), `AvatarImage`, `AvatarFallback`
|
|
92
|
+
**Storybook:** `Data display/Avatar` (`Shapes`, `ShapedFallbacks`), `Data display/Avatar/Initials` (`InitialsScale`), `Data display/Avatar/Tones` · **Vue:** `Avatar` (`status`, `status-pulse`), `AvatarImage`, `AvatarFallback`
|
|
87
93
|
|
|
88
94
|
---
|
|
89
95
|
|
|
@@ -439,7 +445,7 @@ import { StatCard } from '@nxgt/material/components';
|
|
|
439
445
|
<StatCard tone="success" variant="tonal" valueSize="display" label="Revenue" value="€48,200" delta={12.4} icon={<CreditCard />} />
|
|
440
446
|
```
|
|
441
447
|
|
|
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
|
|
448
|
+
**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; for an even layout use `StatCardGroup`.
|
|
443
449
|
|
|
444
450
|
```tsx
|
|
445
451
|
<div className="flex flex-wrap gap-2">
|
|
@@ -448,11 +454,24 @@ import { StatCard } from '@nxgt/material/components';
|
|
|
448
454
|
</div>
|
|
449
455
|
```
|
|
450
456
|
|
|
457
|
+
**`StatCardGroup`: an even grid of compact tiles.** Four hugging tiles leave an orphan at 360 px. Wrap the `size="sm"` tiles in a `StatCardGroup`: while the group is narrower than `md` (28 rem, its own width, not the window's) the tiles are two equal columns, 2×2 for four (an odd last tile takes the whole row); from there they are one row of tiles that hug their content, wrapping if they must. It takes the props of a `div`; the root is `data-slot="stat-card-group"` and `StatCardGroupProps` is exported. `EntityHeader`'s `stats` use it.
|
|
458
|
+
|
|
459
|
+
```tsx
|
|
460
|
+
import { StatCard, StatCardGroup } from '@nxgt/material/components';
|
|
461
|
+
|
|
462
|
+
<StatCardGroup>
|
|
463
|
+
<StatCard size="sm" variant="tonal" tone="primary" label="Students" value={22} />
|
|
464
|
+
<StatCard size="sm" variant="tonal" tone="tertiary" label="Classes" value={6} />
|
|
465
|
+
<StatCard size="sm" label="Mean" value="12.4" />
|
|
466
|
+
<StatCard size="sm" label="Absent" value={0} />
|
|
467
|
+
</StatCardGroup>
|
|
468
|
+
```
|
|
469
|
+
|
|
451
470
|
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.
|
|
452
471
|
|
|
453
472
|
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).
|
|
454
473
|
|
|
455
|
-
**Storybook:** `Data display/StatCard`, `Data display/StatCard/Tones` · **Vue:** `StatCard`
|
|
474
|
+
**Storybook:** `Data display/StatCard/Group` (`GroupOnPhone`, `GroupOnDesktop`), `Data display/StatCard/Tones` · **Vue:** `StatCard` (`StatCardGroup` is React only for now)
|
|
456
475
|
|
|
457
476
|
---
|
|
458
477
|
|
|
@@ -630,15 +649,16 @@ import { Badge, EntityHeader } from '@nxgt/material/components';
|
|
|
630
649
|
|
|
631
650
|
| Prop | Type | Default | Effect |
|
|
632
651
|
| --- | --- | --- | --- |
|
|
633
|
-
| `title` | `ReactNode` | — | the `h1
|
|
652
|
+
| `title` | `ReactNode` | — | the heading (an `h1` unless `headingLevel` says otherwise); optional: without it no heading is rendered (see below) |
|
|
653
|
+
| `headingLevel` | `1 \| 2 \| 3` | `1` | the title's heading level: pass `2` or `3` when the page already has its `h1` (a list title beside a detail header); the look does not change |
|
|
634
654
|
| `icon`, `status` | `ReactNode` | — | before and after the title; `icon` is decoration, shown in an `aria-hidden` badge, so put nothing meaningful or focusable there |
|
|
635
655
|
| `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) |
|
|
656
|
+
| `stats` | `EntityStat[]` | `[]` | figures (counts, a mean) as compact `size="sm"` [StatCard](#statcard) tiles in a `StatCardGroup` (2×2 on a phone, a wrapping row beside) under the metadata; a `tone` paints a tile, `0` is a figure, an empty value drops the tile (see Stats below) |
|
|
637
657
|
| `actions` | `ResponsiveButtonProps[] \| ReactNode` | `[]` | an array becomes responsive buttons (icon-only on phones) |
|
|
638
658
|
|
|
639
659
|
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".
|
|
640
660
|
|
|
641
|
-
**Stats.** Figures (counts, a mean) go in `stats`, shown as compact [StatCard](#statcard) tiles (`size="sm"`) in a
|
|
661
|
+
**Stats.** Figures (counts, a mean) go in `stats`, shown as compact [StatCard](#statcard) tiles (`size="sm"`) in a `StatCardGroup` (2×2 on a phone, a row that wraps beside) 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
662
|
|
|
643
663
|
```tsx
|
|
644
664
|
<EntityHeader
|
|
@@ -667,6 +687,12 @@ import { Badge, EntityHeader } from '@nxgt/material/components';
|
|
|
667
687
|
|
|
668
688
|
The root is a plain `div` in every case, never a landmark, so there is no accessible name to supply.
|
|
669
689
|
|
|
690
|
+
**A page that has its `h1` already.** A detail header beside a list titled "Team" is not the page's title: `headingLevel={2}` keeps one `h1` and an outline in order.
|
|
691
|
+
|
|
692
|
+
```tsx
|
|
693
|
+
<EntityHeader headingLevel={2} title="Ada Lovelace" metadata={[{ label: 'Role', value: 'Analyst' }]} />
|
|
694
|
+
```
|
|
695
|
+
|
|
670
696
|
Without a `title` the band is one row at every width: the icon inline before the status and metadata, the content taking the space, the actions at the end of the row or, when they do not fit, wrapped below and still at the end. An `Avatar` as `icon` is drawn without the badge around it.
|
|
671
697
|
|
|
672
698
|
With a title, in a narrow container (under `@sm`) the order is title, metadata, `children`, then the actions; from `@sm` the actions sit beside the title, yet they come after the `children` in the DOM and so in focus order.
|
|
@@ -695,7 +721,8 @@ A tile with an `href` ends with a discreet chevron after `trailing`: decorative
|
|
|
695
721
|
| `chevron` | `boolean` | `true` with an `href`, else `false` | the decorative chevron after `trailing` |
|
|
696
722
|
| `linkComponent` | `AppShellLinkComponent` | plain `<a>` | the link element, as in the app shell: takes `href`, spreads every other prop, forwards `ref` |
|
|
697
723
|
| `onClick` | `MouseEventHandler` | — | renders a `<button>` |
|
|
698
|
-
| `selected` | `boolean` | `false` |
|
|
724
|
+
| `selected` | `boolean` | `false` | the current row: drawn selected; a link gets `aria-current="page"` (your own `aria-current` replaces it), a button `aria-pressed` |
|
|
725
|
+
| `toggle` | `Switch` props | — | makes the tile a switch row (see below): the whole row toggles a Switch at its end; `href`, `onClick`, `actions`, `chevron` and `linkComponent` are refused beside it by type |
|
|
699
726
|
| `size` | `'sm' \| 'md'` | `'md'` | `sm` is the dense navigation row |
|
|
700
727
|
| `leading`, `trailing`, `subtitle` | `ReactNode` | — | slots; `trailing` sits inside the link, so it holds decoration only |
|
|
701
728
|
| `actions` | `ReactNode` | — | buttons at the end of the tile, outside the link, vertically centred (`data-slot="item-actions"`) |
|
|
@@ -735,11 +762,36 @@ export function SettingsRow() {
|
|
|
735
762
|
}
|
|
736
763
|
```
|
|
737
764
|
|
|
765
|
+
**A selected link, for list-detail navigation.** Give `selected` from the router's location: the open row is a link (a shareable URL), `aria-current="page"` and drawn selected. Pass `aria-current="true"` when the selection is not a page.
|
|
766
|
+
|
|
767
|
+
```tsx
|
|
768
|
+
import { useLocation } from 'react-router';
|
|
769
|
+
|
|
770
|
+
function GuideList({ guides }: { guides: { to: string; title: string }[] }) {
|
|
771
|
+
const { pathname } = useLocation();
|
|
772
|
+
return (
|
|
773
|
+
<nav aria-label="Guides">
|
|
774
|
+
{guides.map((guide) => (
|
|
775
|
+
<ListTile key={guide.to} title={guide.title} href={guide.to} linkComponent={RouterLink} selected={pathname === guide.to} />
|
|
776
|
+
))}
|
|
777
|
+
</nav>
|
|
778
|
+
);
|
|
779
|
+
}
|
|
780
|
+
```
|
|
781
|
+
|
|
782
|
+
**A switch row.** `toggle` takes a Switch's props (`checked`, `defaultChecked`, `onCheckedChange`, `name`, `thumbIcons`…). The tile is then a `<label>` for the switch, so a press anywhere on the row toggles it; the switch is the one tab stop (Space or Enter), named by `title` and described by `subtitle`, and `disabled` on the tile disables it. A switch row is neither a link nor a button: `href`, `onClick`, `actions` and `chevron` do not apply, and `trailing` sits before the switch.
|
|
783
|
+
|
|
784
|
+
```tsx
|
|
785
|
+
const [on, setOn] = useState(true);
|
|
786
|
+
|
|
787
|
+
<ListTile title="Notifications" subtitle="Email and push" toggle={{ checked: on, onCheckedChange: setOn }} />
|
|
788
|
+
```
|
|
789
|
+
|
|
738
790
|
See also [A `ListTile` link reloads the whole page](../troubleshooting.md#a-listtile-link-reloads-the-whole-page). A disabled tile never renders the link component: it is a plain `<a>` without `href`, with `aria-disabled="true"`.
|
|
739
791
|
|
|
740
792
|
The root carries `data-slot="item-detail"` (a tile that is a button or a plain row too).
|
|
741
793
|
|
|
742
|
-
**Storybook:** `Data display/ListTile` · **Vue:** `ListTile`
|
|
794
|
+
**Storybook:** `Data display/ListTile`, `Data display/ListTile links`, `Data display/ListTile current` · **Vue:** `ListTile`
|
|
743
795
|
|
|
744
796
|
---
|
|
745
797
|
|
|
@@ -87,7 +87,7 @@ table enables, for typing your own column defs.
|
|
|
87
87
|
|
|
88
88
|
| Prop | Type | Default | Effect |
|
|
89
89
|
| --- | --- | --- | --- |
|
|
90
|
-
| `columns` | `ColumnDef<DataTableFeatures, T>[]` | — | `meta.className` styles a column's cells; `meta.label` names the column in the cards sort menu |
|
|
90
|
+
| `columns` | `ColumnDef<DataTableFeatures, T>[]` | — | `meta.className` styles a column's cells; `meta.numeric` sets a column of figures flush to the end in tabular digits (`text-end tabular-nums`, header and cells), so decimals line up; `meta.label` names the column in the cards sort menu |
|
|
91
91
|
| `data` | `T[]` | — | rows; sorting, filtering and pagination run client-side |
|
|
92
92
|
| `layout` | `'table' \| 'cards' \| 'tiles' \| { phone, wide }` | `'table'` | `'cards'` lays the rows out as a list of cards, `'tiles'` as one column in a single surface ([Cards and tiles](../guides/cards-and-tiles.md)); both ignore `virtual`, `renderExpandedRow` and `showViewOptions` |
|
|
93
93
|
| `cardDensity` | `'comfortable' \| 'compact'` | `'comfortable'` | `compact` fits cards of at least `--nxgt-cards-min` (15rem), up to four a row, with a tighter padding; `layout="cards"` only ([Cards and tiles](../guides/cards-and-tiles.md#columns)) |
|
|
@@ -38,22 +38,40 @@ import { CircleAlert } from 'lucide-react';
|
|
|
38
38
|
|
|
39
39
|
<Alert
|
|
40
40
|
variant="warning"
|
|
41
|
-
icon={<CircleAlert />}
|
|
42
41
|
title="Check the address"
|
|
43
42
|
description="The postcode does not match the city."
|
|
44
43
|
/>
|
|
45
44
|
```
|
|
46
45
|
|
|
46
|
+
An Alert shows its tone's icon by default, the same one a [Banner](#banner)
|
|
47
|
+
shows (an `info` circle for `primary`, `secondary`, `foreground` and `info`, a
|
|
48
|
+
check, a triangle, an alert circle). Pass `icon={<CircleAlert />}` to use
|
|
49
|
+
your own, or `icon={null}` for none. Before 3.13 an Alert had no icon unless
|
|
50
|
+
you passed one, so an Alert without `icon` now gains one: add `icon={null}` to
|
|
51
|
+
keep the old look.
|
|
52
|
+
|
|
47
53
|
| Prop | Type | Default | Effect |
|
|
48
54
|
| --- | --- | --- | --- |
|
|
49
55
|
| `variant` | `'primary' \| 'secondary' \| 'success' \| 'info' \| 'warning' \| 'error' \| 'foreground'` | `'primary'` | left border, tint and icon colour |
|
|
50
|
-
| `icon` | `ReactNode` |
|
|
56
|
+
| `icon` | `ReactNode` | the tone's icon | shown before the text; `null` (or `false`) shows none, any node replaces the tone's icon |
|
|
51
57
|
| `title` | `ReactNode` | — | bold first line |
|
|
52
58
|
| `description` | `ReactNode` | — | muted body text |
|
|
53
59
|
|
|
54
60
|
The root carries `data-slot="alert"`, the title `data-slot="alert-title"` and the description `data-slot="alert-description"`, for tests and selectors.
|
|
55
61
|
|
|
56
|
-
**
|
|
62
|
+
**In a form grid.** Inside `.form-grid-layout`'s `[data-slot="form-fields"]` or a [FormSection](form-fields.md#formsection)'s fields, an `Alert` or a `Banner` placed directly in the grid takes the whole row (`col-span-full w-full`) without any class from you; a utility class of your own on it still wins.
|
|
63
|
+
|
|
64
|
+
```tsx
|
|
65
|
+
<form className="form-grid-layout">
|
|
66
|
+
<div data-slot="form-fields">
|
|
67
|
+
<Alert variant="warning" title="Check the address" />
|
|
68
|
+
<TextField label="Street" />
|
|
69
|
+
<TextField label="City" />
|
|
70
|
+
</div>
|
|
71
|
+
</form>
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
**Storybook:** `Feedback & status/Alert/Tone icon` (`ToneIcon`), `Feedback & status/Alert/In a form grid` (`InFormGrid`) · **Vue:** `Alert` (the default icon and the form-grid row are React only for now)
|
|
57
75
|
|
|
58
76
|
---
|
|
59
77
|
|
|
@@ -85,7 +103,7 @@ import { Banner, Button } from '@nxgt/material/components';
|
|
|
85
103
|
| Prop | Type | Default | Effect |
|
|
86
104
|
| --- | --- | --- | --- |
|
|
87
105
|
| `tone` | `'info' \| 'success' \| 'warning' \| 'error'` | `'info'` | colours and the default icon |
|
|
88
|
-
| `icon` | `ReactNode` | the tone's icon | replaces the default icon; `false` shows none |
|
|
106
|
+
| `icon` | `ReactNode` | the tone's icon | replaces the default icon; `false` shows none. The default icons are Alert's (one shared map) |
|
|
89
107
|
| `action` | `ReactNode` | — | rendered at the end of the banner; under `sm` it drops below the text, in line with it; there a `ghost` or `link` button (a `LinkButton` too, any size) that is the action itself, not wrapped in another element, is pulled back by its own padding, so its label (or leading icon) starts at the text's edge |
|
|
90
108
|
| `onDismiss` | `() => void` | — | shows a close button; the banner does not hide itself |
|
|
91
109
|
|
|
@@ -144,6 +144,7 @@ export function SettingsForm({ onSave }: { onSave: (values: Values) => void }) {
|
|
|
144
144
|
| `titleAs` | `'h2' \| 'h3' \| 'h4' \| 'h5' \| 'h6'` | `'h2'` | the heading level: `'h3'` when the form already sits under an `h2` |
|
|
145
145
|
| `description` | `ReactNode` | — | a muted line under the title; the group's accessible description |
|
|
146
146
|
| `actions` | `ReactNode` | — | controls beside the title, wrapping under it on a phone |
|
|
147
|
+
| `variant` | `'label' \| 'heading'` | `'label'` | `label` is the small uppercase muted title; `heading` puts the title on the type scale (`title-md` emphasized) and the description in `body-md`, muted |
|
|
147
148
|
| `separated` | `boolean` | `true` | a rule above the section when it follows another `FormSection` |
|
|
148
149
|
|
|
149
150
|
The section is a `role="group"` named by its heading (`aria-labelledby`) and
|
|
@@ -157,11 +158,21 @@ grid uses, by the section's own width (it is a container query container, so
|
|
|
157
158
|
it works outside a form grid too). The form grid's wide-field rules still
|
|
158
159
|
apply inside it: a switch, a textarea, chips, files and the rich text editor
|
|
159
160
|
take a row of their own. The rule between sections is a top border drawn on
|
|
160
|
-
each section that follows another; the first has none. The heading is small,
|
|
161
|
+
each section that follows another; the first has none. The default heading is small,
|
|
161
162
|
semibold, uppercase and muted, set apart from the fields' labels, and reads
|
|
162
|
-
4.5:1 in both themes.
|
|
163
|
+
4.5:1 in both themes. `variant="heading"` is the section title the design
|
|
164
|
+
rules ask for: `title-md` emphasized in the text colour over a `body-md`
|
|
165
|
+
muted description (both 4.5:1 or more); the root carries `data-variant`. An
|
|
166
|
+
`Alert` or a `Banner` placed directly in the section's fields takes the whole
|
|
167
|
+
row.
|
|
163
168
|
|
|
164
|
-
|
|
169
|
+
```tsx
|
|
170
|
+
<FormSection variant="heading" title="Profile" description="How you appear to other members.">
|
|
171
|
+
<TextFormField control={control} name="firstName" label="First name" />
|
|
172
|
+
</FormSection>
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
**Storybook:** `Form fields/FormSection/Heading variant` (`HeadingVariant`) · **Vue:** `FormSection` (no `variant` yet)
|
|
165
176
|
|
|
166
177
|
---
|
|
167
178
|
|
|
@@ -361,12 +361,11 @@ List / details that collapses to one view on mobile, a side pane that becomes a
|
|
|
361
361
|
<img src="../previews/navigation-layout-threadlayout.webp" height="96" alt="ThreadLayout">
|
|
362
362
|
|
|
363
363
|
```tsx
|
|
364
|
-
import { ListDetailsLayout,
|
|
364
|
+
import { ListDetailsLayout, PaneLayout, ThreadLayout } from '@nxgt/material/components';
|
|
365
365
|
|
|
366
366
|
<ListDetailsLayout activeView={view} onActiveViewChange={setView}>
|
|
367
367
|
<ListDetailsLayout.List>{conversations}</ListDetailsLayout.List>
|
|
368
|
-
<ListDetailsLayout.Details>
|
|
369
|
-
<ListDetailsTrigger view="list">Back</ListDetailsTrigger>
|
|
368
|
+
<ListDetailsLayout.Details back>
|
|
370
369
|
{thread}
|
|
371
370
|
</ListDetailsLayout.Details>
|
|
372
371
|
</ListDetailsLayout>
|
|
@@ -397,6 +396,8 @@ app bar.
|
|
|
397
396
|
| --- | --- | --- | --- |
|
|
398
397
|
| `activeView` / `defaultActiveView` / `onActiveViewChange` | `'list' \| 'details'` | `'list'` | `ListDetailsLayout`: which view a phone shows |
|
|
399
398
|
| `listPosition` | `'left' \| 'right'` | `'left'` | `ListDetailsLayout` |
|
|
399
|
+
| `breakpoint` | `'viewport' \| 'container'` | `'viewport'` | `ListDetailsLayout`: what the 648 px phone layout follows, the window or the layout's own width |
|
|
400
|
+
| `back` | `boolean \| ReactNode` | — | `ListDetailsLayout.Details`: a bar with the way back to the list, on a phone only; `true` is the translated "Back to the list" (`material['list-details'].back`), a node is its label |
|
|
400
401
|
| `open` / `defaultOpen` / `onOpenChange` | `boolean` | `false` | `PaneLayout`: pane shown |
|
|
401
402
|
| `paneSide` | `'left' \| 'right'` | `'left'` | `PaneLayout` |
|
|
402
403
|
| `sheetTitle` | `string` | "Side Panel" (translated, `material['pane-layout']['sheet-title']`) | `PaneLayout.Pane`: screen-reader title of the sheet on phones |
|
|
@@ -404,6 +405,10 @@ app bar.
|
|
|
404
405
|
|
|
405
406
|
`useListDetails()` and `usePaneLayout()` read the state from inside.
|
|
406
407
|
|
|
408
|
+
**The way back on a phone.** With `back` on `ListDetailsLayout.Details` the details open under a bar with a labelled back button (`ListDetailsLayout.Back`, also usable in your own header; it renders nothing on a wide screen or when the list shows). On a phone the layout also moves the focus: to the details when a row opens them, then back to that row when the way back is taken, also when `activeView` is controlled by the screen (a URL-driven list and detail). A wide screen shows both panes, with no bar and no focus moves. Leave `back` out and `ListDetailsTrigger view="list"` still works as before.
|
|
409
|
+
|
|
410
|
+
**Sizing.** The layout fills the box it is in: `h-full` and, in a flex column, `flex-1 min-h-0`, so a header above it and the layout under it share the height with no `h-dvh`. `breakpoint="container"` makes the single-view layout follow the layout's own width (a dialog, a column of a split view) rather than the window; the first render is the wide one, as on a server.
|
|
411
|
+
|
|
407
412
|
**Storybook:** `Navigation & layout/ListDetailsLayout`, `Navigation & layout/PaneLayout`, `Navigation & layout/ThreadLayout` · **Vue:** `ListDetailsLayout`, `PaneLayout`, `ThreadLayout`
|
|
408
413
|
|
|
409
414
|
---
|
|
@@ -55,7 +55,7 @@ import '@nxgt/material/backgrounds.css';
|
|
|
55
55
|
| `sky` | `'moon'`, `'eclipse'`, `'meteor-shower'`, `'ringed-planet'` or `'northern-lights'`: a second scene drawn over any preset, before the grain; see [Celestial presets and the sky overlay](#celestial-presets-and-the-sky-overlay) | none |
|
|
56
56
|
| `icons` | for `preset="icons"`: a pack name (`school`, `health`, `finance`, `travel`, `nature`, `tech`) or up to 48 icon names and `{ name, family?, type? }` references; see [Icons](#icons) | the `school` pack |
|
|
57
57
|
| `iconStyle` | for `preset="icons"`: one style (`{ family?, type? }` or a sprite name such as `'sharp-light'`) or a list of at most 8, one drawn per icon | duotone thin |
|
|
58
|
-
| `iconMotif` | for `preset="icons"`: `layout` (`scatter`, `grid`, `constellation`, `drift`), `sizeRange`, `rotation`, `duotone` (`opacity` or `tones`), `secondaryOpacity` (0.4 or, with `tones`, 0.85 unless set), `calm` | `{ layout: 'scatter', duotone: 'opacity', calm: true }` (`secondaryOpacity` unset: 0.4, or 0.85 with `tones`) |
|
|
58
|
+
| `iconMotif` | for `preset="icons"`: `layout` (`scatter`, `grid`, `constellation`, `drift`), `sizeRange`, `rotation`, `duotone` (`opacity` or `tones`), `secondaryOpacity` (0.4 or, with `tones`, 0.85 unless set), `calm`, `quiet` (`start`, `end`, `center`: a band the icons fade out of) | `{ layout: 'scatter', duotone: 'opacity', calm: true }` (`secondaryOpacity` unset: 0.4, or 0.85 with `tones`) |
|
|
59
59
|
| `fill` | `viewport` (the viewport's size) or `container` (its parent's size: the parent needs a definite height) | `viewport` |
|
|
60
60
|
|
|
61
61
|
|
|
@@ -357,7 +357,40 @@ import { Background } from '@nxgt/material/backgrounds';
|
|
|
357
357
|
- `animated` drifts each icon a few pixels on `translate` (never under `prefers-reduced-motion`, paused while hidden).
|
|
358
358
|
- In an `ActivityLayout` the keep-inside pass hides an icon that would cross the layer's edge or the header band.
|
|
359
359
|
- A theme can name the preset with `background: { preset: 'icons', icons, iconStyle, iconMotif, sky }`; the props win. The theme picker draws the first four icons in the theme's tile ([themes](themes.md#icon-motifs-in-a-theme)).
|
|
360
|
-
- CSS: `backgrounds/icons.css` (or `backgrounds.css`). React only: `@nxgt/material-vue` does not draw it.
|
|
360
|
+
- CSS: `backgrounds/icons.css` (or `backgrounds.css`). Both pull in `backgrounds/icons-quiet.css`, the mask behind `iconMotif.quiet`, which can also be imported alone. React only: `@nxgt/material-vue` does not draw it.
|
|
361
|
+
|
|
362
|
+
### Keeping text clear of the icons: `quiet`
|
|
363
|
+
|
|
364
|
+
A greeting band, a hero or a card header often puts text on the motif. `calm`
|
|
365
|
+
only thins the icons in the middle of the frame; `iconMotif.quiet` clears a
|
|
366
|
+
**band** of it: the icons fade out over `start`, `end` or the `center`, so the
|
|
367
|
+
text sits on the wash alone.
|
|
368
|
+
|
|
369
|
+
```tsx
|
|
370
|
+
<Background preset="icons" icons="school" iconMotif={{ quiet: 'start' }} className="h-48 rounded-2xl">
|
|
371
|
+
<div className="absolute inset-y-0 start-0 flex w-[42%] flex-col justify-center ps-6">
|
|
372
|
+
<h2>Good morning, Ada</h2>
|
|
373
|
+
<p>Three classes today, the first at 9:00.</p>
|
|
374
|
+
</div>
|
|
375
|
+
</Background>
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
- `'start'` and `'end'` follow the reading direction (the left, then the right,
|
|
379
|
+
in a left-to-right page; swapped in a right-to-left one). `'center'` keeps the
|
|
380
|
+
middle clear, for a centred title.
|
|
381
|
+
- It is a CSS mask on the scene, so it holds at every width and with every
|
|
382
|
+
`layout`; the icons are the same ones (the seed and the layout are unchanged),
|
|
383
|
+
only hidden. No inline style: it is a class on the scene (`ic-q-start`,
|
|
384
|
+
`ic-q-end`, `ic-q-center`) and a rule of the preset's stylesheet.
|
|
385
|
+
- The band is `--background-quiet` wide, **42%** by default, then the icons fade
|
|
386
|
+
back in over 30% more. Set it with a class: `className="[--background-quiet:60%]"`.
|
|
387
|
+
Keep your text inside the band's width, and widen the band on a phone, where
|
|
388
|
+
the text takes more of the line.
|
|
389
|
+
- Contrast: under the mask the text sits on the surface and the wash (at most
|
|
390
|
+
15% of a tone), never on an icon. The `Backgrounds/Icons/Quiet band` story
|
|
391
|
+
measures the title and the muted line at 4.5:1 against the surface and each
|
|
392
|
+
tone of the wash, light and dark.
|
|
393
|
+
- A theme sets it too: `background: { preset: 'icons', iconMotif: { quiet: 'end' } }`.
|
|
361
394
|
|
|
362
395
|
## Dark mode
|
|
363
396
|
|
package/docs/guides/drawing.md
CHANGED
|
@@ -220,10 +220,25 @@ A `Drawing` takes `title` **or** `decorative`; the type refuses neither.
|
|
|
220
220
|
```tsx
|
|
221
221
|
<Drawing scene={chart} title="Sales by month, rising" /> {/* role="img" aria-label */}
|
|
222
222
|
<Drawing scene={flourish} decorative /> {/* aria-hidden */}
|
|
223
|
+
<Drawing
|
|
224
|
+
scene={chart}
|
|
225
|
+
title="Sales by month, rising"
|
|
226
|
+
description="January 12, February 18, March 25, April 31."
|
|
227
|
+
/> {/* role="img", aria-label, <desc>, aria-describedby */}
|
|
223
228
|
```
|
|
224
229
|
|
|
230
|
+
The `<desc>`'s id is the drawing's own id prefix plus `-desc`, so two drawings on
|
|
231
|
+
a page never share one. `toSVG` takes the same `description` (with a `title`; it
|
|
232
|
+
throws on a `decorative` drawing): `toSVG(scene, { title, description })` writes the
|
|
233
|
+
`<desc id="…-desc">` and the root's `aria-describedby`, escaped, in the file or
|
|
234
|
+
e-mail too.
|
|
235
|
+
|
|
225
236
|
- The name is an `aria-label`, never a `<title>` element: React 19 would hoist a
|
|
226
237
|
`<title>` into the document's head.
|
|
238
|
+
- A long description, such as a chart's data, is the `description` prop, next to
|
|
239
|
+
the `title` (it needs one: a decorative drawing is hidden). It is written as a
|
|
240
|
+
`<desc>` inside the `<svg>`, which the root names in `aria-describedby`, so a
|
|
241
|
+
screen reader reads the name, the role, then the description.
|
|
227
242
|
- A drawing with an interactive node is `role="group"`, because an image cannot
|
|
228
243
|
hold focusable children; a decorative one cannot hold an interactive node and
|
|
229
244
|
throws `A decorative drawing has an interactive node: give the Drawing a title instead`.
|
package/docs/guides/printing.md
CHANGED
|
@@ -121,6 +121,8 @@ import { PrintDocument, PrintHeader } from '@nxgt/material/print';
|
|
|
121
121
|
| `producer` | `creator` | The software that wrote the PDF. |
|
|
122
122
|
| `language` | the `I18nProvider` around the document, then `'en'` | The built-in strings ("Page 2 of 5") and the PDF's language. |
|
|
123
123
|
| `locale`, `timeZone` | `language`, the runtime's | How the data blocks format amounts and dates. |
|
|
124
|
+
| `theme` | — | `{ fonts: { sans, serif, mono }, colors }`: the page's font family, which every block inherits. Pass a family you embedded with [`registerPdfFont`](#embedding-a-font-and-archiving) to ship the font in the file. |
|
|
125
|
+
| `conformance` | — | A PDF/A level (`'PDF/A-2b'`, …) the file claims. Needs every font embedded; see [Embedding a font](#embedding-a-font-and-archiving). |
|
|
124
126
|
|
|
125
127
|
Nothing but `children` is required: `<PrintDocument>{…}</PrintDocument>` is an
|
|
126
128
|
A4 page with the margins the blocks were designed for.
|
|
@@ -366,7 +368,8 @@ TypeScript checks every `key` against `T`.
|
|
|
366
368
|
| `label` | `string` | — | Required. The header text, and the label in a [`PrintKeyValue`](#printkeyvalue-one-record-as-label-and-value-pairs) |
|
|
367
369
|
| `width` | `number \| '<n>%'` | — | A fixed width in points (`40`) or a share of the table (`'25%'`) |
|
|
368
370
|
| `flex` | `number` | `1` | Share of the width the fixed columns leave. Ignored when `width` is set |
|
|
369
|
-
| `align` | `'left' \| 'center' \| 'right'` | `'right'` for `amount`, `number` and `
|
|
371
|
+
| `align` | `'left' \| 'center' \| 'right'` | `'right'` for `amount`, `number`, `percent` and `numeric: true` columns, else `'left'` | Header and cells |
|
|
372
|
+
| `numeric` | `boolean` | `true` for `amount`, `number` and `percent`, else `false` | A column of figures: its cells print with [tabular digits](#tabular-figures) and flush right. Set it on a column whose `format` is a function; `numeric: false` drops the tabular digits of an `amount` column but keeps it flush right (give `align: 'left'` to move it) |
|
|
370
373
|
| `format` | `'amount' \| 'date' \| 'number' \| 'percent' \| (value, row) => string` | none | How the value prints (below) |
|
|
371
374
|
| `currency` | `string \| (row: T) => string \| null \| undefined` | the table's `currency` | ISO 4217 code for `format: 'amount'`, for the column or per row |
|
|
372
375
|
| `total` | `'sum' \| (rows: T[]) => number \| string \| null` | none | A figure in the totals row: the sum of the column, or your own |
|
|
@@ -553,7 +556,7 @@ function resolvePrintTable<T>(
|
|
|
553
556
|
rows: T[],
|
|
554
557
|
options?: { currency?: string; totalLabel?: string },
|
|
555
558
|
): {
|
|
556
|
-
columns: { id: string; label: string; align: 'left' | 'center' | 'right'; width?: number | `${number}%`; flex?: number }[];
|
|
559
|
+
columns: { id: string; label: string; align: 'left' | 'center' | 'right'; width?: number | `${number}%`; flex?: number; numeric?: boolean }[];
|
|
557
560
|
rows: PrintValue[][];
|
|
558
561
|
footer: PrintValue[][]; // one totals row, or none
|
|
559
562
|
empty: boolean;
|
|
@@ -976,6 +979,8 @@ type PdfDocument = {
|
|
|
976
979
|
version: 1;
|
|
977
980
|
meta?: { title?: string; author?: string; subject?: string; keywords?: string; creator?: string; producer?: string };
|
|
978
981
|
language?: string; // the built-in strings ('en', 'fr') and the PDF's language; 'en' by default
|
|
982
|
+
outline?: boolean; // the bookmarks panel: one entry per heading; off by default
|
|
983
|
+
conformance?: 'PDF/A-1' | 'PDF/A-1b' | 'PDF/A-2' | 'PDF/A-2b' | 'PDF/A-3' | 'PDF/A-3b'; // needs embedded fonts
|
|
979
984
|
locale?: string; // how amounts, dates and numbers print; `language` by default
|
|
980
985
|
timeZone?: string;
|
|
981
986
|
theme?: { colors?: Partial<Record<PdfThemeColor, string>>; fonts?: Partial<Record<'sans' | 'serif' | 'mono', string>> };
|
|
@@ -1332,6 +1337,112 @@ Rendering a model throws, before any page is drawn, on:
|
|
|
1332
1337
|
| `[pdf] Unknown node type "chart".` | A `type` this version does not know: a typo in hand-written JSON, or a document from a newer version |
|
|
1333
1338
|
| `[pdf] Unsupported document version 2: this version renders version 1.` | `version` is not `1` |
|
|
1334
1339
|
|
|
1340
|
+
## Accessible, archival and consistent PDFs
|
|
1341
|
+
|
|
1342
|
+
What a PDF built here is, and is not, for assistive technology and archives.
|
|
1343
|
+
|
|
1344
|
+
### What is written, and what is not tagged
|
|
1345
|
+
|
|
1346
|
+
`@react-pdf/renderer` (4.9) draws text and boxes; it never writes the
|
|
1347
|
+
structure tree a *tagged* PDF needs (the `Marked` flag, `StructTreeRoot`,
|
|
1348
|
+
marked content for headings, tables and figures). pdfkit, underneath, can tag,
|
|
1349
|
+
but react-pdf does not call it, so **no document made here is a tagged PDF,
|
|
1350
|
+
and none claims to be PDF/UA**. A screen reader reads the text in the
|
|
1351
|
+
document's order (the order you wrote the nodes), with no heading or table
|
|
1352
|
+
semantics. What *is* written:
|
|
1353
|
+
|
|
1354
|
+
| What | How | Where a reader sees it |
|
|
1355
|
+
| --- | --- | --- |
|
|
1356
|
+
| Language | `language` on the document (or `<PrintDocument language>`) | The reader's speech language, and the file's `/Lang` |
|
|
1357
|
+
| Title, author, subject, keywords | `meta`, or the `PrintDocument` props | The window title (shown instead of the file name), the document properties |
|
|
1358
|
+
| Outline (bookmarks) | `outline: true` on a [model document](#documents-as-data): each `heading` is an entry, a level 2 under the level 1 before it, a level 3 under the level 2 | The bookmarks panel, with a jump to the heading's page |
|
|
1359
|
+
| Reading order | The order of the nodes, header then body | The text selection, copy and screen-reader order |
|
|
1360
|
+
| PDF/A marker | `conformance: 'PDF/A-2b'` with an embedded font | The archive validators' first check (this is not a validation: run a PDF/A validator on what you keep) |
|
|
1361
|
+
|
|
1362
|
+
Text in an image or a drawing is not text: put what it says in the page
|
|
1363
|
+
next to it. The model's `image` node has no `alt`: there is nowhere in an
|
|
1364
|
+
untagged PDF to write it. The outline lists the headings of the page bodies,
|
|
1365
|
+
not of a repeated `header` or `footer`.
|
|
1366
|
+
|
|
1367
|
+
```ts
|
|
1368
|
+
import { heading, page, pdfDocument, text } from '@nxgt/material/print';
|
|
1369
|
+
|
|
1370
|
+
pdfDocument({
|
|
1371
|
+
language: 'fr',
|
|
1372
|
+
meta: { title: 'Facture INV-42' }, // shown as the window title
|
|
1373
|
+
outline: true, // the bookmarks panel
|
|
1374
|
+
pages: [page({ body: [heading('Facture'), heading('Lignes', 2), text('…')] })],
|
|
1375
|
+
});
|
|
1376
|
+
```
|
|
1377
|
+
|
|
1378
|
+
### Embedding a font and archiving
|
|
1379
|
+
|
|
1380
|
+
pdfkit's standard fonts (Helvetica, Times-Roman, Courier) are **not embedded**
|
|
1381
|
+
in the file: a viewer draws its own Helvetica, so the glyph widths, the euro
|
|
1382
|
+
sign and the narrow no-break space of French amounts can differ, and PDF/A
|
|
1383
|
+
refuses the file. To ship the font, register one family once and name it in
|
|
1384
|
+
the theme (`registerPdfFont` is a thin call to react-pdf's `Font.register`):
|
|
1385
|
+
|
|
1386
|
+
```ts
|
|
1387
|
+
import { pdfDocument, registerPdfFont } from '@nxgt/material/print';
|
|
1388
|
+
import regular from '@fontsource/inter/files/inter-latin-400-normal.woff?url';
|
|
1389
|
+
import bold from '@fontsource/inter/files/inter-latin-700-normal.woff?url';
|
|
1390
|
+
|
|
1391
|
+
const sans = registerPdfFont({ family: 'Inter', regular, bold }); // once per process
|
|
1392
|
+
pdfDocument({
|
|
1393
|
+
theme: { fonts: { sans } }, // or <PrintDocument theme={{ fonts: { sans } }}>
|
|
1394
|
+
conformance: 'PDF/A-2b', // optional: the archive marker
|
|
1395
|
+
pages,
|
|
1396
|
+
});
|
|
1397
|
+
```
|
|
1398
|
+
|
|
1399
|
+
The same two options on the component:
|
|
1400
|
+
|
|
1401
|
+
```tsx
|
|
1402
|
+
<PrintDocument theme={{ fonts: { sans } }} conformance="PDF/A-2b" title="Invoice 2026-001">
|
|
1403
|
+
{/* pages */}
|
|
1404
|
+
</PrintDocument>
|
|
1405
|
+
```
|
|
1406
|
+
|
|
1407
|
+
- **Give static files** (TTF, OTF or WOFF), one per style: `regular`, `bold`,
|
|
1408
|
+
`italic`, `boldItalic`. A variable font (`inter-latin-wght-normal.woff2`)
|
|
1409
|
+
has one weight in a PDF, so its bold looks regular, and its bold subset can
|
|
1410
|
+
fail.
|
|
1411
|
+
- The file embeds the glyphs it uses (a subset), so a family adds tens of
|
|
1412
|
+
kilobytes to a document, not its whole size. The library does **not** bundle
|
|
1413
|
+
a font: the size of `@nxgt/material` and `@nxgt/material-core` stays as it
|
|
1414
|
+
is, and the choice (and its licence) is yours. `@fontsource/inter` (OFL) is a
|
|
1415
|
+
static, free option.
|
|
1416
|
+
- A source is a URL or a path react-pdf can fetch where it renders: a file
|
|
1417
|
+
path on a server, a URL (or a bundler's `?url` import) in the browser.
|
|
1418
|
+
- On a server bundle, the registered font is read at render time; nothing
|
|
1419
|
+
needs `pdfkitStandardFonts()` for it.
|
|
1420
|
+
- Register before the first render; registering the same family again adds
|
|
1421
|
+
its faces.
|
|
1422
|
+
|
|
1423
|
+
### Tabular figures
|
|
1424
|
+
|
|
1425
|
+
Figures that should line up (an invoice's amounts, a report card's marks) need
|
|
1426
|
+
digits of one width. The standard fonts' digits already are; a proportional
|
|
1427
|
+
font such as Inter has them as the OpenType `tnum` feature. A `numeric`
|
|
1428
|
+
column, an `amount`, `number` or `percent` column of a `dataTable` or a
|
|
1429
|
+
`PrintDataTable`, the values of `totals` and a `metric` turn it on, and a
|
|
1430
|
+
`style: { tabularNums: true }` does it on any `text`:
|
|
1431
|
+
|
|
1432
|
+
```ts
|
|
1433
|
+
table({
|
|
1434
|
+
columns: [{ label: 'Item', flex: 3 }, { label: 'Qty', numeric: true, width: 50 }],
|
|
1435
|
+
rows: [['Design', 12], ['Review', 140]],
|
|
1436
|
+
});
|
|
1437
|
+
text('1 234,50', { style: { tabularNums: true } });
|
|
1438
|
+
```
|
|
1439
|
+
|
|
1440
|
+
A font without the feature (the standard ones, Liberation Sans) draws the same
|
|
1441
|
+
as before. On screen, a `DataTable` column takes `meta: { numeric: true }`
|
|
1442
|
+
(flush to the end, `tabular-nums`; see
|
|
1443
|
+
[DataTable](../components/data-table-filter-and-lists.md)), and a `DataGrid`'s
|
|
1444
|
+
cells are `tabular-nums` already.
|
|
1445
|
+
|
|
1335
1446
|
## Why it is a separate entry
|
|
1336
1447
|
|
|
1337
1448
|
`@react-pdf/renderer` depends on `pdfkit`, and pdfkit loads its fourteen
|
|
@@ -43,6 +43,9 @@ The URL it reads and writes is `?q=ada&f=level:in:CE1,CE2&archived=1`: the
|
|
|
43
43
|
search under `q`, one `f=<field>:<operator>:<values>` per condition, and, for a
|
|
44
44
|
field that declares `param` and `style` (`'repeat'`, `'single'` or `'flag'`),
|
|
45
45
|
the app's own param, so an older link such as `?archived=1` keeps working.
|
|
46
|
+
A flag alias at `0` (`?archived=0`) means off: it is no condition, so the link
|
|
47
|
+
shows every row. To filter on "No", write `f=archived:is:0`, which the
|
|
48
|
+
serializer does for you.
|
|
46
49
|
A range is written `a..b` with either end open (`6..`, `..8`).
|
|
47
50
|
|
|
48
51
|
## Parse it in the loader
|
|
Binary file
|
package/docs/roadmap.md
CHANGED
|
@@ -168,8 +168,9 @@ version they shipped in, and planned entries may name the major they target.
|
|
|
168
168
|
accessibility (PDF/UA), archival (PDF/A-3) and electronic invoicing (Factur-X,
|
|
169
169
|
EN 16931). It also brings the items below.
|
|
170
170
|
- **Your brand on the page** — a print theme whose defaults follow the screen
|
|
171
|
-
theme, and your own font files registered in one call (the
|
|
172
|
-
|
|
171
|
+
theme, and your own font files registered in one call (the font half ships:
|
|
172
|
+
`registerPdfFont` and `theme.fonts` on the current renderer; the library
|
|
173
|
+
bundles no fonts).
|
|
173
174
|
- **QR codes, barcodes and charts in a PDF** — pass the value or the chart
|
|
174
175
|
options and get vector output, no data URL to prepare.
|
|
175
176
|
- **Rich text and Markdown in a PDF** — the rich-text editor's content and
|
|
@@ -178,7 +179,8 @@ version they shipped in, and planned entries may name the major they target.
|
|
|
178
179
|
renders it itself, refreshing a moment after its data stops changing.
|
|
179
180
|
- **Test helpers for generated PDFs** — read a generated PDF's text, page count
|
|
180
181
|
and metadata in a unit test, without a browser.
|
|
181
|
-
- **Archive-ready PDFs** — PDF/A-3 output through one option
|
|
182
|
+
- **Archive-ready PDFs** — PDF/A-3 output through one option (the PDF/A-1/2/3
|
|
183
|
+
marker ships already through `conformance` on the current renderer), for invoices and
|
|
182
184
|
records that must be kept.
|
|
183
185
|
- **Electronic invoices (Factur-X)** — an invoice PDF that carries its
|
|
184
186
|
machine-readable data (EN 16931), built on archive-ready PDFs and the
|