@jirawatpyk/aura-react 5.13.0 → 5.14.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.
@@ -2,7 +2,7 @@
2
2
  import * as React from "react";
3
3
  import { Drawer } from "./Dialog.js";
4
4
  import { IconButton } from "./IconButton.js";
5
- import { cx } from "./internal.js";
5
+ import { cx, useIsoLayoutEffect, useMergedRef } from "./internal.js";
6
6
  import { useStrings } from "./locale.js";
7
7
  import { useBreakpoint } from "./responsive.js";
8
8
  import { IconMenu } from "./icons.js";
@@ -24,6 +24,27 @@ const AppShell = React.forwardRef(function AppShell2(props, ref) {
24
24
  if (shared) navState[1](id);
25
25
  if (navEl && navEl.props.onChange) navEl.props.onChange(id);
26
26
  }
27
+ const rootRef = React.useRef(null);
28
+ const barRef = React.useRef(null);
29
+ const mergedRef = useMergedRef(ref, rootRef);
30
+ const hasBar = !!(props.header || props.nav);
31
+ useIsoLayoutEffect(
32
+ function() {
33
+ const root = rootRef.current, bar = barRef.current;
34
+ if (!root || !bar) return;
35
+ function sync() {
36
+ root.style.setProperty("--aura-shell-bar-height", bar.getBoundingClientRect().height + "px");
37
+ }
38
+ sync();
39
+ const ro = typeof ResizeObserver === "undefined" ? null : new ResizeObserver(sync);
40
+ if (ro) ro.observe(bar);
41
+ return function() {
42
+ if (ro) ro.disconnect();
43
+ root.style.removeProperty("--aura-shell-bar-height");
44
+ };
45
+ },
46
+ [hasBar]
47
+ );
27
48
  const deskNav = navEl && shared ? React.cloneElement(navEl, { value: navState[0], onChange: onNav }) : props.nav;
28
49
  const drawerNav = navEl ? React.cloneElement(navEl, {
29
50
  value: shared ? navState[0] : navEl.props.value,
@@ -35,32 +56,56 @@ const AppShell = React.forwardRef(function AppShell2(props, ref) {
35
56
  collapsed: false,
36
57
  collapsible: false
37
58
  }) : props.nav;
38
- return /* @__PURE__ */ React.createElement("div", { ref, className: cx("aura-shell", props.bottomNav && "aura-shell--bottomnav", props.className) }, props.nav ? /* @__PURE__ */ React.createElement("div", { className: "aura-shell__nav" }, deskNav) : null, props.nav ? /* @__PURE__ */ React.createElement(
39
- Drawer,
59
+ return /* @__PURE__ */ React.createElement(
60
+ "div",
40
61
  {
41
- open,
42
- onClose: function() {
43
- setOpen(false);
44
- },
45
- side: "left",
46
- size: "nav",
47
- "aria-label": props.navLabel || t.navigation,
48
- dismissible: true
62
+ ref: mergedRef,
63
+ className: cx(
64
+ "aura-shell",
65
+ props.bottomNav && "aura-shell--bottomnav",
66
+ /* 5.14: no top bar at all, or one that only holds the menu button (gone from lg up): the bar-height token is 0. */
67
+ !props.header && !props.nav && "aura-shell--no-bar",
68
+ !props.header && props.nav && "aura-shell--menu-bar",
69
+ props.className
70
+ )
49
71
  },
50
- drawerNav
51
- ) : null, /* @__PURE__ */ React.createElement("div", { className: "aura-shell__main" }, props.header || props.nav ? /* @__PURE__ */ React.createElement("header", { className: cx("aura-shell__bar", !props.header && "aura-shell__bar--menu-only") }, props.nav ? /* @__PURE__ */ React.createElement(
52
- IconButton,
53
- {
54
- className: "aura-shell__menu",
55
- icon: /* @__PURE__ */ React.createElement(IconMenu, null),
56
- label: props.menuLabel || t.openNav,
57
- size: "md",
58
- onClick: function() {
59
- setOpen(true);
72
+ props.nav ? /* @__PURE__ */ React.createElement("div", { className: "aura-shell__nav" }, deskNav) : null,
73
+ props.nav ? /* @__PURE__ */ React.createElement(
74
+ Drawer,
75
+ {
76
+ open,
77
+ onClose: function() {
78
+ setOpen(false);
79
+ },
80
+ side: "left",
81
+ size: "nav",
82
+ "aria-label": props.navLabel || t.navigation,
83
+ dismissible: true
84
+ },
85
+ drawerNav
86
+ ) : null,
87
+ /* @__PURE__ */ React.createElement("div", { className: "aura-shell__main" }, props.header || props.nav ? /* @__PURE__ */ React.createElement("header", { ref: barRef, className: cx("aura-shell__bar", !props.header && "aura-shell__bar--menu-only") }, props.nav ? /* @__PURE__ */ React.createElement(
88
+ IconButton,
89
+ {
90
+ className: "aura-shell__menu",
91
+ icon: /* @__PURE__ */ React.createElement(IconMenu, null),
92
+ label: props.menuLabel || t.openNav,
93
+ size: "md",
94
+ onClick: function() {
95
+ setOpen(true);
96
+ },
97
+ "aria-expanded": open
98
+ }
99
+ ) : null, /* @__PURE__ */ React.createElement("div", { className: "aura-shell__bar-content" }, props.header)) : null, /* @__PURE__ */ React.createElement(
100
+ "main",
101
+ {
102
+ className: cx("aura-shell__content", props.contentPadding === false && "is-flush"),
103
+ id: props.mainId || "main",
104
+ tabIndex: -1
60
105
  },
61
- "aria-expanded": open
62
- }
63
- ) : null, /* @__PURE__ */ React.createElement("div", { className: "aura-shell__bar-content" }, props.header)) : null, /* @__PURE__ */ React.createElement("main", { className: "aura-shell__content", id: props.mainId || "main", tabIndex: -1 }, props.children), props.bottomNav || null));
106
+ props.children
107
+ ), props.bottomNav || null)
108
+ );
64
109
  });
65
110
  export {
66
111
  AppShell
@@ -1,39 +1,11 @@
1
1
  'use client';
2
2
  import * as React from "react";
3
- import { cx } from "./internal.js";
4
- const AVATAR_TONES = ["progress", "ready", "neutral", "warning"];
5
- function initials(name) {
6
- const parts = String(name || "?").trim().split(/\s+/);
7
- return ((parts[0] || "?")[0] + (parts.length > 1 ? parts[parts.length - 1][0] : parts[0][1] || "")).toUpperCase();
8
- }
3
+ import { avatarElement } from "./display.js";
9
4
  const Avatar = React.forwardRef(function Avatar2(props, ref) {
10
- const size = props.size || "md", errState = React.useState(null);
11
- let hash = 0;
12
- String(props.name || "").split("").forEach(function(ch) {
13
- hash = hash * 31 + ch.charCodeAt(0) >>> 0;
5
+ const errState = React.useState(null);
6
+ return avatarElement(props, ref, errState[0], function() {
7
+ errState[1](props.src || null);
14
8
  });
15
- const tone = AVATAR_TONES[hash % AVATAR_TONES.length];
16
- return /* @__PURE__ */ React.createElement(
17
- "span",
18
- {
19
- ref,
20
- className: cx("aura-avatar", "aura-avatar--" + size, "aura-avatar--" + tone, props.className),
21
- role: "img",
22
- "aria-label": props.name + (props.status ? ", " + props.status : "")
23
- },
24
- props.src && errState[0] !== props.src ? /* @__PURE__ */ React.createElement(
25
- "img",
26
- {
27
- key: props.src,
28
- src: props.src,
29
- alt: "",
30
- onError: function() {
31
- errState[1](props.src || null);
32
- }
33
- }
34
- ) : /* @__PURE__ */ React.createElement("span", { "aria-hidden": true }, initials(props.name)),
35
- props.status === "online" ? /* @__PURE__ */ React.createElement("span", { className: "aura-avatar__status", "aria-hidden": true }) : null
36
- );
37
9
  });
38
10
  export {
39
11
  Avatar
@@ -52,6 +52,8 @@ const Button = React.forwardRef(
52
52
  const props = all;
53
53
  const variant = props.variant || "primary";
54
54
  const loading = !!props.loading;
55
+ const ad = props["aria-disabled"];
56
+ const blocked = loading || ad === true || ad === "true";
55
57
  const rest = omit(props, [
56
58
  "variant",
57
59
  "className",
@@ -78,8 +80,8 @@ const Button = React.forwardRef(
78
80
  className: props.className
79
81
  }),
80
82
  "aria-busy": loading || void 0,
81
- "aria-disabled": loading || void 0,
82
- onClick: loading ? function(e) {
83
+ "aria-disabled": blocked || void 0,
84
+ onClick: blocked ? function(e) {
83
85
  e.preventDefault();
84
86
  } : props.onClick
85
87
  },
package/dist/esm/Stat.js CHANGED
@@ -1,38 +1,10 @@
1
1
  'use client';
2
2
  import * as React from "react";
3
- import { cx } from "./internal.js";
4
- import { Icon } from "./Icon.js";
5
3
  import { useLinkComponent } from "./locale.js";
6
- import { IconMinus, IconTrendingDown, IconTrendingUp } from "./icons.js";
4
+ import { statElement } from "./display.js";
7
5
  const Stat = React.forwardRef(function Stat2(props, ref) {
8
- const ch = props.change;
9
- const dir = ch && (ch.direction || "flat");
10
- const tone = ch && (ch.tone || (dir === "up" ? "positive" : dir === "down" ? "negative" : "neutral"));
11
6
  const Link = useLinkComponent(props.linkComponent);
12
- const v = props.value;
13
- const numeric = typeof v === "number" || typeof v === "string" && /\d/.test(v) && /^[\s\d.,:+\-\u2212%()\u0E3F$\u20AC\u00A3\u00A5kKmMbB]+$/.test(v.replace(/\b[A-Z]{3}\b/g, ""));
14
- const Tag = props.href ? Link : props.onClick ? "button" : "div";
15
- const interactive = !!(props.href || props.onClick);
16
- return /* @__PURE__ */ React.createElement(
17
- Tag,
18
- {
19
- ref,
20
- className: cx("aura-stat", interactive && "is-interactive", props.loading && "is-loading", props.className),
21
- href: props.href,
22
- onClick: props.onClick,
23
- type: Tag === "button" ? "button" : void 0,
24
- "aria-busy": props.loading || void 0
25
- },
26
- /* @__PURE__ */ React.createElement("span", { className: "aura-stat__head" }, /* @__PURE__ */ React.createElement("span", { className: "aura-stat__label" }, props.label), props.icon ? /* @__PURE__ */ React.createElement("span", { className: "aura-stat__icon" }, /* @__PURE__ */ React.createElement(Icon, { name: props.icon })) : null),
27
- props.loading ? /* @__PURE__ */ React.createElement("span", { className: "aura-stat__value" }, /* @__PURE__ */ React.createElement("span", { className: "aura-skel aura-stat__skel" })) : /* @__PURE__ */ React.createElement("span", { className: cx("aura-stat__value", numeric && "is-numeric") }, props.value, props.unit ? /* @__PURE__ */ React.createElement("span", { className: "aura-stat__unit" }, props.unit) : null),
28
- ch && !props.loading || props.caption ? /* @__PURE__ */ React.createElement("span", { className: "aura-stat__foot" }, ch && !props.loading ? /* @__PURE__ */ React.createElement("span", { className: cx("aura-stat__change", "is-" + tone) }, /* @__PURE__ */ React.createElement(
29
- Icon,
30
- {
31
- name: dir === "up" ? /* @__PURE__ */ React.createElement(IconTrendingUp, null) : dir === "down" ? /* @__PURE__ */ React.createElement(IconTrendingDown, null) : /* @__PURE__ */ React.createElement(IconMinus, null),
32
- size: 14
33
- }
34
- ), /* @__PURE__ */ React.createElement("span", null, ch.value)) : null, ch && ch.label && !props.loading ? /* @__PURE__ */ React.createElement("span", { className: "aura-stat__caption" }, ch.label) : null, props.caption ? /* @__PURE__ */ React.createElement("span", { className: "aura-stat__caption" }, props.caption) : null) : null
35
- );
7
+ return statElement(props, ref, Link);
36
8
  });
37
9
  export {
38
10
  Stat
package/dist/esm/Tabs.js CHANGED
@@ -81,7 +81,7 @@ const Tabs = React.forwardRef(function Tabs2(props, ref) {
81
81
  key: t.id,
82
82
  href: t.href,
83
83
  className: tabCls(on, own.className),
84
- "aria-current": on ? "page" : void 0,
84
+ "aria-current": on ? props.current || "page" : void 0,
85
85
  onClick: function(e) {
86
86
  if (own.onClick) own.onClick(e);
87
87
  if (e.defaultPrevented || e.button !== 0 || e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return;
@@ -1,7 +1,7 @@
1
1
  'use client';
2
2
  import * as React from "react";
3
3
  import { Icon } from "./Icon.js";
4
- import { cx, omit, tone as badgeTone } from "./classes.js";
4
+ import { cx, devWarnOnce, omit, tone as badgeTone } from "./classes.js";
5
5
  import { toneFor } from "./status.js";
6
6
  import {
7
7
  IconBan,
@@ -11,6 +11,9 @@ import {
11
11
  IconCircleDotDashed,
12
12
  IconInbox,
13
13
  IconInfo,
14
+ IconMinus,
15
+ IconTrendingDown,
16
+ IconTrendingUp,
14
17
  IconTriangleAlert
15
18
  } from "./icons.js";
16
19
  const h = React.createElement;
@@ -101,7 +104,7 @@ function badgeElement(props, ref) {
101
104
  );
102
105
  }
103
106
  function emptyStateElement(props, ref) {
104
- const HT = "h" + (props.headingLevel || 3);
107
+ const HT = props.headingLevel === false ? "p" : "h" + (props.headingLevel || 3);
105
108
  const rest = omit(props, [
106
109
  "title",
107
110
  "description",
@@ -133,6 +136,66 @@ function emptyStateElement(props, ref) {
133
136
  props.action ? /* @__PURE__ */ React.createElement("div", { className: "aura-empty__action" }, props.action) : null
134
137
  );
135
138
  }
139
+ function statElement(props, ref, Link) {
140
+ const ch = props.change;
141
+ const dir = ch && (ch.direction || "flat");
142
+ const tone = ch && (ch.tone || (dir === "up" ? "positive" : dir === "down" ? "negative" : "neutral"));
143
+ const v = props.value;
144
+ const numeric = typeof v === "number" || typeof v === "string" && /\d/.test(v) && /^[\s\d.,:+\-\u2212%()\u0E3F$\u20AC\u00A3\u00A5kKmMbB]+$/.test(v.replace(/\b[A-Z]{3}\b/g, ""));
145
+ const Tag = props.href ? Link : props.onClick ? "button" : "div";
146
+ const interactive = !!(props.href || props.onClick);
147
+ if (props.headingLevel && Tag === "button")
148
+ devWarnOnce(
149
+ "stat-heading-button",
150
+ "Stat `headingLevel` is ignored with `onClick`: a heading can\u2019t sit in a button. Use `href`, or put the heading outside."
151
+ );
152
+ const LabelTag = props.headingLevel && Tag !== "button" ? "h" + props.headingLevel : "span";
153
+ const HeadTag = LabelTag === "span" ? "span" : "div";
154
+ return /* @__PURE__ */ React.createElement(
155
+ Tag,
156
+ {
157
+ ref,
158
+ className: cx("aura-stat", interactive && "is-interactive", props.loading && "is-loading", props.className),
159
+ href: props.href,
160
+ onClick: props.onClick,
161
+ type: Tag === "button" ? "button" : void 0,
162
+ "aria-busy": props.loading || void 0
163
+ },
164
+ /* @__PURE__ */ React.createElement(HeadTag, { className: "aura-stat__head" }, /* @__PURE__ */ React.createElement(LabelTag, { className: "aura-stat__label" }, props.label), props.icon ? /* @__PURE__ */ React.createElement("span", { className: "aura-stat__icon" }, /* @__PURE__ */ React.createElement(Icon, { name: props.icon })) : null),
165
+ props.loading ? /* @__PURE__ */ React.createElement("span", { className: "aura-stat__value" }, /* @__PURE__ */ React.createElement("span", { className: "aura-skel aura-stat__skel" })) : /* @__PURE__ */ React.createElement("span", { className: cx("aura-stat__value", numeric && "is-numeric") }, props.value, props.unit ? /* @__PURE__ */ React.createElement("span", { className: "aura-stat__unit" }, props.unit) : null),
166
+ ch && !props.loading || props.caption ? /* @__PURE__ */ React.createElement("span", { className: "aura-stat__foot" }, ch && !props.loading ? /* @__PURE__ */ React.createElement("span", { className: cx("aura-stat__change", "is-" + tone) }, /* @__PURE__ */ React.createElement(
167
+ Icon,
168
+ {
169
+ name: dir === "up" ? /* @__PURE__ */ React.createElement(IconTrendingUp, null) : dir === "down" ? /* @__PURE__ */ React.createElement(IconTrendingDown, null) : /* @__PURE__ */ React.createElement(IconMinus, null),
170
+ size: 14
171
+ }
172
+ ), /* @__PURE__ */ React.createElement("span", null, ch.value)) : null, ch && ch.label && !props.loading ? /* @__PURE__ */ React.createElement("span", { className: "aura-stat__caption" }, ch.label) : null, props.caption ? /* @__PURE__ */ React.createElement("span", { className: "aura-stat__caption" }, props.caption) : null) : null
173
+ );
174
+ }
175
+ const AVATAR_TONES = ["progress", "ready", "neutral", "warning"];
176
+ function initials(name) {
177
+ const parts = String(name || "?").trim().split(/\s+/);
178
+ return ((parts[0] || "?")[0] + (parts.length > 1 ? parts[parts.length - 1][0] : parts[0][1] || "")).toUpperCase();
179
+ }
180
+ function avatarElement(props, ref, broken, onError) {
181
+ const size = props.size || "md";
182
+ let hash = 0;
183
+ String(props.name || "").split("").forEach(function(ch) {
184
+ hash = hash * 31 + ch.charCodeAt(0) >>> 0;
185
+ });
186
+ const tone = AVATAR_TONES[hash % AVATAR_TONES.length];
187
+ return /* @__PURE__ */ React.createElement(
188
+ "span",
189
+ {
190
+ ref,
191
+ className: cx("aura-avatar", "aura-avatar--" + size, "aura-avatar--" + tone, props.className),
192
+ role: "img",
193
+ "aria-label": props.name + (props.status ? ", " + props.status : "")
194
+ },
195
+ props.src && broken !== props.src ? /* @__PURE__ */ React.createElement("img", { key: props.src, src: props.src, alt: "", onError }) : /* @__PURE__ */ React.createElement("span", { "aria-hidden": true }, initials(props.name)),
196
+ props.status === "online" ? /* @__PURE__ */ React.createElement("span", { className: "aura-avatar__status", "aria-hidden": true }) : null
197
+ );
198
+ }
136
199
  function buttonClass(opts = {}) {
137
200
  return cx(
138
201
  "aura-btn",
@@ -158,17 +221,27 @@ function ServerBadge(props) {
158
221
  function ServerEmptyState(props) {
159
222
  return emptyStateElement(props, void 0);
160
223
  }
224
+ function ServerStat(props) {
225
+ return statElement(omit(props, ["onClick"]), void 0, props.linkComponent || "a");
226
+ }
227
+ function ServerAvatar(props) {
228
+ return avatarElement(props, void 0, null, void 0);
229
+ }
161
230
  export {
162
231
  ALERT_ICON,
163
232
  ServerAlert,
233
+ ServerAvatar,
164
234
  ServerBadge,
165
235
  ServerCard,
166
236
  ServerEmptyState,
237
+ ServerStat,
167
238
  ServerStatusPill,
168
239
  alertElement,
240
+ avatarElement,
169
241
  badgeElement,
170
242
  buttonClass,
171
243
  cardElement,
172
244
  emptyStateElement,
245
+ statElement,
173
246
  statusPillElement
174
247
  };
package/dist/index.d.cts CHANGED
@@ -660,6 +660,9 @@ export interface TabsProps {
660
660
  variant?: "underline" | "segmented" | undefined;
661
661
  /** With `variant="segmented"`: stretch the track to the full width, tabs sharing it equally. (5.10) */
662
662
  fullWidth?: boolean | undefined;
663
+ /** Link tabs: what the current link is (5.14). `page` (default) for routes; `location` for links to sections of
664
+ * this page (`#contacts`). */
665
+ current?: "page" | "location" | undefined;
663
666
  }
664
667
  export interface NavItem {
665
668
  id: string;
@@ -996,6 +999,8 @@ export interface AppShellProps {
996
999
  /** A BottomNav for phones (4.19). Shown below its `hideFrom` breakpoint; the content keeps room for it, and a
997
1000
  * viewport ActionBar sits on top of it. With a `bottomNav` and no `nav`, there is no menu button. */
998
1001
  bottomNav?: React$1.ReactElement | undefined;
1002
+ /** `false` (5.14): `<main>` has no padding, for pages whose containers own the page padding and column. */
1003
+ contentPadding?: boolean | undefined;
999
1004
  className?: string | undefined;
1000
1005
  }
1001
1006
  export interface SeparatorProps {
@@ -1122,6 +1127,9 @@ export interface StatProps {
1122
1127
  /** Makes the whole card a link or a button (drill-down). */
1123
1128
  href?: string | undefined;
1124
1129
  onClick?: (() => void) | undefined;
1130
+ /** The label as a heading of this level (5.14), when the tile titles its section. Not with `onClick` (a heading
1131
+ * can't sit in a button); a link is fine. Default: a plain label. */
1132
+ headingLevel?: 2 | 3 | 4 | 5 | 6 | undefined;
1125
1133
  className?: string | undefined;
1126
1134
  }
1127
1135
  export interface TimePickerProps extends FieldProps {
@@ -1232,7 +1240,9 @@ export interface EmptyStateProps extends Omit<React$1.HTMLAttributes<HTMLDivElem
1232
1240
  /** `danger` (5.13) for a failure — the data couldn't load: the danger tint, a danger-coloured icon, and a solid
1233
1241
  * danger frame with `bordered`. It stays quiet; pass `role="alert"` if it must interrupt. Default `neutral`. */
1234
1242
  tone?: "neutral" | "danger" | undefined;
1235
- headingLevel?: 2 | 3 | 4 | 5 | 6 | undefined;
1243
+ /** The title's heading level (default 3); `false` (5.14) makes it a paragraph, for a card or list whose outline
1244
+ * mustn't change. EmptyState has no live-region role unless you pass `role`. */
1245
+ headingLevel?: 2 | 3 | 4 | 5 | 6 | false | undefined;
1236
1246
  className?: string | undefined;
1237
1247
  }
1238
1248
  export interface PaginationProps {
package/dist/index.d.ts CHANGED
@@ -660,6 +660,9 @@ export interface TabsProps {
660
660
  variant?: "underline" | "segmented" | undefined;
661
661
  /** With `variant="segmented"`: stretch the track to the full width, tabs sharing it equally. (5.10) */
662
662
  fullWidth?: boolean | undefined;
663
+ /** Link tabs: what the current link is (5.14). `page` (default) for routes; `location` for links to sections of
664
+ * this page (`#contacts`). */
665
+ current?: "page" | "location" | undefined;
663
666
  }
664
667
  export interface NavItem {
665
668
  id: string;
@@ -996,6 +999,8 @@ export interface AppShellProps {
996
999
  /** A BottomNav for phones (4.19). Shown below its `hideFrom` breakpoint; the content keeps room for it, and a
997
1000
  * viewport ActionBar sits on top of it. With a `bottomNav` and no `nav`, there is no menu button. */
998
1001
  bottomNav?: React$1.ReactElement | undefined;
1002
+ /** `false` (5.14): `<main>` has no padding, for pages whose containers own the page padding and column. */
1003
+ contentPadding?: boolean | undefined;
999
1004
  className?: string | undefined;
1000
1005
  }
1001
1006
  export interface SeparatorProps {
@@ -1122,6 +1127,9 @@ export interface StatProps {
1122
1127
  /** Makes the whole card a link or a button (drill-down). */
1123
1128
  href?: string | undefined;
1124
1129
  onClick?: (() => void) | undefined;
1130
+ /** The label as a heading of this level (5.14), when the tile titles its section. Not with `onClick` (a heading
1131
+ * can't sit in a button); a link is fine. Default: a plain label. */
1132
+ headingLevel?: 2 | 3 | 4 | 5 | 6 | undefined;
1125
1133
  className?: string | undefined;
1126
1134
  }
1127
1135
  export interface TimePickerProps extends FieldProps {
@@ -1232,7 +1240,9 @@ export interface EmptyStateProps extends Omit<React$1.HTMLAttributes<HTMLDivElem
1232
1240
  /** `danger` (5.13) for a failure — the data couldn't load: the danger tint, a danger-coloured icon, and a solid
1233
1241
  * danger frame with `bordered`. It stays quiet; pass `role="alert"` if it must interrupt. Default `neutral`. */
1234
1242
  tone?: "neutral" | "danger" | undefined;
1235
- headingLevel?: 2 | 3 | 4 | 5 | 6 | undefined;
1243
+ /** The title's heading level (default 3); `false` (5.14) makes it a paragraph, for a card or list whose outline
1244
+ * mustn't change. EmptyState has no live-region role unless you pass `role`. */
1245
+ headingLevel?: 2 | 3 | 4 | 5 | 6 | false | undefined;
1236
1246
  className?: string | undefined;
1237
1247
  }
1238
1248
  export interface PaginationProps {
@@ -31,11 +31,13 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
31
31
  var server_exports = {};
32
32
  __export(server_exports, {
33
33
  Alert: () => ServerAlert,
34
+ Avatar: () => ServerAvatar,
34
35
  Badge: () => ServerBadge,
35
36
  Card: () => ServerCard,
36
37
  EmptyState: () => ServerEmptyState,
37
38
  Icon: () => Icon,
38
39
  STRINGS: () => STRINGS,
40
+ Stat: () => ServerStat,
39
41
  StatusPill: () => ServerStatusPill,
40
42
  brandScale: () => scale,
41
43
  breakpoints: () => breakpoints,
@@ -1015,6 +1017,7 @@ var Icon = React2.forwardRef(function Icon2(props, ref) {
1015
1017
  var iconNames = Object.keys(map);
1016
1018
 
1017
1019
  // src/icons.ts
1020
+ var IconMinus = /* @__PURE__ */ defineIcon("minus", [["path", { d: "M5 12h14" }]]);
1018
1021
  var IconCircleAlert = /* @__PURE__ */ defineIcon("circle-alert", [
1019
1022
  ["circle", { cx: "12", cy: "12", r: "10" }],
1020
1023
  ["line", { x1: "12", x2: "12", y1: "8", y2: "12" }],
@@ -1057,6 +1060,14 @@ var IconInbox = /* @__PURE__ */ defineIcon("inbox", [
1057
1060
  { d: "M5.45 5.11 2 12v6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-6l-3.45-6.89A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z" }
1058
1061
  ]
1059
1062
  ]);
1063
+ var IconTrendingUp = /* @__PURE__ */ defineIcon("trending-up", [
1064
+ ["path", { d: "M16 7h6v6" }],
1065
+ ["path", { d: "m22 7-8.5 8.5-5-5L2 17" }]
1066
+ ]);
1067
+ var IconTrendingDown = /* @__PURE__ */ defineIcon("trending-down", [
1068
+ ["path", { d: "M16 17h6v-6" }],
1069
+ ["path", { d: "m22 17-8.5-8.5-5 5L2 7" }]
1070
+ ]);
1060
1071
 
1061
1072
  // src/display.tsx
1062
1073
  var h2 = React3.createElement;
@@ -1147,7 +1158,7 @@ function badgeElement(props, ref) {
1147
1158
  );
1148
1159
  }
1149
1160
  function emptyStateElement(props, ref) {
1150
- const HT = "h" + (props.headingLevel || 3);
1161
+ const HT = props.headingLevel === false ? "p" : "h" + (props.headingLevel || 3);
1151
1162
  const rest = omit(props, [
1152
1163
  "title",
1153
1164
  "description",
@@ -1179,6 +1190,66 @@ function emptyStateElement(props, ref) {
1179
1190
  props.action ? /* @__PURE__ */ React3.createElement("div", { className: "aura-empty__action" }, props.action) : null
1180
1191
  );
1181
1192
  }
1193
+ function statElement(props, ref, Link) {
1194
+ const ch = props.change;
1195
+ const dir = ch && (ch.direction || "flat");
1196
+ const tone2 = ch && (ch.tone || (dir === "up" ? "positive" : dir === "down" ? "negative" : "neutral"));
1197
+ const v = props.value;
1198
+ const numeric = typeof v === "number" || typeof v === "string" && /\d/.test(v) && /^[\s\d.,:+\-\u2212%()\u0E3F$\u20AC\u00A3\u00A5kKmMbB]+$/.test(v.replace(/\b[A-Z]{3}\b/g, ""));
1199
+ const Tag = props.href ? Link : props.onClick ? "button" : "div";
1200
+ const interactive = !!(props.href || props.onClick);
1201
+ if (props.headingLevel && Tag === "button")
1202
+ devWarnOnce(
1203
+ "stat-heading-button",
1204
+ "Stat `headingLevel` is ignored with `onClick`: a heading can\u2019t sit in a button. Use `href`, or put the heading outside."
1205
+ );
1206
+ const LabelTag = props.headingLevel && Tag !== "button" ? "h" + props.headingLevel : "span";
1207
+ const HeadTag = LabelTag === "span" ? "span" : "div";
1208
+ return /* @__PURE__ */ React3.createElement(
1209
+ Tag,
1210
+ {
1211
+ ref,
1212
+ className: cx("aura-stat", interactive && "is-interactive", props.loading && "is-loading", props.className),
1213
+ href: props.href,
1214
+ onClick: props.onClick,
1215
+ type: Tag === "button" ? "button" : void 0,
1216
+ "aria-busy": props.loading || void 0
1217
+ },
1218
+ /* @__PURE__ */ React3.createElement(HeadTag, { className: "aura-stat__head" }, /* @__PURE__ */ React3.createElement(LabelTag, { className: "aura-stat__label" }, props.label), props.icon ? /* @__PURE__ */ React3.createElement("span", { className: "aura-stat__icon" }, /* @__PURE__ */ React3.createElement(Icon, { name: props.icon })) : null),
1219
+ props.loading ? /* @__PURE__ */ React3.createElement("span", { className: "aura-stat__value" }, /* @__PURE__ */ React3.createElement("span", { className: "aura-skel aura-stat__skel" })) : /* @__PURE__ */ React3.createElement("span", { className: cx("aura-stat__value", numeric && "is-numeric") }, props.value, props.unit ? /* @__PURE__ */ React3.createElement("span", { className: "aura-stat__unit" }, props.unit) : null),
1220
+ ch && !props.loading || props.caption ? /* @__PURE__ */ React3.createElement("span", { className: "aura-stat__foot" }, ch && !props.loading ? /* @__PURE__ */ React3.createElement("span", { className: cx("aura-stat__change", "is-" + tone2) }, /* @__PURE__ */ React3.createElement(
1221
+ Icon,
1222
+ {
1223
+ name: dir === "up" ? /* @__PURE__ */ React3.createElement(IconTrendingUp, null) : dir === "down" ? /* @__PURE__ */ React3.createElement(IconTrendingDown, null) : /* @__PURE__ */ React3.createElement(IconMinus, null),
1224
+ size: 14
1225
+ }
1226
+ ), /* @__PURE__ */ React3.createElement("span", null, ch.value)) : null, ch && ch.label && !props.loading ? /* @__PURE__ */ React3.createElement("span", { className: "aura-stat__caption" }, ch.label) : null, props.caption ? /* @__PURE__ */ React3.createElement("span", { className: "aura-stat__caption" }, props.caption) : null) : null
1227
+ );
1228
+ }
1229
+ var AVATAR_TONES = ["progress", "ready", "neutral", "warning"];
1230
+ function initials(name) {
1231
+ const parts = String(name || "?").trim().split(/\s+/);
1232
+ return ((parts[0] || "?")[0] + (parts.length > 1 ? parts[parts.length - 1][0] : parts[0][1] || "")).toUpperCase();
1233
+ }
1234
+ function avatarElement(props, ref, broken, onError) {
1235
+ const size = props.size || "md";
1236
+ let hash = 0;
1237
+ String(props.name || "").split("").forEach(function(ch) {
1238
+ hash = hash * 31 + ch.charCodeAt(0) >>> 0;
1239
+ });
1240
+ const tone2 = AVATAR_TONES[hash % AVATAR_TONES.length];
1241
+ return /* @__PURE__ */ React3.createElement(
1242
+ "span",
1243
+ {
1244
+ ref,
1245
+ className: cx("aura-avatar", "aura-avatar--" + size, "aura-avatar--" + tone2, props.className),
1246
+ role: "img",
1247
+ "aria-label": props.name + (props.status ? ", " + props.status : "")
1248
+ },
1249
+ props.src && broken !== props.src ? /* @__PURE__ */ React3.createElement("img", { key: props.src, src: props.src, alt: "", onError }) : /* @__PURE__ */ React3.createElement("span", { "aria-hidden": true }, initials(props.name)),
1250
+ props.status === "online" ? /* @__PURE__ */ React3.createElement("span", { className: "aura-avatar__status", "aria-hidden": true }) : null
1251
+ );
1252
+ }
1182
1253
  function buttonClass(opts = {}) {
1183
1254
  return cx(
1184
1255
  "aura-btn",
@@ -1204,3 +1275,9 @@ function ServerBadge(props) {
1204
1275
  function ServerEmptyState(props) {
1205
1276
  return emptyStateElement(props, void 0);
1206
1277
  }
1278
+ function ServerStat(props) {
1279
+ return statElement(omit(props, ["onClick"]), void 0, props.linkComponent || "a");
1280
+ }
1281
+ function ServerAvatar(props) {
1282
+ return avatarElement(props, void 0, null, void 0);
1283
+ }
@@ -81,6 +81,15 @@ export interface CardProps extends Omit<React$1.HTMLAttributes<HTMLElement>, "ti
81
81
  /** id for the title element; the card is then labelled by it (aria-labelledby). */
82
82
  titleId?: string | undefined;
83
83
  }
84
+ export interface AvatarProps {
85
+ /** Accessible name; also gives the initials and a stable colour. */
86
+ name: string;
87
+ src?: string | undefined;
88
+ /** `sm` 24 · `md` 32 (default) · `lg` 40. */
89
+ size?: "sm" | "md" | "lg" | undefined;
90
+ status?: "online" | undefined;
91
+ className?: string | undefined;
92
+ }
84
93
  /** ISO date string, `YYYY-MM-DD` (Gregorian — the era is display only). */
85
94
  export type ISODate = string;
86
95
  export interface DateDisplayOptions {
@@ -89,6 +98,35 @@ export interface DateDisplayOptions {
89
98
  /** `buddhist` (พ.ศ.; default for th) or `gregory` (ค.ศ.; default for en and sv). */
90
99
  calendar?: "buddhist" | "gregory" | undefined;
91
100
  }
101
+ export interface StatChange {
102
+ /** Shown as written, e.g. "+12%" or "−3". */
103
+ value: React$1.ReactNode;
104
+ direction?: "up" | "down" | "flat" | undefined;
105
+ /** Default: up = positive, down = negative. Set it when a rise is bad (cancellations, wait time). */
106
+ tone?: "positive" | "negative" | "neutral" | undefined;
107
+ /** e.g. "vs last month". */
108
+ label?: React$1.ReactNode | undefined;
109
+ }
110
+ export interface StatProps {
111
+ /** Your router's link (e.g. `Link` from `next/link`) for this component; defaults to AuraProvider's `linkComponent`, then `<a>`. */
112
+ linkComponent?: React$1.ElementType | undefined;
113
+ label: React$1.ReactNode;
114
+ value?: React$1.ReactNode | undefined;
115
+ /** Small unit after the value ("งาน", "คน", "%"). */
116
+ unit?: React$1.ReactNode | undefined;
117
+ change?: StatChange | undefined;
118
+ caption?: React$1.ReactNode | undefined;
119
+ icon?: IconInput | undefined;
120
+ /** Skeleton in place of the value. */
121
+ loading?: boolean | undefined;
122
+ /** Makes the whole card a link or a button (drill-down). */
123
+ href?: string | undefined;
124
+ onClick?: (() => void) | undefined;
125
+ /** The label as a heading of this level (5.14), when the tile titles its section. Not with `onClick` (a heading
126
+ * can't sit in a button); a link is fine. Default: a plain label. */
127
+ headingLevel?: 2 | 3 | 4 | 5 | 6 | undefined;
128
+ className?: string | undefined;
129
+ }
92
130
  export type Tone = "neutral" | "accent" | "success" | "warning" | "danger";
93
131
  export interface BadgeProps extends React$1.HTMLAttributes<HTMLSpanElement> {
94
132
  tone?: Tone | undefined;
@@ -107,7 +145,9 @@ export interface EmptyStateProps extends Omit<React$1.HTMLAttributes<HTMLDivElem
107
145
  /** `danger` (5.13) for a failure — the data couldn't load: the danger tint, a danger-coloured icon, and a solid
108
146
  * danger frame with `bordered`. It stays quiet; pass `role="alert"` if it must interrupt. Default `neutral`. */
109
147
  tone?: "neutral" | "danger" | undefined;
110
- headingLevel?: 2 | 3 | 4 | 5 | 6 | undefined;
148
+ /** The title's heading level (default 3); `false` (5.14) makes it a paragraph, for a card or list whose outline
149
+ * mustn't change. EmptyState has no live-region role unless you pass `role`. */
150
+ headingLevel?: 2 | 3 | 4 | 5 | 6 | false | undefined;
111
151
  className?: string | undefined;
112
152
  }
113
153
  export interface ThemeOptions {
@@ -305,6 +345,11 @@ declare function ServerCard(props: CardProps): React$1.ReactElement;
305
345
  declare function ServerStatusPill(props: StatusPillProps): React$1.ReactElement;
306
346
  declare function ServerBadge(props: BadgeProps): React$1.ReactElement;
307
347
  declare function ServerEmptyState(props: EmptyStateProps): React$1.ReactElement;
348
+ /** Stat for Server Components (5.14): no `onClick` (a button needs the client); `href` renders `linkComponent`, else `<a>`. */
349
+ declare function ServerStat(props: Omit<StatProps, "onClick">): React$1.ReactElement;
350
+ /** Avatar for Server Components (5.14): initials and colour. Falling back to the initials when an image fails needs the
351
+ * client, so give a server avatar a `src` you know loads, or none. */
352
+ declare function ServerAvatar(props: AvatarProps): React$1.ReactElement;
308
353
  /** Makes icon names as strings work: `registerIcons(allIcons)` once at startup, with `allIcons` from
309
354
  * `@jirawatpyk/aura-react/icons`, or only the icons you use (`registerIcons([IconUsers, IconPlus])`). In 5.x every
310
355
  * name already works and this only says you are ready for 6.0 (it silences the notice); in 6.0 it is required. Call it in the module
@@ -315,9 +360,11 @@ export declare const Icon: React$1.ForwardRefExoticComponent<IconProps & React$1
315
360
 
316
361
  export {
317
362
  ServerAlert as Alert,
363
+ ServerAvatar as Avatar,
318
364
  ServerBadge as Badge,
319
365
  ServerCard as Card,
320
366
  ServerEmptyState as EmptyState,
367
+ ServerStat as Stat,
321
368
  ServerStatusPill as StatusPill,
322
369
  scale as brandScale,
323
370
  toneFor as statusTone,