@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.
@@ -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,
@@ -958,6 +958,7 @@ var Icon = React2.forwardRef(function Icon2(props, ref) {
958
958
  var iconNames = Object.keys(map);
959
959
 
960
960
  // src/icons.ts
961
+ var IconMinus = /* @__PURE__ */ defineIcon("minus", [["path", { d: "M5 12h14" }]]);
961
962
  var IconCircleAlert = /* @__PURE__ */ defineIcon("circle-alert", [
962
963
  ["circle", { cx: "12", cy: "12", r: "10" }],
963
964
  ["line", { x1: "12", x2: "12", y1: "8", y2: "12" }],
@@ -1000,6 +1001,14 @@ var IconInbox = /* @__PURE__ */ defineIcon("inbox", [
1000
1001
  { 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" }
1001
1002
  ]
1002
1003
  ]);
1004
+ var IconTrendingUp = /* @__PURE__ */ defineIcon("trending-up", [
1005
+ ["path", { d: "M16 7h6v6" }],
1006
+ ["path", { d: "m22 7-8.5 8.5-5-5L2 17" }]
1007
+ ]);
1008
+ var IconTrendingDown = /* @__PURE__ */ defineIcon("trending-down", [
1009
+ ["path", { d: "M16 17h6v-6" }],
1010
+ ["path", { d: "m22 17-8.5-8.5-5 5L2 7" }]
1011
+ ]);
1003
1012
 
1004
1013
  // src/display.tsx
1005
1014
  var h2 = React3.createElement;
@@ -1090,7 +1099,7 @@ function badgeElement(props, ref) {
1090
1099
  );
1091
1100
  }
1092
1101
  function emptyStateElement(props, ref) {
1093
- const HT = "h" + (props.headingLevel || 3);
1102
+ const HT = props.headingLevel === false ? "p" : "h" + (props.headingLevel || 3);
1094
1103
  const rest = omit(props, [
1095
1104
  "title",
1096
1105
  "description",
@@ -1122,6 +1131,66 @@ function emptyStateElement(props, ref) {
1122
1131
  props.action ? /* @__PURE__ */ React3.createElement("div", { className: "aura-empty__action" }, props.action) : null
1123
1132
  );
1124
1133
  }
1134
+ function statElement(props, ref, Link) {
1135
+ const ch = props.change;
1136
+ const dir = ch && (ch.direction || "flat");
1137
+ const tone2 = ch && (ch.tone || (dir === "up" ? "positive" : dir === "down" ? "negative" : "neutral"));
1138
+ const v = props.value;
1139
+ 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, ""));
1140
+ const Tag = props.href ? Link : props.onClick ? "button" : "div";
1141
+ const interactive = !!(props.href || props.onClick);
1142
+ if (props.headingLevel && Tag === "button")
1143
+ devWarnOnce(
1144
+ "stat-heading-button",
1145
+ "Stat `headingLevel` is ignored with `onClick`: a heading can\u2019t sit in a button. Use `href`, or put the heading outside."
1146
+ );
1147
+ const LabelTag = props.headingLevel && Tag !== "button" ? "h" + props.headingLevel : "span";
1148
+ const HeadTag = LabelTag === "span" ? "span" : "div";
1149
+ return /* @__PURE__ */ React3.createElement(
1150
+ Tag,
1151
+ {
1152
+ ref,
1153
+ className: cx("aura-stat", interactive && "is-interactive", props.loading && "is-loading", props.className),
1154
+ href: props.href,
1155
+ onClick: props.onClick,
1156
+ type: Tag === "button" ? "button" : void 0,
1157
+ "aria-busy": props.loading || void 0
1158
+ },
1159
+ /* @__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),
1160
+ 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),
1161
+ 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(
1162
+ Icon,
1163
+ {
1164
+ name: dir === "up" ? /* @__PURE__ */ React3.createElement(IconTrendingUp, null) : dir === "down" ? /* @__PURE__ */ React3.createElement(IconTrendingDown, null) : /* @__PURE__ */ React3.createElement(IconMinus, null),
1165
+ size: 14
1166
+ }
1167
+ ), /* @__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
1168
+ );
1169
+ }
1170
+ var AVATAR_TONES = ["progress", "ready", "neutral", "warning"];
1171
+ function initials(name) {
1172
+ const parts = String(name || "?").trim().split(/\s+/);
1173
+ return ((parts[0] || "?")[0] + (parts.length > 1 ? parts[parts.length - 1][0] : parts[0][1] || "")).toUpperCase();
1174
+ }
1175
+ function avatarElement(props, ref, broken, onError) {
1176
+ const size = props.size || "md";
1177
+ let hash = 0;
1178
+ String(props.name || "").split("").forEach(function(ch) {
1179
+ hash = hash * 31 + ch.charCodeAt(0) >>> 0;
1180
+ });
1181
+ const tone2 = AVATAR_TONES[hash % AVATAR_TONES.length];
1182
+ return /* @__PURE__ */ React3.createElement(
1183
+ "span",
1184
+ {
1185
+ ref,
1186
+ className: cx("aura-avatar", "aura-avatar--" + size, "aura-avatar--" + tone2, props.className),
1187
+ role: "img",
1188
+ "aria-label": props.name + (props.status ? ", " + props.status : "")
1189
+ },
1190
+ 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)),
1191
+ props.status === "online" ? /* @__PURE__ */ React3.createElement("span", { className: "aura-avatar__status", "aria-hidden": true }) : null
1192
+ );
1193
+ }
1125
1194
  function buttonClass(opts = {}) {
1126
1195
  return cx(
1127
1196
  "aura-btn",
@@ -1147,13 +1216,21 @@ function ServerBadge(props) {
1147
1216
  function ServerEmptyState(props) {
1148
1217
  return emptyStateElement(props, void 0);
1149
1218
  }
1219
+ function ServerStat(props) {
1220
+ return statElement(omit(props, ["onClick"]), void 0, props.linkComponent || "a");
1221
+ }
1222
+ function ServerAvatar(props) {
1223
+ return avatarElement(props, void 0, null, void 0);
1224
+ }
1150
1225
  export {
1151
1226
  ServerAlert as Alert,
1227
+ ServerAvatar as Avatar,
1152
1228
  ServerBadge as Badge,
1153
1229
  ServerCard as Card,
1154
1230
  ServerEmptyState as EmptyState,
1155
1231
  Icon,
1156
1232
  STRINGS,
1233
+ ServerStat as Stat,
1157
1234
  ServerStatusPill as StatusPill,
1158
1235
  scale as brandScale,
1159
1236
  breakpoints,
package/dist/styles.css CHANGED
@@ -34,6 +34,16 @@ a.aura-btn { text-decoration: none; }
34
34
  padding-block: var(--aura-space-2); line-height: 1.35; text-align: center; overflow-wrap: anywhere;
35
35
  }
36
36
  .aura-btn.is-loading { cursor: progress; }
37
+ /* 5.14: a button gated with aria-disabled looks unavailable but stays focusable (no pointer-events: none, so the
38
+ * not-allowed cursor and a tooltip still work). */
39
+ button.aura-btn[aria-disabled='true']:not(.is-loading) { opacity: var(--aura-disabled-opacity); cursor: not-allowed; }
40
+ button.aura-btn[aria-disabled='true']:active { transform: none; }
41
+ /* …and hovering it changes nothing: each variant keeps its resting look (the hover rules below are overridden). */
42
+ button.aura-btn--primary[aria-disabled='true']:hover { background: var(--aura-button-primary-bg); }
43
+ button.aura-btn--secondary[aria-disabled='true']:hover, button.aura-btn--ghost[aria-disabled='true']:hover,
44
+ button.aura-btn--danger-secondary[aria-disabled='true']:hover { background: transparent; }
45
+ button.aura-btn--danger[aria-disabled='true']:hover { background: var(--aura-button-danger-bg); }
46
+ button.aura-btn--creative[aria-disabled='true']:hover { box-shadow: var(--aura-shadow-creative); transform: none; }
37
47
  .aura-btn.is-loading:active { transform: none; }
38
48
 
39
49
  /* Focus — ADDED: the source defines none. Solid 2px ring, 2px offset, on every interactive element. */
@@ -868,7 +878,11 @@ a.aura-tab { text-decoration: none; }
868
878
  .aura-drawer--nav .aura-drawer__head { position: absolute; top: var(--aura-space-2); right: var(--aura-space-2); padding: 0; z-index: 1; }
869
879
  .aura-drawer__title { flex: 1; margin: 0; font-size: 16px; line-height: 1.5; font-weight: 600; }
870
880
  .aura-drawer__desc { margin: 0; padding: 0 var(--aura-space-6); font-size: 14px; line-height: 1.7; color: var(--aura-fg-secondary); }
871
- .aura-drawer__body { flex: 1; overflow-y: auto; padding: var(--aura-space-4) var(--aura-space-6); font-size: 14px; line-height: 1.7; }
881
+ .aura-drawer__body {
882
+ flex: 1; overflow-y: auto; padding: var(--aura-space-4) var(--aura-space-6); font-size: 14px; line-height: 1.7;
883
+ /* 5.14 (Chamber-OS 91): a field scrolled into view (focus, the phone keyboard) keeps clear of the head and foot. */
884
+ scroll-padding-block: var(--aura-space-4);
885
+ }
872
886
  .aura-drawer--nav .aura-drawer__body { padding: 0; }
873
887
  .aura-drawer--nav .aura-nav { width: 100%; border-right: 0; }
874
888
  .aura-drawer__foot { display: flex; justify-content: flex-end; flex-wrap: wrap; gap: var(--aura-space-3); padding: var(--aura-space-4) var(--aura-space-6); border-top: 1px solid var(--aura-border-default); }
@@ -913,12 +927,18 @@ a.aura-tab { text-decoration: none; }
913
927
  .aura-grid-layout.is-auto { grid-template-columns: unset; }
914
928
 
915
929
  /* ---------- AppShell ---------- */
916
- .aura-shell { display: flex; min-height: 100vh; min-height: 100dvh; background: var(--aura-bg-canvas); color: var(--aura-fg-primary); font-family: var(--font-sans); }
930
+ .aura-shell {
931
+ display: flex; min-height: 100vh; min-height: 100dvh; background: var(--aura-bg-canvas); color: var(--aura-fg-primary); font-family: var(--font-sans);
932
+ --aura-shell-bar-height: 56px; /* 5.14 (Chamber-OS 104): the top bar's height, for sticky offsets under it */
933
+ }
934
+ .aura-shell--no-bar { --aura-shell-bar-height: 0px; }
935
+ @media (min-width: 1024px) { .aura-shell--menu-bar { --aura-shell-bar-height: 0px; } }
917
936
  .aura-shell__nav { position: sticky; top: 0; height: 100vh; height: 100dvh; flex: none; }
918
937
  .aura-shell__main { flex: 1; min-width: 0; display: flex; flex-direction: column; }
919
938
  .aura-shell__bar {
920
939
  position: sticky; top: 0; z-index: 10; display: flex; align-items: center; gap: var(--aura-space-2);
921
- min-height: 56px; padding: 0 var(--aura-space-6); background: var(--aura-bg-surface); border-bottom: 1px solid var(--aura-border-default);
940
+ box-sizing: border-box; min-height: 56px;
941
+ padding: 0 var(--aura-space-6); background: var(--aura-bg-surface); border-bottom: 1px solid var(--aura-border-default);
922
942
  }
923
943
  /* Responsive in CSS (4.16), so the server's HTML is already right on a phone: below lg the sidebar is hidden and the
924
944
  * menu button shows; from lg up the reverse. JavaScript only opens and closes the drawer. */
@@ -935,10 +955,11 @@ a.aura-tab { text-decoration: none; }
935
955
  .aura-shell__content { flex: 1; min-width: 0; padding: var(--aura-space-6) var(--aura-space-4); }
936
956
  /* 5.7: <main> takes focus (tabIndex -1) for skip links and "the row you acted on is gone"; a landmark gets no ring. */
937
957
  .aura-shell__content:focus { outline: none; }
958
+ .aura-shell__content.is-flush { padding: 0; } /* 5.14 (Chamber-OS 97): contentPadding={false} */
938
959
  @media (min-width: 640px) { .aura-shell__content { padding: var(--aura-space-6); } }
939
960
  @media (min-width: 1024px) { .aura-shell__content { padding: var(--aura-space-8); } }
940
961
  /* The shell already pads its content: a Container directly inside only caps the width. */
941
- .aura-shell__content > .aura-container { padding-left: 0; padding-right: 0; }
962
+ .aura-shell__content:not(.is-flush) > .aura-container { padding-left: 0; padding-right: 0; }
942
963
 
943
964
  /* Totals row (4.19): under the body, a strong top edge; sticky at the bottom of a `height` table with stickyFooter. */
944
965
  .aura-table__total { background: var(--aura-bg-canvas); border-top: 1px solid var(--aura-border-strong); font-weight: 600; }
@@ -958,7 +979,7 @@ button.aura-stat { font: inherit; width: 100%; cursor: pointer; }
958
979
  .aura-stat.is-interactive:hover { border-color: var(--aura-border-strong); }
959
980
  .aura-stat.is-interactive:focus-visible { outline: var(--aura-focus-ring-width) solid var(--aura-focus-ring); outline-offset: var(--aura-focus-ring-offset); }
960
981
  .aura-stat__head { display: flex; align-items: center; justify-content: space-between; gap: var(--aura-space-2); }
961
- .aura-stat__label { font-size: 13px; font-weight: 500; color: var(--aura-fg-secondary); }
982
+ .aura-stat__label { margin: 0; font-size: 13px; font-weight: 500; color: var(--aura-fg-secondary); } /* 5.14: may be a heading */
962
983
  .aura-stat__icon { display: inline-flex; color: var(--aura-fg-secondary); }
963
984
  .aura-stat__value { display: flex; align-items: baseline; gap: var(--aura-space-1); font-size: 28px; line-height: 1.2; font-weight: 600; letter-spacing: -0.01em; min-height: 34px; }
964
985
  .aura-stat__value.is-numeric { font-variant-numeric: tabular-nums; }
@@ -1660,6 +1681,9 @@ button.aura-stepper__step:hover .aura-stepper__label { text-decoration: underlin
1660
1681
  .aura-table__td[data-card='hide'], .aura-table__head > .aura-table__th[data-card='hide'],
1661
1682
  .aura-table__row--skeleton .aura-table__td[data-card='hide'] { display: none; }
1662
1683
  .aura-table__td[data-card='field'] { order: var(--aura-card-order, 5); }
1684
+ /* 5.14 (Chamber-OS 108): in a card every field starts under its label; align: 'end' is for the grid (figures stay tabular). */
1685
+ .aura-table__td.is-end { text-align: start; }
1686
+ .aura-table__row--skeleton .aura-table__td.is-end { justify-content: flex-start; }
1663
1687
  /* hideSelectionInCards: no row boxes, no select-all line. */
1664
1688
  .aura-table--cards-nosel .aura-table__row > .aura-table__sel { display: none; }
1665
1689
  .aura-table--cards-nosel .aura-table__head { position: absolute; width: 1px; height: 1px; padding: 0; overflow: hidden; clip: rect(0 0 0 0); }
@@ -36,6 +36,16 @@ a.aura-btn { text-decoration: none; }
36
36
  padding-block: var(--aura-space-2); line-height: 1.35; text-align: center; overflow-wrap: anywhere;
37
37
  }
38
38
  .aura-btn.is-loading { cursor: progress; }
39
+ /* 5.14: a button gated with aria-disabled looks unavailable but stays focusable (no pointer-events: none, so the
40
+ * not-allowed cursor and a tooltip still work). */
41
+ button.aura-btn[aria-disabled='true']:not(.is-loading) { opacity: var(--aura-disabled-opacity); cursor: not-allowed; }
42
+ button.aura-btn[aria-disabled='true']:active { transform: none; }
43
+ /* …and hovering it changes nothing: each variant keeps its resting look (the hover rules below are overridden). */
44
+ button.aura-btn--primary[aria-disabled='true']:hover { background: var(--aura-button-primary-bg); }
45
+ button.aura-btn--secondary[aria-disabled='true']:hover, button.aura-btn--ghost[aria-disabled='true']:hover,
46
+ button.aura-btn--danger-secondary[aria-disabled='true']:hover { background: transparent; }
47
+ button.aura-btn--danger[aria-disabled='true']:hover { background: var(--aura-button-danger-bg); }
48
+ button.aura-btn--creative[aria-disabled='true']:hover { box-shadow: var(--aura-shadow-creative); transform: none; }
39
49
  .aura-btn.is-loading:active { transform: none; }
40
50
 
41
51
  /* Focus — ADDED: the source defines none. Solid 2px ring, 2px offset, on every interactive element. */
@@ -870,7 +880,11 @@ a.aura-tab { text-decoration: none; }
870
880
  .aura-drawer--nav .aura-drawer__head { position: absolute; top: var(--aura-space-2); right: var(--aura-space-2); padding: 0; z-index: 1; }
871
881
  .aura-drawer__title { flex: 1; margin: 0; font-size: 16px; line-height: 1.5; font-weight: 600; }
872
882
  .aura-drawer__desc { margin: 0; padding: 0 var(--aura-space-6); font-size: 14px; line-height: 1.7; color: var(--aura-fg-secondary); }
873
- .aura-drawer__body { flex: 1; overflow-y: auto; padding: var(--aura-space-4) var(--aura-space-6); font-size: 14px; line-height: 1.7; }
883
+ .aura-drawer__body {
884
+ flex: 1; overflow-y: auto; padding: var(--aura-space-4) var(--aura-space-6); font-size: 14px; line-height: 1.7;
885
+ /* 5.14 (Chamber-OS 91): a field scrolled into view (focus, the phone keyboard) keeps clear of the head and foot. */
886
+ scroll-padding-block: var(--aura-space-4);
887
+ }
874
888
  .aura-drawer--nav .aura-drawer__body { padding: 0; }
875
889
  .aura-drawer--nav .aura-nav { width: 100%; border-right: 0; }
876
890
  .aura-drawer__foot { display: flex; justify-content: flex-end; flex-wrap: wrap; gap: var(--aura-space-3); padding: var(--aura-space-4) var(--aura-space-6); border-top: 1px solid var(--aura-border-default); }
@@ -915,12 +929,18 @@ a.aura-tab { text-decoration: none; }
915
929
  .aura-grid-layout.is-auto { grid-template-columns: unset; }
916
930
 
917
931
  /* ---------- AppShell ---------- */
918
- .aura-shell { display: flex; min-height: 100vh; min-height: 100dvh; background: var(--aura-bg-canvas); color: var(--aura-fg-primary); font-family: var(--font-sans); }
932
+ .aura-shell {
933
+ display: flex; min-height: 100vh; min-height: 100dvh; background: var(--aura-bg-canvas); color: var(--aura-fg-primary); font-family: var(--font-sans);
934
+ --aura-shell-bar-height: 56px; /* 5.14 (Chamber-OS 104): the top bar's height, for sticky offsets under it */
935
+ }
936
+ .aura-shell--no-bar { --aura-shell-bar-height: 0px; }
937
+ @media (min-width: 1024px) { .aura-shell--menu-bar { --aura-shell-bar-height: 0px; } }
919
938
  .aura-shell__nav { position: sticky; top: 0; height: 100vh; height: 100dvh; flex: none; }
920
939
  .aura-shell__main { flex: 1; min-width: 0; display: flex; flex-direction: column; }
921
940
  .aura-shell__bar {
922
941
  position: sticky; top: 0; z-index: 10; display: flex; align-items: center; gap: var(--aura-space-2);
923
- min-height: 56px; padding: 0 var(--aura-space-6); background: var(--aura-bg-surface); border-bottom: 1px solid var(--aura-border-default);
942
+ box-sizing: border-box; min-height: 56px;
943
+ padding: 0 var(--aura-space-6); background: var(--aura-bg-surface); border-bottom: 1px solid var(--aura-border-default);
924
944
  }
925
945
  /* Responsive in CSS (4.16), so the server's HTML is already right on a phone: below lg the sidebar is hidden and the
926
946
  * menu button shows; from lg up the reverse. JavaScript only opens and closes the drawer. */
@@ -937,10 +957,11 @@ a.aura-tab { text-decoration: none; }
937
957
  .aura-shell__content { flex: 1; min-width: 0; padding: var(--aura-space-6) var(--aura-space-4); }
938
958
  /* 5.7: <main> takes focus (tabIndex -1) for skip links and "the row you acted on is gone"; a landmark gets no ring. */
939
959
  .aura-shell__content:focus { outline: none; }
960
+ .aura-shell__content.is-flush { padding: 0; } /* 5.14 (Chamber-OS 97): contentPadding={false} */
940
961
  @media (min-width: 640px) { .aura-shell__content { padding: var(--aura-space-6); } }
941
962
  @media (min-width: 1024px) { .aura-shell__content { padding: var(--aura-space-8); } }
942
963
  /* The shell already pads its content: a Container directly inside only caps the width. */
943
- .aura-shell__content > .aura-container { padding-left: 0; padding-right: 0; }
964
+ .aura-shell__content:not(.is-flush) > .aura-container { padding-left: 0; padding-right: 0; }
944
965
 
945
966
  /* Totals row (4.19): under the body, a strong top edge; sticky at the bottom of a `height` table with stickyFooter. */
946
967
  .aura-table__total { background: var(--aura-bg-canvas); border-top: 1px solid var(--aura-border-strong); font-weight: 600; }
@@ -960,7 +981,7 @@ button.aura-stat { font: inherit; width: 100%; cursor: pointer; }
960
981
  .aura-stat.is-interactive:hover { border-color: var(--aura-border-strong); }
961
982
  .aura-stat.is-interactive:focus-visible { outline: var(--aura-focus-ring-width) solid var(--aura-focus-ring); outline-offset: var(--aura-focus-ring-offset); }
962
983
  .aura-stat__head { display: flex; align-items: center; justify-content: space-between; gap: var(--aura-space-2); }
963
- .aura-stat__label { font-size: 13px; font-weight: 500; color: var(--aura-fg-secondary); }
984
+ .aura-stat__label { margin: 0; font-size: 13px; font-weight: 500; color: var(--aura-fg-secondary); } /* 5.14: may be a heading */
964
985
  .aura-stat__icon { display: inline-flex; color: var(--aura-fg-secondary); }
965
986
  .aura-stat__value { display: flex; align-items: baseline; gap: var(--aura-space-1); font-size: 28px; line-height: 1.2; font-weight: 600; letter-spacing: -0.01em; min-height: 34px; }
966
987
  .aura-stat__value.is-numeric { font-variant-numeric: tabular-nums; }
@@ -1662,6 +1683,9 @@ button.aura-stepper__step:hover .aura-stepper__label { text-decoration: underlin
1662
1683
  .aura-table__td[data-card='hide'], .aura-table__head > .aura-table__th[data-card='hide'],
1663
1684
  .aura-table__row--skeleton .aura-table__td[data-card='hide'] { display: none; }
1664
1685
  .aura-table__td[data-card='field'] { order: var(--aura-card-order, 5); }
1686
+ /* 5.14 (Chamber-OS 108): in a card every field starts under its label; align: 'end' is for the grid (figures stay tabular). */
1687
+ .aura-table__td.is-end { text-align: start; }
1688
+ .aura-table__row--skeleton .aura-table__td.is-end { justify-content: flex-start; }
1665
1689
  /* hideSelectionInCards: no row boxes, no select-all line. */
1666
1690
  .aura-table--cards-nosel .aura-table__row > .aura-table__sel { display: none; }
1667
1691
  .aura-table--cards-nosel .aura-table__head { position: absolute; width: 1px; height: 1px; padding: 0; overflow: hidden; clip: rect(0 0 0 0); }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jirawatpyk/aura-react",
3
- "version": "5.13.0",
3
+ "version": "5.14.0",
4
4
  "description": "AURA Design System — React components (Enterprise Standard, Human Creative). Thai-first: Buddhist-era dates, Thai collation.",
5
5
  "license": "MIT",
6
6
  "type": "module",