@nxgt/material 3.21.0 → 3.22.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.
Files changed (73) hide show
  1. package/CHANGELOG.md +61 -0
  2. package/README.md +6 -4
  3. package/bin/nxgt-backdrop-sprite.js +15 -0
  4. package/dist/background-BtSZsTvY.js +286 -0
  5. package/dist/backgrounds/css-react/icons-quiet.css +5 -5
  6. package/dist/backgrounds/css-react/icons-tint.css +98 -0
  7. package/dist/backgrounds/css-react/icons.css +100 -33
  8. package/dist/backgrounds.js +1 -1
  9. package/dist/{celestial-scene-DaOsRFF3.js → celestial-scene-8Muu6f7O.js} +1 -1
  10. package/dist/components/background-shared.css +5 -0
  11. package/dist/components/components.css +4 -7
  12. package/dist/components/ui/fields/attributes/attributes.css +3 -0
  13. package/dist/components/ui/layout/app-shell/activity-backdrop-shapes.css +1 -1
  14. package/dist/components/ui/layout/auth-card/auth-card-backdrop.css +30 -0
  15. package/dist/{components-CbD5WNVO.js → components-B0oezhGe.js} +10424 -10394
  16. package/dist/components.js +16 -16
  17. package/dist/editor.js +1 -1
  18. package/dist/{empty-state-CJh9f2pc.js → empty-state-lj7vjYWG.js} +1 -1
  19. package/dist/icon-sprites-CaYXzOL4.js +46 -0
  20. package/dist/icons-scene-M4HBeN2c.js +54 -0
  21. package/dist/{illustration-BmQmvW_4.js → illustration-B4_W-OUS.js} +2 -2
  22. package/dist/illustrations.js +1 -1
  23. package/dist/lib/backgrounds/background/partials/icons-check.d.ts +2 -2
  24. package/dist/lib/backgrounds/background/partials/icons-keys.d.ts +21 -1
  25. package/dist/lib/backgrounds/background/partials/icons-scene.d.ts +15 -6
  26. package/dist/lib/backgrounds/background/partials/use-icons-scene.d.ts +13 -8
  27. package/dist/lib/backgrounds/background/types.d.ts +26 -12
  28. package/dist/lib/backgrounds/index.d.ts +1 -1
  29. package/dist/lib/components/ui/layout/app-shell/keep-shapes-inside.d.ts +7 -2
  30. package/dist/lib/components/ui/layout/app-shell/shapes-clear-content.d.ts +10 -3
  31. package/dist/lib/components/ui/layout/app-shell/theme-backdrop.d.ts +3 -1
  32. package/dist/lib/components/ui/layout/app-shell/use-keep-shapes-inside.d.ts +3 -3
  33. package/dist/lib/components/ui/layout/app-shell/watch-content.d.ts +28 -8
  34. package/dist/lib/components/ui/layout/auth-card/auth-card-backdrop.d.ts +23 -0
  35. package/dist/lib/components/ui/layout/auth-card/auth-card.d.ts +15 -1
  36. package/dist/lib/components/ui/primitives/icon/icon-sprites.d.ts +40 -1
  37. package/dist/lib/themes/define-theme.d.ts +8 -0
  38. package/dist/lib/themes/index.d.ts +2 -1
  39. package/dist/lib/vite/index.d.ts +1 -1
  40. package/dist/main.js +28 -28
  41. package/dist/material.css +1 -1
  42. package/dist/pdf-reader.js +1 -1
  43. package/dist/{public-DWuBn4t_.js → public-DAF5SCWH.js} +1 -1
  44. package/dist/{scene-options-CCy3V7XS.js → scene-options-2zNF3ZkQ.js} +4 -2
  45. package/dist/theme-picker.js +3 -3
  46. package/dist/themes.js +9 -4
  47. package/dist/{use-chat-composer-BBFuKNqC.js → use-chat-composer-B0ze1cU9.js} +3 -3
  48. package/docs/components/navigation-and-layout.md +22 -0
  49. package/docs/components/primitives.md +21 -0
  50. package/docs/guides/app-shell.md +9 -8
  51. package/docs/guides/backgrounds.md +214 -16
  52. package/docs/guides/icon-motifs.md +30 -12
  53. package/docs/guides/themes.md +25 -4
  54. package/docs/guides/theming.md +7 -0
  55. package/docs/guides/vite-plugin.md +26 -0
  56. package/docs/previews/backgrounds-background-from-theme.webp +0 -0
  57. package/docs/previews/backgrounds-icons.webp +0 -0
  58. package/docs/previews/backgrounds-matrix.webp +0 -0
  59. package/docs/previews/foundations-themes-icon-motifs.webp +0 -0
  60. package/docs/roadmap.md +6 -0
  61. package/docs/troubleshooting.md +46 -3
  62. package/lib/backgrounds/css-react/icons-quiet.css +5 -5
  63. package/lib/backgrounds/css-react/icons-tint.css +98 -0
  64. package/lib/backgrounds/css-react/icons.css +100 -33
  65. package/lib/components/background-shared.css +5 -0
  66. package/lib/components/components.css +4 -7
  67. package/lib/components/ui/fields/attributes/attributes.css +3 -0
  68. package/lib/components/ui/layout/app-shell/activity-backdrop-shapes.css +1 -1
  69. package/lib/components/ui/layout/auth-card/auth-card-backdrop.css +30 -0
  70. package/package.json +8 -3
  71. package/dist/background-Ce7M5V42.js +0 -250
  72. package/dist/icon-sprites-FxHyqyhg.js +0 -30
  73. package/dist/icons-scene-BpIauJ5d.js +0 -27
@@ -1,3 +1,3 @@
1
- import { t as e } from "./public-DWuBn4t_.js";
1
+ import { t as e } from "./public-DAF5SCWH.js";
2
2
  import { configurePdfReader as t } from "@nxgt/material-core/pdf-reader";
3
3
  export { e as PdfReader, t as configurePdfReader };
@@ -17,7 +17,7 @@ import { n as xe, t as Se } from "./toggle-group-BMJNcFAC.js";
17
17
  import { o as Ce } from "./escape-owner-C4W_sOAU.js";
18
18
  import { n as we } from "./use-slice-reducer-BGkeGtML.js";
19
19
  import { r as Te } from "./use-csp-nonce-ZKs9k-ov.js";
20
- import { n as Ee, t as De } from "./empty-state-CJh9f2pc.js";
20
+ import { n as Ee, t as De } from "./empty-state-lj7vjYWG.js";
21
21
  import { t as Oe } from "./client-only.utils-3nHNzCki.js";
22
22
  import { t as ke } from "./use-scroll-away-PQRFZkT2.js";
23
23
  import { ArrowLeft as Ae, BookOpenText as je, ChevronDown as Me, ChevronLeft as Ne, ChevronRight as Pe, ChevronUp as Fe, Download as Ie, Eraser as Le, FileText as Re, FileWarning as ze, FileX2 as Be, Highlighter as Ve, KeyRound as He, LayoutGrid as Ue, List as We, Loader2 as Ge, Maximize as Ke, MessageSquareText as qe, Minimize as Je, MoreVertical as Ye, MousePointer2 as Xe, MoveHorizontal as Ze, PanelLeft as Qe, Pen as $e, PencilLine as et, Printer as tt, RotateCw as nt, Search as rt, StickyNote as it, Type as at, Underline as ot, Undo2 as st, Volume2 as ct, X as lt, XIcon as ut, ZoomIn as dt, ZoomOut as ft } from "lucide-react";
@@ -3,11 +3,13 @@ import { BODY_DEFAULTS as e } from "@nxgt/material-core/backgrounds";
3
3
  var t = Object.keys({
4
4
  shapes: 1,
5
5
  density: 1,
6
+ layout: 1,
6
7
  size: 1,
7
8
  rotate: 1,
8
9
  shapeFill: 1,
9
- opacitySpread: 1
10
- }).filter((e) => e !== "shapes");
10
+ opacitySpread: 1,
11
+ icons: 1
12
+ }).filter((e) => e !== "shapes" && e !== "icons");
11
13
  function n(e) {
12
14
  return t.map((t) => e?.[t]);
13
15
  }
@@ -1,7 +1,7 @@
1
1
  import { t as e } from "./utils-BLfntITJ.js";
2
2
  import { a as t, i as n, n as r, o as i, r as a, t as o } from "./bubble-input-B7BVd9fX.js";
3
3
  import { t as s } from "./use-material-translation-BFQODuQz.js";
4
- import { n as c, r as l } from "./icon-sprites-FxHyqyhg.js";
4
+ import { a as c, r as l } from "./icon-sprites-CaYXzOL4.js";
5
5
  import { t as u } from "./use-theme-DMcyJ52N.js";
6
6
  import { a as d, r as f } from "./theme-context-Cofk6vgs.js";
7
7
  import { t as p } from "./theme-toggle-mQzIR1hw.js";
@@ -37,7 +37,7 @@ function D() {
37
37
  var O = 4;
38
38
  function k(e, t, n = "pro") {
39
39
  try {
40
- let r = S(e.iconStyle), i = e.icons ?? "school";
40
+ let r = S([e.iconStyle ?? []].flat().map((e) => typeof e == "object" && "style" in e ? e.style : e)), i = e.icons ?? "school";
41
41
  return C(i), (typeof i == "string" ? b[i] : i).slice(0, O).map((e, i) => {
42
42
  let [a] = C([e], r[i % r.length]), o = a;
43
43
  return w(n === "free" ? T(o) : o, t);
@@ -50,7 +50,7 @@ function k(e, t, n = "pro") {
50
50
  //#endregion
51
51
  //#region lib/theme-picker/partials/mini-background.tsx
52
52
  function A({ background: e }) {
53
- let t = e ? e.preset : "none", n = l(), r = c(), i = e && t === "icons" ? k(e, n, r) : [];
53
+ let t = e ? e.preset : "none", n = c(), r = l(), i = e && t === "icons" ? k(e, n, r) : [];
54
54
  return /* @__PURE__ */ v("span", {
55
55
  "aria-hidden": !0,
56
56
  "data-slot": "theme-tile-background",
package/dist/themes.js CHANGED
@@ -9,10 +9,15 @@ import { jsx as y } from "react/jsx-runtime";
9
9
  import { THEME_BOOT_SCRIPT as b, themeBootScript as x } from "@nxgt/material-core/theme/boot";
10
10
  import { ThemeDefinitionError as S, defineTheme as C } from "@nxgt/material-core/theme/define";
11
11
  import { THEME_PRESETS as w, THEME_PRESETS as T, ambre as E, ardoise as D, bleumono as O, bluemoon as k, corail as A, foret as j, lavande as M, menthe as N, ocean as P, pink as F, prune as I, sable as L, slate as R, teal as z } from "@nxgt/material-core/theme/presets";
12
+ //#region lib/themes/define-theme.ts
13
+ function B(t) {
14
+ return C(t, { warn: e() ? void 0 : (e) => console.warn(e) });
15
+ }
16
+ //#endregion
12
17
  //#region lib/themes/nxgt-theme-provider.tsx
13
- var B = () => () => {}, V = () => v(B, () => !1, () => !0);
14
- function H({ themes: l = T, defaultTheme: m = o, defaultMode: b, children: x }) {
15
- let S = v(c.subscribe, c.getSnapshot, c.getServerSnapshot), C = V(), w = _(() => l.map((e) => e.name), [l]);
18
+ var V = () => () => {}, H = () => v(V, () => !1, () => !0);
19
+ function U({ themes: l = T, defaultTheme: m = o, defaultMode: b, children: x }) {
20
+ let S = v(c.subscribe, c.getSnapshot, c.getServerSnapshot), C = H(), w = _(() => l.map((e) => e.name), [l]);
16
21
  !e() && !w.includes(m) && f(`defaultTheme "${m}" is not in themes (${w.join(", ")}).`, "NxgtThemeProvider");
17
22
  let E = s(S, w, m), D = l.find((e) => e.name === E), O = b ?? D?.colorScheme ?? "system";
18
23
  g(() => c.claim({
@@ -39,4 +44,4 @@ function H({ themes: l = T, defaultTheme: m = o, defaultMode: b, children: x })
39
44
  });
40
45
  }
41
46
  //#endregion
42
- export { m as NxgtTheme, H as NxgtThemeProvider, b as THEME_BOOT_SCRIPT, w as THEME_PRESETS, S as ThemeDefinitionError, E as ambre, D as ardoise, O as bleumono, k as bluemoon, A as corail, C as defineTheme, j as foret, M as lavande, N as menthe, P as ocean, F as pink, I as prune, L as sable, R as slate, z as teal, x as themeBootScript, l as useThemeContext };
47
+ export { m as NxgtTheme, U as NxgtThemeProvider, b as THEME_BOOT_SCRIPT, w as THEME_PRESETS, S as ThemeDefinitionError, E as ambre, D as ardoise, O as bleumono, k as bluemoon, A as corail, B as defineTheme, j as foret, M as lavande, N as menthe, P as ocean, F as pink, I as prune, L as sable, R as slate, z as teal, x as themeBootScript, l as useThemeContext };
@@ -23,7 +23,7 @@ import { t as Mt } from "./spinner-B7HCQMFr.js";
23
23
  import { a as Nt } from "./button-tones-BwuVvAmd.js";
24
24
  import { a as Pt, i as Ft, n as It, r as Lt, t as Rt } from "./button-rdIhY2BT.js";
25
25
  import { t as R } from "./icon-button-ifTpXt0A.js";
26
- import { n as zt, r as Bt } from "./icon-sprites-FxHyqyhg.js";
26
+ import { a as zt, r as Bt } from "./icon-sprites-CaYXzOL4.js";
27
27
  import { t as Vt } from "./use-ssr-media-query-C6M5m8sf.js";
28
28
  import { n as Ht, t as Ut } from "./util-4heV2LPI.js";
29
29
  import { t as Wt } from "./useEvent-2fKq-6w8.js";
@@ -38,7 +38,7 @@ import { t as sn } from "./compose-refs-DZ0iIVF-.js";
38
38
  import { n as cn, r as ln, t as un } from "./chip-CEau88ZV.js";
39
39
  import { n as dn, t as fn } from "./icon-badge-CiWXvKCs.js";
40
40
  import { r as pn } from "./use-csp-nonce-ZKs9k-ov.js";
41
- import { t as mn } from "./empty-state-CJh9f2pc.js";
41
+ import { t as mn } from "./empty-state-lj7vjYWG.js";
42
42
  import { t as hn } from "./skeleton-DSdtr2qc.js";
43
43
  import { H as gn, V as _n, X as vn, bt as yn, it as bn, lt as xn, n as Sn, t as Cn, xt as wn, yt as Tn } from "./dist-moutbfbd.js";
44
44
  import { t as En } from "./markdown-Dw_uH7j7.js";
@@ -1140,7 +1140,7 @@ function pu({ items: e, variant: t, tone: n, trigger: r, onCloseAutoFocus: i })
1140
1140
  //#endregion
1141
1141
  //#region lib/components/ui/primitives/icon/icon.tsx
1142
1142
  function mu({ name: e, family: t = "classic", type: r = "duotone", className: i, ...a }) {
1143
- let o = Bt(), s = zt(), c = $a(Nt) && a["aria-label"] === void 0 && a["aria-labelledby"] === void 0;
1143
+ let o = zt(), s = Bt(), c = $a(Nt) && a["aria-label"] === void 0 && a["aria-labelledby"] === void 0;
1144
1144
  return /* @__PURE__ */ G("svg", {
1145
1145
  "data-slot": "icon",
1146
1146
  role: c ? void 0 : "img",
@@ -205,6 +205,28 @@ import { AuthCard, Button, EmailField, LanguageSwitcher, PasswordField } from '@
205
205
  | `brand`, `toolbar` | `ReactNode` | — | the row above the card, left and right; it renders only when one is given |
206
206
  | `banner` | `ReactNode` | — | above that row (an update notice) |
207
207
  | `footer` | `ReactNode` | — | under the card |
208
+ | `backdrop` | `ReactNode \| false` | the theme's background | what is drawn behind the page: a `Background` preset or any node; `false` for none. Without a theme provider nothing is drawn |
209
+ | `keepClear` | `boolean` | `true` | glyphs (shapes or icons) of the backdrop that would meet the brand row, the card or the page's surfaces, text or controls are dropped (never moved); `false` lets them run under the card |
210
+
211
+ ```tsx
212
+ import { Background } from '@nxgt/material/backgrounds';
213
+
214
+ <AuthCard title="Sign in" backdrop={<Background preset="shapes" />}>
215
+ {form}
216
+ </AuthCard>
217
+ ```
218
+
219
+ The backdrop sits in an absolute layer (`data-slot="auth-card-backdrop"`)
220
+ behind the content: `aria-hidden`, `inert`, no pointer events, so it is
221
+ decoration only and never takes a click meant for the form. Under a theme
222
+ provider it is the theme's background (Lavande's gradient included), loaded
223
+ on demand like `ActivityLayout`'s. The keep-clear pass is the one
224
+ `ActivityLayout` uses: after layout, and when the page resizes or changes, a
225
+ glyph (a shape or an icon) whose box meets the card, the brand block or a
226
+ surface, a text or a control of the page (8 px margin) is dropped with a short
227
+ fade, a glyph crossing the layer's edge too; none is cut or moved. Where `ResizeObserver` is missing the scene
228
+ is drawn as it is. The server renders the layer and no measurement, so there
229
+ is no hydration mismatch. Under `prefers-reduced-motion` the fade is off.
208
230
 
209
231
  A non-error notice goes into a `role="status"` region that is always
210
232
  rendered (zero height while empty), so a screen reader announces a notice
@@ -504,6 +504,27 @@ name to its Free twin (`chevrons-left` to `angles-left`). `useIconEdition()` rea
504
504
  // → /static/icons/solid.svg#bell
505
505
  ```
506
506
 
507
+ `IconSpritesProvider` also takes `backdropSprite`, the reduced sprite that only `Background` reads
508
+ (`nxgt-backdrop-sprite` or `nxgtMaterial({ backdropSprite })` builds it; see
509
+ [the backgrounds guide](../guides/backgrounds.md#a-reduced-sprite-for-the-backdrop)). It has two forms, and a nested
510
+ provider without one keeps its parent's. `useBackdropSprite()` returns its `href`, or `undefined` when none is set. A
511
+ path that is not root-relative or does not end in `.svg` throws `IconRefError` (`base`).
512
+
513
+ ```tsx
514
+ // the object form: the sprite's styles count as served
515
+ <IconSpritesProvider
516
+ base="/static/icons/"
517
+ backdropSprite={{ href: '/sprites/backdrop.svg', styles: ['light', 'thin'] }}
518
+ >
519
+ <Background preset="icons" />
520
+ </IconSpritesProvider>;
521
+
522
+ // the string form: served styles still come from servedStyles or the edition
523
+ <IconSpritesProvider base="/static/icons/" edition="free" backdropSprite="/sprites/backdrop.svg">
524
+ <Background preset="icons" />
525
+ </IconSpritesProvider>;
526
+ ```
527
+
507
528
  **Storybook:** `Primitives/Icon` · **Vue:** `Icon`
508
529
 
509
530
  ---
@@ -516,14 +516,15 @@ or an `NxgtTheme` zone around it; see [Themes](./theming.md#the-provider-and-zon
516
516
  | `backdrop={<Background … />}` | that node, for this screen |
517
517
  | `backdrop={false}` | no background and no blobs: a plain page (`data-plain` on the root) |
518
518
 
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
519
+ **Where the shapes go (the drop rule).** The theme's shapes and icons (the motif,
520
+ icons and illustrated presets) are drawn whole or not at all, and only in empty space. A
521
+ glyph that would cross the layer's edge, or reach the app bar, the breadcrumb, the header
522
+ card or an `EntityHeader`, is not drawn (a permanent exclusion). A glyph that lies over (or
523
+ within 8px of) a surface (the whole box of a card, a stat card, a table, a data grid, a list,
524
+ an alert or a dialog), a text (every line) or a control (inputs, buttons, links, images) is
525
+ dropped: it fades out in 150ms (none under `prefers-reduced-motion`) and fades back once the
526
+ content moves away, for instance when the page scrolls or a list changes. Glyphs are dropped,
527
+ never moved or resized, so no glyph is left half hidden at a card's edge. The check reads the content when it changes or
527
528
  resizes and keeps the boxes, so a scroll only shifts them (one `requestAnimationFrame`-throttled
528
529
  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
530
  check has run, so no shape flashes over the content. On a phone, where the
@@ -50,12 +50,12 @@ import '@nxgt/material/backgrounds.css';
50
50
  | `animated` | `boolean`: drifts the layers, see Motion | `false` |
51
51
  | `grain` | `boolean`: film noise over the preset; needs `grain.css` | `false` |
52
52
  | `seed` | `number`: moves a seeded preset's geometry and the grain | `0` |
53
- | `motif` | a `MotifOptions` object (`shapes`, `density`, `size`, `rotate`, `shapeFill`, `opacitySpread`), see [Motif presets](#motif-presets) | all fields optional |
53
+ | `motif` | a `MotifOptions` object (`shapes`, `density`, `layout`, `size`, `rotate`, `shapeFill`, `opacitySpread`), see [Motif presets](#motif-presets) | all fields optional |
54
54
  | `moon` | a `MoonOptions` object for `preset="moon"`, the celestial presets and a `sky`: `position` (`{ x, y }`, fractions of the container), `phase`, `halo`; see [Moon](#moon) | `{ position: { x: 0.8, y: 0.2 }, phase: 0.78, halo: 2.6 }` |
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
- | `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`, `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`) |
57
+ | `iconStyle` | for `preset="icons"` (and `shapes` with `motif.icons`): one style (`{ family?, type? }` or a sprite name such as `'sharp-light'`), a list of at most 8, or a weighted list `[{ style, weight? }]`; one drawn per icon. A weighted list drops the styles whose sprite is not served | light 3, thin 3, sharp-regular 2, sharp-light 2, duotone-thin 1 |
58
+ | `iconMotif` | for `preset="icons"` (and `shapes` with `motif.icons`): `layout` (`even`: shapes and a few icons; `scatter`, `grid`, `constellation`, `drift`: icons only), `colors` (`one`, `palette`, `classic`), `sizeRange`, `rotation`, `duotone` (`opacity` or `tones`), `secondaryOpacity` (0.4 or, with `tones`, 0.85 unless set), `calm` (icons-only layouts), `quiet` (`start`, `end`, `center`: a band the icons fade out of) | `{ layout: 'even', colors: 'one', duotone: 'opacity' }` (`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
 
@@ -181,7 +181,7 @@ of path data; `low-poly` is about 20 KB (210 nodes); `circuit` about 9 KB (90 no
181
181
 
182
182
  <img src="../previews/backgrounds-motifs.webp" width="480" alt="The shapes preset behind a card">
183
183
 
184
- **Behind an `ActivityLayout`** a preset's discrete shapes are hidden wherever they would lie over the page's text or controls, and fade back when the content moves away (see [Where the shapes go](./app-shell.md#the-themes-background)). A `Background` used on its own, on a page you lay out, draws all its shapes.
184
+ **Behind an `ActivityLayout`** a preset's discrete shapes and icons are dropped wherever their box would meet a surface (a card, a table, a list), a text or a control, and fade back when the content moves away (see [The drop rule](#the-drop-rule)). A `Background` used on its own, on a page you lay out, draws all its glyphs.
185
185
 
186
186
  `shapes`, `confetti-grid`, `constellation`, `halftone`, `truchet`, `floating` and `scatter-icons` draw seeded, deterministic geometry from
187
187
  the tone's three colours: the same `seed` gives the same composition on the server and in the
@@ -203,19 +203,26 @@ browser, and a new `seed` redraws it.
203
203
 
204
204
  | Field | Values | Default | Read by |
205
205
  | --- | --- | --- | --- |
206
- | `shapes` | any of `MOTIF_SHAPES_ALL`: `circle`, `ring`, `square`, `triangle`, `plus`, `line`, `arc`, then `star`, `heart`, `hexagon`, `diamond`, `wave`, `zigzag`, `spiral`, then the Material 3 Expressive ones `m3-cookie-6`, `m3-cookie-9`, `m3-flower`, `m3-clover-4`, `m3-soft-burst`, `m3-sunny`, `m3-gem`, `m3-puffy`; none chosen draws circles | `MOTIF_SHAPES`: the first seven | `shapes`, `confetti-grid`, `halftone`, `truchet`, `floating` (`halftone` takes `circle`, else the first of `square`, `ring`, `plus`; `truchet` draws diagonals for `line` without `arc`; `floating` turns `circle`/`triangle` into blobs, `ring`/`arc` into rings, `square` into squircles and ignores `plus`/`line`; `shapes` and `confetti-grid` are the only presets that draw the added shapes, the others ignore them) |
207
- | `density` | `low`, `default`, `high`: how many items | `default` | all fifteen |
206
+ | `shapes` | any of `MOTIF_SHAPES_ALL`: `circle`, `ring`, `square`, `triangle`, `plus`, `line`, `arc`, then `star`, `heart`, `hexagon`, `diamond`, `wave`, `zigzag`, `spiral`, then the Material 3 Expressive ones `m3-cookie-6`, `m3-cookie-9`, `m3-flower`, `m3-clover-4`, `m3-soft-burst`, `m3-sunny`, `m3-gem`, `m3-puffy`, then the radial ones `rays`, `dotted-ring`, `concentric`; none chosen draws circles | `MOTIF_SHAPES`: the first seven | `shapes`, `confetti-grid`, `halftone`, `truchet`, `floating` (`halftone` takes `circle`, else the first of `square`, `ring`, `plus`; `truchet` draws diagonals for `line` without `arc`; `floating` turns `circle`/`triangle` into blobs, `ring`/`arc` into rings, `square` into squircles and ignores `plus`/`line`; `shapes` and `confetti-grid` are the only presets that draw the added shapes, the others ignore them) |
207
+ | `density` | `low`, `default`, `high` (0.55, 1 and 1.7 times the preset's count), `medium` (about 160 items on the scene, more than `high` for `shapes`) or a number, the item count on the scene (up to 600, scaled with the frame's area); `moon` and the celestial presets draw `medium` and numbers as `default` | `default` | all fifteen |
208
+ | `layout` | `scatter` (thrown darts that keep clear of each other) or `even` (a jittered grid over the whole frame, one item per cell: no empty corner, no cluster) | `scatter` | `shapes` |
208
209
  | `size` | `small`, `mixed`, `large` | `mixed` | all fifteen |
209
210
  | `rotate` | `boolean`: turns the items | `false` | `shapes`, `confetti-grid`, `scatter-icons` (tilts up to 35 degrees), the illustrated scenes (up to 35 degrees instead of 8) |
210
211
  | `shapeFill` | `filled`, `outline`, `mixed` | `mixed` | `shapes`, `confetti-grid`, `halftone`, `floating` |
211
212
  | `opacitySpread` | `uniform`, `varied` | `varied` | all but `halftone` |
212
213
 
213
- `MOTIF_SHAPES` (the default) stays the first seven; pass `shapes: MOTIF_SHAPES_ALL`, or a hand-picked list such as `['star', 'm3-flower', 'wave']`, for the rest. In `shapes` and `confetti-grid` the 15 added shapes are drawn once per scene as a repeated pattern, so their tone, fill and (in `confetti-grid`) turn and size are fixed per shape; the first seven behave as before, item by item. The Storybook story `Backgrounds/Motif shapes/ShapeCatalogue` draws every shape filled and outlined.
214
+ `MOTIF_SHAPES` (the default) stays the first seven; pass `shapes: MOTIF_SHAPES_ALL`, or a hand-picked list such as `['star', 'm3-flower', 'wave']`, for the rest. In `shapes` and `confetti-grid` the 15 added shapes before the radial ones are drawn once per scene as a repeated pattern, so their tone, fill and (in `confetti-grid`) turn and size are fixed per shape; the first seven and the three radial ones are drawn item by item. The Storybook story `Backgrounds/Motif shapes/ShapeCatalogue` draws every shape filled and outlined.
214
215
 
215
- <img src="../previews/backgrounds-motif-shapes.webp" width="480" alt="The 22 motif shapes, each filled and outlined">
216
+ <img src="../previews/backgrounds-motif-shapes.webp" width="480" alt="The motif shapes, each filled and outlined">
216
217
 
217
218
  ```tsx
218
219
  <Background preset="shapes" motif={{ shapes: ['star', 'm3-flower', 'wave'] }} />
220
+
221
+ // The radial shapes, spread evenly over the whole frame at medium density
222
+ <Background
223
+ preset="shapes"
224
+ motif={{ shapes: ['rays', 'dotted-ring', 'concentric'], layout: 'even', density: 'medium' }}
225
+ />
219
226
  ```
220
227
 
221
228
  A field a preset does not read is ignored. `rotate` defaults to `false`: motifs stay upright
@@ -326,7 +333,7 @@ them. The theme picker draws a small tile for a theme with a celestial preset.
326
333
 
327
334
  ## Icons
328
335
 
329
- `preset="icons"` scatters Font Awesome icons over a faint wash. The library ships **no icon artwork**: each icon is a
336
+ `preset="icons"` draws a calm mix over a faint wash: motif shapes and, in about one cell in five, a Font Awesome icon, all in one soft tint on an even grid over the whole frame. The library ships **no icon artwork**: each icon is a
330
337
  `<use>` of a sprite file your app serves (`/assets/icons/sprites/` by default; `IconSpritesProvider base="/static/icons/"`
331
338
  moves it, as for `Icon`; `edition="free"` draws the Free sheets, see the [Free edition](icon-motifs.md#the-free-edition)). Font Awesome Pro is licensed to you, not to this package. The [icon motifs guide](icon-motifs.md) puts icons, skies, illustrations and themes together.
332
339
 
@@ -338,12 +345,146 @@ import { Background } from '@nxgt/material/backgrounds';
338
345
  preset="icons"
339
346
  tone="mixed"
340
347
  icons="health" // a pack, or ['rocket', 'star', { name: 'bolt', family: 'sharp', type: 'solid' }]
341
- iconStyle={['duotone-thin', 'duotone-light']} // one style or up to 8; an icon's own family/type wins
342
- iconMotif={{ layout: 'constellation', duotone: 'tones' }}
348
+ iconStyle={[{ style: 'light', weight: 3 }, { style: 'duotone-thin' }]} // a weighted list; an icon's own family/type wins
349
+ iconMotif={{ colors: 'one' }}
350
+ motif={{ icons: { ratio: 0.2 }, density: 'medium' }}
343
351
  animated
344
352
  />;
345
353
  ```
346
354
 
355
+ Since 3.22 the preset is the mix by default; none of this is needed for it:
356
+
357
+ - **The mix**: `motif.icons` is `{ pack?, style?, ratio }`. Each cell of an even, jittered grid is an icon with the
358
+ probability `ratio` (`0.2` for `icons`; `0` is shapes only, `1` icons only), else a motif shape: the `shapes`
359
+ default set, or `motif.shapes`, with the radial ones (`rays`, `dotted-ring`, `concentric`) counted twice so they show.
360
+ `motif.density` counts shapes and icons together (`'medium'` by default: about 160 on a 1600 by 1000 scene;
361
+ `low`, `high` or a number work too). `pack` and `style` default to the `icons` and `iconStyle` props. The same
362
+ mix is available on `preset="shapes"` with `motif={{ icons: { ratio: 0.3 } }}` (default `medium` density, no
363
+ quiet mask); it needs `backgrounds/icons.css` (in `backgrounds.css`). The layout is always even there: `motif.layout` is not read.
364
+ - **One tint**: `iconMotif.colors` is `'one'` (default: the primary tone at low contrast, soft in dark, never bright;
365
+ the shapes sit at about 0.8 of it), `'palette'` (primary, secondary and tertiary at the same contrast, best with
366
+ `tone="mixed"`) or `'classic'` (the three tones at full strength, as before 3.22). It is a class on the scene root
367
+ (`ic-c-one`, `ic-c-palette`, `ic-c-classic`); the tint sets `color` as well as `--fa-primary-color`, so flat styles
368
+ follow.
369
+ - **Styles**: the weighted default is light 3, thin 3, sharp-regular 2, sharp-light 2, duotone-thin 1. A weighted
370
+ list (the default included) is cut to the sprites your app serves: `IconSpritesProvider servedStyles={['light',
371
+ 'thin']}` lists the files, `edition="free"` serves `regular` and `solid`; the weights of what is left keep their
372
+ proportions, and with nothing left it draws Free `regular` and `solid`. A plain style or list is never cut.
373
+
374
+ ### A reduced sprite for the backdrop
375
+
376
+ The backdrop never needs the full per-style sheets (hundreds of KB each). Build one small sprite that holds only the
377
+ backdrop's icon names in the styles you choose, from your own Font Awesome install, and point the provider at it:
378
+
379
+ ```sh
380
+ # reads @fortawesome/fontawesome-pro, else @fortawesome/fontawesome-free, from node_modules
381
+ npx nxgt-backdrop-sprite --pack health --out public/sprites/backdrop.svg --base /sprites/
382
+ # nxgt-backdrop-sprite --pack school --pack health --style light --style thin (more packs, explicit styles)
383
+ ```
384
+
385
+ ```tsx
386
+ <IconSpritesProvider
387
+ base="/assets/icons/sprites/" // still used by Icon and Illustration
388
+ backdropSprite={{ href: '/sprites/backdrop.svg', styles: ['light', 'thin', 'sharp-regular', 'sharp-light', 'duotone-thin'] }}
389
+ >
390
+ ```
391
+
392
+ The command prints the exact `backdropSprite` value. With it set, the icon glyphs of `Background` reference
393
+ `/sprites/backdrop.svg#<file>--<name>` (`thin--book-open`) instead of the per-style sheets, and the styles it holds
394
+ count as served (no `servedStyles` needed). The string form, `backdropSprite="/sprites/backdrop.svg"`, leaves
395
+ which styles are served to `servedStyles` (or `edition="free"`).
396
+
397
+ - `Icon`, `Illustration` and the theme tiles still read `base`; only `Background` reads the reduced sprite
398
+ (`useBackdropSprite()` returns its `href`).
399
+ - The `href` must be root-relative and end in `.svg`, else `IconSpritesProvider` throws `IconRefError` (`base`).
400
+ `--base` / `base` is the path from your web root, not Vite's `base`.
401
+ - The command warns when `--out` is not under the `--base` path (`/sprites/` when you give no `--base`).
402
+
403
+ #### The Free edition
404
+
405
+ With `edition="free"` the styles served are `regular` and `solid` whatever the object lists, and the weighted default
406
+ falls back to those two. Build the sprite with `--style regular --style solid` for a Free app. Do not combine
407
+ `edition="free"` with a sprite built from a Pro install that holds no `regular` and `solid` symbols: the Free edition
408
+ draws exactly those two styles, so the icons are blank. A Free install alone gives `regular` and `solid`.
409
+
410
+ #### A Pro-only sprite
411
+
412
+ A sprite built from a Pro install holds Pro glyphs under your licence. Serve it from your app; never commit it to a
413
+ public repository, publish it in a package or a public Storybook. Keep it out of version control
414
+ (`public/sprites/backdrop.svg` in `.gitignore`) and build it in CI or at `vite build`, from the install you license.
415
+
416
+ - **CLI or plugin, not both**: pick one. A `public/sprites/backdrop.svg` the command wrote is a static file that Vite serves before the plugin's dev route, so it shadows the plugin's sprite (and goes stale); delete it when you move to the plugin.
417
+ - **Free install**: a Pro-only name (`backpack`, `chevrons-left`) built from a Free install is written under its Free twin's id, and the command and the plugin warn: set `edition="free"` on `IconSpritesProvider`. - **Packs and names**: `--pack` (repeat it) for the icon packs your backdrops draw, and `--icon` for the names of your
418
+ own `icons` list. **There is no default pack**: with neither, the command fails and shows the fix. Put every pack or
419
+ name your backdrops draw: a name missing from the sprite shows nothing.
420
+ - **Styles**: `--style` (repeat it). Unset, the default mix cut to what your install has: `light`, `thin`,
421
+ `sharp-regular`, `sharp-light` and `duotone-thin` come from Font Awesome Pro, and Free alone holds `regular` and
422
+ `solid`, which the sprite then holds. A style the install lacks, named with `--style`, is an error. A style that does
423
+ not hold a name takes the glyph of the next style that does, so no symbol is blank.
424
+ - **Where it reads**: each style's `sprites/<file>.svg` in the package (or `svgs/<file>/<name>.svg`), one `<symbol>`
425
+ per name and style. `--from <folder>` names a Font Awesome package folder when it is not in `node_modules`.
426
+ - **Size**: about 10–14 KB gzipped per pack with the five default Pro styles; a Free pack in `regular` and `solid` is
427
+ about 3 KB gzipped.
428
+ - **Licence**: the sprite holds glyphs of your own install, under its licence. Serve it from your app, and never
429
+ commit a Pro sprite to a public repository or a published Storybook.
430
+
431
+ Or, instead of the command, the Vite plugin runs the same build with the app, and the dev server serves it:
432
+
433
+ ```ts
434
+ // vite.config.ts
435
+ nxgtMaterial({ backdropSprite: { packs: ['health'], styles: ['light', 'thin'], base: '/sprites/', outFile: 'backdrop.svg' } })
436
+ ```
437
+
438
+ See [the Vite plugin guide](vite-plugin.md#the-reduced-backdrop-sprite).
439
+
440
+ #### The sprites each style needs
441
+
442
+ Without a reduced sprite, the backdrop draws from per-style sheets at the `IconSpritesProvider` `base`:
443
+
444
+ | Style (`iconStyle`) | Sprite file | Share in the default mix | Font Awesome |
445
+ | --- | --- | --- | --- |
446
+ | `light` | `light.svg` | 3 | Pro |
447
+ | `thin` | `thin.svg` | 3 | Pro |
448
+ | `sharp-regular` | `sharp-regular.svg` | 2 | Pro (Sharp) |
449
+ | `sharp-light` | `sharp-light.svg` | 2 | Pro (Sharp) |
450
+ | `duotone-thin` | `duotone-thin.svg` | 1 | Pro (Duotone) |
451
+ | `regular`, `solid` | `regular.svg`, `solid.svg` | the fallback when none of the above is served, and the only two served with `edition="free"` | Free |
452
+
453
+ A plain `iconStyle` (one style, or a list without weights) is drawn as written and needs its own sheets; only a
454
+ weighted list, the default included, is cut to what is served. A reduced sprite replaces all these files for the
455
+ backdrop (`Icon` still reads them).
456
+
457
+ ### Density
458
+
459
+ `motif.density` counts shapes and icons together.
460
+
461
+ | Value | Meaning |
462
+ | --- | --- |
463
+ | `'low'`, `'default'`, `'high'` | 0.55, 1 and 1.7 times the preset's own count (as before 3.22) |
464
+ | `'medium'` | the calm mix: about 160 glyphs on a 1600 by 1000 scene; the default of the mix |
465
+ | a number | the count on that scene, up to 600, scaled with the frame's area |
466
+
467
+ A small frame draws fewer glyphs, a large one more, so the spread stays the same. The drop rule then thins what meets
468
+ content.
469
+
470
+ ### The look before 3.22
471
+
472
+ The previous look (icons only, scattered, one duotone style, the three tones at full strength) is three explicit props,
473
+ and nothing else changes:
474
+
475
+ ```tsx
476
+ <Background
477
+ preset="icons"
478
+ iconStyle="duotone-thin"
479
+ iconMotif={{ layout: 'scatter', colors: 'classic' }}
480
+ />
481
+ ```
482
+
483
+ Any layout but `even` draws icons only, `calm` and `quiet` apply to them as before, and `density` and `motif.icons` are
484
+ not read there. In a theme, the same three keys do it: `background: { preset: 'icons', iconStyle: 'duotone-thin',
485
+ iconMotif: { layout: 'scatter', colors: 'classic' } }`. An app that serves only some Pro sprites sets `servedStyles`
486
+ (or `edition="free"`) on its `IconSpritesProvider`, since the new default draws five styles.
487
+
347
488
  - The icons come from a builder in its own chunk, loaded on demand (`@nxgt/material-core/backgrounds/icons`), so the
348
489
  preset registry does not grow. A streaming server render has the icons in the first HTML; with `renderToString` the
349
490
  scene layer is empty until the chunk has loaded, on the server and on the client alike (no hydration mismatch).
@@ -352,12 +493,13 @@ import { Background } from '@nxgt/material/backgrounds';
352
493
  - Colours: the primary layer takes the tone's colours; the second layer of a duotone icon is the same colour at
353
494
  `secondaryOpacity` (no default of its own: 0.4 with `duotone: 'opacity'`, 0.85 with `'tones'`, where the second layer takes the next tone; a value you set, 0.4 included, is used in both). Flat styles (`solid`,
354
495
  `regular`) take the tone through `currentColor`. A name missing from the sprite of the style drawn shows
355
- nothing, so keep a list of styles to ones that share your names. The default style needs `duotone-thin.svg` at the
356
- sprite base (see the sprite 404 entry in [troubleshooting](../troubleshooting.md)).
496
+ nothing, so keep a list of styles to ones that share your names. The default mix needs `light.svg`, `thin.svg`,
497
+ `sharp-regular.svg`, `sharp-light.svg` and `duotone-thin.svg` at the sprite base, or `servedStyles` for the ones you
498
+ have (see the sprite 404 entry in [troubleshooting](../troubleshooting.md)).
357
499
  - `animated` drifts each icon a few pixels on `translate` (never under `prefers-reduced-motion`, paused while hidden).
358
- - In an `ActivityLayout` the keep-inside pass hides an icon that would cross the layer's edge or the header band.
500
+ - In an `ActivityLayout` the keep-inside pass drops an icon or a shape that would cross the layer's edge or the header band (see [the drop rule](#the-drop-rule)).
359
501
  - 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`). 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.
502
+ - CSS: `backgrounds/icons.css` (or `backgrounds.css`). Both pull in `backgrounds/icons-quiet.css`, the mask behind `iconMotif.quiet`, and `backgrounds/icons-tint.css`, the `colors` tints; each can also be imported alone. React only: `@nxgt/material-vue` does not draw it.
361
503
 
362
504
  ### Keeping text clear of the icons: `quiet`
363
505
 
@@ -392,6 +534,23 @@ text sits on the wash alone.
392
534
  tone of the wash, light and dark.
393
535
  - A theme sets it too: `background: { preset: 'icons', iconMotif: { quiet: 'end' } }`.
394
536
 
537
+ ## The drop rule
538
+
539
+ Behind an `ActivityLayout` or an `AuthCard` (the only two that run the pass; a bare `Background` never does), a glyph (a shape or an icon)
540
+ whose box meets the content is **dropped**, never moved or resized. The content is a surface (the whole box of a card,
541
+ a stat card, a table, a data grid, a list, an alert or a dialog), a text or a control, with an 8 px margin, so no glyph
542
+ is left half hidden at a card's edge. A glyph that crosses the layer's edge or reaches the header band is dropped for
543
+ good.
544
+
545
+ - The server renders every glyph. The drop happens after the first measure, on the client, in one frame (boxes read
546
+ before any write), so the markup never differs between server and client.
547
+ - A dropped glyph fades out in 150 ms (none under `prefers-reduced-motion`) and fades back when the content moves
548
+ away. It runs again on resize and when the content changes.
549
+ - The open areas keep their glyphs; the space behind and between cards is empty. `keepClear={false}` on an `AuthCard`
550
+ lets the glyphs run under the card.
551
+ - See [The theme's background](./app-shell.md#the-themes-background) for the shell and `AuthCard` in
552
+ [navigation and layout](../components/navigation-and-layout.md#authcard) for the sign-in page.
553
+
395
554
  ## Dark mode
396
555
 
397
556
  In dark the drawn colours are lifted to a lightness floor: the dark `--primary` is as dim as the light one, which made the
@@ -432,7 +591,7 @@ The gradient ends on `--nxgt-bg-cp`, not `--nxgt-bg-c2`: to recolour it, set
432
591
  ## Defaults from the theme
433
592
 
434
593
  The `Background` of `@nxgt/material/backgrounds` takes what you leave out
435
- (`preset`, `tone`, `intensity`, `seed`, `motif`, `moon`, `sky`, `grain`, `animated`) from the
594
+ (`preset`, `tone`, `intensity`, `seed`, `motif`, `moon`, `sky`, `grain`, `animated`, and for `icons` also `icons`, `iconStyle`, `iconMotif` (`layout`, `colors`), `motif.icons` and `motif.density`; `ActivityLayout` and `AuthCard` draw it too) from the
436
595
  nearest theme (`NxgtThemeProvider` or an `NxgtTheme` zone, see
437
596
  [the provider and zones](./theming.md#the-provider-and-zones)), then from the
438
597
  defaults above. A prop you pass always wins; the others still come from the theme.
@@ -447,6 +606,45 @@ import { Background } from '@nxgt/material/backgrounds';
447
606
  </NxgtTheme>
448
607
  ```
449
608
 
609
+ ### The calm backdrop in a theme
610
+
611
+ A theme's `background` takes the same knobs as the props, checked by `defineTheme` (a bad icon key or value throws). A bad `motif` key or value is dropped, not thrown: `defineTheme` from `@nxgt/material/themes` warns for each one in development, the `@nxgt/material-core/theme/define` one only through its `{ warn }` option (4.0: an error; see [troubleshooting](../troubleshooting.md#definetheme-theme-x-backgroundmotif-unknown-key-spin-ignored)).
612
+
613
+
614
+ ```ts
615
+ import { defineTheme } from '@nxgt/material/themes';
616
+
617
+ export const ecole = defineTheme({
618
+ name: 'ecole',
619
+ colors: { primary: '#0b7a75' },
620
+ background: {
621
+ preset: 'icons',
622
+ icons: 'school',
623
+ // weighted families: a share each (default 1); a style the app does not serve is dropped
624
+ iconStyle: [
625
+ { style: 'light', weight: 3 },
626
+ { style: 'sharp-regular', weight: 2 },
627
+ { style: 'duotone-thin', weight: 1 },
628
+ ],
629
+ iconMotif: { layout: 'even', colors: 'one' }, // 'palette' | 'classic'
630
+ motif: { density: 'medium', icons: { ratio: 0.2 } }, // 'low' | 'high' | a number
631
+ },
632
+ });
633
+ ```
634
+
635
+ | Key | Values |
636
+ | --- | --- |
637
+ | `iconMotif.layout` | `even` (the mix), `scatter`, `grid`, `constellation`, `drift` |
638
+ | `iconMotif.colors` | `one`, `palette`, `classic` |
639
+ | `iconStyle` | a style, a list (at most 8), or a weighted list `[{ style, weight? }]`, `weight` from 0 to 1000 |
640
+ | `motif.density` | `low`, `default`, `medium`, `high`, or a number from 1 to 600 |
641
+ | `motif.layout` | `scatter`, `even` (the `shapes` preset) |
642
+ | `motif.icons` | `{ pack?, style?, ratio }`, `ratio` from 0 to 1 (`icons` packs and styles as the props) |
643
+
644
+ `extends` merges `motif` and `iconMotif` key by key; `iconStyle` and `motif.icons` are replaced whole. `definePlainTheme`
645
+ (no icon check) refuses the other icon keys and ignores `motif.icons`. The theme's `iconStyle` list and the app's
646
+ `servedStyles` or `backdropSprite` work together as for the props.
647
+
450
648
  Outside any theme nothing changes. The preset's stylesheet is still yours to
451
649
  import. The `/components` `Background` is the gradient alone and does not read
452
650
  the theme.
@@ -16,7 +16,7 @@ Free works the same way.
16
16
 
17
17
  | You have | Serve | Use |
18
18
  | --- | --- | --- |
19
- | the Pro 7.3 pack | one sheet per style you draw, in `public/assets/icons/sprites/`: `duotone-thin.svg` (the default look), `duotone.svg`, `solid.svg`, `sharp-solid.svg`… A non-classic family is `<family>-<style>.svg` | the defaults |
19
+ | the Pro 7.3 pack | one sheet per style you draw, in `public/assets/icons/sprites/`: the icon backgrounds' default mix needs `light.svg`, `thin.svg`, `sharp-regular.svg`, `sharp-light.svg` and `duotone-thin.svg`; `Icon`'s default look `duotone-thin.svg`; then `duotone.svg`, `solid.svg`, `sharp-solid.svg`… A non-classic family is `<family>-<style>.svg` | the defaults |
20
20
  | Font Awesome Free | `solid.svg`, `regular.svg` and `brands.svg` from `@fortawesome/fontawesome-free/sprites/`, copied to the same folder | `<IconSpritesProvider base="…" edition="free">` once at the root ([Free edition](#the-free-edition)) |
21
21
 
22
22
  The Free icons are CC BY 4.0: credit Font Awesome where you show them (the
@@ -54,23 +54,39 @@ import { Background } from '@nxgt/material/backgrounds';
54
54
  preset="icons"
55
55
  tone="mixed"
56
56
  icons="health" // a pack, or up to 48 names and { name, family?, type? }
57
- iconStyle={['duotone-thin', 'duotone-light']} // one style, or a list of up to 8
58
- iconMotif={{ layout: 'constellation', duotone: 'opacity' }}
57
+ iconStyle={[{ style: 'light', weight: 3 }, { style: 'duotone-thin' }]} // one style, a list of up to 8, or a weighted list
58
+ iconMotif={{ colors: 'one' }} // 'one', 'palette' or 'classic'
59
+ motif={{ icons: { ratio: 0.2 }, density: 'medium' }} // the share of icons among the shapes
59
60
  />;
60
61
  ```
61
62
 
62
- <img src="../previews/backgrounds-icons.webp" width="480" alt="The school pack of icons scattered over a faint wash, around a card">
63
+ Since 3.22 this draws a **calm mix**: motif shapes and, in about one cell in
64
+ five, an icon, all in one soft primary tint on an even grid over the whole
65
+ frame (`preset="shapes"` with `motif.icons` draws the same). The [backgrounds
66
+ guide](backgrounds.md#icons) lists the options.
67
+
68
+ <img src="../previews/backgrounds-icons.webp" width="480" alt="The calm mix of shapes and a few school icons in one soft tint, in the open space below a dashboard">
63
69
 
64
70
  - **`icons`**: a pack name (`school`, `health`, `finance`, `travel`, `nature`,
65
71
  `tech`: lists of names, drawn in any style) or your own names and
66
72
  `{ name, family?, type? }` references. No `icons` draws the `school` pack.
67
- - **`iconStyle`**: one style (`'duotone-thin'`, `{ family: 'sharp', type: 'solid' }`)
68
- or a list of up to 8. Each icon takes one style from the list by the seeded
69
- pick, so a seed always draws the same picture. An icon's own `family` / `type`
70
- always wins. Keep a list to styles that share your names.
71
- - **`iconMotif`**: `layout` (`scatter`, `grid`, `constellation`, `drift`),
72
- `sizeRange`, `rotation`, `duotone` (`'opacity'`: the second layer is the same
73
- tone at 0.4; `'tones'`: the next tone) and `secondaryOpacity`.
73
+ - **`iconStyle`**: one style (`'duotone-thin'`, `{ family: 'sharp', type: 'solid' }`),
74
+ a list of up to 8, or a weighted list `[{ style, weight? }]`. Each icon takes
75
+ one style by the seeded pick, so a seed always draws the same picture. An
76
+ icon's own `family` / `type` always wins. Keep a list to styles that share
77
+ your names. The default is a weighted list (light 3, thin 3, sharp-regular 2,
78
+ sharp-light 2, duotone-thin 1): a weighted list is cut to the sprites your app
79
+ serves (`IconSpritesProvider`'s `servedStyles`, or the Free edition's
80
+ `regular` and `solid`), keeps the others' proportions, and draws Free
81
+ `regular` and `solid` when nothing is left.
82
+ - **`iconMotif`**: `layout` (`even`, the default mix of shapes and icons;
83
+ `scatter`, `grid`, `constellation`, `drift`: icons only), `colors` (`'one'`,
84
+ `'palette'`, `'classic'`), `sizeRange`, `rotation`, `duotone` (`'opacity'`:
85
+ the second layer is the same tone at 0.4; `'tones'`: the next tone) and
86
+ `secondaryOpacity`.
87
+ - **The look before 3.22** is icons only, scattered, in duotone thin and the
88
+ three tones at full strength:
89
+ `<Background preset="icons" iconStyle="duotone-thin" iconMotif={{ layout: 'scatter', colors: 'classic' }} />`.
74
90
  - `animated` drifts each icon a little; nothing moves under
75
91
  `prefers-reduced-motion`. Icons that cannot be drawn (an unknown pack or style,
76
92
  a bad name, more than 48) draw `scatter-icons`, with a console warning outside
@@ -192,7 +208,9 @@ Below it, in every `Icon` (so the app shell's menu, `AccountMenu`,
192
208
  every theme tile:
193
209
 
194
210
  - a style Free lacks is drawn from `solid.svg`: the default duotone, `thin`,
195
- `light`, `sharp-*` and every other family. `regular` and `brands` stay;
211
+ `light`, `sharp-*` and every other family. `regular` and `brands` stay. An
212
+ icon background's weighted `iconStyle` (the default mix included) draws
213
+ `regular` and `solid` instead, since no Pro style is served;
196
214
  - a Pro-only name is renamed to its Free twin, the same one in every place
197
215
  (the table below, plus `chevrons-*` to `angles-*`, `badge-check` to
198
216
  `circle-check`, `loader` to `spinner`).