@dloizides/ui-nav 1.23.2 → 1.25.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,14 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.25.0
4
+
5
+ - `PillNav` / `PillNavMenu` get an optional `menuHeading`: a visible, non-focusable heading at the top of the open `Menu` that also names it (needs `@dloizides/ui-layout` >= 1.30.0) (KEFI-EDITOR-HEADER-1 "Compact the landing editor header").
6
+
7
+ ## 1.24.0
8
+
9
+ - `Topbar` gets an optional `center` slot between `left` and the actions (testID `topbar-center`); callers that do not pass it render as before (KEFI-EDITOR-HEADER-1 "Compact the landing editor header").
10
+ - `PillNav` gets `variant={PillNavVariant.Menu}`: one compact "Current ▾" trigger (44px pill) opening a menu of the same items through the shared `ModalDropdown` (role menu, arrow keys / Home / End / Enter / Escape, highlight starts on the current item). New optional props `menuHint`, `menuTestID`, `dropdownVariant`; new exports `PillNavVariant`, `resolvePillMenuLabel`. Default stays `Pills`.
11
+
3
12
  ## 1.23.2
4
13
 
5
14
  - Topbar account-name button and language toggle get a 44x44 minimum hit box (KEFI-HEADER-HITBOX-1 "kefi-web header account control reaches 44x44").
package/dist/index.d.mts CHANGED
@@ -333,6 +333,8 @@ interface AccountState {
333
333
  interface TopbarProps {
334
334
  /** Left slot — typically the tenant logo. */
335
335
  left?: React.ReactNode;
336
+ /** Optional middle slot between `left` and the actions (e.g. a compact dashboard switch). */
337
+ center?: React.ReactNode;
336
338
  /**
337
339
  * Optional language toggle. `testID` is optional but the repo standard wants
338
340
  * one — before 1.10.0 this inline type declared none, so a testID was
@@ -359,7 +361,7 @@ interface TopbarProps {
359
361
  /** Extra container style overrides. */
360
362
  containerStyle?: ViewStyle | ViewStyle[];
361
363
  }
362
- declare const Topbar: ({ left, language, notificationSlot, user, account, logout, containerStyle, }: TopbarProps) => React.ReactElement;
364
+ declare const Topbar: ({ left, center, language, notificationSlot, user, account, logout, containerStyle, }: TopbarProps) => React.ReactElement;
363
365
 
364
366
  /**
365
367
  * NavBar — the config-driven HORIZONTAL top navigation bar. It is the top-bar
@@ -893,6 +895,12 @@ interface CollapsedRailProps {
893
895
  }
894
896
  declare const CollapsedRail: ({ items, pathname, onNavigate, regionLabel, navigateHint, header, footer, containerStyle, }: CollapsedRailProps) => React.ReactElement;
895
897
 
898
+ /** How `PillNav` lays out its items: a wrapping pill row, or one compact "Current ▾" menu button. */
899
+ declare enum PillNavVariant {
900
+ Pills = "pills",
901
+ Menu = "menu"
902
+ }
903
+
896
904
  /**
897
905
  * PillNav — a horizontal row of fully-rounded "pills", the cross-navigation
898
906
  * switcher promoted from kefi's coral dashboard nav. It renders the same
@@ -929,10 +937,23 @@ interface PillNavProps {
929
937
  * (show when non-empty); kefi passes 2 (a lone dashboard needs no switcher).
930
938
  */
931
939
  minItems?: number;
940
+ /** `Pills` (default) renders the wrapping row; `Menu` renders one compact "Current ▾" menu button. */
941
+ variant?: PillNavVariant;
942
+ /** a11y hint for the `Menu` trigger. Defaults to `regionLabel`. */
943
+ menuHint?: string;
944
+ /** Visible heading at the top of the open `Menu` (e.g. "Your dashboards"); also names the menu. */
945
+ menuHeading?: string;
946
+ /** testID for the `Menu` trigger. Defaults to `NAV_TEST_IDS.pillNavMenu`. */
947
+ menuTestID?: string;
948
+ /** Forces the `Menu` dropdown variant (default: responsive). */
949
+ dropdownVariant?: DropdownVariant;
932
950
  /** Extra container style overrides. */
933
951
  containerStyle?: ViewStyle | ViewStyle[];
934
952
  }
935
- declare const PillNav: ({ items, pathname, onNavigate, regionLabel, navigateHint, minItems, containerStyle, }: PillNavProps) => React.ReactElement | null;
953
+ declare const PillNav: ({ items, pathname, onNavigate, regionLabel, navigateHint, minItems, variant, menuHint, menuHeading, menuTestID, dropdownVariant, containerStyle, }: PillNavProps) => React.ReactElement | null;
954
+
955
+ /** The label the compact menu trigger shows: the active item's label, else `fallback`. */
956
+ declare function resolvePillMenuLabel(items: NavItem[], pathname: string, fallback: string): string;
936
957
 
937
958
  /**
938
959
  * DarkModeControl — the shared colour-scheme control promoted from erevna /
@@ -1077,6 +1098,10 @@ declare const NAV_TEST_IDS: {
1077
1098
  readonly navBarLinks: "navbar-links";
1078
1099
  /** the "More ▾" priority+ overflow trigger in the horizontal NavBar (shown when items spill). */
1079
1100
  readonly navBarOverflow: "navbar-overflow";
1101
+ /** the compact "Current ▾" trigger of `PillNav` with `variant={PillNavVariant.Menu}`. */
1102
+ readonly pillNavMenu: "pill-nav-menu";
1103
+ /** the optional middle slot of `Topbar` (rendered only when `center` is passed). */
1104
+ readonly topbarCenter: "topbar-center";
1080
1105
  };
1081
1106
  /** Suffixes appended to an `AppShell`'s required `testID` to name its regions. */
1082
1107
  declare const APP_SHELL_SUFFIX: {
@@ -1333,6 +1358,13 @@ declare const navStyles: {
1333
1358
  topbarLeft: {
1334
1359
  flex: number;
1335
1360
  };
1361
+ topbarCenter: {
1362
+ flexShrink: number;
1363
+ minWidth: number;
1364
+ flexDirection: "row";
1365
+ alignItems: "center";
1366
+ marginHorizontal: number;
1367
+ };
1336
1368
  topbarRight: {
1337
1369
  flexDirection: "row";
1338
1370
  alignItems: "center";
@@ -1413,6 +1445,9 @@ declare const pillNavStyles: {
1413
1445
  fontSize: number;
1414
1446
  fontWeight: "600";
1415
1447
  };
1448
+ menuTriggerText: {
1449
+ flexShrink: number;
1450
+ };
1416
1451
  };
1417
1452
  declare const darkModeStyles: {
1418
1453
  segmentRow: {
@@ -1552,4 +1587,4 @@ declare const NAV_ICON_SIZE = 14;
1552
1587
  /** Chevron icon size for expandable sections. */
1553
1588
  declare const CHEVRON_ICON_SIZE = 12;
1554
1589
 
1555
- export { ACTIVE_BORDER_RADIUS, ACTIVE_TINT_OPACITY, APP_SHELL_SUFFIX, type AccountPlan, type AccountState, AppShell, type AppShellProps, type AppShellWidth, BASE_INDENT, 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, 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, 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, Topbar, type TopbarAction, type TopbarProps, type TopbarUser, accessibleNavItems, collapsedRailStyles, darkModeStyles, expandableStyles, filterCommands, filterNavItems, isFilterActive, isRouteActive, navStyles, pillNavStyles, resolveActiveRoute, resolveContentMaxWidth, resolveRailMode, resolveSearchAffordance, roleRoutesToNavItems, searchTokens, useCommandPaletteHotkey, useContentMaxWidth };
1590
+ export { ACTIVE_BORDER_RADIUS, ACTIVE_TINT_OPACITY, APP_SHELL_SUFFIX, type AccountPlan, type AccountState, AppShell, type AppShellProps, type AppShellWidth, BASE_INDENT, 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, 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, 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
@@ -333,6 +333,8 @@ interface AccountState {
333
333
  interface TopbarProps {
334
334
  /** Left slot — typically the tenant logo. */
335
335
  left?: React.ReactNode;
336
+ /** Optional middle slot between `left` and the actions (e.g. a compact dashboard switch). */
337
+ center?: React.ReactNode;
336
338
  /**
337
339
  * Optional language toggle. `testID` is optional but the repo standard wants
338
340
  * one — before 1.10.0 this inline type declared none, so a testID was
@@ -359,7 +361,7 @@ interface TopbarProps {
359
361
  /** Extra container style overrides. */
360
362
  containerStyle?: ViewStyle | ViewStyle[];
361
363
  }
362
- declare const Topbar: ({ left, language, notificationSlot, user, account, logout, containerStyle, }: TopbarProps) => React.ReactElement;
364
+ declare const Topbar: ({ left, center, language, notificationSlot, user, account, logout, containerStyle, }: TopbarProps) => React.ReactElement;
363
365
 
364
366
  /**
365
367
  * NavBar — the config-driven HORIZONTAL top navigation bar. It is the top-bar
@@ -893,6 +895,12 @@ interface CollapsedRailProps {
893
895
  }
894
896
  declare const CollapsedRail: ({ items, pathname, onNavigate, regionLabel, navigateHint, header, footer, containerStyle, }: CollapsedRailProps) => React.ReactElement;
895
897
 
898
+ /** How `PillNav` lays out its items: a wrapping pill row, or one compact "Current ▾" menu button. */
899
+ declare enum PillNavVariant {
900
+ Pills = "pills",
901
+ Menu = "menu"
902
+ }
903
+
896
904
  /**
897
905
  * PillNav — a horizontal row of fully-rounded "pills", the cross-navigation
898
906
  * switcher promoted from kefi's coral dashboard nav. It renders the same
@@ -929,10 +937,23 @@ interface PillNavProps {
929
937
  * (show when non-empty); kefi passes 2 (a lone dashboard needs no switcher).
930
938
  */
931
939
  minItems?: number;
940
+ /** `Pills` (default) renders the wrapping row; `Menu` renders one compact "Current ▾" menu button. */
941
+ variant?: PillNavVariant;
942
+ /** a11y hint for the `Menu` trigger. Defaults to `regionLabel`. */
943
+ menuHint?: string;
944
+ /** Visible heading at the top of the open `Menu` (e.g. "Your dashboards"); also names the menu. */
945
+ menuHeading?: string;
946
+ /** testID for the `Menu` trigger. Defaults to `NAV_TEST_IDS.pillNavMenu`. */
947
+ menuTestID?: string;
948
+ /** Forces the `Menu` dropdown variant (default: responsive). */
949
+ dropdownVariant?: DropdownVariant;
932
950
  /** Extra container style overrides. */
933
951
  containerStyle?: ViewStyle | ViewStyle[];
934
952
  }
935
- declare const PillNav: ({ items, pathname, onNavigate, regionLabel, navigateHint, minItems, containerStyle, }: PillNavProps) => React.ReactElement | null;
953
+ declare const PillNav: ({ items, pathname, onNavigate, regionLabel, navigateHint, minItems, variant, menuHint, menuHeading, menuTestID, dropdownVariant, containerStyle, }: PillNavProps) => React.ReactElement | null;
954
+
955
+ /** The label the compact menu trigger shows: the active item's label, else `fallback`. */
956
+ declare function resolvePillMenuLabel(items: NavItem[], pathname: string, fallback: string): string;
936
957
 
937
958
  /**
938
959
  * DarkModeControl — the shared colour-scheme control promoted from erevna /
@@ -1077,6 +1098,10 @@ declare const NAV_TEST_IDS: {
1077
1098
  readonly navBarLinks: "navbar-links";
1078
1099
  /** the "More ▾" priority+ overflow trigger in the horizontal NavBar (shown when items spill). */
1079
1100
  readonly navBarOverflow: "navbar-overflow";
1101
+ /** the compact "Current ▾" trigger of `PillNav` with `variant={PillNavVariant.Menu}`. */
1102
+ readonly pillNavMenu: "pill-nav-menu";
1103
+ /** the optional middle slot of `Topbar` (rendered only when `center` is passed). */
1104
+ readonly topbarCenter: "topbar-center";
1080
1105
  };
1081
1106
  /** Suffixes appended to an `AppShell`'s required `testID` to name its regions. */
1082
1107
  declare const APP_SHELL_SUFFIX: {
@@ -1333,6 +1358,13 @@ declare const navStyles: {
1333
1358
  topbarLeft: {
1334
1359
  flex: number;
1335
1360
  };
1361
+ topbarCenter: {
1362
+ flexShrink: number;
1363
+ minWidth: number;
1364
+ flexDirection: "row";
1365
+ alignItems: "center";
1366
+ marginHorizontal: number;
1367
+ };
1336
1368
  topbarRight: {
1337
1369
  flexDirection: "row";
1338
1370
  alignItems: "center";
@@ -1413,6 +1445,9 @@ declare const pillNavStyles: {
1413
1445
  fontSize: number;
1414
1446
  fontWeight: "600";
1415
1447
  };
1448
+ menuTriggerText: {
1449
+ flexShrink: number;
1450
+ };
1416
1451
  };
1417
1452
  declare const darkModeStyles: {
1418
1453
  segmentRow: {
@@ -1552,4 +1587,4 @@ declare const NAV_ICON_SIZE = 14;
1552
1587
  /** Chevron icon size for expandable sections. */
1553
1588
  declare const CHEVRON_ICON_SIZE = 12;
1554
1589
 
1555
- export { ACTIVE_BORDER_RADIUS, ACTIVE_TINT_OPACITY, APP_SHELL_SUFFIX, type AccountPlan, type AccountState, AppShell, type AppShellProps, type AppShellWidth, BASE_INDENT, 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, 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, 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, Topbar, type TopbarAction, type TopbarProps, type TopbarUser, accessibleNavItems, collapsedRailStyles, darkModeStyles, expandableStyles, filterCommands, filterNavItems, isFilterActive, isRouteActive, navStyles, pillNavStyles, resolveActiveRoute, resolveContentMaxWidth, resolveRailMode, resolveSearchAffordance, roleRoutesToNavItems, searchTokens, useCommandPaletteHotkey, useContentMaxWidth };
1590
+ export { ACTIVE_BORDER_RADIUS, ACTIVE_TINT_OPACITY, APP_SHELL_SUFFIX, type AccountPlan, type AccountState, AppShell, type AppShellProps, type AppShellWidth, BASE_INDENT, 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, 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, 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
@@ -71,6 +71,7 @@ var navStyles = reactNative.StyleSheet.create({
71
71
  justifyContent: "space-between"
72
72
  },
73
73
  topbarLeft: { flex: 1 },
74
+ topbarCenter: { flexShrink: 1, minWidth: 0, flexDirection: "row", alignItems: "center", marginHorizontal: 8 },
74
75
  topbarRight: { flexDirection: "row", alignItems: "center" },
75
76
  topbarRowItem: {
76
77
  marginHorizontal: 8,
@@ -224,7 +225,8 @@ var pillNavStyles = reactNative.StyleSheet.create({
224
225
  columnGap: 6,
225
226
  justifyContent: "center"
226
227
  },
227
- pillText: { fontSize: 14, fontWeight: "600" }
228
+ pillText: { fontSize: 14, fontWeight: "600" },
229
+ menuTriggerText: { flexShrink: 1 }
228
230
  });
229
231
  var darkModeStyles = reactNative.StyleSheet.create({
230
232
  // Segmented Light / Dark / System control.
@@ -881,7 +883,11 @@ var NAV_TEST_IDS = {
881
883
  /** links container in the horizontal NavBar. */
882
884
  navBarLinks: "navbar-links",
883
885
  /** the "More ▾" priority+ overflow trigger in the horizontal NavBar (shown when items spill). */
884
- navBarOverflow: "navbar-overflow"
886
+ navBarOverflow: "navbar-overflow",
887
+ /** the compact "Current ▾" trigger of `PillNav` with `variant={PillNavVariant.Menu}`. */
888
+ pillNavMenu: "pill-nav-menu",
889
+ /** the optional middle slot of `Topbar` (rendered only when `center` is passed). */
890
+ topbarCenter: "topbar-center"
885
891
  };
886
892
  var APP_SHELL_SUFFIX = {
887
893
  content: "-content",
@@ -941,6 +947,7 @@ function planToneColor(theme, tone) {
941
947
  }
942
948
  var Topbar = ({
943
949
  left,
950
+ center,
944
951
  language,
945
952
  notificationSlot,
946
953
  user,
@@ -964,6 +971,7 @@ var Topbar = ({
964
971
  ],
965
972
  children: [
966
973
  /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: navStyles.topbarLeft, children: renderTextSlot(left, { color: colors.text }) }),
974
+ center !== void 0 && center !== null ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: navStyles.topbarCenter, testID: NAV_TEST_IDS.topbarCenter, children: renderTextSlot(center, { color: colors.text }) }) : null,
967
975
  /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: navStyles.topbarRight, children: [
968
976
  language ? /* @__PURE__ */ jsxRuntime.jsx(
969
977
  FocusableTouchable,
@@ -1859,7 +1867,63 @@ var NavShell = ({
1859
1867
  }
1860
1868
  );
1861
1869
  };
1870
+
1871
+ // src/pillMenuLabel.ts
1872
+ function resolvePillMenuLabel(items, pathname, fallback) {
1873
+ const activeRoute = resolveActiveRoute(items, pathname);
1874
+ const active = items.find((item) => item.route === activeRoute);
1875
+ return active?.label ?? fallback;
1876
+ }
1862
1877
  var TEXT_ON_PRIMARY4 = "#ffffff";
1878
+ var NO_ACTIVE_ROUTE2 = "";
1879
+ var PILL_MENU_MIN_WIDTH = 200;
1880
+ var PillNavMenu = ({
1881
+ items,
1882
+ pathname,
1883
+ onNavigate,
1884
+ regionLabel,
1885
+ hint,
1886
+ testID = NAV_TEST_IDS.pillNavMenu,
1887
+ dropdownVariant,
1888
+ menuHeading
1889
+ }) => {
1890
+ const { theme } = uiFeedback.useUi();
1891
+ const primaryColor = theme.palette.primary["500"];
1892
+ const options = React.useMemo(() => items.map((item) => ({ label: item.label, value: item.route })), [items]);
1893
+ const activeRoute = resolveActiveRoute(items, pathname) ?? NO_ACTIVE_ROUTE2;
1894
+ const label = resolvePillMenuLabel(items, pathname, regionLabel);
1895
+ const optionTestID = React.useMemo(() => {
1896
+ const byRoute = new Map(items.map((item) => [item.route, item.testID ?? item.key]));
1897
+ return (route) => byRoute.get(route) ?? `${testID}-option-${route}`;
1898
+ }, [items, testID]);
1899
+ return /* @__PURE__ */ jsxRuntime.jsx(
1900
+ uiLayout.ModalDropdown,
1901
+ {
1902
+ accessibilityHint: hint,
1903
+ accessibilityLabel: label,
1904
+ menuHeading,
1905
+ menuMinWidth: PILL_MENU_MIN_WIDTH,
1906
+ optionTestID,
1907
+ options,
1908
+ testID,
1909
+ value: activeRoute,
1910
+ variant: dropdownVariant,
1911
+ onChange: onNavigate,
1912
+ renderTrigger: ({ isOpen }) => /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: [pillNavStyles.pill, { backgroundColor: primaryColor }], children: [
1913
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { numberOfLines: 1, style: [pillNavStyles.pillText, pillNavStyles.menuTriggerText, { color: TEXT_ON_PRIMARY4 }], children: label }),
1914
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [pillNavStyles.pillText, { color: TEXT_ON_PRIMARY4 }], children: isOpen ? "\u25B4" : "\u25BE" })
1915
+ ] })
1916
+ }
1917
+ );
1918
+ };
1919
+
1920
+ // src/PillNavVariant.ts
1921
+ var PillNavVariant = /* @__PURE__ */ ((PillNavVariant2) => {
1922
+ PillNavVariant2["Pills"] = "pills";
1923
+ PillNavVariant2["Menu"] = "menu";
1924
+ return PillNavVariant2;
1925
+ })(PillNavVariant || {});
1926
+ var TEXT_ON_PRIMARY5 = "#ffffff";
1863
1927
  var DEFAULT_MIN_ITEMS = 1;
1864
1928
  var PillNav = ({
1865
1929
  items,
@@ -1868,12 +1932,31 @@ var PillNav = ({
1868
1932
  regionLabel,
1869
1933
  navigateHint = (label) => label,
1870
1934
  minItems = DEFAULT_MIN_ITEMS,
1935
+ variant = "pills" /* Pills */,
1936
+ menuHint,
1937
+ menuHeading,
1938
+ menuTestID,
1939
+ dropdownVariant,
1871
1940
  containerStyle
1872
1941
  }) => {
1873
1942
  const { theme } = uiFeedback.useUi();
1874
1943
  const colors = theme.colors;
1875
1944
  const primaryColor = theme.palette.primary["500"];
1876
1945
  if (items.length < minItems) return null;
1946
+ if (variant === "menu" /* Menu */)
1947
+ return /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { accessibilityRole: "none", "aria-label": regionLabel, role: "navigation", style: containerStyle, children: /* @__PURE__ */ jsxRuntime.jsx(
1948
+ PillNavMenu,
1949
+ {
1950
+ dropdownVariant,
1951
+ hint: menuHint ?? regionLabel,
1952
+ items,
1953
+ menuHeading,
1954
+ pathname,
1955
+ regionLabel,
1956
+ testID: menuTestID,
1957
+ onNavigate
1958
+ }
1959
+ ) });
1877
1960
  const activeRoute = resolveActiveRoute(items, pathname);
1878
1961
  return /* @__PURE__ */ jsxRuntime.jsx(
1879
1962
  reactNative.View,
@@ -1884,7 +1967,7 @@ var PillNav = ({
1884
1967
  style: [pillNavStyles.container, containerStyle],
1885
1968
  children: items.map((item) => {
1886
1969
  const active = item.route === activeRoute;
1887
- const textColor = active ? TEXT_ON_PRIMARY4 : colors.textSecondary;
1970
+ const textColor = active ? TEXT_ON_PRIMARY5 : colors.textSecondary;
1888
1971
  const pillStyle = active ? { backgroundColor: primaryColor } : { backgroundColor: colors.surfaceElevated };
1889
1972
  return /* @__PURE__ */ jsxRuntime.jsxs(
1890
1973
  FocusableTouchable,
@@ -1907,7 +1990,7 @@ var PillNav = ({
1907
1990
  }
1908
1991
  );
1909
1992
  };
1910
- var TEXT_ON_PRIMARY5 = "#ffffff";
1993
+ var TEXT_ON_PRIMARY6 = "#ffffff";
1911
1994
  var DARK_MODE_ICON_SIZE = 16;
1912
1995
  var DarkModeControl = ({
1913
1996
  value,
@@ -1956,7 +2039,7 @@ var DarkModeControl = ({
1956
2039
  style: [darkModeStyles.segmentRow, { borderColor: colors.border }, containerStyle],
1957
2040
  children: options.map((option) => {
1958
2041
  const active = option.value === value;
1959
- const textColor = active ? TEXT_ON_PRIMARY5 : colors.textSecondary;
2042
+ const textColor = active ? TEXT_ON_PRIMARY6 : colors.textSecondary;
1960
2043
  return /* @__PURE__ */ jsxRuntime.jsxs(
1961
2044
  FocusableTouchable,
1962
2045
  {
@@ -2164,6 +2247,7 @@ exports.NavExpandableItem = NavExpandableItem;
2164
2247
  exports.NavOverflowMenu = NavOverflowMenu;
2165
2248
  exports.NavShell = NavShell;
2166
2249
  exports.PillNav = PillNav;
2250
+ exports.PillNavVariant = PillNavVariant;
2167
2251
  exports.RAIL_FULL_BREAKPOINT = RAIL_FULL_BREAKPOINT;
2168
2252
  exports.Sidebar = Sidebar;
2169
2253
  exports.SidebarChevron = SidebarChevron;
@@ -2181,6 +2265,7 @@ exports.navStyles = navStyles;
2181
2265
  exports.pillNavStyles = pillNavStyles;
2182
2266
  exports.resolveActiveRoute = resolveActiveRoute;
2183
2267
  exports.resolveContentMaxWidth = resolveContentMaxWidth;
2268
+ exports.resolvePillMenuLabel = resolvePillMenuLabel;
2184
2269
  exports.resolveRailMode = resolveRailMode;
2185
2270
  exports.resolveSearchAffordance = resolveSearchAffordance;
2186
2271
  exports.roleRoutesToNavItems = roleRoutesToNavItems;