@jirawatpyk/aura-react 4.13.0 → 4.15.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.
@@ -11,7 +11,7 @@ import {
11
11
  useMergedRef,
12
12
  useMounted
13
13
  } from "./internal.js";
14
- import { useStrings } from "./locale.js";
14
+ import { useDensity, useStrings } from "./locale.js";
15
15
  import { createPortal } from "react-dom";
16
16
  function position(anchor, pop, placement) {
17
17
  const r = anchor.getBoundingClientRect(), pw = pop.offsetWidth, ph = pop.offsetHeight, vw = window.innerWidth, vh = window.innerHeight, gap = 6;
@@ -24,6 +24,7 @@ function position(anchor, pop, placement) {
24
24
  }
25
25
  const Popover = React.forwardRef(function Popover2(props, ref) {
26
26
  const t = useStrings();
27
+ const density = useDensity();
27
28
  const auto = uid(), id = props.id || auto;
28
29
  const st = useMaybeControlled(props.open, !!props.defaultOpen, props.onOpenChange);
29
30
  const open = !!st[0];
@@ -81,6 +82,7 @@ const Popover = React.forwardRef(function Popover2(props, ref) {
81
82
  {
82
83
  ref: popMerged,
83
84
  id,
85
+ "data-density": density,
84
86
  role: "dialog",
85
87
  "aria-modal": false,
86
88
  "aria-label": props.title ? void 0 : props.label,
@@ -1,6 +1,8 @@
1
1
  'use client';
2
2
  import * as React from "react";
3
+ import { createPortal } from "react-dom";
3
4
  import { Icon } from "./Icon.js";
5
+ import { IconButton } from "./IconButton.js";
4
6
  import { cx, uid, useMaybeControlled } from "./internal.js";
5
7
  import { useLinkComponent, useStrings } from "./locale.js";
6
8
  function contains(it, id) {
@@ -18,6 +20,39 @@ const SideNav = React.forwardRef(function SideNav2(props, ref) {
18
20
  props.onChange
19
21
  );
20
22
  const active = st[0];
23
+ const col = useMaybeControlled(props.collapsed, !!props.defaultCollapsed, props.onCollapsedChange);
24
+ const collapsed = col[0];
25
+ const tipState = React.useState(null), tip = tipState[0], setTip = tipState[1];
26
+ React.useEffect(
27
+ function() {
28
+ if (!collapsed) setTip(null);
29
+ },
30
+ [collapsed]
31
+ );
32
+ React.useEffect(
33
+ function() {
34
+ if (!tip) return;
35
+ function esc(e) {
36
+ if (e.key === "Escape") setTip(null);
37
+ }
38
+ document.addEventListener("keydown", esc);
39
+ return function() {
40
+ document.removeEventListener("keydown", esc);
41
+ };
42
+ },
43
+ [tip]
44
+ );
45
+ function tipHandlers(label) {
46
+ if (!collapsed) return {};
47
+ function show(e) {
48
+ const r = e.currentTarget.getBoundingClientRect();
49
+ setTip({ text: label, top: r.top + r.height / 2, left: r.right + 8 });
50
+ }
51
+ function hide() {
52
+ setTip(null);
53
+ }
54
+ return { onMouseEnter: show, onFocus: show, onMouseLeave: hide, onBlur: hide };
55
+ }
21
56
  const sections = props.sections || [{ items: props.items || [] }];
22
57
  const openState = React.useState({}), toggled = openState[0], setToggled = openState[1];
23
58
  function isOpen(it) {
@@ -63,8 +98,10 @@ const SideNav = React.forwardRef(function SideNav2(props, ref) {
63
98
  visible[j].focus();
64
99
  }
65
100
  function inner(it, group, open) {
101
+ const marked = it.count != null && it.count > 0 || it.badge != null;
66
102
  return [
67
- it.icon ? /* @__PURE__ */ React.createElement(Icon, { key: "i", name: it.icon }) : null,
103
+ it.icon ? /* @__PURE__ */ React.createElement(Icon, { key: "i", name: it.icon }) : collapsed ? /* @__PURE__ */ React.createElement("span", { key: "i", className: "aura-nav__initial", "aria-hidden": "true" }, it.label.charAt(0)) : null,
104
+ collapsed && marked ? /* @__PURE__ */ React.createElement("span", { key: "d", className: "aura-nav__dot", "aria-hidden": "true" }) : null,
68
105
  /* @__PURE__ */ React.createElement("span", { key: "l", className: "aura-nav__label" }, it.label),
69
106
  it.badge != null ? /* @__PURE__ */ React.createElement("span", { key: "b", className: "aura-nav__badge" }, it.badge) : null,
70
107
  it.count != null ? /* @__PURE__ */ React.createElement("span", { key: "c", className: "aura-nav__count" }, it.count) : null,
@@ -73,7 +110,7 @@ const SideNav = React.forwardRef(function SideNav2(props, ref) {
73
110
  }
74
111
  function item(it, depth) {
75
112
  if (it.children) {
76
- const open = isOpen(it);
113
+ const open = !collapsed && isOpen(it);
77
114
  const listId = base + "-" + it.id;
78
115
  const holdsActive = contains(it, active);
79
116
  return /* @__PURE__ */ React.createElement("li", { key: it.id, className: "aura-nav__group" }, /* @__PURE__ */ React.createElement(
@@ -84,10 +121,15 @@ const SideNav = React.forwardRef(function SideNav2(props, ref) {
84
121
  "aria-expanded": open,
85
122
  "aria-controls": listId,
86
123
  style: depth ? { ["--aura-nav-depth"]: depth } : void 0,
124
+ ...tipHandlers(it.label),
87
125
  onClick: function() {
88
126
  const next = Object.assign({}, toggled);
89
127
  next[it.id] = !open;
90
128
  setToggled(next);
129
+ if (collapsed) {
130
+ setTip(null);
131
+ col[1](false);
132
+ }
91
133
  },
92
134
  onKeyDown: function(e) {
93
135
  if (e.key === "ArrowRight" && !open) {
@@ -106,6 +148,7 @@ const SideNav = React.forwardRef(function SideNav2(props, ref) {
106
148
  }
107
149
  const on = active === it.id;
108
150
  const common = {
151
+ ...tipHandlers(it.label),
109
152
  className: cx("aura-nav__item", on && "is-active"),
110
153
  "aria-current": on ? "page" : void 0,
111
154
  style: depth ? { ["--aura-nav-depth"]: depth } : void 0,
@@ -124,7 +167,8 @@ const SideNav = React.forwardRef(function SideNav2(props, ref) {
124
167
  if (typeof ref === "function") ref(el);
125
168
  else if (ref) ref.current = el;
126
169
  },
127
- className: cx("aura-nav", props.className),
170
+ className: cx("aura-nav", collapsed && "aura-nav--collapsed", props.className),
171
+ "data-collapsed": collapsed ? "" : void 0,
128
172
  "aria-label": props.label || t.mainNav,
129
173
  onKeyDown
130
174
  },
@@ -134,7 +178,31 @@ const SideNav = React.forwardRef(function SideNav2(props, ref) {
134
178
  return item(it, 0);
135
179
  })));
136
180
  })),
137
- props.footer ? /* @__PURE__ */ React.createElement("div", { className: "aura-nav__footer" }, props.footer) : null
181
+ props.footer ? /* @__PURE__ */ React.createElement("div", { className: "aura-nav__footer" }, props.footer) : null,
182
+ props.collapsible ? /* @__PURE__ */ React.createElement("div", { className: "aura-nav__toggle" }, /* @__PURE__ */ React.createElement(
183
+ IconButton,
184
+ {
185
+ icon: collapsed ? "panel-left-open" : "panel-left-close",
186
+ label: collapsed ? t.expandNav : t.collapseNav,
187
+ size: "md",
188
+ onClick: function() {
189
+ setTip(null);
190
+ col[1](!collapsed);
191
+ }
192
+ }
193
+ )) : null,
194
+ tip && typeof document !== "undefined" ? createPortal(
195
+ /* @__PURE__ */ React.createElement(
196
+ "div",
197
+ {
198
+ className: "aura-tooltip aura-tooltip--right",
199
+ "aria-hidden": "true",
200
+ style: { top: tip.top, left: tip.left }
201
+ },
202
+ tip.text
203
+ ),
204
+ document.body
205
+ ) : null
138
206
  );
139
207
  });
140
208
  export {
package/dist/esm/index.js CHANGED
@@ -34,7 +34,7 @@ import { Container } from "./Container.js";
34
34
  import { AppShell } from "./AppShell.js";
35
35
  import { useBreakpoint, useResponsive, breakpoints } from "./responsive.js";
36
36
  import { Surface } from "./Surface.js";
37
- import { AuraProvider, useAuraLocale, STRINGS } from "./locale.js";
37
+ import { AuraProvider, useAuraLocale, useDensity, STRINGS } from "./locale.js";
38
38
  import { Stat } from "./Stat.js";
39
39
  import { TimePicker, parseTime } from "./TimePicker.js";
40
40
  import { FileUpload, formatBytes } from "./FileUpload.js";
@@ -126,6 +126,7 @@ export {
126
126
  useAuraLocale,
127
127
  useBreakpoint,
128
128
  useColorScheme,
129
+ useDensity,
129
130
  useFormatDate,
130
131
  useResponsive
131
132
  };
@@ -29,6 +29,8 @@ const STRINGS = {
29
29
  breadcrumb: "Breadcrumb",
30
30
  navigation: "Navigation",
31
31
  openNav: "Open navigation",
32
+ collapseNav: "Collapse sidebar",
33
+ expandNav: "Expand sidebar",
32
34
  searching: "Searching\u2026",
33
35
  noMatches: "No matches",
34
36
  clear: function(what) {
@@ -140,6 +142,8 @@ const STRINGS = {
140
142
  breadcrumb: "\u0E40\u0E2A\u0E49\u0E19\u0E17\u0E32\u0E07",
141
143
  navigation: "\u0E40\u0E21\u0E19\u0E39",
142
144
  openNav: "\u0E40\u0E1B\u0E34\u0E14\u0E40\u0E21\u0E19\u0E39",
145
+ collapseNav: "\u0E22\u0E48\u0E2D\u0E41\u0E16\u0E1A\u0E40\u0E21\u0E19\u0E39",
146
+ expandNav: "\u0E02\u0E22\u0E32\u0E22\u0E41\u0E16\u0E1A\u0E40\u0E21\u0E19\u0E39",
143
147
  searching: "\u0E01\u0E33\u0E25\u0E31\u0E07\u0E04\u0E49\u0E19\u0E2B\u0E32\u2026",
144
148
  noMatches: "\u0E44\u0E21\u0E48\u0E1E\u0E1A\u0E23\u0E32\u0E22\u0E01\u0E32\u0E23\u0E17\u0E35\u0E48\u0E15\u0E23\u0E07\u0E01\u0E31\u0E19",
145
149
  clear: function(what) {
@@ -251,6 +255,8 @@ const STRINGS = {
251
255
  breadcrumb: "Br\xF6dsmulor",
252
256
  navigation: "Navigering",
253
257
  openNav: "\xD6ppna menyn",
258
+ collapseNav: "F\xE4ll ihop sidof\xE4ltet",
259
+ expandNav: "F\xE4ll ut sidof\xE4ltet",
254
260
  searching: "S\xF6ker\u2026",
255
261
  noMatches: "Inga tr\xE4ffar",
256
262
  clear: function(what) {
@@ -343,6 +349,8 @@ const STRINGS = {
343
349
  };
344
350
  const LocaleContext = React.createContext(null);
345
351
  function AuraProvider(props) {
352
+ const outer = React.useContext(LocaleContext);
353
+ const density = props.density || outer && outer.density || null;
346
354
  const value = React.useMemo(
347
355
  function() {
348
356
  const base = props.locale && STRINGS[props.locale] || STRINGS.en;
@@ -350,12 +358,13 @@ function AuraProvider(props) {
350
358
  locale: props.locale || "en",
351
359
  calendar: props.calendar || null,
352
360
  strings: props.strings ? Object.assign({}, base, props.strings) : base,
353
- linkComponent: props.linkComponent || null
361
+ linkComponent: props.linkComponent || null,
362
+ density
354
363
  };
355
364
  },
356
- [props.locale, props.calendar, props.strings, props.linkComponent]
365
+ [props.locale, props.calendar, props.strings, props.linkComponent, density]
357
366
  );
358
- return /* @__PURE__ */ React.createElement(LocaleContext.Provider, { value }, props.children);
367
+ return /* @__PURE__ */ React.createElement(LocaleContext.Provider, { value }, props.density ? /* @__PURE__ */ React.createElement("div", { className: "aura-density", "data-density": props.density }, props.children) : props.children);
359
368
  }
360
369
  function useAuraLocale() {
361
370
  return React.useContext(LocaleContext) || { locale: null, calendar: null, strings: STRINGS.en };
@@ -364,6 +373,10 @@ function useLinkComponent(own) {
364
373
  const ctx = React.useContext(LocaleContext);
365
374
  return own || ctx && ctx.linkComponent || "a";
366
375
  }
376
+ function useDensity() {
377
+ const ctx = React.useContext(LocaleContext);
378
+ return ctx && ctx.density || void 0;
379
+ }
367
380
  function useStrings() {
368
381
  return useAuraLocale().strings;
369
382
  }
@@ -371,6 +384,7 @@ export {
371
384
  AuraProvider,
372
385
  STRINGS,
373
386
  useAuraLocale,
387
+ useDensity,
374
388
  useLinkComponent,
375
389
  useStrings
376
390
  };
package/dist/index.d.ts CHANGED
@@ -149,13 +149,15 @@ export interface DataTableProps {
149
149
  /** Controlled pinned columns (keys); pair with onPinnedColumnsChange. */
150
150
  pinnedColumns?: string[];
151
151
  onPinnedColumnsChange?: (keys: string[]) => void;
152
+ /** `compact` = 40px rows (48px on touch screens). Default: the surrounding density. */
153
+ density?: "comfortable" | "compact";
152
154
  /** Container width in px below which rows render as stacked cards (phones). Try 640. Measured with ResizeObserver, so it follows the container, not the window. */
153
155
  stackBelow?: number;
154
156
  className?: string;
155
157
  }
156
158
  /** An AURA icon name, or any icon element (e.g. `<Building />` from lucide-react). AURA sizes it and hides it from screen readers. */
157
159
  export type IconInput = IconName | React$1.ReactElement;
158
- export type IconName = "check" | "x" | "plus" | "minus" | "search" | "chevron-down" | "chevron-up" | "chevron-left" | "chevron-right" | "arrow-right" | "arrow-up-right" | "arrow-up-down" | "loader-circle" | "circle-alert" | "circle-check" | "info" | "triangle-alert" | "settings" | "user" | "users" | "filter" | "ellipsis" | "external-link" | "copy" | "trash-2" | "pencil" | "download" | "upload" | "calendar" | "bell" | "menu" | "eye" | "log-out" | "circle" | "circle-dot-dashed" | "ban" | "arrow-up" | "arrow-down" | "inbox" | "pin" | "pin-off" | "eye-off" | "columns-3" | "arrow-left" | "rotate-ccw" | "house" | "layout-dashboard" | "folder" | "chart-column" | "file-text" | "mail" | "lock" | "clock" | "trending-up" | "trending-down" | "image" | "paperclip" | "cloud-upload" | "file" | "sun" | "moon" | "monitor";
160
+ export type IconName = "check" | "x" | "plus" | "minus" | "search" | "chevron-down" | "chevron-up" | "chevron-left" | "chevron-right" | "arrow-right" | "arrow-up-right" | "arrow-up-down" | "loader-circle" | "circle-alert" | "circle-check" | "info" | "triangle-alert" | "settings" | "user" | "users" | "filter" | "ellipsis" | "external-link" | "copy" | "trash-2" | "pencil" | "download" | "upload" | "calendar" | "bell" | "menu" | "eye" | "log-out" | "circle" | "circle-dot-dashed" | "ban" | "arrow-up" | "arrow-down" | "inbox" | "pin" | "pin-off" | "eye-off" | "columns-3" | "arrow-left" | "rotate-ccw" | "house" | "layout-dashboard" | "folder" | "chart-column" | "file-text" | "mail" | "lock" | "clock" | "trending-up" | "trending-down" | "image" | "paperclip" | "cloud-upload" | "file" | "sun" | "moon" | "monitor" | "panel-left-close" | "panel-left-open";
159
161
  /** Lucide stroke icon drawn inline in currentColor. */
160
162
  export interface IconProps {
161
163
  /** A name from the set, or an icon element of your own (sized and styled the same way). */
@@ -184,6 +186,8 @@ export interface AuraProviderProps {
184
186
  calendar?: "buddhist" | "gregory";
185
187
  /** Override individual strings. */
186
188
  strings?: Partial<Record<string, string | ((...args: any[]) => string)>>;
189
+ /** `compact`: 36px fields and buttons, 40px table rows — dense admin screens. Touch screens keep 44px. Default: comfortable (or an ancestor's `data-density`). */
190
+ density?: "comfortable" | "compact";
187
191
  /** Your router's link (e.g. `Link` from `next/link`), used by every AURA component that renders a link: Button with `href`, SideNav, Breadcrumb, Stat, Pagination, DataTable row links. It gets `href`, `className`, `aria-current`, the children and the ref. A component's own `linkComponent` wins. */
188
192
  linkComponent?: React$1.ElementType;
189
193
  children?: React$1.ReactNode;
@@ -525,6 +529,15 @@ export interface SideNavProps {
525
529
  label?: string;
526
530
  /** Router link for items with `href`; defaults to the AuraProvider's `linkComponent`, then `<a>`. */
527
531
  linkComponent?: React$1.ElementType;
532
+ /** Icon-only rail (`aura-sidenav-rail-width`, 64px). Labels show as tooltips and stay the accessible names; a group
533
+ * expands the rail when clicked. Controlled; pair with `onCollapsedChange`. */
534
+ collapsed?: boolean;
535
+ /** Starting state when `collapsed` isn't controlled. Default false. */
536
+ defaultCollapsed?: boolean;
537
+ /** Called by the toggle button, and by a group clicked in the rail (with `false`). Save it to keep the choice. */
538
+ onCollapsedChange?: (collapsed: boolean) => void;
539
+ /** Adds a collapse / expand button at the bottom. AppShell hides it in its phone drawer. */
540
+ collapsible?: boolean;
528
541
  className?: string;
529
542
  }
530
543
  export interface BreadcrumbProps {
@@ -1147,6 +1160,8 @@ export interface AuraStrings {
1147
1160
  breadcrumb: string;
1148
1161
  navigation: string;
1149
1162
  openNav: string;
1163
+ collapseNav: string;
1164
+ expandNav: string;
1150
1165
  searching: string;
1151
1166
  noMatches: string;
1152
1167
  clear: (what?: string) => string;
@@ -1216,11 +1231,15 @@ export interface AuraLocaleValue {
1216
1231
  strings: AuraStrings;
1217
1232
  /** The router link set on AuraProvider, if any. */
1218
1233
  linkComponent?: React$1.ElementType | null;
1234
+ /** The density set on the nearest AuraProvider that sets one (null: none, i.e. comfortable unless an ancestor's data-density says otherwise). */
1235
+ density?: "comfortable" | "compact" | null;
1219
1236
  }
1220
1237
  /** Sets the language of built-in labels (and the default date locale) for everything inside. */
1221
1238
  export declare function AuraProvider(props: AuraProviderProps): React$1.ReactElement;
1222
1239
  /** { locale, calendar, strings } from the nearest AuraProvider (English strings when there is none). */
1223
1240
  export declare function useAuraLocale(): AuraLocaleValue;
1241
+ /** The provider's density, for portals that render outside its wrapper. */
1242
+ export declare function useDensity(): "comfortable" | "compact" | undefined;
1224
1243
  export declare const Stat: React$1.ForwardRefExoticComponent<StatProps & React$1.RefAttributes<HTMLElement>>;
1225
1244
  /** Parse typed time: 9 · 09 · 930 · 0930 · 9:30 · 9.30 · 09.30 น. · 9:30 pm → "HH:mm" (24-hour) or null. */
1226
1245
  export declare function parseTime(text: string | null | undefined): string | null;
package/dist/styles.css CHANGED
@@ -7,7 +7,7 @@
7
7
  box-sizing: border-box;
8
8
  height: var(--aura-button-height);
9
9
  white-space: nowrap; flex-shrink: 0; /* a pill never wraps its label */
10
- padding: 0 var(--aura-space-6);
10
+ padding: 0 var(--aura-button-padding-x, var(--aura-space-6));
11
11
  border-radius: var(--aura-button-radius);
12
12
  border: 0;
13
13
  background: transparent;
@@ -74,14 +74,14 @@ a.aura-btn { text-decoration: none; }
74
74
  }
75
75
  .aura-table__scroll { position: relative; overflow: auto; overscroll-behavior: contain; }
76
76
  .aura-table__head {
77
- position: sticky; top: 0; z-index: 3;
78
- display: flex; height: var(--aura-space-12);
77
+ position: sticky; top: 0; z-index: 3; box-sizing: border-box; /* the hairline is inside the row height */
78
+ display: flex; height: var(--aura-table-row-height);
79
79
  background: var(--aura-bg-canvas);
80
80
  border-bottom: 1px solid var(--aura-border-default);
81
81
  font-family: var(--font-mono); font-size: 12px; color: var(--aura-fg-secondary);
82
82
  }
83
83
  .aura-table__row {
84
- display: flex; height: var(--aura-space-12);
84
+ box-sizing: border-box; display: flex; height: var(--aura-table-row-height); /* exactly the virtual row height */
85
85
  background: var(--aura-bg-surface);
86
86
  border-bottom: 1px solid var(--aura-border-default);
87
87
  font-size: 13px;
@@ -105,7 +105,7 @@ a.aura-btn { text-decoration: none; }
105
105
  /* Server mode refresh: the previous page stays, inactive, under a progress bar until the next one arrives */
106
106
  .aura-table.is-refreshing .aura-table__row, .aura-table.is-refreshing .aura-table__card { opacity: 0.6; pointer-events: none; }
107
107
  .aura-table__busy-bar {
108
- position: absolute; z-index: 6; top: calc(var(--aura-space-12) - 2px); left: 0; right: 0; height: 2px; overflow: hidden;
108
+ position: absolute; z-index: 6; top: calc(var(--aura-table-row-height) - 2px); left: 0; right: 0; height: 2px; overflow: hidden;
109
109
  border-radius: var(--aura-radius-full); pointer-events: none;
110
110
  }
111
111
  .aura-table--stacked .aura-table__busy-bar { top: 0; }
@@ -117,7 +117,7 @@ a.aura-btn { text-decoration: none; }
117
117
 
118
118
  /* Cells: text cells stay block so long values end in an ellipsis */
119
119
  .aura-table__td {
120
- box-sizing: border-box; height: 100%; line-height: calc(var(--aura-space-12) - 1px);
120
+ box-sizing: border-box; height: 100%; line-height: calc(var(--aura-table-row-height) - 1px);
121
121
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; padding-right: var(--aura-space-4);
122
122
  }
123
123
  .aura-table__td .aura-pill, .aura-table__td .aura-dropdown, .aura-table__td > .aura-icon-btn, .aura-table__td > .aura-btn { vertical-align: middle; }
@@ -130,7 +130,7 @@ a.aura-btn { text-decoration: none; }
130
130
  .aura-table__gutter { flex: none; width: var(--aura-space-6); position: sticky; left: 0; z-index: 2; background: inherit; }
131
131
  .aura-table__gutter--end { position: static; }
132
132
  .aura-table__picker {
133
- position: absolute; top: 0; right: 0; z-index: 5; width: var(--aura-space-12); height: calc(var(--aura-space-12) - 1px);
133
+ position: absolute; top: 0; right: 0; z-index: 5; width: var(--aura-space-12); height: calc(var(--aura-table-row-height) - 1px);
134
134
  display: flex; align-items: center; justify-content: center; background: var(--aura-bg-canvas); border-top-right-radius: calc(var(--aura-card-radius) - 1px);
135
135
  }
136
136
  .aura-table__gutter--end.has-picker {
@@ -228,7 +228,7 @@ a.aura-btn { text-decoration: none; }
228
228
 
229
229
  /* Footer + pager */
230
230
  .aura-table__foot {
231
- height: var(--aura-space-12); display: flex; align-items: center; justify-content: space-between;
231
+ height: var(--aura-table-row-height); display: flex; align-items: center; justify-content: space-between;
232
232
  padding: 0 var(--aura-space-3) 0 var(--aura-space-6);
233
233
  border-top: 1px solid var(--aura-border-default); background: var(--aura-bg-surface);
234
234
  font-size: 13px; color: var(--aura-fg-secondary); font-variant-numeric: tabular-nums;
@@ -596,6 +596,30 @@ a.aura-btn { text-decoration: none; }
596
596
  .aura-nav__sub { margin-top: 2px; }
597
597
  .aura-nav__sub[hidden] { display: none; }
598
598
  .aura-nav__footer { padding: var(--aura-space-3); border-top: 1px solid var(--aura-border-default); }
599
+ /* Collapse toggle and the icon-only rail (4.15). */
600
+ .aura-nav { transition: width var(--aura-duration-base) var(--aura-ease); }
601
+ .aura-nav__toggle { display: flex; padding: var(--aura-space-2) var(--aura-space-3); border-top: 1px solid var(--aura-border-default); }
602
+ .aura-nav--collapsed { width: var(--aura-sidenav-rail-width); }
603
+ .aura-nav--collapsed .aura-nav__toggle { justify-content: center; }
604
+ .aura-nav--collapsed .aura-nav__header, .aura-nav--collapsed .aura-nav__footer { padding-left: var(--aura-space-2); padding-right: var(--aura-space-2); overflow: hidden; }
605
+ .aura-nav--collapsed .aura-nav__item { position: relative; justify-content: center; gap: 0; padding: 0; }
606
+ .aura-nav--collapsed .aura-nav__label, .aura-nav--collapsed .aura-nav__count, .aura-nav--collapsed .aura-nav__badge, .aura-nav--collapsed .aura-nav__title {
607
+ position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
608
+ }
609
+ .aura-nav--collapsed .aura-nav__chevron { display: none; }
610
+ .aura-nav--collapsed .aura-nav__section + .aura-nav__section { margin-top: var(--aura-space-3); padding-top: var(--aura-space-3); border-top: 1px solid var(--aura-border-default); }
611
+ .aura-nav--collapsed .aura-nav__item--group.has-active { background: var(--aura-bg-selected); }
612
+ .aura-nav--collapsed .aura-nav__item--group.has-active .aura-icon { color: var(--aura-fg-accent); }
613
+ .aura-nav__initial {
614
+ display: inline-flex; align-items: center; justify-content: center; width: 24px; height: 24px; border-radius: var(--aura-radius-sm);
615
+ background: var(--aura-status-neutral-bg); color: var(--aura-status-neutral-fg); font-size: 12px; font-weight: 600; text-transform: uppercase;
616
+ }
617
+ .aura-nav__dot {
618
+ position: absolute; top: 6px; left: calc(50% + 5px); width: 8px; height: 8px; border-radius: var(--aura-radius-full);
619
+ background: var(--aura-fg-accent); box-shadow: 0 0 0 2px var(--aura-bg-canvas);
620
+ }
621
+ .aura-tooltip--right { transform: translateY(-50%); white-space: nowrap; }
622
+ @media (prefers-reduced-motion: reduce) { .aura-nav { transition: none; } }
599
623
 
600
624
  /* ---------- Breadcrumb ---------- */
601
625
  .aura-crumbs ol { display: flex; flex-wrap: wrap; align-items: center; gap: var(--aura-space-1) var(--aura-space-2); list-style: none; margin: 0; padding: 0; font-family: var(--font-sans); font-size: 13px; }
@@ -1241,3 +1265,19 @@ button.aura-stepper__step:hover .aura-stepper__label { text-decoration: underlin
1241
1265
  }
1242
1266
  @media (prefers-reduced-motion: reduce) { .aura-command { animation: none; } }
1243
1267
 
1268
+ /* Density. data-density="compact" on <html> or any container (AuraProvider density="compact" and DataTable
1269
+ * density do it for you): 36px fields and buttons, 40px table rows, less button padding — for dense admin screens.
1270
+ * Touch screens keep 44px targets whatever the density. "comfortable" inside compact switches back. */
1271
+ [data-density='compact'] {
1272
+ --aura-input-height: 36px; --aura-button-height: 36px; --aura-table-row-height: 40px;
1273
+ --aura-button-padding-x: var(--aura-space-4);
1274
+ }
1275
+ [data-density='comfortable'] {
1276
+ --aura-input-height: 44px; --aura-button-height: 44px; --aura-table-row-height: 48px;
1277
+ --aura-button-padding-x: var(--aura-space-6);
1278
+ }
1279
+ @media (pointer: coarse) {
1280
+ [data-density='compact'] { --aura-input-height: 44px; --aura-button-height: 44px; --aura-table-row-height: 48px; --aura-button-padding-x: var(--aura-space-6); }
1281
+ }
1282
+ .aura-density { display: contents; }
1283
+
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jirawatpyk/aura-react",
3
- "version": "4.13.0",
3
+ "version": "4.15.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",