@nizivo/react 0.1.0 → 0.2.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 (238) hide show
  1. package/THIRD_PARTY_NOTICES +12 -5
  2. package/dist/_engines/react-aria/dialog.client.js +16 -24
  3. package/dist/_engines/react-aria/drawer.client.js +8 -0
  4. package/dist/_engines/react-aria/file-upload.client.js +50 -0
  5. package/dist/_engines/react-aria/menu.client.js +17 -13
  6. package/dist/_engines/react-aria/open-state.client.js +53 -0
  7. package/dist/_engines/react-aria/outside-press.client.js +61 -0
  8. package/dist/_engines/react-aria/overlay.client.js +6 -1
  9. package/dist/_engines/react-aria/popover.client.js +148 -0
  10. package/dist/_engines/react-aria/popup.client.js +31 -5
  11. package/dist/_engines/react-aria/select.client.js +22 -12
  12. package/dist/_engines/react-aria/toast.client.js +38 -0
  13. package/dist/_engines/react-aria/tooltip.client.js +11 -11
  14. package/dist/_internal/data-status.d.ts +3 -0
  15. package/dist/_internal/data-status.js +13 -0
  16. package/dist/_internal/glyphs.js +54 -1
  17. package/dist/_internal/modal.client.js +54 -0
  18. package/dist/_internal/nested-focus.js +33 -0
  19. package/dist/alert/alert-dismiss.client.d.ts +1 -1
  20. package/dist/alert/alert-dismiss.client.js +3 -1
  21. package/dist/app-shell/app-shell.d.ts +24 -0
  22. package/dist/app-shell/app-shell.js +56 -0
  23. package/dist/app-shell/index.d.ts +2 -0
  24. package/dist/app-shell/index.js +1 -0
  25. package/dist/avatar/avatar.d.ts +96 -0
  26. package/dist/avatar/avatar.js +94 -0
  27. package/dist/avatar/index.d.ts +2 -0
  28. package/dist/avatar/index.js +1 -0
  29. package/dist/banner/banner-dismiss.client.d.ts +32 -0
  30. package/dist/banner/banner-dismiss.client.js +38 -0
  31. package/dist/banner/banner.d.ts +34 -0
  32. package/dist/banner/banner.js +47 -0
  33. package/dist/banner/index.d.ts +3 -0
  34. package/dist/banner/index.js +3 -0
  35. package/dist/breadcrumb/breadcrumb.d.ts +46 -0
  36. package/dist/breadcrumb/breadcrumb.js +46 -0
  37. package/dist/breadcrumb/breadcrumb.parts.d.ts +6 -0
  38. package/dist/breadcrumb/breadcrumb.parts.js +9 -0
  39. package/dist/breadcrumb/index.d.ts +3 -0
  40. package/dist/breadcrumb/index.js +3 -0
  41. package/dist/button/button.d.ts +3 -2
  42. package/dist/card/card.d.ts +9 -2
  43. package/dist/chip-group/chip-group.client.d.ts +88 -0
  44. package/dist/chip-group/chip-group.client.js +157 -0
  45. package/dist/chip-group/index.d.ts +2 -0
  46. package/dist/chip-group/index.js +1 -0
  47. package/dist/choice-card/choice-card.client.d.ts +66 -0
  48. package/dist/choice-card/choice-card.client.js +138 -0
  49. package/dist/choice-card/index.d.ts +2 -0
  50. package/dist/choice-card/index.js +1 -0
  51. package/dist/config/config.client.d.ts +16 -1
  52. package/dist/config/config.client.js +18 -3
  53. package/dist/config/index.d.ts +2 -2
  54. package/dist/contracts/index.d.ts +1 -1
  55. package/dist/css/app-shell.css +2 -0
  56. package/dist/css/avatar.css +2 -0
  57. package/dist/css/banner.css +2 -0
  58. package/dist/css/base.css +1 -1
  59. package/dist/css/breadcrumb.css +2 -0
  60. package/dist/css/card.css +1 -1
  61. package/dist/css/chip-group.css +2 -0
  62. package/dist/css/choice-card.css +2 -0
  63. package/dist/css/delta.css +1 -1
  64. package/dist/css/drawer.css +2 -0
  65. package/dist/css/file-upload.css +2 -0
  66. package/dist/css/form.css +2 -0
  67. package/dist/css/heading.css +1 -1
  68. package/dist/css/kpi-tile.css +2 -0
  69. package/dist/css/metric.css +2 -0
  70. package/dist/css/mobile-tab-bar.css +2 -0
  71. package/dist/css/mobile-top-bar.css +2 -0
  72. package/dist/css/motion.css +2 -0
  73. package/dist/css/nav-item.css +2 -0
  74. package/dist/css/number-value.css +2 -0
  75. package/dist/css/page-header.css +2 -0
  76. package/dist/css/pagination.css +2 -0
  77. package/dist/css/password-input.css +2 -0
  78. package/dist/css/popover.css +2 -0
  79. package/dist/css/ranked-list.css +2 -0
  80. package/dist/css/search-field.css +2 -0
  81. package/dist/css/sidebar.css +2 -0
  82. package/dist/css/split.css +2 -0
  83. package/dist/css/sticky-bar.css +2 -0
  84. package/dist/css/table-selection.css +2 -0
  85. package/dist/css/table.css +2 -0
  86. package/dist/css/text.css +1 -1
  87. package/dist/css/toast.css +2 -0
  88. package/dist/css/top-bar.css +2 -0
  89. package/dist/css/trend-card.css +2 -0
  90. package/dist/dialog/dialog.client.d.ts +1 -1
  91. package/dist/dialog/dialog.client.js +15 -37
  92. package/dist/drawer/drawer.client.d.ts +25 -0
  93. package/dist/drawer/drawer.client.js +154 -0
  94. package/dist/drawer/drawer.contract.d.ts +98 -0
  95. package/dist/drawer/drawer.parts.d.ts +6 -0
  96. package/dist/drawer/drawer.parts.js +16 -0
  97. package/dist/drawer/index.d.ts +5 -0
  98. package/dist/drawer/index.js +3 -0
  99. package/dist/field/field-scope.client.js +9 -2
  100. package/dist/file-upload/file-upload.client.d.ts +9 -0
  101. package/dist/file-upload/file-upload.client.js +406 -0
  102. package/dist/file-upload/file-upload.contract.d.ts +112 -0
  103. package/dist/file-upload/index.d.ts +3 -0
  104. package/dist/file-upload/index.js +1 -0
  105. package/dist/file-upload/seam.d.ts +1 -0
  106. package/dist/form/form.client.d.ts +38 -0
  107. package/dist/form/form.client.js +127 -0
  108. package/dist/form/form.d.ts +36 -0
  109. package/dist/form/form.js +29 -0
  110. package/dist/form/index.d.ts +4 -0
  111. package/dist/form/index.js +4 -0
  112. package/dist/form/submit-button.client.d.ts +20 -0
  113. package/dist/form/submit-button.client.js +19 -0
  114. package/dist/heading/heading.d.ts +4 -1
  115. package/dist/i18n/format-label.d.ts +24 -0
  116. package/dist/i18n/format-label.js +19 -0
  117. package/dist/i18n/index.d.ts +2 -0
  118. package/dist/i18n/index.js +1 -0
  119. package/dist/icon-button/icon-button.client.js +1 -1
  120. package/dist/icon-button/icon-button.js +1 -1
  121. package/dist/index.d.ts +84 -5
  122. package/dist/index.js +49 -4
  123. package/dist/kpi-tile/index.d.ts +2 -0
  124. package/dist/kpi-tile/index.js +1 -0
  125. package/dist/kpi-tile/kpi-tile.d.ts +30 -0
  126. package/dist/kpi-tile/kpi-tile.js +48 -0
  127. package/dist/locales/en-US.d.ts +77 -0
  128. package/dist/locales/en-US.js +83 -0
  129. package/dist/menu/menu.contract.d.ts +1 -1
  130. package/dist/metric/index.d.ts +2 -0
  131. package/dist/metric/index.js +1 -0
  132. package/dist/metric/metric.d.ts +72 -0
  133. package/dist/metric/metric.js +116 -0
  134. package/dist/mobile-tab-bar/index.d.ts +4 -0
  135. package/dist/mobile-tab-bar/index.js +4 -0
  136. package/dist/mobile-tab-bar/mobile-tab-bar-inset.client.js +38 -0
  137. package/dist/mobile-tab-bar/mobile-tab-bar-more.client.d.ts +26 -0
  138. package/dist/mobile-tab-bar/mobile-tab-bar-more.client.js +60 -0
  139. package/dist/mobile-tab-bar/mobile-tab-bar.d.ts +39 -0
  140. package/dist/mobile-tab-bar/mobile-tab-bar.js +54 -0
  141. package/dist/mobile-tab-bar/mobile-tab-bar.parts.d.ts +6 -0
  142. package/dist/mobile-tab-bar/mobile-tab-bar.parts.js +9 -0
  143. package/dist/mobile-top-bar/index.d.ts +2 -0
  144. package/dist/mobile-top-bar/index.js +1 -0
  145. package/dist/mobile-top-bar/mobile-top-bar.d.ts +17 -0
  146. package/dist/mobile-top-bar/mobile-top-bar.js +31 -0
  147. package/dist/motion/count-up.client.d.ts +39 -0
  148. package/dist/motion/count-up.client.js +109 -0
  149. package/dist/motion/index.d.ts +2 -0
  150. package/dist/motion/index.js +1 -0
  151. package/dist/nav-item/index.d.ts +2 -0
  152. package/dist/nav-item/index.js +1 -0
  153. package/dist/nav-item/nav-item.d.ts +35 -0
  154. package/dist/nav-item/nav-item.js +50 -0
  155. package/dist/number-value/index.d.ts +2 -0
  156. package/dist/number-value/index.js +1 -0
  157. package/dist/number-value/number-value.d.ts +25 -0
  158. package/dist/number-value/number-value.js +26 -0
  159. package/dist/page-header/index.d.ts +2 -0
  160. package/dist/page-header/index.js +1 -0
  161. package/dist/page-header/page-header.d.ts +19 -0
  162. package/dist/page-header/page-header.js +39 -0
  163. package/dist/pagination/index.d.ts +2 -0
  164. package/dist/pagination/index.js +1 -0
  165. package/dist/pagination/pagination.d.ts +74 -0
  166. package/dist/pagination/pagination.js +136 -0
  167. package/dist/password-input/index.d.ts +2 -0
  168. package/dist/password-input/index.js +1 -0
  169. package/dist/password-input/password-input.client.d.ts +52 -0
  170. package/dist/password-input/password-input.client.js +106 -0
  171. package/dist/popover/index.d.ts +5 -0
  172. package/dist/popover/index.js +3 -0
  173. package/dist/popover/popover.client.d.ts +20 -0
  174. package/dist/popover/popover.client.js +113 -0
  175. package/dist/popover/popover.contract.d.ts +88 -0
  176. package/dist/popover/popover.parts.d.ts +6 -0
  177. package/dist/popover/popover.parts.js +13 -0
  178. package/dist/ranked-list/index.d.ts +3 -0
  179. package/dist/ranked-list/index.js +3 -0
  180. package/dist/ranked-list/ranked-list.d.ts +85 -0
  181. package/dist/ranked-list/ranked-list.js +153 -0
  182. package/dist/ranked-list/ranked-list.parts.d.ts +6 -0
  183. package/dist/ranked-list/ranked-list.parts.js +10 -0
  184. package/dist/search-field/index.d.ts +2 -0
  185. package/dist/search-field/index.js +1 -0
  186. package/dist/search-field/search-field.client.d.ts +55 -0
  187. package/dist/search-field/search-field.client.js +136 -0
  188. package/dist/select/select.contract.d.ts +1 -1
  189. package/dist/sidebar/index.d.ts +4 -0
  190. package/dist/sidebar/index.js +4 -0
  191. package/dist/sidebar/sidebar-item.client.d.ts +10 -0
  192. package/dist/sidebar/sidebar-item.client.js +55 -0
  193. package/dist/sidebar/sidebar.d.ts +45 -0
  194. package/dist/sidebar/sidebar.js +62 -0
  195. package/dist/sidebar/sidebar.parts.d.ts +6 -0
  196. package/dist/sidebar/sidebar.parts.js +11 -0
  197. package/dist/split/index.d.ts +3 -0
  198. package/dist/split/index.js +3 -0
  199. package/dist/split/split.d.ts +28 -0
  200. package/dist/split/split.js +33 -0
  201. package/dist/split/split.parts.d.ts +6 -0
  202. package/dist/split/split.parts.js +10 -0
  203. package/dist/sticky-bar/index.d.ts +2 -0
  204. package/dist/sticky-bar/index.js +1 -0
  205. package/dist/sticky-bar/sticky-bar.d.ts +16 -0
  206. package/dist/sticky-bar/sticky-bar.js +26 -0
  207. package/dist/styles.css +66 -7
  208. package/dist/table/index.d.ts +3 -0
  209. package/dist/table/index.js +3 -0
  210. package/dist/table/table.d.ts +101 -0
  211. package/dist/table/table.js +121 -0
  212. package/dist/table/table.parts.d.ts +6 -0
  213. package/dist/table/table.parts.js +18 -0
  214. package/dist/table-selection/index.d.ts +3 -0
  215. package/dist/table-selection/index.js +3 -0
  216. package/dist/table-selection/table-selection.client.d.ts +14 -0
  217. package/dist/table-selection/table-selection.client.js +84 -0
  218. package/dist/table-selection/table-selection.d.ts +34 -0
  219. package/dist/table-selection/table-selection.js +50 -0
  220. package/dist/text/text.d.ts +13 -2
  221. package/dist/toast/index.d.ts +4 -0
  222. package/dist/toast/index.js +3 -0
  223. package/dist/toast/seam.d.ts +2 -0
  224. package/dist/toast/store.d.ts +19 -0
  225. package/dist/toast/store.js +136 -0
  226. package/dist/toast/toast.contract.d.ts +56 -0
  227. package/dist/toast/toaster.client.d.ts +10 -0
  228. package/dist/toast/toaster.client.js +305 -0
  229. package/dist/tooltip/tooltip.contract.d.ts +1 -1
  230. package/dist/top-bar/index.d.ts +2 -0
  231. package/dist/top-bar/index.js +1 -0
  232. package/dist/top-bar/top-bar.d.ts +17 -0
  233. package/dist/top-bar/top-bar.js +35 -0
  234. package/dist/trend-card/index.d.ts +2 -0
  235. package/dist/trend-card/index.js +1 -0
  236. package/dist/trend-card/trend-card.d.ts +34 -0
  237. package/dist/trend-card/trend-card.js +74 -0
  238. package/package.json +167 -4
@@ -0,0 +1,56 @@
1
+ import "./seam.js";
2
+ import { RefObject } from "react";
3
+ //#region src/toast/toast.contract.d.ts
4
+ /** Figma "Tone". Critical is announced assertively and stays until dismissed. */
5
+ export type ToastTone = "neutral" | "success" | "critical";
6
+ /** How a message is spoken: `polite` waits for the reader to finish, `assertive` interrupts. */
7
+ export type Politeness = "polite" | "assertive";
8
+ export interface ToastAction {
9
+ /** The button's text, one or two words: "Undo", "View". */
10
+ label: string;
11
+ /** Runs on press; the toast then closes. */
12
+ onAction: () => void;
13
+ }
14
+ export interface ToastOptions {
15
+ /** @default 'neutral' */
16
+ tone?: ToastTone;
17
+ /** A second line under the message. */
18
+ description?: string;
19
+ /**
20
+ * One action, a real button. A toast with an action stays until it is dismissed (WCAG 2.2.1,
21
+ * §12.6), whatever `timeout` says.
22
+ */
23
+ action?: ToastAction;
24
+ /**
25
+ * Milliseconds before a toast without an action, and not critical, leaves. The time stops while
26
+ * the pointer is over the stack, while focus is in it and while the page is hidden.
27
+ * @default 5000
28
+ */
29
+ timeout?: number;
30
+ /**
31
+ * Updates the toast with this id in place, if it is still shown, instead of adding one:
32
+ * `toast('Saving…', { id: 'save' })`, then `toast('Saved', { id: 'save', tone: 'success' })`.
33
+ */
34
+ id?: string;
35
+ /** Called once when the toast leaves, however it left. */
36
+ onClose?: () => void;
37
+ }
38
+ /** Strings the Toaster renders (§16.4). */
39
+ export interface ToasterLabels {
40
+ /** The region's accessible name, which names its shortcut. @default 'Notifications (F8)' */
41
+ region: string;
42
+ /** Each toast's close button. @default 'Dismiss' */
43
+ dismiss: string;
44
+ }
45
+ export interface ToasterProps {
46
+ labels?: Partial<ToasterLabels>;
47
+ /** Toasts shown at once, newest last. Older ones wait, their time stopped, until one leaves. @default 3 */
48
+ limit?: number;
49
+ /**
50
+ * The key that moves focus into the stack and, pressed again there, back to where it was (the
51
+ * `KeyboardEvent.key` value). @default 'F8'
52
+ */
53
+ hotkey?: string;
54
+ className?: string;
55
+ }
56
+ //#endregion
@@ -0,0 +1,10 @@
1
+ import { ToasterProps } from "./toast.contract.js";
2
+ import { ReactElement } from "react";
3
+ //#region src/toast/toaster.client.d.ts
4
+ /**
5
+ * Shows the toasts `toast()` adds, bottom-centre, newest last, and speaks each once. F8 moves focus
6
+ * to the stack; Tab moves within it; Escape dismisses the focused toast; F8 again, or Escape on the
7
+ * stack itself, returns focus to where it was. Render one, in the root layout.
8
+ */
9
+ export declare function Toaster({ labels, limit, hotkey, className }: ToasterProps): ReactElement;
10
+ //#endregion
@@ -0,0 +1,305 @@
1
+ "use client";
2
+ import { cx } from "../_internal/cx.js";
3
+ import { useLabels } from "../config/config.client.js";
4
+ import { CircleCheckGlyph, CircleXGlyph, CloseGlyph, InfoGlyph } from "../_internal/glyphs.js";
5
+ import { Button } from "../button/button.js";
6
+ import { markTopLayer } from "../_engines/react-aria/overlay.client.js";
7
+ import { iconButtonElement } from "../icon-button/icon-button-static.js";
8
+ import { useItem, useRegion } from "../_engines/react-aria/toast.client.js";
9
+ import { attach, getServerSnapshot, getSnapshot, pause, resume, show, speakThrough, subscribe, toast } from "./store.js";
10
+ import { useEffect, useLayoutEffect, useRef, useState, useSyncExternalStore } from "react";
11
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
12
+ import { createPortal } from "react-dom";
13
+ //#region src/toast/toaster.client.tsx
14
+ const LABELS = {
15
+ region: "Notifications (F8)",
16
+ dismiss: "Dismiss"
17
+ };
18
+ /** How long a spoken message stays in the announcer, as React Aria's announcer keeps it. */
19
+ const SPOKEN_FOR = 7e3;
20
+ const GLYPHS = {
21
+ neutral: InfoGlyph,
22
+ success: CircleCheckGlyph,
23
+ critical: CircleXGlyph
24
+ };
25
+ const popoverApi = (element) => "showPopover" in element;
26
+ const noSubscription = () => () => {};
27
+ /** On the page in the browser only: the queue, the portals and the announcer are client state. */
28
+ function useHydrated() {
29
+ return useSyncExternalStore(noSubscription, () => true, () => false);
30
+ }
31
+ /** Focus back to where it was before F8, if that element is still on the page. */
32
+ function returnFocus(returnTo) {
33
+ const to = returnTo.current;
34
+ returnTo.current = null;
35
+ if (to?.isConnected === true) to.focus();
36
+ }
37
+ /**
38
+ * Two visually hidden live regions, polite and assertive, on the page from the Toaster's mount
39
+ * before any message, and marked top layer so a modal's aria-hidden never silences them (O-06).
40
+ */
41
+ function Announcer() {
42
+ const ref = useRef(null);
43
+ const [said, setSaid] = useState({
44
+ polite: "",
45
+ assertive: ""
46
+ });
47
+ useLayoutEffect(() => {
48
+ const element = ref.current;
49
+ return element === null ? void 0 : markTopLayer(element);
50
+ }, []);
51
+ useEffect(() => {
52
+ const timers = /* @__PURE__ */ new Set();
53
+ const later = (ms, run) => {
54
+ const timer = setTimeout(() => {
55
+ timers.delete(timer);
56
+ run();
57
+ }, ms);
58
+ timers.add(timer);
59
+ };
60
+ const unregister = speakThrough((text, politeness) => {
61
+ setSaid((current) => ({
62
+ ...current,
63
+ [politeness]: ""
64
+ }));
65
+ later(100, () => setSaid((current) => ({
66
+ ...current,
67
+ [politeness]: text
68
+ })));
69
+ later(SPOKEN_FOR, () => setSaid((current) => current[politeness] === text ? {
70
+ ...current,
71
+ [politeness]: ""
72
+ } : current));
73
+ });
74
+ return () => {
75
+ unregister();
76
+ for (const timer of timers) clearTimeout(timer);
77
+ };
78
+ }, []);
79
+ return /* @__PURE__ */ jsxs("div", {
80
+ ref,
81
+ className: "nz-toast-announcer nz-visually-hidden",
82
+ "data-slot": "toast-announcer",
83
+ "data-nz-top-layer": "",
84
+ children: [/* @__PURE__ */ jsx("div", {
85
+ "aria-live": "polite",
86
+ "aria-atomic": "true",
87
+ children: said.polite
88
+ }), /* @__PURE__ */ jsx("div", {
89
+ "aria-live": "assertive",
90
+ "aria-atomic": "true",
91
+ children: said.assertive
92
+ })]
93
+ });
94
+ }
95
+ function ToastItem({ toast, options, labels }) {
96
+ const ref = useRef(null);
97
+ const { toastProps, messageProps, descriptionProps } = useItem(toast, options, ref);
98
+ const { close } = options;
99
+ const { id } = toast;
100
+ useEffect(() => {
101
+ const element = ref.current;
102
+ if (element === null) return void 0;
103
+ const onKey = (event) => {
104
+ if (event.key !== "Escape" || event.isComposing) return;
105
+ event.preventDefault();
106
+ event.stopPropagation();
107
+ close(id);
108
+ };
109
+ element.addEventListener("keydown", onKey);
110
+ return () => element.removeEventListener("keydown", onKey);
111
+ }, [close, id]);
112
+ const Glyph = GLYPHS[toast.tone];
113
+ const closePart = {
114
+ "data-slot": "toast-close",
115
+ className: "nz-toast-close"
116
+ };
117
+ const { action } = toast;
118
+ return /* @__PURE__ */ jsxs("div", {
119
+ ...toastProps,
120
+ ref,
121
+ className: "nz-toast",
122
+ "data-slot": "toast",
123
+ "data-tone": toast.tone,
124
+ "data-nz-scheme": "inverse",
125
+ children: [
126
+ /* @__PURE__ */ jsx("span", {
127
+ className: "nz-toast-icon",
128
+ "data-slot": "toast-icon",
129
+ "aria-hidden": "true",
130
+ children: /* @__PURE__ */ jsx(Glyph, {})
131
+ }),
132
+ /* @__PURE__ */ jsxs("div", {
133
+ className: "nz-toast-content",
134
+ "data-slot": "toast-content",
135
+ children: [/* @__PURE__ */ jsx("div", {
136
+ ...messageProps,
137
+ className: "nz-toast-message",
138
+ "data-slot": "toast-message",
139
+ children: toast.message
140
+ }), toast.description !== void 0 && /* @__PURE__ */ jsx("div", {
141
+ ...descriptionProps,
142
+ className: "nz-toast-description",
143
+ "data-slot": "toast-description",
144
+ children: toast.description
145
+ })]
146
+ }),
147
+ action !== void 0 && /* @__PURE__ */ jsx(Button, {
148
+ variant: "tertiary",
149
+ size: "sm",
150
+ className: "nz-toast-action",
151
+ "data-slot": "toast-action",
152
+ onClick: () => {
153
+ action.onAction();
154
+ options.close(toast.id);
155
+ },
156
+ children: action.label
157
+ }),
158
+ iconButtonElement({
159
+ label: labels.dismiss,
160
+ icon: /* @__PURE__ */ jsx(CloseGlyph, {}),
161
+ variant: "tertiary",
162
+ size: "sm",
163
+ ...closePart,
164
+ onClick: () => options.close(toast.id)
165
+ })
166
+ ]
167
+ });
168
+ }
169
+ /** The region and its toasts, once the page has hydrated. */
170
+ function Stack({ toasts, strings, hotkey, className }) {
171
+ const region = useRef(null);
172
+ const returnTo = useRef(null);
173
+ const count = toasts.length;
174
+ const showing = useRef(count);
175
+ useLayoutEffect(() => {
176
+ showing.current = count;
177
+ });
178
+ useEffect(() => {
179
+ const onKey = (event) => {
180
+ if (event.key !== hotkey || event.altKey || event.ctrlKey || event.metaKey) return;
181
+ const element = region.current;
182
+ if (element === null || showing.current === 0) return;
183
+ event.preventDefault();
184
+ const active = document.activeElement;
185
+ if (active !== null && element.contains(active)) returnFocus(returnTo);
186
+ else {
187
+ returnTo.current = active instanceof HTMLElement ? active : null;
188
+ element.focus();
189
+ }
190
+ };
191
+ document.addEventListener("keydown", onKey);
192
+ return () => document.removeEventListener("keydown", onKey);
193
+ }, [hotkey]);
194
+ useEffect(() => {
195
+ const element = region.current;
196
+ if (element === null) return void 0;
197
+ const onKey = (event) => {
198
+ if (event.key !== "Escape" || event.target !== element) return;
199
+ event.preventDefault();
200
+ event.stopPropagation();
201
+ returnFocus(returnTo);
202
+ };
203
+ element.addEventListener("keydown", onKey);
204
+ return () => element.removeEventListener("keydown", onKey);
205
+ }, []);
206
+ const latest = toasts.at(-1)?.id;
207
+ useLayoutEffect(() => {
208
+ const element = region.current;
209
+ if (element === null) return;
210
+ element.hidden = latest === void 0;
211
+ if (!popoverApi(element)) return;
212
+ const open = element.matches(":popover-open");
213
+ if (latest === void 0) {
214
+ if (open) element.hidePopover();
215
+ return;
216
+ }
217
+ if (open && !element.contains(document.activeElement)) element.hidePopover();
218
+ if (!element.matches(":popover-open")) element.showPopover();
219
+ }, [latest]);
220
+ useLayoutEffect(() => {
221
+ const element = region.current;
222
+ return () => {
223
+ if (element === null) return;
224
+ element.hidden = true;
225
+ if (popoverApi(element) && element.matches(":popover-open")) element.hidePopover();
226
+ };
227
+ }, []);
228
+ useLayoutEffect(() => {
229
+ const element = region.current;
230
+ const root = document.documentElement;
231
+ if (element === null || count === 0) {
232
+ root.style.removeProperty("--nz-toast-inset");
233
+ return;
234
+ }
235
+ const measure = () => {
236
+ const inset = Math.max(0, window.innerHeight - element.getBoundingClientRect().top);
237
+ root.style.setProperty("--nz-toast-inset", `${Math.ceil(inset)}px`);
238
+ };
239
+ measure();
240
+ const observer = new ResizeObserver(measure);
241
+ observer.observe(element);
242
+ window.addEventListener("resize", measure);
243
+ return () => {
244
+ observer.disconnect();
245
+ window.removeEventListener("resize", measure);
246
+ root.style.removeProperty("--nz-toast-inset");
247
+ };
248
+ }, [count]);
249
+ const options = {
250
+ toasts,
251
+ label: strings.region,
252
+ close: toast.dismiss,
253
+ pause: () => pause("region"),
254
+ resume: () => resume("region")
255
+ };
256
+ const regionProps = useRegion(options, region);
257
+ return /* @__PURE__ */ jsx("section", {
258
+ ...regionProps,
259
+ ref: region,
260
+ popover: "manual",
261
+ hidden: count === 0,
262
+ className: cx("nz-toast-region", className),
263
+ "data-slot": "toast-region",
264
+ "data-nz-top-layer": "",
265
+ children: toasts.map((toast) => /* @__PURE__ */ jsx(ToastItem, {
266
+ toast,
267
+ options,
268
+ labels: strings
269
+ }, toast.id))
270
+ });
271
+ }
272
+ /**
273
+ * Shows the toasts `toast()` adds, bottom-centre, newest last, and speaks each once. F8 moves focus
274
+ * to the stack; Tab moves within it; Escape dismisses the focused toast; F8 again, or Escape on the
275
+ * stack itself, returns focus to where it was. Render one, in the root layout.
276
+ */
277
+ function Toaster({ labels, limit = 3, hotkey = "F8", className }) {
278
+ const toasts = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot).slice(-limit);
279
+ const hydrated = useHydrated();
280
+ const ids = toasts.map((toast) => toast.id).join(" ");
281
+ const strings = {
282
+ ...LABELS,
283
+ ...useLabels("toaster", labels)
284
+ };
285
+ useEffect(attach, []);
286
+ useEffect(() => show(ids === "" ? [] : ids.split(" ")), [ids]);
287
+ useEffect(() => {
288
+ const update = () => document.hidden ? pause("hidden") : resume("hidden");
289
+ update();
290
+ document.addEventListener("visibilitychange", update);
291
+ return () => {
292
+ document.removeEventListener("visibilitychange", update);
293
+ resume("hidden");
294
+ };
295
+ }, []);
296
+ if (!hydrated) return /* @__PURE__ */ jsx(Fragment, {});
297
+ return /* @__PURE__ */ jsxs(Fragment, { children: [createPortal(/* @__PURE__ */ jsx(Stack, {
298
+ toasts,
299
+ strings,
300
+ hotkey,
301
+ className
302
+ }), document.body), createPortal(/* @__PURE__ */ jsx(Announcer, {}), document.body)] });
303
+ }
304
+ //#endregion
305
+ export { Toaster };
@@ -1,7 +1,7 @@
1
1
  import { RenderProp } from "../contracts/render.js";
2
2
  import { ChangeDetails, OpenReason } from "../contracts/details.js";
3
- import { Align, Side } from "../contracts/state.js";
4
3
  import "../_internal/engine-part.js";
4
+ import { Align, Side } from "../contracts/state.js";
5
5
  import { ComponentPropsWithRef, ReactElement, ReactNode } from "react";
6
6
  //#region src/tooltip/tooltip.contract.d.ts
7
7
  /** Logical side; the resolved physical side is `data-side` on the popup. */
@@ -0,0 +1,2 @@
1
+ import { TopBar, TopBarProps } from "./top-bar.js";
2
+ export { TopBar, type TopBarProps };
@@ -0,0 +1 @@
1
+ export { TopBar } from "./top-bar.js";
@@ -0,0 +1,17 @@
1
+ import { RenderProp } from "../contracts/render.js";
2
+ import { ComponentPropsWithRef, ReactElement, ReactNode } from "react";
3
+ //#region src/top-bar/top-bar.d.ts
4
+ export interface TopBarProps extends Omit<ComponentPropsWithRef<"header">, "title" | "children"> {
5
+ /** The page's name, in Heading/H3's look (Figma's Title). Not a heading element. */
6
+ title?: ReactNode;
7
+ /** In place of `title`, for example a Breadcrumb for a page deeper in the app. */
8
+ start?: ReactNode;
9
+ /** The search, usually a `SearchField` (M49: a real input). 360 px at most. */
10
+ search?: ReactNode;
11
+ /** At the end: the notifications IconButton, the account's Avatar or menu. */
12
+ actions?: ReactNode;
13
+ render?: RenderProp<Record<string, never>>;
14
+ }
15
+ /** The bar above the page on screens 640 px and wider (AppShell's `topBar`). */
16
+ export declare function TopBar({ title, start, search, actions, render, className, ...rest }: TopBarProps): ReactElement;
17
+ //#endregion
@@ -0,0 +1,35 @@
1
+ import { cx } from "../_internal/cx.js";
2
+ import { renderElement } from "../contracts/render.js";
3
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
4
+ //#region src/top-bar/top-bar.tsx
5
+ /** The bar above the page on screens 640 px and wider (AppShell's `topBar`). */
6
+ function TopBar({ title, start, search, actions, render, className, ...rest }) {
7
+ return renderElement("header", render, {}, {
8
+ "data-slot": "top-bar",
9
+ ...rest,
10
+ className: cx("nz-top-bar", className),
11
+ children: /* @__PURE__ */ jsxs(Fragment, { children: [
12
+ /* @__PURE__ */ jsx("div", {
13
+ className: "nz-top-bar-start",
14
+ "data-slot": "top-bar-start",
15
+ children: start ?? (title != null && /* @__PURE__ */ jsx("span", {
16
+ className: "nz-top-bar-title",
17
+ "data-slot": "top-bar-title",
18
+ children: title
19
+ }))
20
+ }),
21
+ search != null && /* @__PURE__ */ jsx("div", {
22
+ className: "nz-top-bar-search",
23
+ "data-slot": "top-bar-search",
24
+ children: search
25
+ }),
26
+ actions != null && /* @__PURE__ */ jsx("div", {
27
+ className: "nz-top-bar-actions",
28
+ "data-slot": "top-bar-actions",
29
+ children: actions
30
+ })
31
+ ] })
32
+ });
33
+ }
34
+ //#endregion
35
+ export { TopBar };
@@ -0,0 +1,2 @@
1
+ import { TrendCard, TrendCardProps, TrendCardState } from "./trend-card.js";
2
+ export { TrendCard, type TrendCardProps, type TrendCardState };
@@ -0,0 +1 @@
1
+ export { TrendCard } from "./trend-card.js";
@@ -0,0 +1,34 @@
1
+ import { RenderProp } from "../contracts/render.js";
2
+ import { CardTone } from "../card/card.js";
3
+ import { DataStatus } from "../_internal/data-status.js";
4
+ import { MetricDataProps } from "../metric/metric.js";
5
+ import { ComponentPropsWithRef, ReactElement, ReactNode } from "react";
6
+ //#region src/trend-card/trend-card.d.ts
7
+ export interface TrendCardState {
8
+ /** The status as rendered: `'empty'` when a ready card has no value. */
9
+ status: DataStatus;
10
+ pending: boolean;
11
+ tone: CardTone;
12
+ }
13
+ export interface TrendCardProps extends MetricDataProps, Omit<ComponentPropsWithRef<"div">, "children"> {
14
+ /** Card's tone; `inverse` scopes the inverse scheme. @default 'surface' */
15
+ tone?: CardTone;
16
+ /** The control at the end of the header, for example the period `Select` or a `SegmentedControl`. */
17
+ action?: ReactNode;
18
+ /**
19
+ * The chart, for example `<AreaChart … />` from `@nizivo/charts`. Rendered in every status, so the
20
+ * box stays put: give the chart the same `status` (ChartFrame keeps its box in all four).
21
+ */
22
+ chart?: ReactNode;
23
+ /** The legend under the chart, for example `<Legend items={…} />`. */
24
+ legend?: ReactNode;
25
+ /** A last row, for example a link to the report. */
26
+ footer?: ReactNode;
27
+ render?: RenderProp<TrendCardState>;
28
+ }
29
+ /**
30
+ * A headline number with its change, a period control and a comparison chart. A `role="group"`
31
+ * named by the label. The status contract (§16.5) applies to the headline; the slots render as given.
32
+ */
33
+ export declare function TrendCard({ label, value, format, locale, delta, comparison, tone, action, chart, legend, footer, status, pending, empty, error, labels, render, className, ...rest }: TrendCardProps): ReactElement;
34
+ //#endregion
@@ -0,0 +1,74 @@
1
+ import { cx } from "../_internal/cx.js";
2
+ import { renderElement } from "../contracts/render.js";
3
+ import { dataStatusAttrs } from "../_internal/data-status.js";
4
+ import { Metric, resolveStatus } from "../metric/metric.js";
5
+ import { useId } from "react";
6
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
7
+ //#region src/trend-card/trend-card.tsx
8
+ /**
9
+ * A headline number with its change, a period control and a comparison chart. A `role="group"`
10
+ * named by the label. The status contract (§16.5) applies to the headline; the slots render as given.
11
+ */
12
+ function TrendCard({ label, value, format, locale, delta, comparison, tone = "surface", action, chart, legend, footer, status = "ready", pending = false, empty, error, labels, render, className, ...rest }) {
13
+ const labelId = useId();
14
+ const st = resolveStatus(status, value);
15
+ const busy = st === "ready" && pending;
16
+ return renderElement("div", render, {
17
+ status: st,
18
+ pending: busy,
19
+ tone
20
+ }, {
21
+ "data-slot": "trend-card",
22
+ "data-tone": tone,
23
+ "data-padding": "md",
24
+ "data-nz-scheme": tone === "inverse" ? "inverse" : void 0,
25
+ ...dataStatusAttrs(st, busy),
26
+ role: "group",
27
+ "aria-labelledby": labelId,
28
+ ...rest,
29
+ className: cx("nz-card", "nz-trend-card", className),
30
+ children: /* @__PURE__ */ jsxs(Fragment, { children: [
31
+ /* @__PURE__ */ jsxs("div", {
32
+ className: "nz-trend-card-header",
33
+ "data-slot": "trend-card-header",
34
+ children: [/* @__PURE__ */ jsx(Metric, {
35
+ size: "display",
36
+ reserveChange: true,
37
+ labelId,
38
+ label,
39
+ value,
40
+ format,
41
+ locale,
42
+ delta,
43
+ comparison,
44
+ status,
45
+ pending,
46
+ empty,
47
+ error,
48
+ labels
49
+ }), action != null && /* @__PURE__ */ jsx("div", {
50
+ className: "nz-trend-card-action",
51
+ "data-slot": "trend-card-action",
52
+ children: action
53
+ })]
54
+ }),
55
+ chart != null && /* @__PURE__ */ jsx("div", {
56
+ className: "nz-trend-card-chart",
57
+ "data-slot": "trend-card-chart",
58
+ children: chart
59
+ }),
60
+ legend != null && /* @__PURE__ */ jsx("div", {
61
+ className: "nz-trend-card-legend",
62
+ "data-slot": "trend-card-legend",
63
+ children: legend
64
+ }),
65
+ footer != null && /* @__PURE__ */ jsx("div", {
66
+ className: "nz-trend-card-footer",
67
+ "data-slot": "trend-card-footer",
68
+ children: footer
69
+ })
70
+ ] })
71
+ });
72
+ }
73
+ //#endregion
74
+ export { TrendCard };