@nxgt/material 3.5.0 → 3.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +92 -0
- package/README.md +46 -1
- package/dist/components/components.css +2 -0
- package/dist/components/theme-tokens.css +84 -72
- package/dist/components/tones/tokens.css +5 -5
- package/dist/components/ui/display/chip/chip-hairline.css +1 -1
- package/dist/components/ui/layout/sidebar/sidebar-tokens.css +1 -1
- package/dist/components/ui/overlays/dialog/dialog.css +2 -2
- package/dist/components/ui/primitives/field-box/field-box-tokens.css +1 -1
- package/dist/components/ui/primitives/toggle-group/toggle-group-segmented.css +2 -2
- package/dist/components/ui/primitives/toggle-group/toggle-group-separate.css +1 -1
- package/dist/components/ui/widgets/drawing/drawing-animate.css +123 -0
- package/dist/components/ui/widgets/drawing/drawing.css +459 -0
- package/dist/{components-CXpL1zEs.js → components-BX4icy-g.js} +734 -734
- package/dist/components.js +16 -16
- package/dist/{dialog-JQT6iETD.js → dialog-DdJ1C4BR.js} +168 -168
- package/dist/dnd.js +1 -1
- package/dist/draw-animate.js +106 -0
- package/dist/draw-png.js +2 -0
- package/dist/draw-sketch.js +38 -0
- package/dist/draw.js +65 -0
- package/dist/drawing-B8VuG7nx.js +390 -0
- package/dist/drawing-context-CeKeZE6b.js +10 -0
- package/dist/editor.js +110 -110
- package/dist/{icon-badge-C30yKsAM.js → icon-badge-Bb5-73XP.js} +18 -18
- package/dist/{icon-button-BXlI3DJ_.js → icon-button-5A6Mrj6w.js} +60 -90
- package/dist/lib/components/ui/display/shape/use-shape-morph.d.ts +1 -0
- package/dist/lib/components/ui/widgets/drawing/drawing-a11y.d.ts +20 -0
- package/dist/lib/components/ui/widgets/drawing/drawing-context.d.ts +23 -0
- package/dist/lib/components/ui/widgets/drawing/drawing-types.d.ts +63 -0
- package/dist/lib/components/ui/widgets/drawing/drawing.d.ts +17 -0
- package/dist/lib/components/ui/widgets/drawing/index.d.ts +5 -0
- package/dist/lib/components/ui/widgets/drawing/partials/drawing-root.d.ts +17 -0
- package/dist/lib/components/ui/widgets/drawing/partials/interactive-node.d.ts +22 -0
- package/dist/lib/components/ui/widgets/drawing/partials/primitives/content.d.ts +20 -0
- package/dist/lib/components/ui/widgets/drawing/partials/primitives/path.d.ts +29 -0
- package/dist/lib/components/ui/widgets/drawing/partials/primitives/primitive.d.ts +19 -0
- package/dist/lib/components/ui/widgets/drawing/partials/primitives/shapes.d.ts +18 -0
- package/dist/lib/components/ui/widgets/drawing/partials/react-attrs.d.ts +5 -0
- package/dist/lib/components/ui/widgets/drawing/partials/ring.d.ts +9 -0
- package/dist/lib/components/ui/widgets/drawing/partials/svg-tree.d.ts +29 -0
- package/dist/lib/components/ui/widgets/drawing/use-exact-text.d.ts +16 -0
- package/dist/lib/draw/index.d.ts +32 -0
- package/dist/lib/draw-animate/index.d.ts +19 -0
- package/dist/lib/draw-animate/morph-drawing.d.ts +32 -0
- package/dist/lib/draw-animate/reduced-motion.d.ts +2 -0
- package/dist/lib/draw-animate/use-drawing-controls.d.ts +27 -0
- package/dist/lib/draw-animate/use-scene-morph.d.ts +15 -0
- package/dist/lib/draw-png/index.d.ts +11 -0
- package/dist/lib/draw-sketch/index.d.ts +17 -0
- package/dist/lib/draw-sketch/sketch.d.ts +19 -0
- package/dist/lib/print/components/print-drawing.d.ts +16 -0
- package/dist/lib/print/index.d.ts +2 -1
- package/dist/main.js +21 -21
- package/dist/map.js +63 -63
- package/dist/material.css +1 -1
- package/dist/pdf-reader.js +1 -1
- package/dist/print.js +123 -115
- package/dist/{public-Cde8_SN8.js → public-CBlV2XuA.js} +742 -742
- package/dist/{sortable-provider-DYlpq8lP.js → sortable-provider-BvD98s8Y.js} +19 -19
- package/dist/{use-chat-composer-BwpSgP_D.js → use-chat-composer-BhdPNmYm.js} +912 -911
- package/dist/use-shape-morph-BKA_R5P5.js +35 -0
- package/docs/README.md +1 -0
- package/docs/components/data-display.md +2 -2
- package/docs/components/rich-widgets.md +196 -0
- package/docs/guides/drawing.md +499 -0
- package/docs/guides/printing.md +47 -0
- package/docs/guides/theming.md +86 -43
- package/docs/previews/rich-widgets-drawing.webp +0 -0
- package/docs/roadmap.md +17 -0
- package/docs/troubleshooting.md +166 -2
- package/lib/components/components.css +2 -0
- package/lib/components/theme-tokens.css +84 -72
- package/lib/components/tones/tokens.css +5 -5
- package/lib/components/ui/display/chip/chip-hairline.css +1 -1
- package/lib/components/ui/layout/sidebar/sidebar-tokens.css +1 -1
- package/lib/components/ui/overlays/dialog/dialog.css +2 -2
- package/lib/components/ui/primitives/field-box/field-box-tokens.css +1 -1
- package/lib/components/ui/primitives/toggle-group/toggle-group-segmented.css +2 -2
- package/lib/components/ui/primitives/toggle-group/toggle-group-separate.css +1 -1
- package/lib/components/ui/widgets/drawing/drawing-animate.css +123 -0
- package/lib/components/ui/widgets/drawing/drawing.css +459 -0
- package/package.json +22 -2
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { morph as e, morphAt as t, toSvgPath as n } from "@nxgt/material-core/shapes";
|
|
2
|
+
import { useLayoutEffect as r, useRef as i } from "react";
|
|
3
|
+
//#region lib/components/ui/display/shape/use-shape-morph.ts
|
|
4
|
+
var a = .8, o = 14, s = o * Math.sqrt(1 - a * a);
|
|
5
|
+
function c(e) {
|
|
6
|
+
let t = e / 1e3;
|
|
7
|
+
return 1 - Math.exp(-.8 * o * t) * (Math.cos(s * t) + a * o / s * Math.sin(s * t));
|
|
8
|
+
}
|
|
9
|
+
var l = () => typeof window.matchMedia == "function" && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
10
|
+
function u(a, o, s) {
|
|
11
|
+
let u = i(o);
|
|
12
|
+
r(() => {
|
|
13
|
+
let r = a.current, i = u.current;
|
|
14
|
+
if (!r || i === o || !s || l()) {
|
|
15
|
+
r && i !== o && r.setAttribute("d", n(o)), u.current = o;
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
let d = e(i, o), f = n(o), p = 0, m = performance.now(), h = (e) => {
|
|
19
|
+
let i = e - m;
|
|
20
|
+
if (i >= 520) {
|
|
21
|
+
u.current = o, r.setAttribute("d", f);
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
let a = t(d, c(i));
|
|
25
|
+
u.current = a, r.setAttribute("d", n(a)), p = requestAnimationFrame(h);
|
|
26
|
+
};
|
|
27
|
+
return r.setAttribute("d", n(d.from)), p = requestAnimationFrame(h), () => cancelAnimationFrame(p);
|
|
28
|
+
}, [
|
|
29
|
+
a,
|
|
30
|
+
o,
|
|
31
|
+
s
|
|
32
|
+
]);
|
|
33
|
+
}
|
|
34
|
+
//#endregion
|
|
35
|
+
export { u as n, c as t };
|
package/docs/README.md
CHANGED
|
@@ -21,6 +21,7 @@ detail: every option, the second example, and the traps.
|
|
|
21
21
|
| [Media players](guides/media.md) | You play video or audio: embedded, audio cards, playlists, chapters and transcript, sound across pages with `MediaPlayerHost`, the lightbox, CSP and keyboard |
|
|
22
22
|
| [Streaming (HLS)](guides/media-streaming.md) | You play `.m3u8` streams in `MediaPlayer` with `@nxgt/material/media-hls` |
|
|
23
23
|
| [Maps](guides/maps.md) | You render Leaflet maps or a location field with `@nxgt/material/map` |
|
|
24
|
+
| [Drawing](guides/drawing.md) | You draw diagrams, badges or illustrations from code with `@nxgt/material/draw`: scenes and JSX primitives, roles and tones, a strict CSP, accessibility and events, sketch, animation, PNG, PDF and e-mail |
|
|
24
25
|
| [Backgrounds](guides/backgrounds.md) | You want a gradient or patterned backdrop: tones, intensity, the opt-in `backgrounds.css`, custom colours, motion and CSP |
|
|
25
26
|
| [SSR](guides/ssr.md) | Your app renders on a server and bundles this package |
|
|
26
27
|
| [Content Security Policy](guides/csp.md) | Your app sends a strict CSP: the directives to use, the components that still inject a `<style>`, the zod and KaTeX settings, self-hosting a font, the per-request nonce |
|
|
@@ -60,7 +60,7 @@ Parts: `Avatar` (the composed one), `AvatarBase`, `AvatarImage`,
|
|
|
60
60
|
| `shape` | `ShapeName` | — | clips the photo to one of the expressive shapes instead of a circle; the fallback is framed by it when there is no `src` or it fails to load. `alt` names it. Unset: the round avatar, unchanged |
|
|
61
61
|
| `tone` | the eight named tones, or a tone of your own declared in `AppTones` | neutral | the fallback fills with the tone's container, draws its initials or icon in the tone's text colour (`--nxgt-tone-text`, also when there is no `tone`) and a hairline in it (3:1 against the page or card). A loaded photo is not tinted |
|
|
62
62
|
|
|
63
|
-
The hairline's strength is `--avatar-ring-share`, the share of the tone's text colour mixed into the surface (default `
|
|
63
|
+
The hairline's strength is `--avatar-ring-share`, the share of the tone's text colour mixed into the surface (default `68%`, from `--nxgt-tone-ring-share`; 3:1 or more at that value). Override it in your CSS:
|
|
64
64
|
|
|
65
65
|
```css
|
|
66
66
|
:root { --avatar-ring-share: 80%; }
|
|
@@ -435,7 +435,7 @@ import { StatCard } from '@nxgt/material/components';
|
|
|
435
435
|
|
|
436
436
|
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 types `StatCardProps`, `StatCardVariant` and `StatCardValueSize` are exported.
|
|
437
437
|
|
|
438
|
-
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` (`
|
|
438
|
+
In `filled`, the delta and its trend arrow take the on-colour too (the arrow and the sign still say up or down). The outline's share is `--stat-card-ring-share` (`68%`, or `--nxgt-tone-ring-share` for every ring); it draws the tonal card's border, the filled card's edge in a calm zone and the hairline round the icon chip. Known limit: the dark theme's primary `filled` card has an edge of about 2.3:1 on a `Card` and 2.5:1 on the page (its text is 7:1); a `tonal` card avoids it. See [App tones](../guides/theming.md#app-tones-on-shape-fab-avatar-statcard-and-the-metric-cards).
|
|
439
439
|
|
|
440
440
|
**Storybook:** `Data display/StatCard`, `Data display/StatCard/Tones` · **Vue:** `StatCard`
|
|
441
441
|
|
|
@@ -10,6 +10,7 @@ the app owns the data.
|
|
|
10
10
|
| <img src="../previews/rich-widgets-kanban.webp" width="96" alt=""> | [Kanban](#kanban) | Board with columns, swimlanes, WIP limits and drag-and-drop |
|
|
11
11
|
| <img src="../previews/rich-widgets-eventcalendar.webp" width="96" alt=""> | [EventCalendar](#eventcalendar) | Month / week / day / agenda calendar, phone to desktop |
|
|
12
12
|
| <img src="../previews/rich-widgets-native-charts-linechart.webp" width="96" alt=""> | [Charts](#charts) | Native SVG charts (line, area, bar, pie, donut, gauge, radial, funnel, heat map, radar, scatter, combo) with keyboard, summary and View data; the Nivo ones were removed in 3.0 |
|
|
13
|
+
| <img src="../previews/rich-widgets-drawing.webp" width="96" alt=""> | [Drawing](#drawing) | Vector drawing from a scene of plain data or JSX primitives: theme colours, accessible, focusable nodes, strict-CSP safe |
|
|
13
14
|
| <img src="../previews/rich-widgets-emojipicker.webp" width="96" alt=""> | [EmojiPicker](#emojipicker) | Shared emoji picker and list |
|
|
14
15
|
| <img src="../previews/rich-widgets-mentionlist.webp" width="96" alt=""> | [MentionList](#mentionlist) | `@` mention list with local search |
|
|
15
16
|
| <img src="../previews/rich-widgets-documenteditor.webp" width="96" alt=""> | [DocumentEditor](#documenteditor) | Rich text editor for long-form writing, built on a headless core |
|
|
@@ -309,6 +310,201 @@ Parts: `LineChart`, `AreaChart`, `BarChart`, `PieChart`, `DonutChart`, `Gauge`,
|
|
|
309
310
|
|
|
310
311
|
---
|
|
311
312
|
|
|
313
|
+
## Drawing
|
|
314
|
+
|
|
315
|
+

|
|
316
|
+
|
|
317
|
+
Vector drawings from `@nxgt/material/draw`: a **scene** of plain data (the
|
|
318
|
+
builders `scene`, `rect`, `path`, `text`... of `@nxgt/material-core/draw`,
|
|
319
|
+
re-exported here) or JSX **primitives** (`Rect`, `Ellipse`, `Circle`, `Line`,
|
|
320
|
+
`Polygon`, `Path`, `Text`, `Group`, `Image`) inside a `Drawing`. Colours are
|
|
321
|
+
theme roles (`primary-container`, `on-primary-container`, `surface`,
|
|
322
|
+
`outline`...), `tone('success')` or free colours; roles are classes that
|
|
323
|
+
`styles.css` maps to the theme's variables, so a drawing follows light and dark
|
|
324
|
+
with no inline `style` (a strict CSP is satisfied). The names `text` and `image`
|
|
325
|
+
clash with `@nxgt/material/print`'s: import one under
|
|
326
|
+
another name. **Its own entry, client-only** (render `Drawing` from a
|
|
327
|
+
`'use client'` file; the builders and `toSVG` run anywhere).
|
|
328
|
+
|
|
329
|
+
```tsx
|
|
330
|
+
'use client';
|
|
331
|
+
|
|
332
|
+
import { Circle, Drawing, Rect, Text } from '@nxgt/material/draw';
|
|
333
|
+
|
|
334
|
+
<Drawing w={200} h={80} title="Release status">
|
|
335
|
+
<Rect x={0} y={0} w={200} h={80} r={16} fill="primary-container" />
|
|
336
|
+
<Circle cx={36} cy={40} r={14} fill="primary" />
|
|
337
|
+
<Text x={64} y={46} fill="on-primary-container">Shipped</Text>
|
|
338
|
+
</Drawing>;
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
The same drawing from a scene, which a server can send as JSON:
|
|
342
|
+
|
|
343
|
+
```tsx
|
|
344
|
+
import { Drawing, rect, scene, text } from '@nxgt/material/draw';
|
|
345
|
+
|
|
346
|
+
const status = scene({ w: 200, h: 80 }, [
|
|
347
|
+
rect({ x: 0, y: 0, w: 200, h: 80, r: 16, fill: 'primary-container' }),
|
|
348
|
+
text('Shipped', { x: 100, y: 46, anchor: 'middle', fill: 'on-primary-container' }),
|
|
349
|
+
]);
|
|
350
|
+
|
|
351
|
+
<Drawing scene={status} title="Release status" />;
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
### Accessibility
|
|
355
|
+
|
|
356
|
+
`Drawing` takes `title` **or** `decorative`, never neither: the type enforces it.
|
|
357
|
+
A named drawing is `role="img"` with `aria-label={title}`, never a `<title>`
|
|
358
|
+
element (React 19 would hoist it into the page's head); a `decorative` one is
|
|
359
|
+
`aria-hidden`. A drawing with an interactive node is `role="group"` (an image
|
|
360
|
+
cannot hold focusable children); set `interactive` on a JSX `Drawing` that is
|
|
361
|
+
server rendered so the first markup says so too. An interactive node needs a
|
|
362
|
+
`label`: it is a focusable `role="button"` (it has `onClick`) or `role="img"`,
|
|
363
|
+
Enter and Space click it, and a focus ring (a second, unfilled copy of its
|
|
364
|
+
outline, class `nxgt-draw-ring`) shows on `:focus-visible`; text, and a group with
|
|
365
|
+
no shape, show a CSS outline instead. A decorative
|
|
366
|
+
drawing cannot hold an interactive node: it throws.
|
|
367
|
+
|
|
368
|
+
### Events
|
|
369
|
+
|
|
370
|
+
In scene mode, set events by node `id`, or listen to all of them:
|
|
371
|
+
|
|
372
|
+
```tsx
|
|
373
|
+
<Drawing
|
|
374
|
+
scene={flow}
|
|
375
|
+
title="Release flow"
|
|
376
|
+
handlers={{ review: { onPointerEnter: preview, onFocus: preview } }}
|
|
377
|
+
onNodeClick={(id, event) => open(id)}
|
|
378
|
+
/>
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
A node is interactive when its scene node has `interactive: true`, an entry in
|
|
382
|
+
`handlers`, or (JSX) any of `onClick`, `onPointerEnter`, `onPointerLeave`,
|
|
383
|
+
`onPointerDown`, `onPointerUp`, `onFocus`, `onBlur` or `onKeyDown`. A JSX `Group`
|
|
384
|
+
with an event shows its focus as a CSS outline instead of an outline copy
|
|
385
|
+
(a scene `group` keeps the outline copy).
|
|
386
|
+
|
|
387
|
+
### Text
|
|
388
|
+
|
|
389
|
+
Wrapped text (`maxWidth`) breaks with Inter's metrics, the same on the server
|
|
390
|
+
and in the first client render. Once mounted, and again when the fonts load,
|
|
391
|
+
the browser re-measures each wrapped text (`exactText`, on by default), so a
|
|
392
|
+
fallback font cannot overflow its box; the lines may move at that point. Set
|
|
393
|
+
`exactText={false}` to keep the metrics' breaks.
|
|
394
|
+
|
|
395
|
+
### Shapes and layout
|
|
396
|
+
|
|
397
|
+
`@nxgt/material/draw` re-exports `shape`, `morph`, `SHAPE_NAMES`, `toCubics` and
|
|
398
|
+
`bbox`, `stack`, `grid`, `align`, `distribute` from `@nxgt/material-core`, by
|
|
399
|
+
name (a bundler keeps only the ones you import; `Drawing` alone carries no
|
|
400
|
+
shape library). `shape('cookie-9', { x, y, w, h, fill })` is a path node, so it
|
|
401
|
+
goes to a scene or to `<Path node={...} />`; `morph(a, b, t)` is the path at `t`
|
|
402
|
+
between two shapes or nodes; `stack` and `grid` place labelled boxes.
|
|
403
|
+
|
|
404
|
+
```tsx
|
|
405
|
+
import { Drawing, Path, shape } from '@nxgt/material/draw';
|
|
406
|
+
|
|
407
|
+
<Drawing w={120} h={120} title="A cookie">
|
|
408
|
+
<Path node={shape('cookie-9', { x: 10, y: 10, w: 100, h: 100, fill: 'tertiary' })} />
|
|
409
|
+
</Drawing>;
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
### Sketch
|
|
413
|
+
|
|
414
|
+
`@nxgt/material/draw/sketch` draws a drawing by hand. `Sketch` wraps a `Drawing`
|
|
415
|
+
(scene or JSX), or primitives inside a JSX one; `sketch(sceneOrNode, options)`
|
|
416
|
+
does the same to plain data. Strokes are two wobbling passes with a bow,
|
|
417
|
+
ellipses are loose loops, filled shapes are hatched (`fill`: `hachure`,
|
|
418
|
+
`cross-hatch`, `solid`, `none`). Texts and images stay as they are. The wobble
|
|
419
|
+
is seeded (`seed`, else the scene's, else 0): the markup is the same on the
|
|
420
|
+
server and in the browser. Ids, labels, the focus ring and events keep working;
|
|
421
|
+
the hatch lines are fill, so they get no focus ring.
|
|
422
|
+
|
|
423
|
+
```tsx
|
|
424
|
+
'use client';
|
|
425
|
+
|
|
426
|
+
import { Drawing, Rect } from '@nxgt/material/draw';
|
|
427
|
+
import { Sketch } from '@nxgt/material/draw/sketch';
|
|
428
|
+
|
|
429
|
+
<Sketch seed={7} fill="cross-hatch">
|
|
430
|
+
<Drawing w={200} h={100} title="A sketchy button">
|
|
431
|
+
<Rect id="go" label="Go" x={10} y={10} w={180} h={80} r={12} fill="primary" stroke="primary" onClick={go} />
|
|
432
|
+
</Drawing>
|
|
433
|
+
</Sketch>;
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
Options: `seed`, `roughness` (1; 0 is clean double strokes), `bowing` (1), `fill`
|
|
437
|
+
(`hachure`), `hachureAngle` (-41), `hachureGap` (three stroke widths, at least 4),
|
|
438
|
+
`strokeWidth` (1.5 for a shape that sets none). Types: `SketchProps`,
|
|
439
|
+
`SketchOptions`, `SketchFill`.
|
|
440
|
+
|
|
441
|
+
### Props
|
|
442
|
+
|
|
443
|
+
| Prop | Type | Default | Effect |
|
|
444
|
+
| --- | --- | --- | --- |
|
|
445
|
+
| `title` / `decorative` | `string` / `true` | required, one of two | the accessible name, or hidden from assistive technology |
|
|
446
|
+
| `scene` | `Scene` | — | draw a scene of plain data (scene mode) |
|
|
447
|
+
| `w`, `h`, `viewBox`, `background`, `children` | `number`, `[x, y, w, h]`, `Paint`, `ReactNode` | — | the size, the view and the primitives (JSX mode) |
|
|
448
|
+
| `handlers`, `onNodeClick` | `Record<id, NodeHandlers>`, `(id, event) => void` | — | events by scene `id` (scene mode); `onNodeClick` in both modes |
|
|
449
|
+
| `interactive` | `boolean` | `false` | JSX mode: the root is a group in the server's markup |
|
|
450
|
+
| `exactText` | `boolean` | `true` | re-measure wrapped text in the browser |
|
|
451
|
+
| `className` | `string` | — | on the `<svg>`; it scales to its container (`max-width: 100%`) |
|
|
452
|
+
|
|
453
|
+
A `Path` takes `d` (SVG path data), `build` (`(p) => p.moveTo(0, 0).lineTo(10, 10)`)
|
|
454
|
+
or `node` (a ready node, such as a shape; the other props override its style).
|
|
455
|
+
`Text` takes strings and numbers as children (`\n` breaks a line). Every primitive takes the node's style (`fill`, `stroke`, `strokeWidth`, `opacity`,
|
|
456
|
+
`transform`, `clip`, `mask`, `className`, `id`, `label`, `interactive`).
|
|
457
|
+
|
|
458
|
+
### Animation
|
|
459
|
+
|
|
460
|
+
`@nxgt/material/draw/animate` animates a drawing without inline style. Wrap a
|
|
461
|
+
scene with `drawOn` (strokes trace themselves, fills fade in) or `appear`
|
|
462
|
+
(nodes fade and grow in), one after the next; they only add class names that
|
|
463
|
+
`styles.css` animates, so a server renders them and a strict CSP is satisfied.
|
|
464
|
+
`useDrawingControls(ref)` replays, pauses, plays or finishes them through the
|
|
465
|
+
Web Animations API, and `MorphDrawing` (or `useSceneMorph` with a `Drawing`)
|
|
466
|
+
springs one scene into another when `active` changes. Under reduced motion
|
|
467
|
+
nothing moves and the last frame shows at once.
|
|
468
|
+
|
|
469
|
+
```tsx
|
|
470
|
+
'use client';
|
|
471
|
+
|
|
472
|
+
import { Drawing, scene, shape } from '@nxgt/material/draw';
|
|
473
|
+
import { drawOn, MorphDrawing, useDrawingControls } from '@nxgt/material/draw/animate';
|
|
474
|
+
import { useRef } from 'react';
|
|
475
|
+
|
|
476
|
+
const flow = drawOn(scene({ w: 240, h: 80 }, [/* ... */]), { dur: 'l' }); // keep scenes stable
|
|
477
|
+
const cookie = scene({ w: 120, h: 120 }, [shape('cookie-9', { id: 'blob', x: 10, y: 10, w: 100, h: 100, fill: 'primary-container' })]);
|
|
478
|
+
const heart = scene({ w: 120, h: 120 }, [shape('heart', { id: 'blob', x: 10, y: 10, w: 100, h: 100, fill: 'tertiary-container' })]);
|
|
479
|
+
|
|
480
|
+
export function Demo({ liked }: { liked: boolean }) {
|
|
481
|
+
const ref = useRef<SVGSVGElement>(null);
|
|
482
|
+
const { replay } = useDrawingControls(ref);
|
|
483
|
+
return (
|
|
484
|
+
<>
|
|
485
|
+
<Drawing ref={ref} scene={flow} title="Release flow" />
|
|
486
|
+
<button onClick={replay}>Replay</button>
|
|
487
|
+
<MorphDrawing from={cookie} to={heart} active={liked} title="Like" />
|
|
488
|
+
</>
|
|
489
|
+
);
|
|
490
|
+
}
|
|
491
|
+
```
|
|
492
|
+
|
|
493
|
+
`MorphDrawing` animates the closed paths the two scenes share by `id` (`shape()`
|
|
494
|
+
gives them); any other node (text, a line, an arrowed, interactive or sketched
|
|
495
|
+
one) shows the scene being moved to at once, and `useSceneMorph` cross-fades
|
|
496
|
+
everything. The options of `drawOn` and `appear`, the delay per step
|
|
497
|
+
(`--nxgt-draw-stagger`, 60 ms) and `morphScenes` are in the
|
|
498
|
+
[core drawing reference](https://cdn.jsdelivr.net/npm/@nxgt/material-core@0/docs/draw.md#animation-nxgtmaterial-coredrawanimate).
|
|
499
|
+
|
|
500
|
+
How to use it in an app (server rendering, CSP, colours, events, PNG, PDF, e-mail) is in the
|
|
501
|
+
[Drawing guide](../guides/drawing.md). The scene model, colours, ids and the CSP rules are in the
|
|
502
|
+
[vector drawing reference of `@nxgt/material-core`](https://cdn.jsdelivr.net/npm/@nxgt/material-core@0/docs/draw.md).
|
|
503
|
+
|
|
504
|
+
**Storybook:** `Rich widgets/Drawing`, `Rich widgets/Drawing/Shapes and layout`, `Rich widgets/Drawing/Sketch`, `Rich widgets/Drawing/Animation`, `Rich widgets/Drawing/PNG export`, `Rich widgets/Drawing/Gallery` · **Vue:** React only for now; the engine is framework-free in core
|
|
505
|
+
|
|
506
|
+
---
|
|
507
|
+
|
|
312
508
|
## EmojiPicker
|
|
313
509
|
|
|
314
510
|

|