@jirawatpyk/aura-react 5.44.0 → 5.46.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,25 @@ const PageHeader = React.forwardRef(function PageHeader2(props, ref) {
11
11
  const level = props.headingLevel || 1;
12
12
  const below = props.actionsFullWidthBelow;
13
13
  const hide = props.titleHideBelow;
14
- const hasMeta = props.meta != null && props.meta !== false;
14
+ const shown = function(n) {
15
+ return n != null && n !== false && n !== "";
16
+ };
17
+ const hasMeta = shown(props.meta) || shown(props.subtitle) || shown(props.titleAddon);
18
+ const size = props.size === "hero" || props.size === "hero-lg" ? props.size : null;
19
+ const eyebrow = props.eyebrow;
20
+ const plainEyebrow = typeof eyebrow === "string" || typeof eyebrow === "number";
21
+ const heading = h(
22
+ "h" + level,
23
+ {
24
+ className: "aura-page-header__title",
25
+ id: props.titleId,
26
+ tabIndex: props.focusTitleOnMount ? -1 : void 0,
27
+ "data-aura-focus-on-mount": props.focusTitleOnMount ? "" : void 0
28
+ },
29
+ props.title
30
+ );
31
+ const hasActions = props.actions != null && props.actions !== false;
32
+ const bar = hasActions && (props.actionsBarBelow === "sm" || props.actionsBarBelow === "md") ? props.actionsBarBelow : null;
15
33
  return h(
16
34
  "div",
17
35
  Object.assign(attrs, {
@@ -21,6 +39,9 @@ const PageHeader = React.forwardRef(function PageHeader2(props, ref) {
21
39
  (below === "sm" || below === "md" || below === "lg") && "aura-page-header--actions-full-below-" + below,
22
40
  (hide === "sm" || hide === "md" || hide === "lg") && "aura-page-header--title-hide-below-" + hide,
23
41
  (hide === "sm" || hide === "md" || hide === "lg") && !hasMeta && "has-no-meta",
42
+ bar && "aura-page-header--actions-bar-below-" + bar,
43
+ size && "aura-page-header--" + size,
44
+ props.actionsAlign === "start" && "aura-page-header--actions-start",
24
45
  props.className
25
46
  ),
26
47
  style: props.style
@@ -29,11 +50,22 @@ const PageHeader = React.forwardRef(function PageHeader2(props, ref) {
29
50
  "div",
30
51
  { className: "aura-page-header__text" },
31
52
  props.breadcrumb != null && props.breadcrumb !== false ? h("div", { className: "aura-page-header__breadcrumb" }, props.breadcrumb) : null,
32
- props.eyebrow != null && props.eyebrow !== false ? h("p", { className: "aura-page-header__eyebrow" }, props.eyebrow) : null,
33
- h("h" + level, { className: "aura-page-header__title", id: props.titleId }, props.title),
53
+ shown(eyebrow) ? plainEyebrow ? h("p", { className: "aura-page-header__eyebrow" }, eyebrow) : h("div", { className: "aura-page-header__eyebrow aura-page-header__eyebrow--rich" }, eyebrow) : null,
54
+ shown(props.titleAddon) ? h(
55
+ "div",
56
+ { className: "aura-page-header__title-row" },
57
+ heading,
58
+ h("div", { className: "aura-page-header__title-addon" }, props.titleAddon)
59
+ ) : heading,
60
+ shown(props.subtitle) ? h("div", { className: "aura-page-header__subtitle" }, props.subtitle) : null,
34
61
  props.meta != null && props.meta !== false ? h("div", { className: "aura-page-header__meta" }, props.meta) : null
35
62
  ),
36
- props.actions != null && props.actions !== false ? h("div", { className: "aura-page-header__actions" }, props.actions) : null
63
+ props.actions != null && props.actions !== false ? h(
64
+ "div",
65
+ { className: "aura-page-header__actions" },
66
+ bar && props.actionsStatus != null && props.actionsStatus !== false ? h("div", { className: "aura-page-header__status", role: "status" }, props.actionsStatus) : null,
67
+ props.actions
68
+ ) : null
37
69
  );
38
70
  });
39
71
  export {
@@ -0,0 +1,68 @@
1
+ 'use client';
2
+ import * as React from "react";
3
+ import { PageHeader as StaticPageHeader } from "./PageHeader.js";
4
+ import { devWarnOnce, useMergedRef } from "./internal.js";
5
+ const NAME = "--aura-page-actions-height";
6
+ const bars = [];
7
+ function trapped(el) {
8
+ for (let p = el.parentElement; p && p !== document.documentElement; p = p.parentElement) {
9
+ const s = getComputedStyle(p);
10
+ if (s.transform !== "none" || s.filter !== "none" || (s.backdropFilter || "none") !== "none" || s.contentVisibility === "auto" || s.perspective !== "none" || /transform|filter|perspective/.test(s.willChange) || /paint|layout|strict|content/.test(s.contain))
11
+ return true;
12
+ }
13
+ return false;
14
+ }
15
+ const PageHeader = React.forwardRef(function PageHeader2(props, ref) {
16
+ const own = React.useRef(null);
17
+ const merged = useMergedRef(ref, own);
18
+ const bar = props.actionsBarBelow;
19
+ const hasActions = props.actions != null && props.actions !== false;
20
+ React.useEffect(
21
+ function() {
22
+ if (bar !== "sm" && bar !== "md" || !hasActions || !own.current) return;
23
+ const el = own.current.querySelector(":scope > .aura-page-header__actions");
24
+ if (!el) return;
25
+ const root = document.documentElement;
26
+ let checked = false;
27
+ function sync() {
28
+ if (bars[bars.length - 1] !== sync) return;
29
+ if (getComputedStyle(el).position === "fixed") {
30
+ root.style.setProperty(NAME, el.offsetHeight + "px");
31
+ if (!checked && (checked = true, trapped(el)))
32
+ devWarnOnce(
33
+ "pageheader-bar-trapped",
34
+ "PageHeader actionsBarBelow: an ancestor has a transform, filter, will-change or paint containment, so the fixed bar is placed against it instead of the screen. Remove it from the page header's ancestors."
35
+ );
36
+ } else root.style.removeProperty(NAME);
37
+ }
38
+ if (bars.length)
39
+ devWarnOnce(
40
+ "pageheader-bar-twice",
41
+ "PageHeader actionsBarBelow: two bars on one page overlap at the bottom of the screen. Give it to one PageHeader per page."
42
+ );
43
+ bars.push(sync);
44
+ const ro = typeof ResizeObserver === "undefined" ? null : new ResizeObserver(sync);
45
+ if (ro) ro.observe(el);
46
+ window.addEventListener("resize", sync);
47
+ sync();
48
+ return function() {
49
+ if (ro) ro.disconnect();
50
+ window.removeEventListener("resize", sync);
51
+ bars.splice(bars.indexOf(sync), 1);
52
+ if (bars.length) bars[bars.length - 1]();
53
+ else root.style.removeProperty(NAME);
54
+ };
55
+ },
56
+ [bar, hasActions]
57
+ );
58
+ const focusTitle = !!props.focusTitleOnMount;
59
+ React.useEffect(function() {
60
+ if (!focusTitle || !own.current) return;
61
+ const t = own.current.querySelector(".aura-page-header__title");
62
+ if (t) t.focus({ preventScroll: true });
63
+ }, []);
64
+ return /* @__PURE__ */ React.createElement(StaticPageHeader, { ...props, ref: merged });
65
+ });
66
+ export {
67
+ PageHeader
68
+ };
package/dist/esm/index.js CHANGED
@@ -35,7 +35,7 @@ import { Avatar } from "./Avatar.js";
35
35
  import { Stack } from "./Stack.js";
36
36
  import { Grid } from "./Grid.js";
37
37
  import { Container } from "./Container.js";
38
- import { PageHeader } from "./PageHeader.js";
38
+ import { PageHeader } from "./PageHeaderClient.js";
39
39
  import { StatusTile } from "./StatusTile.js";
40
40
  import { TileGrid, Sparkline } from "./tiles.js";
41
41
  import { AppShell } from "./AppShell.js";
package/dist/index.d.cts CHANGED
@@ -1932,7 +1932,8 @@ export declare const Container: React$1.ForwardRefExoticComponent<ContainerProps
1932
1932
  * `/server`. It renders a plain `div`: to name a region by its title, put `aria-labelledby={titleId}` on your own
1933
1933
  * `<section>`, or pass `role="region"` with it. */
1934
1934
  export interface PageHeaderProps extends Pick<React$1.HTMLAttributes<HTMLElement>, "id" | "lang" | "dir" | "role">, React$1.AriaAttributes, DataAttributes {
1935
- /** Small mono line above the title — a section or breadcrumb-like context ("Monitor / Sites"). */
1935
+ /** Small mono line above the title — a section or breadcrumb-like context ("Monitor / Sites"). 5.46 (Chamber-OS
1936
+ * 151): anything but text (chips: a status, a member number, badges) is a wrapping row in a `div`, 8px apart. */
1936
1937
  eyebrow?: React$1.ReactNode | undefined;
1937
1938
  /** 5.33 (DxT Monitor #7): a `<Breadcrumb>` above the title, 4px from it like the eyebrow, in its own `div` (a `nav`
1938
1939
  * can't sit in the eyebrow's `p`) and its own 13px sans. Detail pages show a breadcrumb or an eyebrow; with both,
@@ -1940,6 +1941,23 @@ export interface PageHeaderProps extends Pick<React$1.HTMLAttributes<HTMLElement
1940
1941
  breadcrumb?: React$1.ReactNode | undefined;
1941
1942
  /** The page's heading. */
1942
1943
  title: React$1.ReactNode;
1944
+ /** 5.46 (Chamber-OS 151): a sentence under the title at body size (14px/1.7, secondary colour) — "Members, plans
1945
+ * and renewals for SweCham" — before `meta`. A `div`, so a loading skeleton's blocks are valid inside. */
1946
+ subtitle?: React$1.ReactNode | undefined;
1947
+ /** 5.46: a chip or badge on the title's line (a role, an invoice's status, "Archived"). Title and addon share a
1948
+ * wrapping row, 8px apart vertically and 12px across; on a narrow screen the addon wraps under the title. */
1949
+ titleAddon?: React$1.ReactNode | undefined;
1950
+ /** 5.46: the title's size. `default` 28px below 640px, 36px from 640px; `hero` 30px on a 36px line with 16px more
1951
+ * room above; `hero-lg` 26px below 1024px and 36px from 1024px, with the same room. Per page, the custom
1952
+ * properties `--aura-page-header-title-size` and `--aura-page-header-title-line-height` override any of them. */
1953
+ size?: "default" | "hero" | "hero-lg" | undefined;
1954
+ /** 5.46: focus the heading when the header mounts (`tabIndex={-1}`, `preventScroll`, no visible ring) — a page
1955
+ * reached by a server redirect (renewal done, back from a payment page), so a screen reader starts there (WCAG
1956
+ * 2.4.3). The main entry's PageHeader does it; from `/server` the heading is focusable but not focused. */
1957
+ focusTitleOnMount?: boolean | undefined;
1958
+ /** 5.46: `end` (default) lines the actions up with the bottom of the text; `start` with its top (the title, when
1959
+ * there is no breadcrumb or eyebrow). */
1960
+ actionsAlign?: "end" | "start" | undefined;
1943
1961
  /** A line under the title — counts, last update, owner. */
1944
1962
  meta?: React$1.ReactNode | undefined;
1945
1963
  /** Buttons for the page, bottom-aligned at the end; they wrap below the text on a narrow screen. */
@@ -1953,6 +1971,19 @@ export interface PageHeaderProps extends Pick<React$1.HTMLAttributes<HTMLElement
1953
1971
  * readers — and the breadcrumb and eyebrow are hidden, for phone layouts that show the title in the top bar. The
1954
1972
  * meta line and actions stay; with no meta, the actions start the header. */
1955
1973
  titleHideBelow?: "sm" | "md" | "lg" | undefined;
1974
+ /** 5.45 (Chamber-OS 150): below this breakpoint (`sm` 640px, `md` 768px) the same `actions` — rendered once, so a
1975
+ * dialog trigger or test id is never doubled — become a bar fixed to the bottom of the screen with ActionBar's look:
1976
+ * above a BottomNav, clear of the home indicator, `actionsStatus` as its status line. The page's end and a focused
1977
+ * field scroll clear of it (AppShell's content, or the body, gains the bar's height). With an IconButton among them
1978
+ * the actions share one row, labels wrapping inside the buttons (as `actionsFullWidthBelow`, Chamber-OS 149); with only icon
1979
+ * buttons the status and the icons share one row. From the breakpoint up nothing changes. Use it for a detail page's
1980
+ * own actions (Issue credit note…, ⋯) — one per page. The bar is `position: fixed`: no ancestor of the header may
1981
+ * have a transform, filter, backdrop-filter, will-change, paint/layout containment or `content-visibility: auto`, or it is placed
1982
+ * against that ancestor instead of the screen — the main entry warns in development. */
1983
+ actionsBarBelow?: "sm" | "md" | undefined;
1984
+ /** 5.45: the bar's status line ("Total 10,700.00 THB incl. VAT · due 22 Oct 2026"), shown only while the actions are
1985
+ * a bar, in a polite live region. */
1986
+ actionsStatus?: React$1.ReactNode | undefined;
1956
1987
  /** Default 1. Use 2 when the page already has an h1 (e.g. in the AppShell bar). */
1957
1988
  headingLevel?: 1 | 2 | 3 | undefined;
1958
1989
  /** The heading's id, for `aria-labelledby` on the page's region. */
package/dist/index.d.ts CHANGED
@@ -1932,7 +1932,8 @@ export declare const Container: React$1.ForwardRefExoticComponent<ContainerProps
1932
1932
  * `/server`. It renders a plain `div`: to name a region by its title, put `aria-labelledby={titleId}` on your own
1933
1933
  * `<section>`, or pass `role="region"` with it. */
1934
1934
  export interface PageHeaderProps extends Pick<React$1.HTMLAttributes<HTMLElement>, "id" | "lang" | "dir" | "role">, React$1.AriaAttributes, DataAttributes {
1935
- /** Small mono line above the title — a section or breadcrumb-like context ("Monitor / Sites"). */
1935
+ /** Small mono line above the title — a section or breadcrumb-like context ("Monitor / Sites"). 5.46 (Chamber-OS
1936
+ * 151): anything but text (chips: a status, a member number, badges) is a wrapping row in a `div`, 8px apart. */
1936
1937
  eyebrow?: React$1.ReactNode | undefined;
1937
1938
  /** 5.33 (DxT Monitor #7): a `<Breadcrumb>` above the title, 4px from it like the eyebrow, in its own `div` (a `nav`
1938
1939
  * can't sit in the eyebrow's `p`) and its own 13px sans. Detail pages show a breadcrumb or an eyebrow; with both,
@@ -1940,6 +1941,23 @@ export interface PageHeaderProps extends Pick<React$1.HTMLAttributes<HTMLElement
1940
1941
  breadcrumb?: React$1.ReactNode | undefined;
1941
1942
  /** The page's heading. */
1942
1943
  title: React$1.ReactNode;
1944
+ /** 5.46 (Chamber-OS 151): a sentence under the title at body size (14px/1.7, secondary colour) — "Members, plans
1945
+ * and renewals for SweCham" — before `meta`. A `div`, so a loading skeleton's blocks are valid inside. */
1946
+ subtitle?: React$1.ReactNode | undefined;
1947
+ /** 5.46: a chip or badge on the title's line (a role, an invoice's status, "Archived"). Title and addon share a
1948
+ * wrapping row, 8px apart vertically and 12px across; on a narrow screen the addon wraps under the title. */
1949
+ titleAddon?: React$1.ReactNode | undefined;
1950
+ /** 5.46: the title's size. `default` 28px below 640px, 36px from 640px; `hero` 30px on a 36px line with 16px more
1951
+ * room above; `hero-lg` 26px below 1024px and 36px from 1024px, with the same room. Per page, the custom
1952
+ * properties `--aura-page-header-title-size` and `--aura-page-header-title-line-height` override any of them. */
1953
+ size?: "default" | "hero" | "hero-lg" | undefined;
1954
+ /** 5.46: focus the heading when the header mounts (`tabIndex={-1}`, `preventScroll`, no visible ring) — a page
1955
+ * reached by a server redirect (renewal done, back from a payment page), so a screen reader starts there (WCAG
1956
+ * 2.4.3). The main entry's PageHeader does it; from `/server` the heading is focusable but not focused. */
1957
+ focusTitleOnMount?: boolean | undefined;
1958
+ /** 5.46: `end` (default) lines the actions up with the bottom of the text; `start` with its top (the title, when
1959
+ * there is no breadcrumb or eyebrow). */
1960
+ actionsAlign?: "end" | "start" | undefined;
1943
1961
  /** A line under the title — counts, last update, owner. */
1944
1962
  meta?: React$1.ReactNode | undefined;
1945
1963
  /** Buttons for the page, bottom-aligned at the end; they wrap below the text on a narrow screen. */
@@ -1953,6 +1971,19 @@ export interface PageHeaderProps extends Pick<React$1.HTMLAttributes<HTMLElement
1953
1971
  * readers — and the breadcrumb and eyebrow are hidden, for phone layouts that show the title in the top bar. The
1954
1972
  * meta line and actions stay; with no meta, the actions start the header. */
1955
1973
  titleHideBelow?: "sm" | "md" | "lg" | undefined;
1974
+ /** 5.45 (Chamber-OS 150): below this breakpoint (`sm` 640px, `md` 768px) the same `actions` — rendered once, so a
1975
+ * dialog trigger or test id is never doubled — become a bar fixed to the bottom of the screen with ActionBar's look:
1976
+ * above a BottomNav, clear of the home indicator, `actionsStatus` as its status line. The page's end and a focused
1977
+ * field scroll clear of it (AppShell's content, or the body, gains the bar's height). With an IconButton among them
1978
+ * the actions share one row, labels wrapping inside the buttons (as `actionsFullWidthBelow`, Chamber-OS 149); with only icon
1979
+ * buttons the status and the icons share one row. From the breakpoint up nothing changes. Use it for a detail page's
1980
+ * own actions (Issue credit note…, ⋯) — one per page. The bar is `position: fixed`: no ancestor of the header may
1981
+ * have a transform, filter, backdrop-filter, will-change, paint/layout containment or `content-visibility: auto`, or it is placed
1982
+ * against that ancestor instead of the screen — the main entry warns in development. */
1983
+ actionsBarBelow?: "sm" | "md" | undefined;
1984
+ /** 5.45: the bar's status line ("Total 10,700.00 THB incl. VAT · due 22 Oct 2026"), shown only while the actions are
1985
+ * a bar, in a polite live region. */
1986
+ actionsStatus?: React$1.ReactNode | undefined;
1956
1987
  /** Default 1. Use 2 when the page already has an h1 (e.g. in the AppShell bar). */
1957
1988
  headingLevel?: 1 | 2 | 3 | undefined;
1958
1989
  /** The heading's id, for `aria-labelledby` on the page's region. */
@@ -1442,7 +1442,25 @@ var PageHeader = React5.forwardRef(function PageHeader2(props, ref) {
1442
1442
  const level = props.headingLevel || 1;
1443
1443
  const below = props.actionsFullWidthBelow;
1444
1444
  const hide = props.titleHideBelow;
1445
- const hasMeta = props.meta != null && props.meta !== false;
1445
+ const shown = function(n3) {
1446
+ return n3 != null && n3 !== false && n3 !== "";
1447
+ };
1448
+ const hasMeta = shown(props.meta) || shown(props.subtitle) || shown(props.titleAddon);
1449
+ const size = props.size === "hero" || props.size === "hero-lg" ? props.size : null;
1450
+ const eyebrow = props.eyebrow;
1451
+ const plainEyebrow = typeof eyebrow === "string" || typeof eyebrow === "number";
1452
+ const heading = h4(
1453
+ "h" + level,
1454
+ {
1455
+ className: "aura-page-header__title",
1456
+ id: props.titleId,
1457
+ tabIndex: props.focusTitleOnMount ? -1 : void 0,
1458
+ "data-aura-focus-on-mount": props.focusTitleOnMount ? "" : void 0
1459
+ },
1460
+ props.title
1461
+ );
1462
+ const hasActions = props.actions != null && props.actions !== false;
1463
+ const bar = hasActions && (props.actionsBarBelow === "sm" || props.actionsBarBelow === "md") ? props.actionsBarBelow : null;
1446
1464
  return h4(
1447
1465
  "div",
1448
1466
  Object.assign(attrs2, {
@@ -1452,6 +1470,9 @@ var PageHeader = React5.forwardRef(function PageHeader2(props, ref) {
1452
1470
  (below === "sm" || below === "md" || below === "lg") && "aura-page-header--actions-full-below-" + below,
1453
1471
  (hide === "sm" || hide === "md" || hide === "lg") && "aura-page-header--title-hide-below-" + hide,
1454
1472
  (hide === "sm" || hide === "md" || hide === "lg") && !hasMeta && "has-no-meta",
1473
+ bar && "aura-page-header--actions-bar-below-" + bar,
1474
+ size && "aura-page-header--" + size,
1475
+ props.actionsAlign === "start" && "aura-page-header--actions-start",
1455
1476
  props.className
1456
1477
  ),
1457
1478
  style: props.style
@@ -1460,11 +1481,22 @@ var PageHeader = React5.forwardRef(function PageHeader2(props, ref) {
1460
1481
  "div",
1461
1482
  { className: "aura-page-header__text" },
1462
1483
  props.breadcrumb != null && props.breadcrumb !== false ? h4("div", { className: "aura-page-header__breadcrumb" }, props.breadcrumb) : null,
1463
- props.eyebrow != null && props.eyebrow !== false ? h4("p", { className: "aura-page-header__eyebrow" }, props.eyebrow) : null,
1464
- h4("h" + level, { className: "aura-page-header__title", id: props.titleId }, props.title),
1484
+ shown(eyebrow) ? plainEyebrow ? h4("p", { className: "aura-page-header__eyebrow" }, eyebrow) : h4("div", { className: "aura-page-header__eyebrow aura-page-header__eyebrow--rich" }, eyebrow) : null,
1485
+ shown(props.titleAddon) ? h4(
1486
+ "div",
1487
+ { className: "aura-page-header__title-row" },
1488
+ heading,
1489
+ h4("div", { className: "aura-page-header__title-addon" }, props.titleAddon)
1490
+ ) : heading,
1491
+ shown(props.subtitle) ? h4("div", { className: "aura-page-header__subtitle" }, props.subtitle) : null,
1465
1492
  props.meta != null && props.meta !== false ? h4("div", { className: "aura-page-header__meta" }, props.meta) : null
1466
1493
  ),
1467
- props.actions != null && props.actions !== false ? h4("div", { className: "aura-page-header__actions" }, props.actions) : null
1494
+ props.actions != null && props.actions !== false ? h4(
1495
+ "div",
1496
+ { className: "aura-page-header__actions" },
1497
+ bar && props.actionsStatus != null && props.actionsStatus !== false ? h4("div", { className: "aura-page-header__status", role: "status" }, props.actionsStatus) : null,
1498
+ props.actions
1499
+ ) : null
1468
1500
  );
1469
1501
  });
1470
1502
 
@@ -490,7 +490,8 @@ export declare const Container: React$1.ForwardRefExoticComponent<ContainerProps
490
490
  * `/server`. It renders a plain `div`: to name a region by its title, put `aria-labelledby={titleId}` on your own
491
491
  * `<section>`, or pass `role="region"` with it. */
492
492
  export interface PageHeaderProps extends Pick<React$1.HTMLAttributes<HTMLElement>, "id" | "lang" | "dir" | "role">, React$1.AriaAttributes, DataAttributes {
493
- /** Small mono line above the title — a section or breadcrumb-like context ("Monitor / Sites"). */
493
+ /** Small mono line above the title — a section or breadcrumb-like context ("Monitor / Sites"). 5.46 (Chamber-OS
494
+ * 151): anything but text (chips: a status, a member number, badges) is a wrapping row in a `div`, 8px apart. */
494
495
  eyebrow?: React$1.ReactNode | undefined;
495
496
  /** 5.33 (DxT Monitor #7): a `<Breadcrumb>` above the title, 4px from it like the eyebrow, in its own `div` (a `nav`
496
497
  * can't sit in the eyebrow's `p`) and its own 13px sans. Detail pages show a breadcrumb or an eyebrow; with both,
@@ -498,6 +499,23 @@ export interface PageHeaderProps extends Pick<React$1.HTMLAttributes<HTMLElement
498
499
  breadcrumb?: React$1.ReactNode | undefined;
499
500
  /** The page's heading. */
500
501
  title: React$1.ReactNode;
502
+ /** 5.46 (Chamber-OS 151): a sentence under the title at body size (14px/1.7, secondary colour) — "Members, plans
503
+ * and renewals for SweCham" — before `meta`. A `div`, so a loading skeleton's blocks are valid inside. */
504
+ subtitle?: React$1.ReactNode | undefined;
505
+ /** 5.46: a chip or badge on the title's line (a role, an invoice's status, "Archived"). Title and addon share a
506
+ * wrapping row, 8px apart vertically and 12px across; on a narrow screen the addon wraps under the title. */
507
+ titleAddon?: React$1.ReactNode | undefined;
508
+ /** 5.46: the title's size. `default` 28px below 640px, 36px from 640px; `hero` 30px on a 36px line with 16px more
509
+ * room above; `hero-lg` 26px below 1024px and 36px from 1024px, with the same room. Per page, the custom
510
+ * properties `--aura-page-header-title-size` and `--aura-page-header-title-line-height` override any of them. */
511
+ size?: "default" | "hero" | "hero-lg" | undefined;
512
+ /** 5.46: focus the heading when the header mounts (`tabIndex={-1}`, `preventScroll`, no visible ring) — a page
513
+ * reached by a server redirect (renewal done, back from a payment page), so a screen reader starts there (WCAG
514
+ * 2.4.3). The main entry's PageHeader does it; from `/server` the heading is focusable but not focused. */
515
+ focusTitleOnMount?: boolean | undefined;
516
+ /** 5.46: `end` (default) lines the actions up with the bottom of the text; `start` with its top (the title, when
517
+ * there is no breadcrumb or eyebrow). */
518
+ actionsAlign?: "end" | "start" | undefined;
501
519
  /** A line under the title — counts, last update, owner. */
502
520
  meta?: React$1.ReactNode | undefined;
503
521
  /** Buttons for the page, bottom-aligned at the end; they wrap below the text on a narrow screen. */
@@ -511,6 +529,19 @@ export interface PageHeaderProps extends Pick<React$1.HTMLAttributes<HTMLElement
511
529
  * readers — and the breadcrumb and eyebrow are hidden, for phone layouts that show the title in the top bar. The
512
530
  * meta line and actions stay; with no meta, the actions start the header. */
513
531
  titleHideBelow?: "sm" | "md" | "lg" | undefined;
532
+ /** 5.45 (Chamber-OS 150): below this breakpoint (`sm` 640px, `md` 768px) the same `actions` — rendered once, so a
533
+ * dialog trigger or test id is never doubled — become a bar fixed to the bottom of the screen with ActionBar's look:
534
+ * above a BottomNav, clear of the home indicator, `actionsStatus` as its status line. The page's end and a focused
535
+ * field scroll clear of it (AppShell's content, or the body, gains the bar's height). With an IconButton among them
536
+ * the actions share one row, labels wrapping inside the buttons (as `actionsFullWidthBelow`, Chamber-OS 149); with only icon
537
+ * buttons the status and the icons share one row. From the breakpoint up nothing changes. Use it for a detail page's
538
+ * own actions (Issue credit note…, ⋯) — one per page. The bar is `position: fixed`: no ancestor of the header may
539
+ * have a transform, filter, backdrop-filter, will-change, paint/layout containment or `content-visibility: auto`, or it is placed
540
+ * against that ancestor instead of the screen — the main entry warns in development. */
541
+ actionsBarBelow?: "sm" | "md" | undefined;
542
+ /** 5.45: the bar's status line ("Total 10,700.00 THB incl. VAT · due 22 Oct 2026"), shown only while the actions are
543
+ * a bar, in a polite live region. */
544
+ actionsStatus?: React$1.ReactNode | undefined;
514
545
  /** Default 1. Use 2 when the page already has an h1 (e.g. in the AppShell bar). */
515
546
  headingLevel?: 1 | 2 | 3 | undefined;
516
547
  /** The heading's id, for `aria-labelledby` on the page's region. */
@@ -490,7 +490,8 @@ export declare const Container: React$1.ForwardRefExoticComponent<ContainerProps
490
490
  * `/server`. It renders a plain `div`: to name a region by its title, put `aria-labelledby={titleId}` on your own
491
491
  * `<section>`, or pass `role="region"` with it. */
492
492
  export interface PageHeaderProps extends Pick<React$1.HTMLAttributes<HTMLElement>, "id" | "lang" | "dir" | "role">, React$1.AriaAttributes, DataAttributes {
493
- /** Small mono line above the title — a section or breadcrumb-like context ("Monitor / Sites"). */
493
+ /** Small mono line above the title — a section or breadcrumb-like context ("Monitor / Sites"). 5.46 (Chamber-OS
494
+ * 151): anything but text (chips: a status, a member number, badges) is a wrapping row in a `div`, 8px apart. */
494
495
  eyebrow?: React$1.ReactNode | undefined;
495
496
  /** 5.33 (DxT Monitor #7): a `<Breadcrumb>` above the title, 4px from it like the eyebrow, in its own `div` (a `nav`
496
497
  * can't sit in the eyebrow's `p`) and its own 13px sans. Detail pages show a breadcrumb or an eyebrow; with both,
@@ -498,6 +499,23 @@ export interface PageHeaderProps extends Pick<React$1.HTMLAttributes<HTMLElement
498
499
  breadcrumb?: React$1.ReactNode | undefined;
499
500
  /** The page's heading. */
500
501
  title: React$1.ReactNode;
502
+ /** 5.46 (Chamber-OS 151): a sentence under the title at body size (14px/1.7, secondary colour) — "Members, plans
503
+ * and renewals for SweCham" — before `meta`. A `div`, so a loading skeleton's blocks are valid inside. */
504
+ subtitle?: React$1.ReactNode | undefined;
505
+ /** 5.46: a chip or badge on the title's line (a role, an invoice's status, "Archived"). Title and addon share a
506
+ * wrapping row, 8px apart vertically and 12px across; on a narrow screen the addon wraps under the title. */
507
+ titleAddon?: React$1.ReactNode | undefined;
508
+ /** 5.46: the title's size. `default` 28px below 640px, 36px from 640px; `hero` 30px on a 36px line with 16px more
509
+ * room above; `hero-lg` 26px below 1024px and 36px from 1024px, with the same room. Per page, the custom
510
+ * properties `--aura-page-header-title-size` and `--aura-page-header-title-line-height` override any of them. */
511
+ size?: "default" | "hero" | "hero-lg" | undefined;
512
+ /** 5.46: focus the heading when the header mounts (`tabIndex={-1}`, `preventScroll`, no visible ring) — a page
513
+ * reached by a server redirect (renewal done, back from a payment page), so a screen reader starts there (WCAG
514
+ * 2.4.3). The main entry's PageHeader does it; from `/server` the heading is focusable but not focused. */
515
+ focusTitleOnMount?: boolean | undefined;
516
+ /** 5.46: `end` (default) lines the actions up with the bottom of the text; `start` with its top (the title, when
517
+ * there is no breadcrumb or eyebrow). */
518
+ actionsAlign?: "end" | "start" | undefined;
501
519
  /** A line under the title — counts, last update, owner. */
502
520
  meta?: React$1.ReactNode | undefined;
503
521
  /** Buttons for the page, bottom-aligned at the end; they wrap below the text on a narrow screen. */
@@ -511,6 +529,19 @@ export interface PageHeaderProps extends Pick<React$1.HTMLAttributes<HTMLElement
511
529
  * readers — and the breadcrumb and eyebrow are hidden, for phone layouts that show the title in the top bar. The
512
530
  * meta line and actions stay; with no meta, the actions start the header. */
513
531
  titleHideBelow?: "sm" | "md" | "lg" | undefined;
532
+ /** 5.45 (Chamber-OS 150): below this breakpoint (`sm` 640px, `md` 768px) the same `actions` — rendered once, so a
533
+ * dialog trigger or test id is never doubled — become a bar fixed to the bottom of the screen with ActionBar's look:
534
+ * above a BottomNav, clear of the home indicator, `actionsStatus` as its status line. The page's end and a focused
535
+ * field scroll clear of it (AppShell's content, or the body, gains the bar's height). With an IconButton among them
536
+ * the actions share one row, labels wrapping inside the buttons (as `actionsFullWidthBelow`, Chamber-OS 149); with only icon
537
+ * buttons the status and the icons share one row. From the breakpoint up nothing changes. Use it for a detail page's
538
+ * own actions (Issue credit note…, ⋯) — one per page. The bar is `position: fixed`: no ancestor of the header may
539
+ * have a transform, filter, backdrop-filter, will-change, paint/layout containment or `content-visibility: auto`, or it is placed
540
+ * against that ancestor instead of the screen — the main entry warns in development. */
541
+ actionsBarBelow?: "sm" | "md" | undefined;
542
+ /** 5.45: the bar's status line ("Total 10,700.00 THB incl. VAT · due 22 Oct 2026"), shown only while the actions are
543
+ * a bar, in a polite live region. */
544
+ actionsStatus?: React$1.ReactNode | undefined;
514
545
  /** Default 1. Use 2 when the page already has an h1 (e.g. in the AppShell bar). */
515
546
  headingLevel?: 1 | 2 | 3 | undefined;
516
547
  /** The heading's id, for `aria-labelledby` on the page's region. */
@@ -1378,7 +1378,25 @@ var PageHeader = React5.forwardRef(function PageHeader2(props, ref) {
1378
1378
  const level = props.headingLevel || 1;
1379
1379
  const below = props.actionsFullWidthBelow;
1380
1380
  const hide = props.titleHideBelow;
1381
- const hasMeta = props.meta != null && props.meta !== false;
1381
+ const shown = function(n3) {
1382
+ return n3 != null && n3 !== false && n3 !== "";
1383
+ };
1384
+ const hasMeta = shown(props.meta) || shown(props.subtitle) || shown(props.titleAddon);
1385
+ const size = props.size === "hero" || props.size === "hero-lg" ? props.size : null;
1386
+ const eyebrow = props.eyebrow;
1387
+ const plainEyebrow = typeof eyebrow === "string" || typeof eyebrow === "number";
1388
+ const heading = h4(
1389
+ "h" + level,
1390
+ {
1391
+ className: "aura-page-header__title",
1392
+ id: props.titleId,
1393
+ tabIndex: props.focusTitleOnMount ? -1 : void 0,
1394
+ "data-aura-focus-on-mount": props.focusTitleOnMount ? "" : void 0
1395
+ },
1396
+ props.title
1397
+ );
1398
+ const hasActions = props.actions != null && props.actions !== false;
1399
+ const bar = hasActions && (props.actionsBarBelow === "sm" || props.actionsBarBelow === "md") ? props.actionsBarBelow : null;
1382
1400
  return h4(
1383
1401
  "div",
1384
1402
  Object.assign(attrs2, {
@@ -1388,6 +1406,9 @@ var PageHeader = React5.forwardRef(function PageHeader2(props, ref) {
1388
1406
  (below === "sm" || below === "md" || below === "lg") && "aura-page-header--actions-full-below-" + below,
1389
1407
  (hide === "sm" || hide === "md" || hide === "lg") && "aura-page-header--title-hide-below-" + hide,
1390
1408
  (hide === "sm" || hide === "md" || hide === "lg") && !hasMeta && "has-no-meta",
1409
+ bar && "aura-page-header--actions-bar-below-" + bar,
1410
+ size && "aura-page-header--" + size,
1411
+ props.actionsAlign === "start" && "aura-page-header--actions-start",
1391
1412
  props.className
1392
1413
  ),
1393
1414
  style: props.style
@@ -1396,11 +1417,22 @@ var PageHeader = React5.forwardRef(function PageHeader2(props, ref) {
1396
1417
  "div",
1397
1418
  { className: "aura-page-header__text" },
1398
1419
  props.breadcrumb != null && props.breadcrumb !== false ? h4("div", { className: "aura-page-header__breadcrumb" }, props.breadcrumb) : null,
1399
- props.eyebrow != null && props.eyebrow !== false ? h4("p", { className: "aura-page-header__eyebrow" }, props.eyebrow) : null,
1400
- h4("h" + level, { className: "aura-page-header__title", id: props.titleId }, props.title),
1420
+ shown(eyebrow) ? plainEyebrow ? h4("p", { className: "aura-page-header__eyebrow" }, eyebrow) : h4("div", { className: "aura-page-header__eyebrow aura-page-header__eyebrow--rich" }, eyebrow) : null,
1421
+ shown(props.titleAddon) ? h4(
1422
+ "div",
1423
+ { className: "aura-page-header__title-row" },
1424
+ heading,
1425
+ h4("div", { className: "aura-page-header__title-addon" }, props.titleAddon)
1426
+ ) : heading,
1427
+ shown(props.subtitle) ? h4("div", { className: "aura-page-header__subtitle" }, props.subtitle) : null,
1401
1428
  props.meta != null && props.meta !== false ? h4("div", { className: "aura-page-header__meta" }, props.meta) : null
1402
1429
  ),
1403
- props.actions != null && props.actions !== false ? h4("div", { className: "aura-page-header__actions" }, props.actions) : null
1430
+ props.actions != null && props.actions !== false ? h4(
1431
+ "div",
1432
+ { className: "aura-page-header__actions" },
1433
+ bar && props.actionsStatus != null && props.actionsStatus !== false ? h4("div", { className: "aura-page-header__status", role: "status" }, props.actionsStatus) : null,
1434
+ props.actions
1435
+ ) : null
1404
1436
  );
1405
1437
  });
1406
1438
 
package/dist/styles.css CHANGED
@@ -1152,7 +1152,7 @@ a.aura-tab { text-decoration: none; }
1152
1152
  .aura-page-header { display: flex; flex-wrap: wrap; align-items: flex-end; justify-content: space-between; gap: var(--aura-space-4) var(--aura-space-6); min-width: 0; font-family: var(--font-sans); }
1153
1153
  .aura-page-header__text { display: flex; flex-direction: column; gap: var(--aura-space-1); flex: 1 1 320px; min-width: 0; }
1154
1154
  .aura-page-header__eyebrow { margin: 0; font-family: var(--font-mono); font-size: max(12px, .75rem); line-height: 1.7; color: var(--aura-fg-secondary); overflow-wrap: anywhere; }
1155
- .aura-page-header__title { margin: 0; font-family: var(--font-display); font-size: max(28px, 1.75rem); line-height: 1.15; font-weight: 600; letter-spacing: -0.01em; color: var(--aura-fg-primary); overflow-wrap: anywhere; }
1155
+ .aura-page-header__title { margin: 0; font-family: var(--font-display); font-size: var(--aura-page-header-title-size, max(28px, 1.75rem)); line-height: var(--aura-page-header-title-line-height, 1.15); font-weight: 600; letter-spacing: -0.01em; color: var(--aura-fg-primary); overflow-wrap: anywhere; }
1156
1156
  .aura-page-header__breadcrumb { min-width: 0; } /* 5.33 (DxT Monitor #7): Breadcrumb keeps its own type */
1157
1157
  .aura-page-header__meta { min-width: 0; font-size: max(13px, .8125rem); line-height: 1.5; color: var(--aura-fg-secondary); overflow-wrap: anywhere; }
1158
1158
  .aura-page-header__actions { display: flex; flex-wrap: wrap; align-items: center; gap: var(--aura-space-2); }
@@ -1160,15 +1160,15 @@ a.aura-tab { text-decoration: none; }
1160
1160
  * are gone; with no meta line the text block takes no room, so the actions start the header. */
1161
1161
  @media (max-width: 639.98px) {
1162
1162
  .aura-page-header--title-hide-below-sm .aura-page-header__title, .aura-page-header--title-hide-below-sm.has-no-meta > .aura-page-header__text { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }
1163
- .aura-page-header--title-hide-below-sm :is(.aura-page-header__breadcrumb, .aura-page-header__eyebrow) { display: none; }
1163
+ .aura-page-header--title-hide-below-sm :is(.aura-page-header__breadcrumb, .aura-page-header__eyebrow, .aura-page-header__title-addon) { display: none; }
1164
1164
  }
1165
1165
  @media (max-width: 767.98px) {
1166
1166
  .aura-page-header--title-hide-below-md .aura-page-header__title, .aura-page-header--title-hide-below-md.has-no-meta > .aura-page-header__text { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }
1167
- .aura-page-header--title-hide-below-md :is(.aura-page-header__breadcrumb, .aura-page-header__eyebrow) { display: none; }
1167
+ .aura-page-header--title-hide-below-md :is(.aura-page-header__breadcrumb, .aura-page-header__eyebrow, .aura-page-header__title-addon) { display: none; }
1168
1168
  }
1169
1169
  @media (max-width: 1023.98px) {
1170
1170
  .aura-page-header--title-hide-below-lg .aura-page-header__title, .aura-page-header--title-hide-below-lg.has-no-meta > .aura-page-header__text { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }
1171
- .aura-page-header--title-hide-below-lg :is(.aura-page-header__breadcrumb, .aura-page-header__eyebrow) { display: none; }
1171
+ .aura-page-header--title-hide-below-lg :is(.aura-page-header__breadcrumb, .aura-page-header__eyebrow, .aura-page-header__title-addon) { display: none; }
1172
1172
  }
1173
1173
  /* 5.37 (DxT Monitor #17): actionsFullWidthBelow — the actions take the row under the text; their children share
1174
1174
  * each line equally (wrapping when a label won't fit), a trigger inside a DropdownMenu, Tooltip or Popover wrapper
@@ -1184,6 +1184,7 @@ a.aura-tab { text-decoration: none; }
1184
1184
  .aura-page-header--actions-full-below-sm > .aura-page-header__actions:has(> .aura-icon-btn, > * > .aura-icon-btn) { flex-wrap: nowrap; align-items: stretch; }
1185
1185
  .aura-page-header--actions-full-below-sm > .aura-page-header__actions:has(> .aura-icon-btn, > * > .aura-icon-btn) > * { min-width: 0; }
1186
1186
  .aura-page-header--actions-full-below-sm > .aura-page-header__actions:has(> .aura-icon-btn, > * > .aura-icon-btn) > :is(.aura-btn, * > .aura-btn):not(.aura-icon-btn) { height: auto; min-height: var(--aura-button-height); white-space: normal; padding: var(--aura-space-2) var(--aura-space-3); line-height: 1.35; text-align: center; overflow-wrap: anywhere; hyphens: auto; border-radius: min(var(--aura-button-radius), 22px); }
1187
+ .aura-page-header--actions-full-below-sm > .aura-page-header__actions:has(> .aura-icon-btn, > * > .aura-icon-btn) > :is(.aura-icon-btn, :has(> .aura-icon-btn)) { align-self: center; } /* 5.45 (Chamber-OS addendum 52): centred on a taller button */
1187
1188
  }
1188
1189
  @media (max-width: 767.98px) {
1189
1190
  .aura-page-header--actions-full-below-md > .aura-page-header__actions { flex: 1 1 100%; gap: var(--aura-space-3); }
@@ -1194,6 +1195,7 @@ a.aura-tab { text-decoration: none; }
1194
1195
  .aura-page-header--actions-full-below-md > .aura-page-header__actions:has(> .aura-icon-btn, > * > .aura-icon-btn) { flex-wrap: nowrap; align-items: stretch; }
1195
1196
  .aura-page-header--actions-full-below-md > .aura-page-header__actions:has(> .aura-icon-btn, > * > .aura-icon-btn) > * { min-width: 0; }
1196
1197
  .aura-page-header--actions-full-below-md > .aura-page-header__actions:has(> .aura-icon-btn, > * > .aura-icon-btn) > :is(.aura-btn, * > .aura-btn):not(.aura-icon-btn) { height: auto; min-height: var(--aura-button-height); white-space: normal; padding: var(--aura-space-2) var(--aura-space-3); line-height: 1.35; text-align: center; overflow-wrap: anywhere; hyphens: auto; border-radius: min(var(--aura-button-radius), 22px); }
1198
+ .aura-page-header--actions-full-below-md > .aura-page-header__actions:has(> .aura-icon-btn, > * > .aura-icon-btn) > :is(.aura-icon-btn, :has(> .aura-icon-btn)) { align-self: center; } /* 5.45 (Chamber-OS addendum 52): centred on a taller button */
1197
1199
  }
1198
1200
  @media (max-width: 1023.98px) {
1199
1201
  .aura-page-header--actions-full-below-lg > .aura-page-header__actions { flex: 1 1 100%; gap: var(--aura-space-3); }
@@ -1204,8 +1206,57 @@ a.aura-tab { text-decoration: none; }
1204
1206
  .aura-page-header--actions-full-below-lg > .aura-page-header__actions:has(> .aura-icon-btn, > * > .aura-icon-btn) { flex-wrap: nowrap; align-items: stretch; }
1205
1207
  .aura-page-header--actions-full-below-lg > .aura-page-header__actions:has(> .aura-icon-btn, > * > .aura-icon-btn) > * { min-width: 0; }
1206
1208
  .aura-page-header--actions-full-below-lg > .aura-page-header__actions:has(> .aura-icon-btn, > * > .aura-icon-btn) > :is(.aura-btn, * > .aura-btn):not(.aura-icon-btn) { height: auto; min-height: var(--aura-button-height); white-space: normal; padding: var(--aura-space-2) var(--aura-space-3); line-height: 1.35; text-align: center; overflow-wrap: anywhere; hyphens: auto; border-radius: min(var(--aura-button-radius), 22px); }
1209
+ .aura-page-header--actions-full-below-lg > .aura-page-header__actions:has(> .aura-icon-btn, > * > .aura-icon-btn) > :is(.aura-icon-btn, :has(> .aura-icon-btn)) { align-self: center; } /* 5.45 (Chamber-OS addendum 52): centred on a taller button */
1207
1210
  }
1208
- @media (min-width: 640px) { .aura-page-header__title { font-size: max(36px, 2.25rem); } }
1211
+ /* 5.45 (Chamber-OS 150): actionsBarBelow — the same actions become a fixed bar with a status line; the page gains its
1212
+ * height (--aura-page-actions-height, measured on the client) so its end and a focused field scroll clear. */
1213
+ .aura-page-header__status { display: none; }
1214
+ @media (max-width: 639.98px) {
1215
+ .aura-page-header--actions-bar-below-sm > .aura-page-header__actions { position: fixed; z-index: var(--aura-z-bar); left: var(--aura-space-4); right: var(--aura-space-4); bottom: calc(var(--aura-bottomnav-offset, 0px) + max(var(--aura-space-3), env(safe-area-inset-bottom, 0px))); box-sizing: border-box; min-height: 60px; flex-wrap: wrap; align-items: center; justify-content: flex-end; gap: var(--aura-space-2); margin: 0; padding: var(--aura-space-2) var(--aura-space-3) var(--aura-space-2) var(--aura-space-4); background: var(--aura-bg-surface); color: var(--aura-fg-primary); border: 1px solid var(--aura-border-default); border-radius: var(--aura-radius-lg); box-shadow: var(--aura-shadow-overlay); transition: bottom var(--aura-duration-base) var(--aura-ease); }
1216
+ .aura-page-header--actions-bar-below-sm > .aura-page-header__actions > .aura-page-header__status { display: block; flex: 1 1 100%; min-width: 0; font-size: max(14px, .875rem); line-height: 1.5; color: var(--aura-fg-secondary); font-variant-numeric: tabular-nums; overflow-wrap: anywhere; }
1217
+ .aura-page-header--actions-bar-below-sm > .aura-page-header__actions > :not(.aura-page-header__status) { flex: 1 1 0; min-width: max-content; }
1218
+ .aura-page-header--actions-bar-below-sm > .aura-page-header__actions > :is(.aura-dropdown, .aura-tooltip-anchor, .aura-popover-anchor) > * { flex: 1 1 auto; }
1219
+ .aura-page-header--actions-bar-below-sm > .aura-page-header__actions > .aura-icon-btn { flex: none; }
1220
+ .aura-page-header--actions-bar-below-sm > .aura-page-header__actions > :has(> .aura-icon-btn) { flex: none; }
1221
+ .aura-page-header--actions-bar-below-sm > .aura-page-header__actions:has(> .aura-icon-btn, > * > .aura-icon-btn) { flex-wrap: wrap; align-items: stretch; }
1222
+ .aura-page-header--actions-bar-below-sm > .aura-page-header__actions:has(> .aura-icon-btn, > * > .aura-icon-btn) > :not(.aura-page-header__status) { min-width: 0; }
1223
+ .aura-page-header--actions-bar-below-sm > .aura-page-header__actions:has(> .aura-icon-btn, > * > .aura-icon-btn) > :is(.aura-icon-btn, :has(> .aura-icon-btn)) { align-self: center; }
1224
+ .aura-page-header--actions-bar-below-sm > .aura-page-header__actions:has(> .aura-icon-btn, > * > .aura-icon-btn) > :is(.aura-btn, * > .aura-btn):not(.aura-icon-btn) { height: auto; min-height: var(--aura-button-height); white-space: normal; padding: var(--aura-space-2) var(--aura-space-3); line-height: 1.35; text-align: center; overflow-wrap: anywhere; hyphens: auto; border-radius: min(var(--aura-button-radius), 22px); }
1225
+ .aura-page-header--actions-bar-below-sm > .aura-page-header__actions:not(:has(> .aura-btn, > * > .aura-btn)) > .aura-page-header__status { flex: 1 1 0; align-self: center; }
1226
+ :root:has(.aura-page-header--actions-bar-below-sm) { --aura-page-actions-space: calc(var(--aura-page-actions-height, 7rem) + var(--aura-bottomnav-offset, 0px) + max(var(--aura-space-3), env(safe-area-inset-bottom, 0px)) + var(--aura-space-3)); scroll-padding-bottom: var(--aura-page-actions-space); }
1227
+ :root:has(.aura-page-header--actions-bar-below-sm) .aura-shell__content { padding-bottom: calc(var(--aura-space-6) + var(--aura-page-actions-space)); }
1228
+ :root:has(.aura-page-header--actions-bar-below-sm):not(:has(.aura-shell__content)) body { padding-bottom: var(--aura-page-actions-space); }
1229
+ }
1230
+ @media (max-width: 767.98px) {
1231
+ .aura-page-header--actions-bar-below-md > .aura-page-header__actions { position: fixed; z-index: var(--aura-z-bar); left: var(--aura-space-4); right: var(--aura-space-4); bottom: calc(var(--aura-bottomnav-offset, 0px) + max(var(--aura-space-3), env(safe-area-inset-bottom, 0px))); box-sizing: border-box; min-height: 60px; flex-wrap: wrap; align-items: center; justify-content: flex-end; gap: var(--aura-space-2); margin: 0; padding: var(--aura-space-2) var(--aura-space-3) var(--aura-space-2) var(--aura-space-4); background: var(--aura-bg-surface); color: var(--aura-fg-primary); border: 1px solid var(--aura-border-default); border-radius: var(--aura-radius-lg); box-shadow: var(--aura-shadow-overlay); transition: bottom var(--aura-duration-base) var(--aura-ease); }
1232
+ .aura-page-header--actions-bar-below-md > .aura-page-header__actions > .aura-page-header__status { display: block; flex: 1 1 100%; min-width: 0; font-size: max(14px, .875rem); line-height: 1.5; color: var(--aura-fg-secondary); font-variant-numeric: tabular-nums; overflow-wrap: anywhere; }
1233
+ .aura-page-header--actions-bar-below-md > .aura-page-header__actions > :not(.aura-page-header__status) { flex: 1 1 0; min-width: max-content; }
1234
+ .aura-page-header--actions-bar-below-md > .aura-page-header__actions > :is(.aura-dropdown, .aura-tooltip-anchor, .aura-popover-anchor) > * { flex: 1 1 auto; }
1235
+ .aura-page-header--actions-bar-below-md > .aura-page-header__actions > .aura-icon-btn { flex: none; }
1236
+ .aura-page-header--actions-bar-below-md > .aura-page-header__actions > :has(> .aura-icon-btn) { flex: none; }
1237
+ .aura-page-header--actions-bar-below-md > .aura-page-header__actions:has(> .aura-icon-btn, > * > .aura-icon-btn) { flex-wrap: wrap; align-items: stretch; }
1238
+ .aura-page-header--actions-bar-below-md > .aura-page-header__actions:has(> .aura-icon-btn, > * > .aura-icon-btn) > :not(.aura-page-header__status) { min-width: 0; }
1239
+ .aura-page-header--actions-bar-below-md > .aura-page-header__actions:has(> .aura-icon-btn, > * > .aura-icon-btn) > :is(.aura-icon-btn, :has(> .aura-icon-btn)) { align-self: center; }
1240
+ .aura-page-header--actions-bar-below-md > .aura-page-header__actions:has(> .aura-icon-btn, > * > .aura-icon-btn) > :is(.aura-btn, * > .aura-btn):not(.aura-icon-btn) { height: auto; min-height: var(--aura-button-height); white-space: normal; padding: var(--aura-space-2) var(--aura-space-3); line-height: 1.35; text-align: center; overflow-wrap: anywhere; hyphens: auto; border-radius: min(var(--aura-button-radius), 22px); }
1241
+ .aura-page-header--actions-bar-below-md > .aura-page-header__actions:not(:has(> .aura-btn, > * > .aura-btn)) > .aura-page-header__status { flex: 1 1 0; align-self: center; }
1242
+ :root:has(.aura-page-header--actions-bar-below-md) { --aura-page-actions-space: calc(var(--aura-page-actions-height, 7rem) + var(--aura-bottomnav-offset, 0px) + max(var(--aura-space-3), env(safe-area-inset-bottom, 0px)) + var(--aura-space-3)); scroll-padding-bottom: var(--aura-page-actions-space); }
1243
+ :root:has(.aura-page-header--actions-bar-below-md) .aura-shell__content { padding-bottom: calc(var(--aura-space-6) + var(--aura-page-actions-space)); }
1244
+ :root:has(.aura-page-header--actions-bar-below-md):not(:has(.aura-shell__content)) body { padding-bottom: var(--aura-page-actions-space); }
1245
+ }
1246
+ @media (prefers-reduced-motion: reduce) { .aura-page-header__actions { transition: none; } }
1247
+ @media (min-width: 640px) { .aura-page-header__title { font-size: var(--aura-page-header-title-size, max(36px, 2.25rem)); } }
1248
+ /* 5.46 (Chamber-OS 151): subtitle, a title addon, a chip eyebrow, hero sizes, a focusable heading, top-aligned actions. */
1249
+ .aura-page-header__subtitle { min-width: 0; font-size: max(14px, .875rem); line-height: 1.7; color: var(--aura-fg-secondary); overflow-wrap: anywhere; }
1250
+ .aura-page-header__title-row { display: flex; flex-wrap: wrap; align-items: center; gap: var(--aura-space-2) var(--aura-space-3); min-width: 0; }
1251
+ .aura-page-header__title-row > .aura-page-header__title { min-width: 0; }
1252
+ .aura-page-header__title-addon { display: flex; flex-wrap: wrap; align-items: center; gap: var(--aura-space-2); min-width: 0; }
1253
+ .aura-page-header__eyebrow--rich { display: flex; flex-wrap: wrap; align-items: center; gap: var(--aura-space-2); font-family: var(--font-sans); line-height: 1.5; }
1254
+ .aura-page-header--hero, .aura-page-header--hero-lg { padding-top: var(--aura-space-4); }
1255
+ .aura-page-header--hero .aura-page-header__title { font-size: var(--aura-page-header-title-size, max(30px, 1.875rem)); line-height: var(--aura-page-header-title-line-height, 1.2); }
1256
+ .aura-page-header--hero-lg .aura-page-header__title { font-size: var(--aura-page-header-title-size, max(26px, 1.625rem)); line-height: var(--aura-page-header-title-line-height, 1.25); }
1257
+ @media (min-width: 1024px) { .aura-page-header--hero-lg .aura-page-header__title { font-size: var(--aura-page-header-title-size, max(36px, 2.25rem)); line-height: var(--aura-page-header-title-line-height, 1.2); } }
1258
+ .aura-page-header__title[tabindex="-1"]:focus { outline: none; }
1259
+ .aura-page-header--actions-start { align-items: flex-start; }
1209
1260
  /* Thai marks need the room a 1.15 line takes away. */
1210
1261
  .aura-page-header__title:lang(th) { line-height: 1.4; letter-spacing: 0; }
1211
1262
  .aura-page-header__meta:lang(th) { line-height: 1.6; }