@dloizides/ui-nav 1.27.0 → 1.28.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/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.28.0
4
+
5
+ - INVOICE-5 "Edit invoices before sending, shared background and menu hover" (AC-UI-3, AC-UI-6): `AppShell` draws `theme.backdrop` (the `@dloizides/theme-web` 1.5.0 `resolveBackdrop` shape, typed here as `AppShellBackdrop`): its colour and web image on the root, and for `horizon` a brand band behind header and nav that the content overlaps by `overlap.compact` (< 768 px) or `overlap.wide`. No `theme.backdrop` keeps today's flat background.
6
+
3
7
  ## 1.27.0
4
8
 
5
9
  - INVOICE-5 "Edit invoices before sending, shared background and menu hover" (AC-UI-2, AC-UI-6): `NavBar` takes `hoverFeedback` (`NavHoverFeedback`: `default`, `tint-pill`, `underline`, `lift`, `sliding-highlight`); the new phone `BottomTabBar` takes `tabFeedback` (`TabFeedback`: `default`, `indicator-pill`, `dim-spring`, `sliding-bar`, `icon-lift`). Timings come from the `@dloizides/ui-motion` tokens (<= 300 ms); reduced motion keeps every option still. `default` keeps today's look, so existing apps do not change.
package/dist/index.d.mts CHANGED
@@ -1085,6 +1085,21 @@ interface NavOverflowMenuProps {
1085
1085
  }
1086
1086
  declare const NavOverflowMenu: ({ items, pathname, onNavigate, colors, label, hint, testID, variant, }: NavOverflowMenuProps) => React.ReactElement;
1087
1087
 
1088
+ interface AppShellBackdropBand {
1089
+ color: string;
1090
+ text: string;
1091
+ overlap: {
1092
+ compact: number;
1093
+ wide: number;
1094
+ };
1095
+ }
1096
+ interface AppShellBackdrop {
1097
+ color: string;
1098
+ image: string | null;
1099
+ imageSize: string | null;
1100
+ band: AppShellBackdropBand | null;
1101
+ }
1102
+
1088
1103
  /**
1089
1104
  * Default testIDs for `@dloizides/ui-nav`. Kept as a small central map (mirrors
1090
1105
  * `@dloizides/ui-layout`'s `LAYOUT_TEST_IDS`) so the app-agnostic chrome exposes
@@ -1132,6 +1147,7 @@ declare const APP_SHELL_SUFFIX: {
1132
1147
  readonly loading: "-loading";
1133
1148
  readonly error: "-error";
1134
1149
  readonly forbidden: "-forbidden";
1150
+ readonly band: "-backdrop-band";
1135
1151
  };
1136
1152
 
1137
1153
  /**
@@ -1628,4 +1644,4 @@ interface BottomTabBarProps {
1628
1644
  /** Phone tab bar; `tabFeedback` picks the press feedback, `Default` adds no motion. */
1629
1645
  declare const BottomTabBar: ({ items, pathname, onNavigate, tabFeedback, navigateHint, regionLabel, containerStyle, testID, }: BottomTabBarProps) => React.ReactElement;
1630
1646
 
1631
- export { ACTIVE_BORDER_RADIUS, ACTIVE_TINT_OPACITY, APP_SHELL_SUFFIX, type AccountPlan, type AccountState, AppShell, type AppShellProps, type AppShellWidth, BASE_INDENT, BottomTabBar, type BottomTabBarProps, CHEVRON_ICON_SIZE, CollapsedRail, type CollapsedRailProps, type CollapsedRailRange, type CommandItem, CommandPalette, type CommandPaletteLabels, type CommandPaletteProps, CommandPaletteTrigger, type CommandPaletteTriggerProps, DEFAULT_COLLAPSED_RAIL_MAX, DEFAULT_SEARCH_BREAKPOINT, DarkModeControl, type DarkModeControlProps, type DarkModeOption, type DarkModeVariant, HOVER_TINT_OPACITY, NAV_ICON_SIZE, NAV_LINK_GAP, NAV_ROW_RADIUS, NAV_TEST_IDS, Nav, NavBar, type NavBarProps, NavExpandableItem, type NavExpandableItemProps, NavHoverFeedback, type NavItem, type NavOrientation, NavOverflowMenu, type NavOverflowMenuProps, type NavProps, NavShell, type NavShellCollapsedRail, type NavShellLayout, type NavShellProps, type NavShellSideRail, type NavShellTopBar, type NavUser, PillNav, type PillNavProps, PillNavVariant, RAIL_FULL_BREAKPOINT, type RailMode, type ResolveRailModeArgs, type SearchAffordance, type ShellMessage, Sidebar, SidebarChevron, type SidebarChevronProps, type SidebarPaletteTriggerConfig, type SidebarProps, SidebarSearch, type SidebarSearchConfig, type SidebarSearchLabels, type SidebarSearchProps, TabFeedback, Topbar, type TopbarAction, type TopbarProps, type TopbarUser, accessibleNavItems, collapsedRailStyles, darkModeStyles, expandableStyles, filterCommands, filterNavItems, isFilterActive, isRouteActive, navStyles, pillNavStyles, resolveActiveRoute, resolveContentMaxWidth, resolvePillMenuLabel, resolveRailMode, resolveSearchAffordance, roleRoutesToNavItems, searchTokens, useCommandPaletteHotkey, useContentMaxWidth };
1647
+ export { ACTIVE_BORDER_RADIUS, ACTIVE_TINT_OPACITY, APP_SHELL_SUFFIX, type AccountPlan, type AccountState, AppShell, type AppShellBackdrop, type AppShellBackdropBand, type AppShellProps, type AppShellWidth, BASE_INDENT, BottomTabBar, type BottomTabBarProps, CHEVRON_ICON_SIZE, CollapsedRail, type CollapsedRailProps, type CollapsedRailRange, type CommandItem, CommandPalette, type CommandPaletteLabels, type CommandPaletteProps, CommandPaletteTrigger, type CommandPaletteTriggerProps, DEFAULT_COLLAPSED_RAIL_MAX, DEFAULT_SEARCH_BREAKPOINT, DarkModeControl, type DarkModeControlProps, type DarkModeOption, type DarkModeVariant, HOVER_TINT_OPACITY, NAV_ICON_SIZE, NAV_LINK_GAP, NAV_ROW_RADIUS, NAV_TEST_IDS, Nav, NavBar, type NavBarProps, NavExpandableItem, type NavExpandableItemProps, NavHoverFeedback, type NavItem, type NavOrientation, NavOverflowMenu, type NavOverflowMenuProps, type NavProps, NavShell, type NavShellCollapsedRail, type NavShellLayout, type NavShellProps, type NavShellSideRail, type NavShellTopBar, type NavUser, PillNav, type PillNavProps, PillNavVariant, RAIL_FULL_BREAKPOINT, type RailMode, type ResolveRailModeArgs, type SearchAffordance, type ShellMessage, Sidebar, SidebarChevron, type SidebarChevronProps, type SidebarPaletteTriggerConfig, type SidebarProps, SidebarSearch, type SidebarSearchConfig, type SidebarSearchLabels, type SidebarSearchProps, TabFeedback, Topbar, type TopbarAction, type TopbarProps, type TopbarUser, accessibleNavItems, collapsedRailStyles, darkModeStyles, expandableStyles, filterCommands, filterNavItems, isFilterActive, isRouteActive, navStyles, pillNavStyles, resolveActiveRoute, resolveContentMaxWidth, resolvePillMenuLabel, resolveRailMode, resolveSearchAffordance, roleRoutesToNavItems, searchTokens, useCommandPaletteHotkey, useContentMaxWidth };
package/dist/index.d.ts CHANGED
@@ -1085,6 +1085,21 @@ interface NavOverflowMenuProps {
1085
1085
  }
1086
1086
  declare const NavOverflowMenu: ({ items, pathname, onNavigate, colors, label, hint, testID, variant, }: NavOverflowMenuProps) => React.ReactElement;
1087
1087
 
1088
+ interface AppShellBackdropBand {
1089
+ color: string;
1090
+ text: string;
1091
+ overlap: {
1092
+ compact: number;
1093
+ wide: number;
1094
+ };
1095
+ }
1096
+ interface AppShellBackdrop {
1097
+ color: string;
1098
+ image: string | null;
1099
+ imageSize: string | null;
1100
+ band: AppShellBackdropBand | null;
1101
+ }
1102
+
1088
1103
  /**
1089
1104
  * Default testIDs for `@dloizides/ui-nav`. Kept as a small central map (mirrors
1090
1105
  * `@dloizides/ui-layout`'s `LAYOUT_TEST_IDS`) so the app-agnostic chrome exposes
@@ -1132,6 +1147,7 @@ declare const APP_SHELL_SUFFIX: {
1132
1147
  readonly loading: "-loading";
1133
1148
  readonly error: "-error";
1134
1149
  readonly forbidden: "-forbidden";
1150
+ readonly band: "-backdrop-band";
1135
1151
  };
1136
1152
 
1137
1153
  /**
@@ -1628,4 +1644,4 @@ interface BottomTabBarProps {
1628
1644
  /** Phone tab bar; `tabFeedback` picks the press feedback, `Default` adds no motion. */
1629
1645
  declare const BottomTabBar: ({ items, pathname, onNavigate, tabFeedback, navigateHint, regionLabel, containerStyle, testID, }: BottomTabBarProps) => React.ReactElement;
1630
1646
 
1631
- export { ACTIVE_BORDER_RADIUS, ACTIVE_TINT_OPACITY, APP_SHELL_SUFFIX, type AccountPlan, type AccountState, AppShell, type AppShellProps, type AppShellWidth, BASE_INDENT, BottomTabBar, type BottomTabBarProps, CHEVRON_ICON_SIZE, CollapsedRail, type CollapsedRailProps, type CollapsedRailRange, type CommandItem, CommandPalette, type CommandPaletteLabels, type CommandPaletteProps, CommandPaletteTrigger, type CommandPaletteTriggerProps, DEFAULT_COLLAPSED_RAIL_MAX, DEFAULT_SEARCH_BREAKPOINT, DarkModeControl, type DarkModeControlProps, type DarkModeOption, type DarkModeVariant, HOVER_TINT_OPACITY, NAV_ICON_SIZE, NAV_LINK_GAP, NAV_ROW_RADIUS, NAV_TEST_IDS, Nav, NavBar, type NavBarProps, NavExpandableItem, type NavExpandableItemProps, NavHoverFeedback, type NavItem, type NavOrientation, NavOverflowMenu, type NavOverflowMenuProps, type NavProps, NavShell, type NavShellCollapsedRail, type NavShellLayout, type NavShellProps, type NavShellSideRail, type NavShellTopBar, type NavUser, PillNav, type PillNavProps, PillNavVariant, RAIL_FULL_BREAKPOINT, type RailMode, type ResolveRailModeArgs, type SearchAffordance, type ShellMessage, Sidebar, SidebarChevron, type SidebarChevronProps, type SidebarPaletteTriggerConfig, type SidebarProps, SidebarSearch, type SidebarSearchConfig, type SidebarSearchLabels, type SidebarSearchProps, TabFeedback, Topbar, type TopbarAction, type TopbarProps, type TopbarUser, accessibleNavItems, collapsedRailStyles, darkModeStyles, expandableStyles, filterCommands, filterNavItems, isFilterActive, isRouteActive, navStyles, pillNavStyles, resolveActiveRoute, resolveContentMaxWidth, resolvePillMenuLabel, resolveRailMode, resolveSearchAffordance, roleRoutesToNavItems, searchTokens, useCommandPaletteHotkey, useContentMaxWidth };
1647
+ export { ACTIVE_BORDER_RADIUS, ACTIVE_TINT_OPACITY, APP_SHELL_SUFFIX, type AccountPlan, type AccountState, AppShell, type AppShellBackdrop, type AppShellBackdropBand, type AppShellProps, type AppShellWidth, BASE_INDENT, BottomTabBar, type BottomTabBarProps, CHEVRON_ICON_SIZE, CollapsedRail, type CollapsedRailProps, type CollapsedRailRange, type CommandItem, CommandPalette, type CommandPaletteLabels, type CommandPaletteProps, CommandPaletteTrigger, type CommandPaletteTriggerProps, DEFAULT_COLLAPSED_RAIL_MAX, DEFAULT_SEARCH_BREAKPOINT, DarkModeControl, type DarkModeControlProps, type DarkModeOption, type DarkModeVariant, HOVER_TINT_OPACITY, NAV_ICON_SIZE, NAV_LINK_GAP, NAV_ROW_RADIUS, NAV_TEST_IDS, Nav, NavBar, type NavBarProps, NavExpandableItem, type NavExpandableItemProps, NavHoverFeedback, type NavItem, type NavOrientation, NavOverflowMenu, type NavOverflowMenuProps, type NavProps, NavShell, type NavShellCollapsedRail, type NavShellLayout, type NavShellProps, type NavShellSideRail, type NavShellTopBar, type NavUser, PillNav, type PillNavProps, PillNavVariant, RAIL_FULL_BREAKPOINT, type RailMode, type ResolveRailModeArgs, type SearchAffordance, type ShellMessage, Sidebar, SidebarChevron, type SidebarChevronProps, type SidebarPaletteTriggerConfig, type SidebarProps, SidebarSearch, type SidebarSearchConfig, type SidebarSearchLabels, type SidebarSearchProps, TabFeedback, Topbar, type TopbarAction, type TopbarProps, type TopbarUser, accessibleNavItems, collapsedRailStyles, darkModeStyles, expandableStyles, filterCommands, filterNavItems, isFilterActive, isRouteActive, navStyles, pillNavStyles, resolveActiveRoute, resolveContentMaxWidth, resolvePillMenuLabel, resolveRailMode, resolveSearchAffordance, roleRoutesToNavItems, searchTokens, useCommandPaletteHotkey, useContentMaxWidth };
package/dist/index.js CHANGED
@@ -903,7 +903,8 @@ var APP_SHELL_SUFFIX = {
903
903
  pending: "-pending",
904
904
  loading: "-loading",
905
905
  error: "-error",
906
- forbidden: "-forbidden"
906
+ forbidden: "-forbidden",
907
+ band: "-backdrop-band"
907
908
  };
908
909
  var FocusableTouchable = ({
909
910
  onPress,
@@ -1542,13 +1543,53 @@ var Nav = (props) => {
1542
1543
  }
1543
1544
  return /* @__PURE__ */ jsxRuntime.jsx(NavBar, { ...props });
1544
1545
  };
1546
+ var RAIL_FULL_BREAKPOINT = uiLayout.LAYOUT_COLLAPSE_BREAKPOINT;
1547
+ var DEFAULT_COLLAPSED_RAIL_MAX = 1024;
1548
+ function resolveRailMode({ viewport, hasSidebar, hasCollapsed, range }) {
1549
+ if (!hasSidebar) return "none";
1550
+ if (hasCollapsed) {
1551
+ if (viewport >= range.max) return "full";
1552
+ if (viewport >= range.min) return "collapsed";
1553
+ return "drawer";
1554
+ }
1555
+ return viewport >= RAIL_FULL_BREAKPOINT ? "full" : "drawer";
1556
+ }
1557
+ var styles3 = reactNative.StyleSheet.create({
1558
+ band: { left: 0, position: "absolute", right: 0, top: 0 }
1559
+ });
1560
+ function readBackdrop(theme) {
1561
+ return theme.backdrop;
1562
+ }
1563
+ function rootStyle(backdrop, fallback) {
1564
+ if (backdrop === void 0) return { backgroundColor: fallback };
1565
+ const base = { backgroundColor: backdrop.color };
1566
+ if (!IS_WEB || backdrop.image === null) return base;
1567
+ const web = { backgroundImage: backdrop.image, backgroundSize: backdrop.imageSize ?? "auto" };
1568
+ return { ...base, ...web };
1569
+ }
1570
+ function resolveShellBackdrop(theme, viewport, contentPadding, testID) {
1571
+ const backdrop = readBackdrop(theme);
1572
+ const band = backdrop?.band ?? null;
1573
+ const root = rootStyle(backdrop, theme.colors.background);
1574
+ if (band === null) return { root, chrome: void 0, chromeText: theme.colors.text, band: null };
1575
+ const overlap = viewport < RAIL_FULL_BREAKPOINT ? band.overlap.compact : band.overlap.wide;
1576
+ const element = /* @__PURE__ */ jsxRuntime.jsx(
1577
+ reactNative.View,
1578
+ {
1579
+ pointerEvents: "none",
1580
+ style: [styles3.band, { backgroundColor: band.color, height: contentPadding + overlap }],
1581
+ testID: `${testID}${APP_SHELL_SUFFIX.band}`
1582
+ }
1583
+ );
1584
+ return { root, chrome: { backgroundColor: band.color }, chromeText: band.text, band: element };
1585
+ }
1545
1586
  var CARD_PADDING = 20;
1546
1587
  var CARD_BORDER_RADIUS = 12;
1547
1588
  var CARD_BORDER_WIDTH = 1;
1548
1589
  var CARD_TITLE_FONT_SIZE = 16;
1549
1590
  var CARD_MESSAGE_FONT_SIZE = 14;
1550
1591
  var CARD_TITLE_MARGIN_BOTTOM = 8;
1551
- var styles3 = reactNative.StyleSheet.create({
1592
+ var styles4 = reactNative.StyleSheet.create({
1552
1593
  // Loading is a TRANSITIONAL state: the spinner is an absolute overlay ON TOP of
1553
1594
  // stably-mounted children, never a replacement for them. Replacing children here
1554
1595
  // would unmount whatever they host (e.g. a navigator), and on remount React
@@ -1580,9 +1621,9 @@ function MessageCard({
1580
1621
  }) {
1581
1622
  const { theme } = uiFeedback.useUi();
1582
1623
  const colors = theme.colors;
1583
- return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: [styles3.card, { backgroundColor: colors.surface, borderColor: accentColor }], testID, children: [
1584
- /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles3.cardTitle, { color: accentColor }], children: message.titleText }),
1585
- /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles3.cardMessage, { color: colors.textSecondary }], children: message.messageText })
1624
+ return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: [styles4.card, { backgroundColor: colors.surface, borderColor: accentColor }], testID, children: [
1625
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles4.cardTitle, { color: accentColor }], children: message.titleText }),
1626
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles4.cardMessage, { color: colors.textSecondary }], children: message.messageText })
1586
1627
  ] });
1587
1628
  }
1588
1629
  function useContentBody(state, children, testID) {
@@ -1594,12 +1635,12 @@ function useContentBody(state, children, testID) {
1594
1635
  return /* @__PURE__ */ jsxRuntime.jsx(MessageCard, { accentColor: errorColor, message: state.forbidden, testID: `${testID}${APP_SHELL_SUFFIX.forbidden}` });
1595
1636
  if (state.error)
1596
1637
  return /* @__PURE__ */ jsxRuntime.jsx(MessageCard, { accentColor: errorColor, message: state.error, testID: `${testID}${APP_SHELL_SUFFIX.error}` });
1597
- return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles3.loadingHost, children: [
1638
+ return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles4.loadingHost, children: [
1598
1639
  children,
1599
1640
  state.loading === true ? /* @__PURE__ */ jsxRuntime.jsx(
1600
1641
  reactNative.View,
1601
1642
  {
1602
- style: [styles3.loadingOverlay, { backgroundColor: theme.colors.background }],
1643
+ style: [styles4.loadingOverlay, { backgroundColor: theme.colors.background }],
1603
1644
  testID: `${testID}${APP_SHELL_SUFFIX.loading}`,
1604
1645
  children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.ActivityIndicator, { color: primary, size: "large" })
1605
1646
  }
@@ -1626,7 +1667,7 @@ var DEFAULT_DRAWER_LABELS = {
1626
1667
  closeLabel: "Close menu",
1627
1668
  closeHint: "Close the navigation menu"
1628
1669
  };
1629
- var styles4 = reactNative.StyleSheet.create({
1670
+ var styles5 = reactNative.StyleSheet.create({
1630
1671
  overlay: { ...reactNative.StyleSheet.absoluteFillObject, zIndex: DRAWER_Z_INDEX },
1631
1672
  // The scrim's tap-to-close hit area is anchored to the RIGHT of the panel
1632
1673
  // (`left: DRAWER_WIDTH`), so it NEVER overlaps the panel: nav-item taps always
@@ -1661,10 +1702,10 @@ var MenuToggle = ({ label, hint, onOpen, testID }) => {
1661
1702
  accessibilityHint: hint,
1662
1703
  accessibilityLabel: label,
1663
1704
  ringColor: theme.palette.primary["500"],
1664
- style: styles4.menuToggle,
1705
+ style: styles5.menuToggle,
1665
1706
  testID,
1666
1707
  onPress: onOpen,
1667
- children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles4.menuToggleGlyph, { color: theme.colors.text }], children: MENU_GLYPH2 })
1708
+ children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles5.menuToggleGlyph, { color: theme.colors.text }], children: MENU_GLYPH2 })
1668
1709
  }
1669
1710
  );
1670
1711
  };
@@ -1690,14 +1731,14 @@ var MobileDrawer = ({
1690
1731
  node.addEventListener("click", handleClick);
1691
1732
  return () => node.removeEventListener("click", handleClick);
1692
1733
  }, [onClose]);
1693
- return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles4.overlay, children: [
1734
+ return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles5.overlay, children: [
1694
1735
  /* @__PURE__ */ jsxRuntime.jsx(
1695
1736
  reactNative.Pressable,
1696
1737
  {
1697
1738
  accessibilityHint: labels.closeHint,
1698
1739
  accessibilityLabel: labels.closeLabel,
1699
1740
  accessibilityRole: "button",
1700
- style: styles4.scrim,
1741
+ style: styles5.scrim,
1701
1742
  testID: scrimTestID,
1702
1743
  onPress: onClose
1703
1744
  }
@@ -1708,24 +1749,13 @@ var MobileDrawer = ({
1708
1749
  ref: panelRef,
1709
1750
  "aria-modal": true,
1710
1751
  role: "dialog",
1711
- style: [styles4.panel, { backgroundColor: theme.colors.surface }],
1752
+ style: [styles5.panel, { backgroundColor: theme.colors.surface }],
1712
1753
  testID: drawerTestID,
1713
1754
  children: renderTextSlot(sidebar, { color: theme.colors.text })
1714
1755
  }
1715
1756
  )
1716
1757
  ] });
1717
1758
  };
1718
- var RAIL_FULL_BREAKPOINT = uiLayout.LAYOUT_COLLAPSE_BREAKPOINT;
1719
- var DEFAULT_COLLAPSED_RAIL_MAX = 1024;
1720
- function resolveRailMode({ viewport, hasSidebar, hasCollapsed, range }) {
1721
- if (!hasSidebar) return "none";
1722
- if (hasCollapsed) {
1723
- if (viewport >= range.max) return "full";
1724
- if (viewport >= range.min) return "collapsed";
1725
- return "drawer";
1726
- }
1727
- return viewport >= RAIL_FULL_BREAKPOINT ? "full" : "drawer";
1728
- }
1729
1759
  function resolveContentMaxWidth(width, viewport) {
1730
1760
  if (width === "full") return "full";
1731
1761
  const wideMinViewport = width.wideMinViewport ?? Number.POSITIVE_INFINITY;
@@ -1737,7 +1767,7 @@ function useContentMaxWidth(width) {
1737
1767
  return resolveContentMaxWidth(width, viewport);
1738
1768
  }
1739
1769
  var DEFAULT_CONTENT_PADDING = 24;
1740
- var styles5 = reactNative.StyleSheet.create({
1770
+ var styles6 = reactNative.StyleSheet.create({
1741
1771
  root: { flex: 1 },
1742
1772
  // Auth-pending spinner: an opaque absolute overlay over the mounted shell, so
1743
1773
  // `children` (and any navigator they host) are never unmounted while the gate
@@ -1777,6 +1807,7 @@ var AppShell = ({
1777
1807
  const maxWidth = useContentMaxWidth(width);
1778
1808
  const body = useContentBody(state, children, testID);
1779
1809
  const { width: viewportWidth } = reactNative.useWindowDimensions();
1810
+ const look = resolveShellBackdrop(theme, viewportWidth, contentPadding, testID);
1780
1811
  const railRange = collapsedRailRange ?? { min: RAIL_FULL_BREAKPOINT, max: DEFAULT_COLLAPSED_RAIL_MAX };
1781
1812
  const railMode = resolveRailMode({
1782
1813
  viewport: viewportWidth,
@@ -1798,18 +1829,21 @@ var AppShell = ({
1798
1829
  }, [isUnauthenticated, gate]);
1799
1830
  const gatePending = gate?.pending === true;
1800
1831
  if (isUnauthenticated) return null;
1801
- const columnStyle = maxWidth === "full" ? styles5.columnFull : [styles5.columnCapped, { maxWidth }];
1802
- const navInnerStyle = chromeAlignment === "content" && maxWidth !== "full" ? [styles5.chromeCapped, { maxWidth, paddingHorizontal: contentPadding }] : void 0;
1803
- const scroller = /* @__PURE__ */ jsxRuntime.jsx(
1832
+ const columnStyle = maxWidth === "full" ? styles6.columnFull : [styles6.columnCapped, { maxWidth }];
1833
+ const navInnerStyle = chromeAlignment === "content" && maxWidth !== "full" ? [styles6.chromeCapped, { maxWidth, paddingHorizontal: contentPadding }] : void 0;
1834
+ const scroller = /* @__PURE__ */ jsxRuntime.jsxs(
1804
1835
  reactNative.ScrollView,
1805
1836
  {
1806
- contentContainerStyle: [styles5.scrollContent, { padding: contentPadding }],
1807
- style: styles5.scroll,
1837
+ contentContainerStyle: [styles6.scrollContent, { padding: contentPadding }],
1838
+ style: styles6.scroll,
1808
1839
  testID: `${testID}${APP_SHELL_SUFFIX.content}`,
1809
- children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: columnStyle, children: body })
1840
+ children: [
1841
+ look.band,
1842
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: columnStyle, children: body })
1843
+ ]
1810
1844
  }
1811
1845
  );
1812
- const headerRegion = useDrawer ? /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles5.headerRow, testID: `${testID}${APP_SHELL_SUFFIX.header}`, children: [
1846
+ const headerRegion = useDrawer ? /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: [styles6.headerRow, look.chrome], testID: `${testID}${APP_SHELL_SUFFIX.header}`, children: [
1813
1847
  /* @__PURE__ */ jsxRuntime.jsx(
1814
1848
  MenuToggle,
1815
1849
  {
@@ -1819,14 +1853,14 @@ var AppShell = ({
1819
1853
  onOpen: openDrawer
1820
1854
  }
1821
1855
  ),
1822
- /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: styles5.headerFill, children: renderTextSlot(header, { color: theme.colors.text }) })
1823
- ] }) : /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { testID: `${testID}${APP_SHELL_SUFFIX.header}`, children: renderTextSlot(header, { color: theme.colors.text }) });
1856
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: styles6.headerFill, children: renderTextSlot(header, { color: look.chromeText }) })
1857
+ ] }) : /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: look.chrome, testID: `${testID}${APP_SHELL_SUFFIX.header}`, children: renderTextSlot(header, { color: look.chromeText }) });
1824
1858
  const railRegion = railMode === "full" ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { testID: `${testID}${APP_SHELL_SUFFIX.sidebar}`, children: renderTextSlot(sidebar, { color: theme.colors.text }) }) : railMode === "collapsed" ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { testID: `${testID}${APP_SHELL_SUFFIX.collapsedSidebar}`, children: renderTextSlot(collapsedSidebar, { color: theme.colors.text }) }) : null;
1825
- return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: [styles5.root, { backgroundColor: theme.colors.background }], testID, children: [
1859
+ return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: [styles6.root, look.root], testID, children: [
1826
1860
  headerRegion,
1827
- nav !== void 0 ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { testID: `${testID}${APP_SHELL_SUFFIX.nav}`, children: navInnerStyle !== void 0 ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: navInnerStyle, children: renderTextSlot(nav, { color: theme.colors.text }) }) : renderTextSlot(nav, { color: theme.colors.text }) }) : null,
1861
+ nav !== void 0 ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: look.chrome, testID: `${testID}${APP_SHELL_SUFFIX.nav}`, children: navInnerStyle !== void 0 ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: navInnerStyle, children: renderTextSlot(nav, { color: look.chromeText }) }) : renderTextSlot(nav, { color: look.chromeText }) }) : null,
1828
1862
  banner !== void 0 ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { testID: `${testID}${APP_SHELL_SUFFIX.banner}`, children: renderTextSlot(banner, { color: theme.colors.text }) }) : null,
1829
- railRegion !== null ? /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles5.bodyRow, children: [
1863
+ railRegion !== null ? /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles6.bodyRow, children: [
1830
1864
  railRegion,
1831
1865
  scroller
1832
1866
  ] }) : scroller,
@@ -1843,7 +1877,7 @@ var AppShell = ({
1843
1877
  gatePending ? /* @__PURE__ */ jsxRuntime.jsx(
1844
1878
  reactNative.View,
1845
1879
  {
1846
- style: [styles5.pendingOverlay, { backgroundColor: theme.colors.background }],
1880
+ style: [styles6.pendingOverlay, { backgroundColor: theme.colors.background }],
1847
1881
  testID: `${testID}${APP_SHELL_SUFFIX.pending}`,
1848
1882
  children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.ActivityIndicator, { color: primary, size: "large" })
1849
1883
  }
@@ -2210,7 +2244,7 @@ var KEY_DOWN = "ArrowDown";
2210
2244
  var KEY_UP = "ArrowUp";
2211
2245
  var KEY_ENTER = "Enter";
2212
2246
  var KEY_ESCAPE = "Escape";
2213
- var styles6 = reactNative.StyleSheet.create({
2247
+ var styles7 = reactNative.StyleSheet.create({
2214
2248
  overlay: { ...reactNative.StyleSheet.absoluteFillObject, zIndex: OVERLAY_Z_INDEX, alignItems: "center" },
2215
2249
  scrim: { ...reactNative.StyleSheet.absoluteFillObject, backgroundColor: SCRIM_COLOR2 },
2216
2250
  panel: {
@@ -2239,14 +2273,14 @@ var PaletteRow = ({ item, selected, onChoose }) => {
2239
2273
  accessibilityLabel: item.label,
2240
2274
  accessibilityRole: "button",
2241
2275
  accessibilityState: { selected },
2242
- style: [styles6.row, selected ? { backgroundColor: colors.border } : void 0],
2276
+ style: [styles7.row, selected ? { backgroundColor: colors.border } : void 0],
2243
2277
  testID: item.testID ?? item.key,
2244
2278
  onPress: () => onChoose(item),
2245
2279
  children: [
2246
2280
  typeof item.renderIcon === "function" ? item.renderIcon(colors.textSecondary, NAV_ICON_SIZE) : null,
2247
- /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles6.rowText, children: [
2248
- /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles6.rowLabel, { color: colors.text }], children: item.label }),
2249
- item.hint ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles6.rowHint, { color: colors.textSecondary }], children: item.hint }) : null
2281
+ /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles7.rowText, children: [
2282
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles7.rowLabel, { color: colors.text }], children: item.label }),
2283
+ item.hint ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles7.rowHint, { color: colors.textSecondary }], children: item.hint }) : null
2250
2284
  ] })
2251
2285
  ]
2252
2286
  }
@@ -2286,14 +2320,14 @@ var CommandPalette = ({ open, items, onClose, labels, testID }) => {
2286
2320
  [results, selected, choose, onClose]
2287
2321
  );
2288
2322
  if (!open) return null;
2289
- return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles6.overlay, children: [
2323
+ return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles7.overlay, children: [
2290
2324
  /* @__PURE__ */ jsxRuntime.jsx(
2291
2325
  reactNative.Pressable,
2292
2326
  {
2293
2327
  accessibilityHint: labels.closeHint,
2294
2328
  accessibilityLabel: labels.closeLabel,
2295
2329
  accessibilityRole: "button",
2296
- style: styles6.scrim,
2330
+ style: styles7.scrim,
2297
2331
  testID: `${testID ?? "command-palette"}-scrim`,
2298
2332
  onPress: onClose
2299
2333
  }
@@ -2304,7 +2338,7 @@ var CommandPalette = ({ open, items, onClose, labels, testID }) => {
2304
2338
  "aria-modal": true,
2305
2339
  "aria-label": labels.regionLabel,
2306
2340
  role: "dialog",
2307
- style: [styles6.panel, { backgroundColor: colors.surface, borderColor: colors.border }],
2341
+ style: [styles7.panel, { backgroundColor: colors.surface, borderColor: colors.border }],
2308
2342
  testID: testID ?? "command-palette",
2309
2343
  children: [
2310
2344
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -2314,14 +2348,14 @@ var CommandPalette = ({ open, items, onClose, labels, testID }) => {
2314
2348
  accessibilityLabel: labels.placeholder,
2315
2349
  placeholder: labels.placeholder,
2316
2350
  placeholderTextColor: colors.textSecondary,
2317
- style: [styles6.input, { color: colors.text, borderBottomColor: colors.border }],
2351
+ style: [styles7.input, { color: colors.text, borderBottomColor: colors.border }],
2318
2352
  testID: `${testID ?? "command-palette"}-input`,
2319
2353
  value: query,
2320
2354
  onChangeText: setQuery,
2321
2355
  onKeyPress
2322
2356
  }
2323
2357
  ),
2324
- results.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles6.empty, { color: colors.textSecondary }], children: labels.emptyText }) : /* @__PURE__ */ jsxRuntime.jsx(reactNative.ScrollView, { keyboardShouldPersistTaps: "handled", style: styles6.list, children: results.map((item, index) => /* @__PURE__ */ jsxRuntime.jsx(PaletteRow, { item, selected: index === selected, onChoose: choose }, item.key)) })
2358
+ results.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles7.empty, { color: colors.textSecondary }], children: labels.emptyText }) : /* @__PURE__ */ jsxRuntime.jsx(reactNative.ScrollView, { keyboardShouldPersistTaps: "handled", style: styles7.list, children: results.map((item, index) => /* @__PURE__ */ jsxRuntime.jsx(PaletteRow, { item, selected: index === selected, onChoose: choose }, item.key)) })
2325
2359
  ]
2326
2360
  }
2327
2361
  )
@@ -2426,7 +2460,7 @@ var TAB_MIN_HEIGHT = 56;
2426
2460
  var LABEL_SIZE = 12;
2427
2461
  var LABEL_GAP = 2;
2428
2462
  var TAB_PADDING = 4;
2429
- var styles7 = reactNative.StyleSheet.create({
2463
+ var styles8 = reactNative.StyleSheet.create({
2430
2464
  tab: {
2431
2465
  flex: 1,
2432
2466
  minHeight: TAB_MIN_HEIGHT,
@@ -2463,31 +2497,31 @@ var BottomTab = ({
2463
2497
  accessibilityLabel: item.label,
2464
2498
  accessibilityRole: "link",
2465
2499
  accessibilityState: { selected: isActive },
2466
- style: [styles7.tab, { opacity: feedback.opacity }],
2500
+ style: [styles8.tab, { opacity: feedback.opacity }],
2467
2501
  testID: item.testID ?? item.key,
2468
2502
  onPress: () => onPress(item.route),
2469
2503
  onPressIn: () => setPressed(true),
2470
2504
  onPressOut: () => setPressed(false),
2471
2505
  ...ariaCurrentProps3(isActive),
2472
2506
  children: [
2473
- /* @__PURE__ */ jsxRuntime.jsxs(reactNative.Animated.View, { style: [styles7.iconWrap, { transform: [{ scale }] }], children: [
2507
+ /* @__PURE__ */ jsxRuntime.jsxs(reactNative.Animated.View, { style: [styles8.iconWrap, { transform: [{ scale }] }], children: [
2474
2508
  feedback.pillScale === null ? null : /* @__PURE__ */ jsxRuntime.jsx(
2475
2509
  reactNative.View,
2476
2510
  {
2477
2511
  pointerEvents: "none",
2478
- style: [styles7.pill, { backgroundColor: colors.pill, transform: [{ scale: feedback.pillScale }] }, motion]
2512
+ style: [styles8.pill, { backgroundColor: colors.pill, transform: [{ scale: feedback.pillScale }] }, motion]
2479
2513
  }
2480
2514
  ),
2481
2515
  /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [{ transform: feedback.iconTransform }, motion], children: item.renderIcon?.(color, ICON_SIZE) })
2482
2516
  ] }),
2483
- /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles7.label, { color }], children: item.label })
2517
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles8.label, { color }], children: item.label })
2484
2518
  ]
2485
2519
  }
2486
2520
  );
2487
2521
  };
2488
2522
  var DEFAULT_TEST_ID = "bottom-tab-bar";
2489
2523
  var SLIDER_HEIGHT = 3;
2490
- var styles8 = reactNative.StyleSheet.create({
2524
+ var styles9 = reactNative.StyleSheet.create({
2491
2525
  bar: { flexDirection: "row", borderTopWidth: reactNative.StyleSheet.hairlineWidth },
2492
2526
  slider: { position: "absolute", top: 0, height: SLIDER_HEIGHT }
2493
2527
  });
@@ -2514,14 +2548,14 @@ var BottomTabBar = ({
2514
2548
  {
2515
2549
  "aria-label": regionLabel,
2516
2550
  role: "navigation",
2517
- style: [styles8.bar, { backgroundColor: theme.colors.surface, borderTopColor: theme.colors.border }, containerStyle],
2551
+ style: [styles9.bar, { backgroundColor: theme.colors.surface, borderTopColor: theme.colors.border }, containerStyle],
2518
2552
  testID,
2519
2553
  children: [
2520
2554
  bar === null ? null : /* @__PURE__ */ jsxRuntime.jsx(
2521
2555
  reactNative.View,
2522
2556
  {
2523
2557
  pointerEvents: "none",
2524
- style: [styles8.slider, { left: bar.left, width: bar.width, backgroundColor: primary }, webTransition("left", barMs, reducedMotion)]
2558
+ style: [styles9.slider, { left: bar.left, width: bar.width, backgroundColor: primary }, webTransition("left", barMs, reducedMotion)]
2525
2559
  }
2526
2560
  ),
2527
2561
  items.map((item) => /* @__PURE__ */ jsxRuntime.jsx(