@dloizides/ui-nav 1.28.0 → 1.29.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/dist/index.d.mts CHANGED
@@ -398,8 +398,7 @@ declare enum NavHoverFeedback {
398
398
  *
399
399
  * Contract discipline (same as the rest of `@dloizides/ui-nav`): NO FM, router,
400
400
  * icon set, or store imports. Labels are pre-localized strings, icons are render
401
- * slots, and every colour is read from the theme. This is purely additive —
402
- * `Sidebar`/`Topbar` are unchanged and the new overflow labels are optional.
401
+ * slots, and every colour is read from the theme.
403
402
  */
404
403
 
405
404
  interface NavBarProps {
@@ -463,6 +462,15 @@ interface NavBarProps {
463
462
  */
464
463
  declare const NavBar: ({ barTheme, ...props }: NavBarProps) => React.ReactElement;
465
464
 
465
+ interface BrandHomeLinkProps {
466
+ route: string;
467
+ label: string;
468
+ onNavigate: (route: string) => void;
469
+ children: React.ReactNode;
470
+ }
471
+ /** The app logo as a link to the app's home route, for `NavBar` and apps with their own header. */
472
+ declare const BrandHomeLink: ({ route, label, onNavigate, children }: BrandHomeLinkProps) => React.ReactElement;
473
+
466
474
  /**
467
475
  * `Nav` — ONE component that renders EITHER the horizontal top bar (`NavBar`) or the
468
476
  * vertical rail (`Sidebar`) from the same `NavItem[]`, chosen by a single
@@ -1126,6 +1134,7 @@ declare const NAV_TEST_IDS: {
1126
1134
  readonly pillNavMenu: "pill-nav-menu";
1127
1135
  /** the optional middle slot of `Topbar` (rendered only when `center` is passed). */
1128
1136
  readonly topbarCenter: "topbar-center";
1137
+ readonly brandHome: "nav-brand-home";
1129
1138
  };
1130
1139
  /** Suffixes appended to an `AppShell`'s required `testID` to name its regions. */
1131
1140
  declare const APP_SHELL_SUFFIX: {
@@ -1644,4 +1653,4 @@ interface BottomTabBarProps {
1644
1653
  /** Phone tab bar; `tabFeedback` picks the press feedback, `Default` adds no motion. */
1645
1654
  declare const BottomTabBar: ({ items, pathname, onNavigate, tabFeedback, navigateHint, regionLabel, containerStyle, testID, }: BottomTabBarProps) => React.ReactElement;
1646
1655
 
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 };
1656
+ 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, BrandHomeLink, type BrandHomeLinkProps, 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
@@ -398,8 +398,7 @@ declare enum NavHoverFeedback {
398
398
  *
399
399
  * Contract discipline (same as the rest of `@dloizides/ui-nav`): NO FM, router,
400
400
  * icon set, or store imports. Labels are pre-localized strings, icons are render
401
- * slots, and every colour is read from the theme. This is purely additive —
402
- * `Sidebar`/`Topbar` are unchanged and the new overflow labels are optional.
401
+ * slots, and every colour is read from the theme.
403
402
  */
404
403
 
405
404
  interface NavBarProps {
@@ -463,6 +462,15 @@ interface NavBarProps {
463
462
  */
464
463
  declare const NavBar: ({ barTheme, ...props }: NavBarProps) => React.ReactElement;
465
464
 
465
+ interface BrandHomeLinkProps {
466
+ route: string;
467
+ label: string;
468
+ onNavigate: (route: string) => void;
469
+ children: React.ReactNode;
470
+ }
471
+ /** The app logo as a link to the app's home route, for `NavBar` and apps with their own header. */
472
+ declare const BrandHomeLink: ({ route, label, onNavigate, children }: BrandHomeLinkProps) => React.ReactElement;
473
+
466
474
  /**
467
475
  * `Nav` — ONE component that renders EITHER the horizontal top bar (`NavBar`) or the
468
476
  * vertical rail (`Sidebar`) from the same `NavItem[]`, chosen by a single
@@ -1126,6 +1134,7 @@ declare const NAV_TEST_IDS: {
1126
1134
  readonly pillNavMenu: "pill-nav-menu";
1127
1135
  /** the optional middle slot of `Topbar` (rendered only when `center` is passed). */
1128
1136
  readonly topbarCenter: "topbar-center";
1137
+ readonly brandHome: "nav-brand-home";
1129
1138
  };
1130
1139
  /** Suffixes appended to an `AppShell`'s required `testID` to name its regions. */
1131
1140
  declare const APP_SHELL_SUFFIX: {
@@ -1644,4 +1653,4 @@ interface BottomTabBarProps {
1644
1653
  /** Phone tab bar; `tabFeedback` picks the press feedback, `Default` adds no motion. */
1645
1654
  declare const BottomTabBar: ({ items, pathname, onNavigate, tabFeedback, navigateHint, regionLabel, containerStyle, testID, }: BottomTabBarProps) => React.ReactElement;
1646
1655
 
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 };
1656
+ 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, BrandHomeLink, type BrandHomeLinkProps, 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
@@ -883,7 +883,8 @@ var NAV_TEST_IDS = {
883
883
  /** the compact "Current ▾" trigger of `PillNav` with `variant={PillNavVariant.Menu}`. */
884
884
  pillNavMenu: "pill-nav-menu",
885
885
  /** the optional middle slot of `Topbar` (rendered only when `center` is passed). */
886
- topbarCenter: "topbar-center"
886
+ topbarCenter: "topbar-center",
887
+ brandHome: "nav-brand-home"
887
888
  };
888
889
  var APP_SHELL_SUFFIX = {
889
890
  content: "-content",
@@ -1390,6 +1391,7 @@ var NavBarInner = ({
1390
1391
  () => contentMaxWidth === void 0 ? null : { maxWidth: contentMaxWidth, width: "100%", alignSelf: "center" },
1391
1392
  [contentMaxWidth]
1392
1393
  );
1394
+ React.useEffect(() => setOpen(false), [pathname]);
1393
1395
  const toggleMenu = React.useCallback(() => setOpen((v) => !v), []);
1394
1396
  const handlePress = React.useCallback(
1395
1397
  (route) => {
@@ -1537,6 +1539,39 @@ var NavBar = ({ barTheme, ...props }) => {
1537
1539
  if (barTheme === void 0) return /* @__PURE__ */ jsxRuntime.jsx(NavBarInner, { ...props });
1538
1540
  return /* @__PURE__ */ jsxRuntime.jsx(uiFeedback.UiProvider, { navigate, t, theme: barTheme, children: /* @__PURE__ */ jsxRuntime.jsx(NavBarInner, { ...props }) });
1539
1541
  };
1542
+ var styles3 = reactNative.StyleSheet.create({
1543
+ hitBox: { minHeight: MIN_TOUCH_TARGET, minWidth: MIN_TOUCH_TARGET, justifyContent: "center" }
1544
+ });
1545
+ function anchorProps(route) {
1546
+ return { href: route };
1547
+ }
1548
+ function hasNewTabModifier(event) {
1549
+ const { ctrlKey, metaKey, shiftKey } = event.nativeEvent;
1550
+ return ctrlKey === true || metaKey === true || shiftKey === true;
1551
+ }
1552
+ var BrandHomeLink = ({ route, label, onNavigate, children }) => {
1553
+ const { theme } = uiFeedback.useUi();
1554
+ const [focused, setFocused] = React.useState(false);
1555
+ const handlePress = (event) => {
1556
+ if (hasNewTabModifier(event)) return;
1557
+ event.preventDefault();
1558
+ onNavigate(route);
1559
+ };
1560
+ return /* @__PURE__ */ jsxRuntime.jsx(
1561
+ reactNative.Pressable,
1562
+ {
1563
+ accessibilityLabel: label,
1564
+ accessibilityRole: "link",
1565
+ style: [styles3.hitBox, focusRingStyle(focused, theme.palette.primary["500"])],
1566
+ testID: NAV_TEST_IDS.brandHome,
1567
+ onBlur: () => setFocused(false),
1568
+ onFocus: () => setFocused(true),
1569
+ onPress: handlePress,
1570
+ ...anchorProps(route),
1571
+ children
1572
+ }
1573
+ );
1574
+ };
1540
1575
  var Nav = (props) => {
1541
1576
  if (props.orientation === "vertical") {
1542
1577
  return /* @__PURE__ */ jsxRuntime.jsx(Sidebar, { ...props });
@@ -1554,7 +1589,7 @@ function resolveRailMode({ viewport, hasSidebar, hasCollapsed, range }) {
1554
1589
  }
1555
1590
  return viewport >= RAIL_FULL_BREAKPOINT ? "full" : "drawer";
1556
1591
  }
1557
- var styles3 = reactNative.StyleSheet.create({
1592
+ var styles4 = reactNative.StyleSheet.create({
1558
1593
  band: { left: 0, position: "absolute", right: 0, top: 0 }
1559
1594
  });
1560
1595
  function readBackdrop(theme) {
@@ -1577,7 +1612,7 @@ function resolveShellBackdrop(theme, viewport, contentPadding, testID) {
1577
1612
  reactNative.View,
1578
1613
  {
1579
1614
  pointerEvents: "none",
1580
- style: [styles3.band, { backgroundColor: band.color, height: contentPadding + overlap }],
1615
+ style: [styles4.band, { backgroundColor: band.color, height: contentPadding + overlap }],
1581
1616
  testID: `${testID}${APP_SHELL_SUFFIX.band}`
1582
1617
  }
1583
1618
  );
@@ -1589,7 +1624,7 @@ var CARD_BORDER_WIDTH = 1;
1589
1624
  var CARD_TITLE_FONT_SIZE = 16;
1590
1625
  var CARD_MESSAGE_FONT_SIZE = 14;
1591
1626
  var CARD_TITLE_MARGIN_BOTTOM = 8;
1592
- var styles4 = reactNative.StyleSheet.create({
1627
+ var styles5 = reactNative.StyleSheet.create({
1593
1628
  // Loading is a TRANSITIONAL state: the spinner is an absolute overlay ON TOP of
1594
1629
  // stably-mounted children, never a replacement for them. Replacing children here
1595
1630
  // would unmount whatever they host (e.g. a navigator), and on remount React
@@ -1621,9 +1656,9 @@ function MessageCard({
1621
1656
  }) {
1622
1657
  const { theme } = uiFeedback.useUi();
1623
1658
  const colors = theme.colors;
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 })
1659
+ return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: [styles5.card, { backgroundColor: colors.surface, borderColor: accentColor }], testID, children: [
1660
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles5.cardTitle, { color: accentColor }], children: message.titleText }),
1661
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles5.cardMessage, { color: colors.textSecondary }], children: message.messageText })
1627
1662
  ] });
1628
1663
  }
1629
1664
  function useContentBody(state, children, testID) {
@@ -1635,12 +1670,12 @@ function useContentBody(state, children, testID) {
1635
1670
  return /* @__PURE__ */ jsxRuntime.jsx(MessageCard, { accentColor: errorColor, message: state.forbidden, testID: `${testID}${APP_SHELL_SUFFIX.forbidden}` });
1636
1671
  if (state.error)
1637
1672
  return /* @__PURE__ */ jsxRuntime.jsx(MessageCard, { accentColor: errorColor, message: state.error, testID: `${testID}${APP_SHELL_SUFFIX.error}` });
1638
- return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles4.loadingHost, children: [
1673
+ return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles5.loadingHost, children: [
1639
1674
  children,
1640
1675
  state.loading === true ? /* @__PURE__ */ jsxRuntime.jsx(
1641
1676
  reactNative.View,
1642
1677
  {
1643
- style: [styles4.loadingOverlay, { backgroundColor: theme.colors.background }],
1678
+ style: [styles5.loadingOverlay, { backgroundColor: theme.colors.background }],
1644
1679
  testID: `${testID}${APP_SHELL_SUFFIX.loading}`,
1645
1680
  children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.ActivityIndicator, { color: primary, size: "large" })
1646
1681
  }
@@ -1667,7 +1702,7 @@ var DEFAULT_DRAWER_LABELS = {
1667
1702
  closeLabel: "Close menu",
1668
1703
  closeHint: "Close the navigation menu"
1669
1704
  };
1670
- var styles5 = reactNative.StyleSheet.create({
1705
+ var styles6 = reactNative.StyleSheet.create({
1671
1706
  overlay: { ...reactNative.StyleSheet.absoluteFillObject, zIndex: DRAWER_Z_INDEX },
1672
1707
  // The scrim's tap-to-close hit area is anchored to the RIGHT of the panel
1673
1708
  // (`left: DRAWER_WIDTH`), so it NEVER overlaps the panel: nav-item taps always
@@ -1702,10 +1737,10 @@ var MenuToggle = ({ label, hint, onOpen, testID }) => {
1702
1737
  accessibilityHint: hint,
1703
1738
  accessibilityLabel: label,
1704
1739
  ringColor: theme.palette.primary["500"],
1705
- style: styles5.menuToggle,
1740
+ style: styles6.menuToggle,
1706
1741
  testID,
1707
1742
  onPress: onOpen,
1708
- children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles5.menuToggleGlyph, { color: theme.colors.text }], children: MENU_GLYPH2 })
1743
+ children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles6.menuToggleGlyph, { color: theme.colors.text }], children: MENU_GLYPH2 })
1709
1744
  }
1710
1745
  );
1711
1746
  };
@@ -1731,14 +1766,14 @@ var MobileDrawer = ({
1731
1766
  node.addEventListener("click", handleClick);
1732
1767
  return () => node.removeEventListener("click", handleClick);
1733
1768
  }, [onClose]);
1734
- return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles5.overlay, children: [
1769
+ return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles6.overlay, children: [
1735
1770
  /* @__PURE__ */ jsxRuntime.jsx(
1736
1771
  reactNative.Pressable,
1737
1772
  {
1738
1773
  accessibilityHint: labels.closeHint,
1739
1774
  accessibilityLabel: labels.closeLabel,
1740
1775
  accessibilityRole: "button",
1741
- style: styles5.scrim,
1776
+ style: styles6.scrim,
1742
1777
  testID: scrimTestID,
1743
1778
  onPress: onClose
1744
1779
  }
@@ -1749,7 +1784,7 @@ var MobileDrawer = ({
1749
1784
  ref: panelRef,
1750
1785
  "aria-modal": true,
1751
1786
  role: "dialog",
1752
- style: [styles5.panel, { backgroundColor: theme.colors.surface }],
1787
+ style: [styles6.panel, { backgroundColor: theme.colors.surface }],
1753
1788
  testID: drawerTestID,
1754
1789
  children: renderTextSlot(sidebar, { color: theme.colors.text })
1755
1790
  }
@@ -1767,7 +1802,7 @@ function useContentMaxWidth(width) {
1767
1802
  return resolveContentMaxWidth(width, viewport);
1768
1803
  }
1769
1804
  var DEFAULT_CONTENT_PADDING = 24;
1770
- var styles6 = reactNative.StyleSheet.create({
1805
+ var styles7 = reactNative.StyleSheet.create({
1771
1806
  root: { flex: 1 },
1772
1807
  // Auth-pending spinner: an opaque absolute overlay over the mounted shell, so
1773
1808
  // `children` (and any navigator they host) are never unmounted while the gate
@@ -1829,13 +1864,13 @@ var AppShell = ({
1829
1864
  }, [isUnauthenticated, gate]);
1830
1865
  const gatePending = gate?.pending === true;
1831
1866
  if (isUnauthenticated) return null;
1832
- const columnStyle = maxWidth === "full" ? styles6.columnFull : [styles6.columnCapped, { maxWidth }];
1833
- const navInnerStyle = chromeAlignment === "content" && maxWidth !== "full" ? [styles6.chromeCapped, { maxWidth, paddingHorizontal: contentPadding }] : void 0;
1867
+ const columnStyle = maxWidth === "full" ? styles7.columnFull : [styles7.columnCapped, { maxWidth }];
1868
+ const navInnerStyle = chromeAlignment === "content" && maxWidth !== "full" ? [styles7.chromeCapped, { maxWidth, paddingHorizontal: contentPadding }] : void 0;
1834
1869
  const scroller = /* @__PURE__ */ jsxRuntime.jsxs(
1835
1870
  reactNative.ScrollView,
1836
1871
  {
1837
- contentContainerStyle: [styles6.scrollContent, { padding: contentPadding }],
1838
- style: styles6.scroll,
1872
+ contentContainerStyle: [styles7.scrollContent, { padding: contentPadding }],
1873
+ style: styles7.scroll,
1839
1874
  testID: `${testID}${APP_SHELL_SUFFIX.content}`,
1840
1875
  children: [
1841
1876
  look.band,
@@ -1843,7 +1878,7 @@ var AppShell = ({
1843
1878
  ]
1844
1879
  }
1845
1880
  );
1846
- const headerRegion = useDrawer ? /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: [styles6.headerRow, look.chrome], testID: `${testID}${APP_SHELL_SUFFIX.header}`, children: [
1881
+ const headerRegion = useDrawer ? /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: [styles7.headerRow, look.chrome], testID: `${testID}${APP_SHELL_SUFFIX.header}`, children: [
1847
1882
  /* @__PURE__ */ jsxRuntime.jsx(
1848
1883
  MenuToggle,
1849
1884
  {
@@ -1853,14 +1888,14 @@ var AppShell = ({
1853
1888
  onOpen: openDrawer
1854
1889
  }
1855
1890
  ),
1856
- /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: styles6.headerFill, children: renderTextSlot(header, { color: look.chromeText }) })
1891
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: styles7.headerFill, children: renderTextSlot(header, { color: look.chromeText }) })
1857
1892
  ] }) : /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: look.chrome, testID: `${testID}${APP_SHELL_SUFFIX.header}`, children: renderTextSlot(header, { color: look.chromeText }) });
1858
1893
  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;
1859
- return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: [styles6.root, look.root], testID, children: [
1894
+ return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: [styles7.root, look.root], testID, children: [
1860
1895
  headerRegion,
1861
1896
  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,
1862
1897
  banner !== void 0 ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { testID: `${testID}${APP_SHELL_SUFFIX.banner}`, children: renderTextSlot(banner, { color: theme.colors.text }) }) : null,
1863
- railRegion !== null ? /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles6.bodyRow, children: [
1898
+ railRegion !== null ? /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles7.bodyRow, children: [
1864
1899
  railRegion,
1865
1900
  scroller
1866
1901
  ] }) : scroller,
@@ -1877,7 +1912,7 @@ var AppShell = ({
1877
1912
  gatePending ? /* @__PURE__ */ jsxRuntime.jsx(
1878
1913
  reactNative.View,
1879
1914
  {
1880
- style: [styles6.pendingOverlay, { backgroundColor: theme.colors.background }],
1915
+ style: [styles7.pendingOverlay, { backgroundColor: theme.colors.background }],
1881
1916
  testID: `${testID}${APP_SHELL_SUFFIX.pending}`,
1882
1917
  children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.ActivityIndicator, { color: primary, size: "large" })
1883
1918
  }
@@ -2244,7 +2279,7 @@ var KEY_DOWN = "ArrowDown";
2244
2279
  var KEY_UP = "ArrowUp";
2245
2280
  var KEY_ENTER = "Enter";
2246
2281
  var KEY_ESCAPE = "Escape";
2247
- var styles7 = reactNative.StyleSheet.create({
2282
+ var styles8 = reactNative.StyleSheet.create({
2248
2283
  overlay: { ...reactNative.StyleSheet.absoluteFillObject, zIndex: OVERLAY_Z_INDEX, alignItems: "center" },
2249
2284
  scrim: { ...reactNative.StyleSheet.absoluteFillObject, backgroundColor: SCRIM_COLOR2 },
2250
2285
  panel: {
@@ -2273,14 +2308,14 @@ var PaletteRow = ({ item, selected, onChoose }) => {
2273
2308
  accessibilityLabel: item.label,
2274
2309
  accessibilityRole: "button",
2275
2310
  accessibilityState: { selected },
2276
- style: [styles7.row, selected ? { backgroundColor: colors.border } : void 0],
2311
+ style: [styles8.row, selected ? { backgroundColor: colors.border } : void 0],
2277
2312
  testID: item.testID ?? item.key,
2278
2313
  onPress: () => onChoose(item),
2279
2314
  children: [
2280
2315
  typeof item.renderIcon === "function" ? item.renderIcon(colors.textSecondary, NAV_ICON_SIZE) : 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
2316
+ /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles8.rowText, children: [
2317
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles8.rowLabel, { color: colors.text }], children: item.label }),
2318
+ item.hint ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles8.rowHint, { color: colors.textSecondary }], children: item.hint }) : null
2284
2319
  ] })
2285
2320
  ]
2286
2321
  }
@@ -2320,14 +2355,14 @@ var CommandPalette = ({ open, items, onClose, labels, testID }) => {
2320
2355
  [results, selected, choose, onClose]
2321
2356
  );
2322
2357
  if (!open) return null;
2323
- return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles7.overlay, children: [
2358
+ return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles8.overlay, children: [
2324
2359
  /* @__PURE__ */ jsxRuntime.jsx(
2325
2360
  reactNative.Pressable,
2326
2361
  {
2327
2362
  accessibilityHint: labels.closeHint,
2328
2363
  accessibilityLabel: labels.closeLabel,
2329
2364
  accessibilityRole: "button",
2330
- style: styles7.scrim,
2365
+ style: styles8.scrim,
2331
2366
  testID: `${testID ?? "command-palette"}-scrim`,
2332
2367
  onPress: onClose
2333
2368
  }
@@ -2338,7 +2373,7 @@ var CommandPalette = ({ open, items, onClose, labels, testID }) => {
2338
2373
  "aria-modal": true,
2339
2374
  "aria-label": labels.regionLabel,
2340
2375
  role: "dialog",
2341
- style: [styles7.panel, { backgroundColor: colors.surface, borderColor: colors.border }],
2376
+ style: [styles8.panel, { backgroundColor: colors.surface, borderColor: colors.border }],
2342
2377
  testID: testID ?? "command-palette",
2343
2378
  children: [
2344
2379
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -2348,14 +2383,14 @@ var CommandPalette = ({ open, items, onClose, labels, testID }) => {
2348
2383
  accessibilityLabel: labels.placeholder,
2349
2384
  placeholder: labels.placeholder,
2350
2385
  placeholderTextColor: colors.textSecondary,
2351
- style: [styles7.input, { color: colors.text, borderBottomColor: colors.border }],
2386
+ style: [styles8.input, { color: colors.text, borderBottomColor: colors.border }],
2352
2387
  testID: `${testID ?? "command-palette"}-input`,
2353
2388
  value: query,
2354
2389
  onChangeText: setQuery,
2355
2390
  onKeyPress
2356
2391
  }
2357
2392
  ),
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)) })
2393
+ results.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles8.empty, { color: colors.textSecondary }], children: labels.emptyText }) : /* @__PURE__ */ jsxRuntime.jsx(reactNative.ScrollView, { keyboardShouldPersistTaps: "handled", style: styles8.list, children: results.map((item, index) => /* @__PURE__ */ jsxRuntime.jsx(PaletteRow, { item, selected: index === selected, onChoose: choose }, item.key)) })
2359
2394
  ]
2360
2395
  }
2361
2396
  )
@@ -2460,7 +2495,7 @@ var TAB_MIN_HEIGHT = 56;
2460
2495
  var LABEL_SIZE = 12;
2461
2496
  var LABEL_GAP = 2;
2462
2497
  var TAB_PADDING = 4;
2463
- var styles8 = reactNative.StyleSheet.create({
2498
+ var styles9 = reactNative.StyleSheet.create({
2464
2499
  tab: {
2465
2500
  flex: 1,
2466
2501
  minHeight: TAB_MIN_HEIGHT,
@@ -2497,31 +2532,31 @@ var BottomTab = ({
2497
2532
  accessibilityLabel: item.label,
2498
2533
  accessibilityRole: "link",
2499
2534
  accessibilityState: { selected: isActive },
2500
- style: [styles8.tab, { opacity: feedback.opacity }],
2535
+ style: [styles9.tab, { opacity: feedback.opacity }],
2501
2536
  testID: item.testID ?? item.key,
2502
2537
  onPress: () => onPress(item.route),
2503
2538
  onPressIn: () => setPressed(true),
2504
2539
  onPressOut: () => setPressed(false),
2505
2540
  ...ariaCurrentProps3(isActive),
2506
2541
  children: [
2507
- /* @__PURE__ */ jsxRuntime.jsxs(reactNative.Animated.View, { style: [styles8.iconWrap, { transform: [{ scale }] }], children: [
2542
+ /* @__PURE__ */ jsxRuntime.jsxs(reactNative.Animated.View, { style: [styles9.iconWrap, { transform: [{ scale }] }], children: [
2508
2543
  feedback.pillScale === null ? null : /* @__PURE__ */ jsxRuntime.jsx(
2509
2544
  reactNative.View,
2510
2545
  {
2511
2546
  pointerEvents: "none",
2512
- style: [styles8.pill, { backgroundColor: colors.pill, transform: [{ scale: feedback.pillScale }] }, motion]
2547
+ style: [styles9.pill, { backgroundColor: colors.pill, transform: [{ scale: feedback.pillScale }] }, motion]
2513
2548
  }
2514
2549
  ),
2515
2550
  /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [{ transform: feedback.iconTransform }, motion], children: item.renderIcon?.(color, ICON_SIZE) })
2516
2551
  ] }),
2517
- /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles8.label, { color }], children: item.label })
2552
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles9.label, { color }], children: item.label })
2518
2553
  ]
2519
2554
  }
2520
2555
  );
2521
2556
  };
2522
2557
  var DEFAULT_TEST_ID = "bottom-tab-bar";
2523
2558
  var SLIDER_HEIGHT = 3;
2524
- var styles9 = reactNative.StyleSheet.create({
2559
+ var styles10 = reactNative.StyleSheet.create({
2525
2560
  bar: { flexDirection: "row", borderTopWidth: reactNative.StyleSheet.hairlineWidth },
2526
2561
  slider: { position: "absolute", top: 0, height: SLIDER_HEIGHT }
2527
2562
  });
@@ -2548,14 +2583,14 @@ var BottomTabBar = ({
2548
2583
  {
2549
2584
  "aria-label": regionLabel,
2550
2585
  role: "navigation",
2551
- style: [styles9.bar, { backgroundColor: theme.colors.surface, borderTopColor: theme.colors.border }, containerStyle],
2586
+ style: [styles10.bar, { backgroundColor: theme.colors.surface, borderTopColor: theme.colors.border }, containerStyle],
2552
2587
  testID,
2553
2588
  children: [
2554
2589
  bar === null ? null : /* @__PURE__ */ jsxRuntime.jsx(
2555
2590
  reactNative.View,
2556
2591
  {
2557
2592
  pointerEvents: "none",
2558
- style: [styles9.slider, { left: bar.left, width: bar.width, backgroundColor: primary }, webTransition("left", barMs, reducedMotion)]
2593
+ style: [styles10.slider, { left: bar.left, width: bar.width, backgroundColor: primary }, webTransition("left", barMs, reducedMotion)]
2559
2594
  }
2560
2595
  ),
2561
2596
  items.map((item) => /* @__PURE__ */ jsxRuntime.jsx(
@@ -2582,6 +2617,7 @@ exports.APP_SHELL_SUFFIX = APP_SHELL_SUFFIX;
2582
2617
  exports.AppShell = AppShell;
2583
2618
  exports.BASE_INDENT = BASE_INDENT;
2584
2619
  exports.BottomTabBar = BottomTabBar;
2620
+ exports.BrandHomeLink = BrandHomeLink;
2585
2621
  exports.CHEVRON_ICON_SIZE = CHEVRON_ICON_SIZE;
2586
2622
  exports.CollapsedRail = CollapsedRail;
2587
2623
  exports.CommandPalette = CommandPalette;