@jirawatpyk/aura-tokens 5.4.0 → 5.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/aura-tailwind.css CHANGED
@@ -1,4 +1,4 @@
1
- /* AURA v5.4.0 — Tailwind v4 theme. GENERATED by scripts/build-tokens.mts from tokens.json. Do not edit by hand.
1
+ /* AURA v5.6.0 — Tailwind v4 theme. GENERATED by scripts/build-tokens.mts from tokens.json. Do not edit by hand.
2
2
  * @import "tailwindcss";
3
3
  * @import "@jirawatpyk/aura-tokens/tailwind.css";
4
4
  * Gives bg-bg-surface, text-fg-primary, border-border-strong, p-aura-6, rounded-aura-xl, shadow-aura-overlay,
@@ -1,4 +1,4 @@
1
- /* AURA v5.4.0 — Tailwind v4 theme with aura- prefixed names. GENERATED by scripts/build-tokens.mts. Do not edit by hand.
1
+ /* AURA v5.6.0 — Tailwind v4 theme with aura- prefixed names. GENERATED by scripts/build-tokens.mts. Do not edit by hand.
2
2
  * For apps that already have a Tailwind theme (shadcn): nothing here redefines an existing name.
3
3
  * @layer aura-tokens, theme, base, aura, components, utilities;
4
4
  * @import "tailwindcss";
package/aura.css CHANGED
@@ -1,4 +1,4 @@
1
- /* AURA v5.4.0 — GENERATED by scripts/build-tokens.mts from tokens.json. Do not edit by hand. */
1
+ /* AURA v5.6.0 — GENERATED by scripts/build-tokens.mts from tokens.json. Do not edit by hand. */
2
2
  /* Fonts: load aura-fonts.css (or the <link> tags in README) separately, so a blocked Google Fonts never breaks this file. */
3
3
 
4
4
  :root, [data-theme="light"] {
@@ -68,7 +68,7 @@ window.Aura = (() => {
68
68
  Combobox: () => Combobox,
69
69
  Command: () => Command,
70
70
  Container: () => Container,
71
- DataTable: () => DataTable,
71
+ DataTable: () => DataTable2,
72
72
  DatePicker: () => DatePicker,
73
73
  DateRangePicker: () => DateRangePicker,
74
74
  Dialog: () => Dialog,
@@ -3079,10 +3079,35 @@ window.Aura = (() => {
3079
3079
  promote();
3080
3080
  emitToasts();
3081
3081
  };
3082
+ var hotkeyOrigin = /* @__PURE__ */ new WeakMap();
3083
+ function outsideToaster(el) {
3084
+ return el && el !== document.body && !el.closest(".aura-toaster") ? el : null;
3085
+ }
3082
3086
  function ToastItem(props) {
3083
3087
  const str = useStrings();
3088
+ const Link = useLinkComponent();
3089
+ const node = React19.useRef(null);
3090
+ const origin = React19.useRef(null);
3091
+ useIsoLayoutEffect(function() {
3092
+ origin.current = outsideToaster(document.activeElement);
3093
+ const el = node.current;
3094
+ return function() {
3095
+ if (!el || !el.contains(document.activeElement)) return;
3096
+ const back = [hotkeyOrigin.get(el), origin.current].filter(function(b) {
3097
+ return !!b && b.isConnected;
3098
+ })[0];
3099
+ hotkeyOrigin.delete(el);
3100
+ setTimeout(function() {
3101
+ if (back && back.isConnected && (!document.activeElement || document.activeElement === document.body))
3102
+ back.focus();
3103
+ }, 0);
3104
+ };
3105
+ }, []);
3084
3106
  const t = props.toast, timer = React19.useRef(null), left = React19.useRef(t.duration || 5e3), since = React19.useRef(0);
3085
- const hover = React19.useRef(false);
3107
+ const hover = React19.useRef(false), focused = React19.useRef(false);
3108
+ function syncFocus() {
3109
+ if (focused.current && !(node.current && node.current.contains(document.activeElement))) focused.current = false;
3110
+ }
3086
3111
  function start() {
3087
3112
  if (left.current === Infinity || timer.current) return;
3088
3113
  since.current = Date.now();
@@ -3091,11 +3116,10 @@ window.Aura = (() => {
3091
3116
  }, left.current);
3092
3117
  }
3093
3118
  function resume() {
3094
- hover.current = false;
3095
- start();
3119
+ syncFocus();
3120
+ if (!hover.current && !focused.current) start();
3096
3121
  }
3097
3122
  function pause() {
3098
- hover.current = true;
3099
3123
  if (timer.current) {
3100
3124
  clearTimeout(timer.current);
3101
3125
  timer.current = null;
@@ -3107,23 +3131,56 @@ window.Aura = (() => {
3107
3131
  if (timer.current) clearTimeout(timer.current);
3108
3132
  timer.current = null;
3109
3133
  left.current = t.duration || 5e3;
3110
- if (!hover.current) start();
3134
+ syncFocus();
3135
+ if (!hover.current && !focused.current) start();
3111
3136
  return function() {
3112
3137
  if (timer.current) clearTimeout(timer.current);
3113
3138
  };
3114
3139
  },
3115
3140
  [t.rev]
3116
3141
  );
3142
+ const rich = t.description != null && typeof t.description !== "boolean" && t.description !== "";
3143
+ const actions = (t.actions && t.actions.length ? t.actions : t.action ? [t.action] : []).slice(0, 2);
3144
+ function run(a) {
3145
+ if (a.onClick) a.onClick();
3146
+ if (a.dismiss !== false) toast.dismiss(t.id);
3147
+ }
3117
3148
  return /* @__PURE__ */ React19.createElement(
3118
3149
  "div",
3119
3150
  {
3120
- className: cx("aura-toast", "aura-toast--" + t.tone, t.loading && "is-loading"),
3151
+ ref: node,
3152
+ onKeyDown: function(e) {
3153
+ if (e.key === "Escape") {
3154
+ e.stopPropagation();
3155
+ toast.dismiss(t.id);
3156
+ }
3157
+ },
3158
+ className: cx(
3159
+ "aura-toast",
3160
+ "aura-toast--" + t.tone,
3161
+ t.loading && "is-loading",
3162
+ /* Two actions, or one beside a description, go on their own row under the text so it keeps its width. */
3163
+ (actions.length > 1 || actions.length && rich) && "has-action-row"
3164
+ ),
3121
3165
  role: t.tone === "danger" ? "alert" : "status",
3122
3166
  "aria-busy": t.loading || void 0,
3123
- onMouseEnter: pause,
3124
- onMouseLeave: resume,
3125
- onFocus: pause,
3126
- onBlur: resume
3167
+ onMouseEnter: function() {
3168
+ hover.current = true;
3169
+ pause();
3170
+ },
3171
+ onMouseLeave: function() {
3172
+ hover.current = false;
3173
+ resume();
3174
+ },
3175
+ onFocus: function() {
3176
+ focused.current = true;
3177
+ pause();
3178
+ },
3179
+ onBlur: function(e) {
3180
+ if (node.current && e.relatedTarget && node.current.contains(e.relatedTarget)) return;
3181
+ focused.current = false;
3182
+ resume();
3183
+ }
3127
3184
  },
3128
3185
  /* @__PURE__ */ React19.createElement(
3129
3186
  Icon,
@@ -3132,19 +3189,33 @@ window.Aura = (() => {
3132
3189
  className: cx("aura-toast__icon", t.loading && "aura-spin")
3133
3190
  }
3134
3191
  ),
3135
- /* @__PURE__ */ React19.createElement("div", { className: "aura-toast__body" }, /* @__PURE__ */ React19.createElement("p", { className: "aura-toast__title" }, t.title), t.description ? /* @__PURE__ */ React19.createElement("p", { className: "aura-toast__text" }, t.description) : null),
3136
- t.action ? /* @__PURE__ */ React19.createElement(
3137
- "button",
3138
- {
3139
- type: "button",
3140
- className: "aura-toast__action",
3141
- onClick: function() {
3142
- t.action.onClick && t.action.onClick();
3143
- toast.dismiss(t.id);
3144
- }
3145
- },
3146
- t.action.label
3147
- ) : null,
3192
+ /* @__PURE__ */ React19.createElement("div", { className: "aura-toast__body" }, /* @__PURE__ */ React19.createElement("p", { className: "aura-toast__title" }, t.title), rich ? /* @__PURE__ */ React19.createElement("div", { className: "aura-toast__text" }, t.description) : null),
3193
+ actions.length ? /* @__PURE__ */ React19.createElement("div", { className: "aura-toast__actions" }, actions.map(function(a, i) {
3194
+ return a.href ? /* @__PURE__ */ React19.createElement(
3195
+ Link,
3196
+ {
3197
+ key: i,
3198
+ href: a.href,
3199
+ className: "aura-toast__action",
3200
+ onClick: function(e) {
3201
+ if (plainClick(e)) run(a);
3202
+ else if (a.onClick) a.onClick();
3203
+ }
3204
+ },
3205
+ a.label
3206
+ ) : /* @__PURE__ */ React19.createElement(
3207
+ "button",
3208
+ {
3209
+ key: i,
3210
+ type: "button",
3211
+ className: "aura-toast__action",
3212
+ onClick: function() {
3213
+ run(a);
3214
+ }
3215
+ },
3216
+ a.label
3217
+ );
3218
+ })) : null,
3148
3219
  /* @__PURE__ */ React19.createElement(
3149
3220
  IconButton,
3150
3221
  {
@@ -3158,7 +3229,22 @@ window.Aura = (() => {
3158
3229
  )
3159
3230
  );
3160
3231
  }
3161
- function Toaster(props) {
3232
+ function hotkeyMatcher(spec) {
3233
+ const parts = spec.split("+").map(function(p) {
3234
+ return p.trim().toLowerCase();
3235
+ });
3236
+ const key = parts[parts.length - 1] || "";
3237
+ const code = /^[a-z]$/.test(key) ? "Key" + key.toUpperCase() : /^[0-9]$/.test(key) ? "Digit" + key : null;
3238
+ const want = function(m) {
3239
+ return parts.indexOf(m) >= 0 && parts.indexOf(m) < parts.length - 1;
3240
+ };
3241
+ return function(e) {
3242
+ if (e.altKey !== want("alt") || e.ctrlKey !== want("ctrl") || e.shiftKey !== want("shift") || e.metaKey !== (want("meta") || want("cmd")))
3243
+ return false;
3244
+ return code ? e.code === code : e.key.toLowerCase() === key;
3245
+ };
3246
+ }
3247
+ function Toaster(props = {}) {
3162
3248
  const t = useStrings();
3163
3249
  const mounted = useMounted();
3164
3250
  const s = React19.useState(toastState.list);
@@ -3171,15 +3257,49 @@ window.Aura = (() => {
3171
3257
  });
3172
3258
  };
3173
3259
  }, []);
3260
+ const region = React19.useRef(null);
3261
+ const hotkey = props.hotkey === void 0 ? "Alt+T" : props.hotkey;
3262
+ React19.useEffect(
3263
+ function() {
3264
+ if (!hotkey) return;
3265
+ const match = hotkeyMatcher(hotkey);
3266
+ function onKey(e) {
3267
+ if (!e.key || e.repeat || e.isComposing || !match(e) || !region.current) return;
3268
+ const el = e.target;
3269
+ const editable = !!el && (el.isContentEditable || /^(INPUT|TEXTAREA)$/.test(el.tagName || ""));
3270
+ const mac = /Mac|iPhone|iPad|iPod/.test(navigator.platform || navigator.userAgent || "");
3271
+ if (editable && mac && e.altKey && !e.ctrlKey && !e.metaKey && e.key.length === 1) return;
3272
+ const items2 = region.current.querySelectorAll(".aura-toast");
3273
+ const last = items2[items2.length - 1];
3274
+ if (!last) return;
3275
+ const target = last.querySelector(".aura-toast__action") || last.querySelector("button");
3276
+ if (!target) return;
3277
+ e.preventDefault();
3278
+ const from = outsideToaster(document.activeElement);
3279
+ if (from) hotkeyOrigin.set(last, from);
3280
+ target.focus();
3281
+ }
3282
+ document.addEventListener("keydown", onKey);
3283
+ return function() {
3284
+ document.removeEventListener("keydown", onKey);
3285
+ };
3286
+ },
3287
+ [hotkey]
3288
+ );
3174
3289
  if (!mounted) return null;
3290
+ const pos = props.position || "bottom";
3291
+ const offset = props.offset;
3175
3292
  return (0, import_react_dom5.createPortal)(
3176
3293
  /* @__PURE__ */ React19.createElement(
3177
3294
  "div",
3178
3295
  {
3179
- className: cx("aura-toaster", props && props.position === "top" && "is-top"),
3296
+ ref: region,
3297
+ className: cx("aura-toaster", pos.indexOf("top") === 0 && "is-top", /center$/.test(pos) && "is-center"),
3298
+ style: offset != null ? { "--aura-toaster-offset": typeof offset === "number" ? offset + "px" : offset } : void 0,
3180
3299
  role: "region",
3181
3300
  "aria-live": "polite",
3182
- "aria-label": t.notifications
3301
+ "aria-label": hotkey ? t.notifications + " (" + hotkey + ")" : t.notifications,
3302
+ "aria-keyshortcuts": hotkey || void 0
3183
3303
  },
3184
3304
  s[0].map(function(t2) {
3185
3305
  return /* @__PURE__ */ React19.createElement(ToastItem, { key: t2.id, toast: t2 });
@@ -3982,7 +4102,7 @@ window.Aura = (() => {
3982
4102
  var ROW_H_DEFAULT = 48;
3983
4103
  var OVERSCAN = 8;
3984
4104
  var FLEX_MIN = 160;
3985
- var DataTable = React28.forwardRef(function DataTable2(props, ref) {
4105
+ var DataTableImpl = React28.forwardRef(function DataTable(props, ref) {
3986
4106
  const t = useStrings();
3987
4107
  const columns = props.columns || DEFAULT_COLUMNS;
3988
4108
  const byKey = {};
@@ -4007,7 +4127,27 @@ window.Aura = (() => {
4007
4127
  );
4008
4128
  const sort = sortState[0], setSort = sortState[1];
4009
4129
  const selState = useMaybeControlled(props.selected, props.defaultSelected || [], props.onSelectionChange);
4010
- const selected = selState[0], setSelected = selState[1];
4130
+ const canSelect = function(r) {
4131
+ return !!r && (!props.isRowSelectable || !!props.isRowSelectable(r));
4132
+ };
4133
+ const rejected = {};
4134
+ if (props.isRowSelectable)
4135
+ rows.forEach(function(r) {
4136
+ if (!canSelect(r)) rejected[String(r[rowKey])] = true;
4137
+ });
4138
+ const selected = selState[0].filter(function(k) {
4139
+ return !rejected[String(k)];
4140
+ }), setSelected = selState[1];
4141
+ const droppedSig = selState[0].length === selected.length ? "" : "#" + selState[0].filter(function(k) {
4142
+ return rejected[String(k)];
4143
+ }).join("\0") + "#" + selected.join("\0");
4144
+ React28.useEffect(
4145
+ function() {
4146
+ if (droppedSig !== "") setSelected(selected);
4147
+ },
4148
+ // eslint-disable-next-line react-hooks/exhaustive-deps
4149
+ [droppedSig]
4150
+ );
4011
4151
  const pageState = useMaybeControlled(
4012
4152
  props.page,
4013
4153
  props.defaultPage || 1,
@@ -4418,7 +4558,7 @@ window.Aura = (() => {
4418
4558
  el.focus();
4419
4559
  }
4420
4560
  }
4421
- const pageKeys = pageRows.map(function(r) {
4561
+ const pageKeys = pageRows.filter(canSelect).map(function(r) {
4422
4562
  return r[rowKey];
4423
4563
  });
4424
4564
  const selSet = {};
@@ -4682,10 +4822,12 @@ window.Aura = (() => {
4682
4822
  if (inner) inner.focus();
4683
4823
  else handled = false;
4684
4824
  } else if (k === " " || k === "Enter") {
4685
- if (r === 0 && !col && selectable && nRows) toggleAll();
4686
- else if (r === 0 && col && col.sortable && canSortAny) sortBy(col.key);
4687
- else if (r > 0 && k === " " && selectable) toggle(row[rowKey], !selSet[row[rowKey]]);
4688
- else if (r > 0 && k === "Enter" && rowHref && !target.querySelector("button, a[href]:not(.aura-table__row-link), input, select, textarea"))
4825
+ if (r === 0 && !col && selectable && nRows) {
4826
+ if (pageKeys.length) toggleAll();
4827
+ } else if (r === 0 && col && col.sortable && canSortAny) sortBy(col.key);
4828
+ else if (r > 0 && k === " " && selectable) {
4829
+ if (canSelect(row)) toggle(row[rowKey], !selSet[row[rowKey]]);
4830
+ } else if (r > 0 && k === "Enter" && rowHref && !target.querySelector("button, a[href]:not(.aura-table__row-link), input, select, textarea"))
4689
4831
  followRow(target.closest('[role="row"]'), e);
4690
4832
  else if (r > 0 && k === "Enter" && target.querySelector("button, a[href], input, select, textarea"))
4691
4833
  target.querySelector("button, a[href], input, select, textarea").focus();
@@ -4714,7 +4856,7 @@ window.Aura = (() => {
4714
4856
  activeState[1]({ r: 0, c: 0 });
4715
4857
  }
4716
4858
  },
4717
- nRows ? /* @__PURE__ */ React28.createElement(
4859
+ pageKeys.length ? /* @__PURE__ */ React28.createElement(
4718
4860
  Checkbox,
4719
4861
  {
4720
4862
  hideLabel: true,
@@ -4725,7 +4867,7 @@ window.Aura = (() => {
4725
4867
  onChange: toggleAll
4726
4868
  }
4727
4869
  ) : null,
4728
- nRows ? /* @__PURE__ */ React28.createElement("span", { className: "aura-table__sel-text" }, nSel ? t.selectedCount(nSel) : t.selectAll) : /* @__PURE__ */ React28.createElement("span", { className: "aura-sr-only" }, t.selectRows)
4870
+ pageKeys.length ? /* @__PURE__ */ React28.createElement("span", { className: "aura-table__sel-text" }, nSel ? t.selectedCount(nSel) : t.selectAll) : /* @__PURE__ */ React28.createElement("span", { className: "aura-sr-only" }, t.selectRows)
4729
4871
  )
4730
4872
  );
4731
4873
  vis.forEach(function(c, i) {
@@ -4863,12 +5005,12 @@ window.Aura = (() => {
4863
5005
  className: cx("aura-table__sel", nPinned && "is-pinned"),
4864
5006
  tabIndex: tabFor(ri, 0),
4865
5007
  "data-rc": ri + ":0",
4866
- "aria-label": t.selectRow(k),
5008
+ "aria-label": canSelect(r) ? t.selectRow(k) : props.rowSelectDisabledLabel ? props.rowSelectDisabledLabel(r) : void 0,
4867
5009
  onFocus: function(e) {
4868
5010
  if (e.target === e.currentTarget) activeState[1]({ r: ri, c: 0 });
4869
5011
  }
4870
5012
  },
4871
- /* @__PURE__ */ React28.createElement(
5013
+ canSelect(r) ? /* @__PURE__ */ React28.createElement(
4872
5014
  Checkbox,
4873
5015
  {
4874
5016
  hideLabel: true,
@@ -4879,7 +5021,7 @@ window.Aura = (() => {
4879
5021
  toggle(k, on);
4880
5022
  }
4881
5023
  }
4882
- )
5024
+ ) : null
4883
5025
  )
4884
5026
  );
4885
5027
  vis.forEach(function(c, j) {
@@ -4982,7 +5124,7 @@ window.Aura = (() => {
4982
5124
  key: k,
4983
5125
  role: "row",
4984
5126
  "aria-rowindex": first + i + 2,
4985
- "aria-selected": selectable ? isSel : void 0,
5127
+ "aria-selected": selectable && canSelect(r) ? isSel : void 0,
4986
5128
  className: cx(
4987
5129
  "aura-table__row",
4988
5130
  isSel && "is-selected",
@@ -5217,6 +5359,7 @@ window.Aura = (() => {
5217
5359
  }
5218
5360
  return /* @__PURE__ */ React28.createElement("div", { ref, className: cx("aura-table-box", props.className) }, out);
5219
5361
  });
5362
+ var DataTable2 = DataTableImpl;
5220
5363
 
5221
5364
  // src/Card.tsx
5222
5365
  var React29 = __toESM(require_react(), 1);
@@ -496,7 +496,9 @@ a.aura-icon-btn { text-decoration: none; }
496
496
  display: flex; flex-direction: column; gap: var(--aura-space-2); width: min(380px, calc(100vw - 2 * var(--aura-space-4)));
497
497
  font-family: var(--font-sans);
498
498
  }
499
- .aura-toaster.is-top { bottom: auto; top: var(--aura-space-6); }
499
+ .aura-toaster.is-top { bottom: auto; top: calc(var(--aura-toaster-offset, var(--aura-space-6)) + env(safe-area-inset-top, 0px)); }
500
+ /* 5.6: top-center / bottom-center; `offset` (or --aura-toaster-offset) clears an app's top bar. */
501
+ .aura-toaster.is-center { right: auto; left: 50%; transform: translateX(-50%); }
500
502
  .aura-toast {
501
503
  display: flex; align-items: flex-start; gap: var(--aura-space-3); padding: var(--aura-space-3) var(--aura-space-2) var(--aura-space-3) var(--aura-space-4);
502
504
  background: var(--aura-bg-surface); color: var(--aura-fg-primary);
@@ -511,10 +513,27 @@ a.aura-icon-btn { text-decoration: none; }
511
513
  .aura-toast__body { flex: 1; min-width: 0; padding-top: 1px; }
512
514
  .aura-toast__title { margin: 0; font-size: 14px; font-weight: 500; line-height: 20px; }
513
515
  .aura-toast__text { margin: 2px 0 0; font-size: 13px; line-height: 1.5; color: var(--aura-fg-secondary); }
516
+ .aura-toast__text > * { margin: 0; }
517
+ .aura-toast__text a { color: var(--aura-fg-accent); text-underline-offset: 2px; }
518
+ .aura-toast__text a:focus-visible { outline: var(--aura-focus-ring-width) solid var(--aura-focus-ring); outline-offset: 1px; border-radius: var(--aura-radius-xs); }
519
+ .aura-toast__actions { flex: none; align-self: center; display: flex; gap: var(--aura-space-1); }
520
+ /* Two actions, or one under a description (5.6): the actions take a row of their own below the text, lined up with it
521
+ * (the action's own padding sits left of the text column), so the text keeps the toast's width. */
522
+ .aura-toast.has-action-row { flex-wrap: wrap; row-gap: var(--aura-space-1); }
523
+ .aura-toast.has-action-row .aura-toast__body { flex-basis: 0; }
524
+ .aura-toast.has-action-row .aura-toast__actions { order: 3; flex-basis: 100%; flex-wrap: wrap; margin-left: calc(16px + var(--aura-space-3) - var(--aura-space-2)); margin-bottom: calc(-1 * var(--aura-space-1)); }
514
525
  .aura-toast__action {
515
526
  flex: none; align-self: center; height: var(--aura-control-sm); padding: 0 var(--aura-space-2);
516
527
  border: 0; border-radius: var(--aura-radius-sm); background: transparent; color: var(--aura-fg-accent);
517
528
  font: inherit; font-size: 13px; font-weight: 500; cursor: pointer;
529
+ display: inline-flex; align-items: center; text-decoration: none; white-space: nowrap;
530
+ }
531
+ /* Touch (5.6): the 32px action gets a 44px hit area, as IconButton does. Links in the text only get a little padding —
532
+ * lines sit 20px apart, so a 44px box would overlap the next line's link. */
533
+ @media (pointer: coarse) {
534
+ .aura-toast__action { position: relative; }
535
+ .aura-toast__action::after { content: ''; position: absolute; top: 50%; left: 50%; width: max(100%, var(--aura-touch-target)); height: max(100%, var(--aura-touch-target)); transform: translate(-50%, -50%); }
536
+ .aura-toast__text a { padding-block: 2px; }
518
537
  }
519
538
  .aura-toast__action:hover { background: var(--aura-bg-surface-hover); }
520
539
  .aura-toast__action:focus-visible { outline: var(--aura-focus-ring-width) solid var(--aura-focus-ring); outline-offset: 0; }
@@ -1203,6 +1222,8 @@ button.aura-stepper__step:hover .aura-stepper__label { text-decoration: underlin
1203
1222
  .aura-dialog__foot > * { width: 100%; }
1204
1223
  .aura-drawer:not(.aura-drawer--nav) { max-width: none; }
1205
1224
  .aura-toaster { left: var(--aura-space-4); right: var(--aura-space-4); bottom: var(--aura-space-4); width: auto; }
1225
+ .aura-toaster.is-center { left: var(--aura-space-4); right: var(--aura-space-4); transform: none; }
1226
+ .aura-toaster.is-top { top: calc(var(--aura-toaster-offset, var(--aura-space-4)) + env(safe-area-inset-top, 0px)); }
1206
1227
  .aura-card { padding: var(--aura-space-4); }
1207
1228
  .aura-stat { padding: var(--aura-space-4); }
1208
1229
  .aura-stat__value { font-size: 24px; }
@@ -1385,9 +1406,9 @@ button.aura-stepper__step:hover .aura-stepper__label { text-decoration: underlin
1385
1406
  @media (max-width: 767.98px) { :root:has(.aura-bottomnav--below-md) { --aura-bottomnav-offset: calc(var(--aura-bottomnav-height) + env(safe-area-inset-bottom, 0px)); } }
1386
1407
  @media (max-width: 1023.98px) { :root:has(.aura-bottomnav--below-lg) { --aura-bottomnav-offset: calc(var(--aura-bottomnav-height) + env(safe-area-inset-bottom, 0px)); } }
1387
1408
  @media (max-width: 1279.98px) { :root:has(.aura-bottomnav--below-xl) { --aura-bottomnav-offset: calc(var(--aura-bottomnav-height) + env(safe-area-inset-bottom, 0px)); } }
1388
- /* Toasts clear a BottomNav wherever it shows (phones and tablets). */
1389
- .aura-toaster:not(.is-top) { bottom: calc(var(--aura-space-6) + var(--aura-bottomnav-offset, 0px)); }
1390
- @media (max-width: 639.98px) { .aura-toaster:not(.is-top) { bottom: calc(var(--aura-space-4) + var(--aura-bottomnav-offset, 0px)); } }
1409
+ /* Toasts clear a BottomNav wherever it shows (phones and tablets), else the bottom safe-area inset (5.6). */
1410
+ .aura-toaster:not(.is-top) { bottom: calc(var(--aura-toaster-offset, var(--aura-space-6)) + var(--aura-bottomnav-offset, env(safe-area-inset-bottom, 0px))); }
1411
+ @media (max-width: 639.98px) { .aura-toaster:not(.is-top) { bottom: calc(var(--aura-toaster-offset, var(--aura-space-4)) + var(--aura-bottomnav-offset, env(safe-area-inset-bottom, 0px))); } }
1391
1412
 
1392
1413
  /* ---------- 4.19: ActionBar ---------- */
1393
1414
  /* Sticky, not fixed: it keeps its place at the end of its parent, so it never covers the last field. */
@@ -46,12 +46,18 @@ export interface DataTableState {
46
46
  sort: DataTableSort | null;
47
47
  page: number;
48
48
  }
49
- /** A callback that gets one row. Written with your own row type (`(r: Order) => …`) it still fits (5.4):
50
- * the row is checked the way method parameters are, so no cast from `Record<string, any>` is needed. */
51
- export type RowCallback<R> = {
52
- bivarianceHack(row: Record<string, any>): R;
49
+ /** Internal helper (exported only because the declarations reference it): `T` as written, but never inferred from
50
+ * this position (works before TypeScript 5.4's own `NoInfer`), so a table's row type comes from its `rows`. */
51
+ export type NoInferRow<T> = [
52
+ T
53
+ ][T extends any ? 0 : never];
54
+ /** A callback that gets one row of type `Row` and returns `T`. Checked the way method parameters are, so a callback
55
+ * written for a narrower or wider row type still fits; one written for an unrelated type is an error (5.5). */
56
+ export type RowCallback<T, Row = Record<string, any>> = {
57
+ bivarianceHack(row: NoInferRow<Row>): T;
53
58
  }["bivarianceHack"];
54
- export interface DataTableColumn {
59
+ /** One column. `Row` is the table's row type (5.5): `DataTableColumn<Order>` types `render` and `sortValue`. */
60
+ export interface DataTableColumn<Row extends Record<string, any> = Record<string, any>> {
55
61
  /** Key into each row object. */
56
62
  key: string;
57
63
  /** Header label (source style: UPPERCASE). */
@@ -67,9 +73,9 @@ export interface DataTableColumn {
67
73
  /** Header becomes a button cycling asc → desc → unsorted. */
68
74
  sortable?: boolean | undefined;
69
75
  /** Value to sort by, when not the cell value (dates, amounts). */
70
- sortValue?: RowCallback<string | number | null> | undefined;
76
+ sortValue?: RowCallback<string | number | null, Row> | undefined;
71
77
  /** Custom cell content. */
72
- render?: RowCallback<React$1.ReactNode> | undefined;
78
+ render?: RowCallback<React$1.ReactNode, Row> | undefined;
73
79
  /** Set false to keep a sized column fixed when the table is `resizable`. */
74
80
  resizable?: boolean | undefined;
75
81
  /** Resize limits in px. Defaults 64 / 480. For the flexible column, minWidth defaults to 160. */
@@ -99,13 +105,14 @@ export interface DataTableEmpty {
99
105
  /** One action, usually a secondary Button. */
100
106
  action?: React$1.ReactNode | undefined;
101
107
  }
102
- /** Enterprise data table: 48px rows, hairline dividers, mono header band. */
103
- export interface DataTableProps {
108
+ /** Enterprise data table: 48px rows, hairline dividers, mono header band. Generic over the row type (5.5): with
109
+ * `rows={orders}` every `render`, `sortValue`, `getRowHref` and `onRowActivate` gets an `Order`. */
110
+ export interface DataTableProps<Row extends Record<string, any> = Record<string, any>> {
104
111
  /** Defaults to ID · NAME · STATUS · OWNER (96 / 160 / 112 / auto px). */
105
- columns?: DataTableColumn[] | undefined;
106
- /** Row objects. Any typed interface works (5.4: before, `interface Order {…}` rows needed a cast); cells show
107
- * the value at each column's key unless the column has `render`. */
108
- rows: ReadonlyArray<Record<string, any>>;
112
+ columns?: DataTableColumn<Row>[] | undefined;
113
+ /** Row objects, of any interface; their type is the table's row type. Cells show the value at each column's key
114
+ * unless the column has `render`. */
115
+ rows: ReadonlyArray<Row>;
109
116
  /** Column key giving each row a unique React key. Default: the first column's key. */
110
117
  rowKey?: string | undefined;
111
118
  /** Accessible name for the table. */
@@ -117,6 +124,11 @@ export interface DataTableProps {
117
124
  onSortChange?: ((sort: DataTableSort | null) => void) | undefined;
118
125
  /** Adds a checkbox column. */
119
126
  selectable?: boolean | undefined;
127
+ /** Which rows can be selected (5.6). A row it rejects shows no checkbox (the cell stays, so columns line up), is
128
+ * skipped by select-all, Space and the count, and never appears in `onSelectionChange` — even if passed in. */
129
+ isRowSelectable?: RowCallback<boolean, Row> | undefined;
130
+ /** Accessible name for a rejected row's empty cell, e.g. `(r) => 'Not awaiting review'` (5.6). */
131
+ rowSelectDisabledLabel?: RowCallback<string, Row> | undefined;
120
132
  /** Controlled selection (row keys); pair with onSelectionChange. */
121
133
  selected?: Array<string | number> | undefined;
122
134
  /** Initial selection when uncontrolled. */
@@ -152,7 +164,7 @@ export interface DataTableProps {
152
164
  totalRows?: number | undefined;
153
165
  /** Makes each row a link: the first column's content renders as the provider's linkComponent (or `<a>`), and a click
154
166
  * or Enter anywhere on the row follows it. Ctrl/⌘-click opens a new tab as usual. */
155
- getRowHref?: RowCallback<string> | undefined;
167
+ getRowHref?: RowCallback<string, Row> | undefined;
156
168
  /** Pager arrows become links to these URLs (search-param paging). Without onPageChange the link navigates. */
157
169
  getPageHref?: ((page: number) => string) | undefined;
158
170
  /** Router link for getRowHref / getPageHref. Default: AuraProvider's linkComponent, else `<a>`. */
@@ -160,7 +172,7 @@ export interface DataTableProps {
160
172
  /** Skeleton row count when there is no pageSize. Default 5. */
161
173
  skeletonRows?: number | undefined;
162
174
  /** Called on row click or Enter. */
163
- onRowActivate?: RowCallback<void> | undefined;
175
+ onRowActivate?: RowCallback<void, Row> | undefined;
164
176
  /** Fixed height in px: sticky header, and only the rows in view are rendered. */
165
177
  height?: number | undefined;
166
178
  /** Column menu on every header, drag-to-reorder, and the show/hide columns button. */
@@ -480,14 +492,23 @@ export interface AlertProps {
480
492
  onDismiss?: (() => void) | undefined;
481
493
  className?: string | undefined;
482
494
  }
495
+ /** A button (or, with `href`, a link through AuraProvider's `linkComponent`) on a toast (5.6: `href`, `dismiss`). */
496
+ export interface ToastAction {
497
+ label: string;
498
+ onClick?: (() => void) | undefined;
499
+ /** Makes the action a link (routes through `linkComponent`, so `next/link` navigates client-side). */
500
+ href?: string | undefined;
501
+ /** Default true: the toast closes when the action runs. `false` keeps it (a list of items to work through). */
502
+ dismiss?: boolean | undefined;
503
+ }
483
504
  export interface ToastOptions {
484
505
  title: string;
485
- description?: string | undefined;
506
+ /** Text, or (5.6) rich content — lines with their own links, read once inside the toast's live region. */
507
+ description?: React$1.ReactNode | undefined;
486
508
  tone?: FeedbackTone | undefined;
487
- action?: {
488
- label: string;
489
- onClick?: (() => void) | undefined;
490
- } | undefined;
509
+ action?: ToastAction | undefined;
510
+ /** Up to two actions in a row (5.6); used instead of `action` when given. */
511
+ actions?: ToastAction[] | undefined;
491
512
  /** ms before it closes itself. Default 5000; Infinity keeps it until dismissed. */
492
513
  duration?: number | undefined;
493
514
  /** Reuse an id to replace a toast in place (same position, timer restarts) — never a second toast. */
@@ -1278,10 +1299,18 @@ export declare namespace toast {
1278
1299
  var dismiss: (id: string) => void;
1279
1300
  }
1280
1301
  export interface ToasterProps {
1281
- /** Default `bottom`. */
1282
- position?: "bottom" | "top" | undefined;
1283
- }
1284
- export declare function Toaster(props: ToasterProps): React$1.ReactElement | null;
1302
+ /** Default `bottom` (bottom right). `top` is top right; `top-center` / `bottom-center` centre the stack (5.6).
1303
+ * Below 640px toasts span the width either way. */
1304
+ position?: "bottom" | "bottom-right" | "bottom-center" | "top" | "top-right" | "top-center" | undefined;
1305
+ /** Distance from the top or bottom edge (5.6), e.g. `64` to clear a 56px top bar; px or any CSS length. Sets
1306
+ * `--aura-toaster-offset`; the safe-area inset is added on top. Default 24px (16px below 640px). */
1307
+ offset?: number | string | undefined;
1308
+ /** Keyboard shortcut to the newest toast (5.6): focuses its action, else its close button, and pauses its timer;
1309
+ * Escape closes it and returns focus. Default `"Alt+T"` (matched on the physical key, so macOS Option+T works);
1310
+ * `false` turns it off. */
1311
+ hotkey?: string | false | undefined;
1312
+ }
1313
+ export declare function Toaster(props?: ToasterProps): React$1.ReactElement | null;
1285
1314
  export declare const PasswordField: React$1.ForwardRefExoticComponent<PasswordFieldProps & React$1.RefAttributes<HTMLInputElement>>;
1286
1315
  export declare const FormErrorSummary: React$1.ForwardRefExoticComponent<FormErrorSummaryProps & React$1.RefAttributes<HTMLDivElement>>;
1287
1316
  export declare const FilterBar: React$1.ForwardRefExoticComponent<FilterBarProps & React$1.RefAttributes<HTMLDivElement>>;
@@ -1290,8 +1319,15 @@ export declare const Tooltip: React$1.ForwardRefExoticComponent<TooltipProps & R
1290
1319
  export declare const Dialog: React$1.ForwardRefExoticComponent<DialogProps & React$1.RefAttributes<HTMLDivElement>>;
1291
1320
  /** Side panel over the page: record detail, filters, mobile navigation. Modal (focus trap, scroll lock, focus restore). */
1292
1321
  export declare const Drawer: React$1.ForwardRefExoticComponent<DrawerProps & React$1.RefAttributes<HTMLDivElement>>;
1293
- /** Enterprise data table: 48px rows, hairline dividers, mono header band. */
1294
- export declare const DataTable: React$1.ForwardRefExoticComponent<DataTableProps & React$1.RefAttributes<HTMLDivElement>>;
1322
+ /** Enterprise data table: 48px rows, hairline dividers, mono header band. Generic over the row type (5.5): the
1323
+ * type of `rows` types every `render`, `sortValue`, `getRowHref` and `onRowActivate`. */
1324
+ export declare const DataTable: {
1325
+ (props: DataTableProps & {
1326
+ rows: readonly never[];
1327
+ } & React$1.RefAttributes<HTMLDivElement>): React$1.ReactElement | null;
1328
+ <Row extends Record<string, any> = Record<string, any>>(props: DataTableProps<Row> & React$1.RefAttributes<HTMLDivElement>): React$1.ReactElement | null;
1329
+ displayName?: string | undefined;
1330
+ };
1295
1331
  export declare const Card: React$1.ForwardRefExoticComponent<CardProps & React$1.RefAttributes<HTMLElement>>;
1296
1332
  export declare const Tabs: React$1.ForwardRefExoticComponent<TabsProps & React$1.RefAttributes<HTMLDivElement>>;
1297
1333
  /** Side navigation: sections of links or buttons, collapsible groups, counts and badges. Arrow keys move between items. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jirawatpyk/aura-tokens",
3
- "version": "5.4.0",
3
+ "version": "5.6.0",
4
4
  "description": "AURA Design System — tokens (CSS variables, Tailwind preset, Figma variables), theme contrast checks and token lint.",
5
5
  "main": "tailwind.tokens.cjs",
6
6
  "types": "components/index.d.ts",
package/tokens.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "5.4.0",
2
+ "version": "5.6.0",
3
3
  "meta": {
4
4
  "name": "AURA Design System",
5
5
  "tier": "3-tier",