@dloizides/ui-nav 1.6.0 → 1.7.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,47 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.7.0
4
+
5
+ Two additive navigation capabilities on the horizontal bar, plus a unifying component.
6
+
7
+ **Priority+ overflow ("…" menu) on `NavBar`.** Above the `collapseBelow` breakpoint the
8
+ inline links sit on ONE non-wrapping row; when they don't all fit, a measure pass
9
+ (`useNavOverflow` + the pure `computeVisibleCount`) keeps the leading items that fit inline
10
+ and collapses the rest behind a **"More ▾"** trigger — the shared `@dloizides/ui-layout`
11
+ `ModalDropdown`, so the overflow menu is portalled, keyboard-navigable, and dismissed on
12
+ outside-click / Escape like every other menu. No item is ever cut off; the surplus simply
13
+ moves into the dropdown. When the active route lives in the overflow set the trigger wears
14
+ the accent pill so it stays visibly reachable. BELOW `collapseBelow` the whole set still
15
+ collapses into the responsive hamburger drawer (the two mechanisms never overlap). Two new
16
+ optional props localize the trigger: `overflowLabel` (visible + accessible, default `"More"`)
17
+ and `overflowHint`. New `NAV_TEST_IDS.navBarOverflow` (`navbar-overflow`).
18
+
19
+ **`Nav` — one component, two orientations.** `<Nav orientation="horizontal" />` renders the
20
+ `NavBar`; `<Nav orientation="vertical" />` renders the `Sidebar` — from the SAME `NavItem[]`,
21
+ so an app flips between a top bar and a side rail without swapping components. `orientation`
22
+ defaults to `"horizontal"`; the remaining props are a discriminated union of the delegate's
23
+ props (TypeScript enforces the right prop set per orientation). Vertical nav is the existing
24
+ `Sidebar` (stacked, vertically scrolling — no overflow menu needed).
25
+
26
+ **Backwards compatible.** Purely additive — `Sidebar`, `Topbar`, `NavBar`, `AppShell`, and
27
+ the role-gating helpers keep their exact signatures; the new overflow props are optional
28
+ (default `"More"` / `""`) so aml-v2 (the only `NavBar` consumer) and every `Sidebar`/`Topbar`
29
+ consumer (erevna / katalogos / kefi / agora) are untouched. New exports (`Nav`, `NavProps`,
30
+ `NavOrientation`, `NavOverflowMenu`, `NavOverflowMenuProps`, `NAV_LINK_GAP`) are additive.
31
+
32
+ ## 1.6.1
33
+
34
+ Keep the expanded links on ONE row — the row must be `nowrap` so `flex-shrink` distributes
35
+ negative space (a `wrap` row drops the right slot onto a 2nd line instead of shrinking the
36
+ links). Renames the internal expanded links style to `linksRow` (grow + shrink-to-0,
37
+ `minWidth:0`, `overflow:hidden`) with a per-link `linkCell` (never shrinks, so each link keeps
38
+ its natural measured width). Brand + right stay pinned (`flexShrink:0`).
39
+
40
+ ## 1.6.0
41
+
42
+ Keep the bar to ONE row — horizontal-scroll / clip the links instead of wrapping the bar
43
+ onto a second line when brand + links + right slot overflow the available width.
44
+
3
45
  ## 1.5.0
4
46
 
5
47
  Make the `AppShell` back-office `sidebar` layout **responsive** — a mobile drawer.
package/README.md CHANGED
@@ -68,9 +68,39 @@ import { NavBar, type NavItem } from '@dloizides/ui-nav';
68
68
  menuLabel={FM('menu.toggle')} // a11y label for the hamburger
69
69
  menuHint={FM('menu.toggleHint')}
70
70
  collapseBelow={760} // hamburger under this viewport width
71
+ overflowLabel={FM('menu.more')} // "More ▾" priority+ trigger label
72
+ overflowHint={FM('menu.moreHint')}
71
73
  />
72
74
  ```
73
75
 
76
+ **Priority+ overflow ("…" menu).** Above `collapseBelow` the links sit on ONE
77
+ non-wrapping row. When they don't all fit, a measure pass keeps the leading items
78
+ that fit inline and collapses the rest behind a **"More ▾"** trigger — the shared
79
+ `@dloizides/ui-layout` `ModalDropdown` — so no item is ever cut off; the surplus
80
+ moves into a portalled, keyboard-navigable dropdown. If the active route lives in
81
+ the overflow set, the trigger wears the accent pill so it stays reachable. Localize
82
+ the trigger with the optional `overflowLabel` (visible + accessible, default `"More"`)
83
+ and `overflowHint`. Below `collapseBelow` the whole set still collapses into the
84
+ responsive hamburger drawer, so the two mechanisms never overlap.
85
+
86
+ ### One component, two orientations (`Nav`)
87
+
88
+ `Nav` renders EITHER the horizontal `NavBar` or the vertical `Sidebar` from the same
89
+ `NavItem[]`, chosen by a single `orientation` prop — flip between a top bar and a side
90
+ rail without swapping components or re-plumbing props. `orientation` defaults to
91
+ `"horizontal"`; the remaining props are a discriminated union of the delegate's props
92
+ (TypeScript enforces the right set per orientation — e.g. `title` only on vertical,
93
+ `collapseBelow` / `overflowLabel` only on horizontal).
94
+
95
+ ```tsx
96
+ import { Nav, type NavItem } from '@dloizides/ui-nav';
97
+
98
+ <Nav orientation="horizontal" items={items} pathname={p} onNavigate={go} regionLabel={r} /> // NavBar
99
+ <Nav orientation="vertical" items={items} pathname={p} onNavigate={go} title={t} regionLabel={r} /> // Sidebar
100
+ ```
101
+
102
+ `Nav` is purely additive: `Sidebar` / `Topbar` / `NavBar` stay exported and unchanged.
103
+
74
104
  An app that renders a `Sidebar` today switches to a top bar by swapping the
75
105
  `Sidebar` element for `NavBar` (same `items` / `pathname` / `onNavigate`) — e.g.
76
106
  the AML v2 app drops `NavBar` into the `AppShell` `header` (or `nav`) slot so its
package/dist/index.d.mts CHANGED
@@ -1,5 +1,6 @@
1
1
  import React from 'react';
2
2
  import { ViewStyle } from 'react-native';
3
+ import { DropdownVariant } from '@dloizides/ui-layout';
3
4
  import { resolveAccessibleRoutes, RoleRouteTable, RoleRoute } from '@dloizides/auth-web';
4
5
 
5
6
  /**
@@ -161,10 +162,19 @@ declare const Topbar: ({ left, language, notificationSlot, user, account, logout
161
162
  * hover bg=`surfaceElevated`, active=`palette.primary`, ring=`palette.primary`).
162
163
  * Hover/focus are web-only (react-native-web); native stays at the rest style.
163
164
  *
165
+ * Overflow ("…" priority+ menu). Above the `collapseBelow` breakpoint the links sit
166
+ * on ONE row; when they don't all fit, a measure pass ({@link useNavOverflow} +
167
+ * {@link computeVisibleCount}) keeps the leading items that fit inline and collapses
168
+ * the rest behind a "More ▾" trigger — the shared `ModalDropdown` (see
169
+ * {@link NavOverflowMenu}) — so no item is ever cut off. BELOW `collapseBelow` the
170
+ * whole set still collapses into the responsive hamburger drawer (all items stacked),
171
+ * so the two mechanisms don't overlap: hamburger for true mobile, "…" for the
172
+ * in-between desktop case where only SOME items spill.
173
+ *
164
174
  * Contract discipline (same as the rest of `@dloizides/ui-nav`): NO FM, router,
165
175
  * icon set, or store imports. Labels are pre-localized strings, icons are render
166
176
  * slots, and every colour is read from the theme. This is purely additive —
167
- * `Sidebar`/`Topbar` are unchanged.
177
+ * `Sidebar`/`Topbar` are unchanged and the new overflow labels are optional.
168
178
  */
169
179
 
170
180
  interface NavBarProps {
@@ -190,10 +200,118 @@ interface NavBarProps {
190
200
  renderMenuIcon?: (color: string, open: boolean) => React.ReactNode;
191
201
  /** Collapse the links behind a hamburger below this viewport width (default 760). */
192
202
  collapseBelow?: number;
203
+ /**
204
+ * Localized visible + accessible label for the "More ▾" overflow trigger shown when
205
+ * the inline links don't all fit on one row (priority+ menu). Defaults to `"More"`.
206
+ */
207
+ overflowLabel?: string;
208
+ /** Localized accessibility hint for the overflow trigger. */
209
+ overflowHint?: string;
193
210
  /** Extra container style overrides. */
194
211
  containerStyle?: ViewStyle | ViewStyle[];
195
212
  }
196
- declare const NavBar: ({ items, pathname, onNavigate, regionLabel, navigateHint, brand, right, menuLabel, menuHint, renderMenuIcon, collapseBelow, containerStyle, }: NavBarProps) => React.ReactElement;
213
+ declare const NavBar: ({ items, pathname, onNavigate, regionLabel, navigateHint, brand, right, menuLabel, menuHint, renderMenuIcon, collapseBelow, overflowLabel, overflowHint, containerStyle, }: NavBarProps) => React.ReactElement;
214
+
215
+ /**
216
+ * `Nav` — ONE component that renders EITHER the horizontal top bar (`NavBar`) or the
217
+ * vertical rail (`Sidebar`) from the same `NavItem[]`, chosen by a single
218
+ * `orientation` prop. It lets an app flip its navigation between a top bar and a side
219
+ * menu without swapping components or re-plumbing props:
220
+ *
221
+ * <Nav orientation="horizontal" items={items} pathname={p} onNavigate={go} .../> // NavBar
222
+ * <Nav orientation="vertical" items={items} pathname={p} onNavigate={go} .../> // Sidebar
223
+ *
224
+ * `orientation` defaults to `"horizontal"`. The remaining props are exactly the
225
+ * delegate's props — a discriminated union — so TypeScript enforces the right prop
226
+ * set per orientation (e.g. `title` is required only for the vertical/Sidebar side,
227
+ * `collapseBelow` / `overflowLabel` only for the horizontal/NavBar side).
228
+ *
229
+ * `Nav` is purely additive: `NavBar` and `Sidebar` remain exported and unchanged, so
230
+ * every existing consumer (erevna / katalogos / kefi / agora) is untouched. Vertical
231
+ * navigation is the existing Sidebar — stacked links that scroll vertically, so it
232
+ * needs no overflow menu; the priority+ "…" overflow is a horizontal-bar concern.
233
+ */
234
+
235
+ /** Which axis the navigation renders on. */
236
+ type NavOrientation = 'horizontal' | 'vertical';
237
+ type NavProps = ({
238
+ orientation?: 'horizontal';
239
+ } & NavBarProps) | ({
240
+ orientation: 'vertical';
241
+ } & SidebarProps);
242
+ declare const Nav: (props: NavProps) => React.ReactElement;
243
+
244
+ /**
245
+ * NavBarLink — a SINGLE horizontal `NavBar` link, extracted so each link owns its
246
+ * own hover/focus state (React hooks can't be per-item inside a map otherwise).
247
+ *
248
+ * It renders the v1 `.gn-links a` affordance, THEME-DRIVEN (no hard-coded colours
249
+ * beyond white ink on the accent pill):
250
+ * - rest → muted text (`colors.rest`), no background
251
+ * - hover → text brightens to full contrast (`colors.hoverText`) + a subtle
252
+ * rounded-pill background (`colors.hoverBg`, e.g. v1 `#16223a`)
253
+ * - active → solid accent pill (`colors.activeBg`) with white ink + aria-current
254
+ * - focus → a themed keyboard focus ring (`colors.ring`)
255
+ * Hover/focus are WEB-only (react-native-web fires `onHoverIn/Out`); on native
256
+ * those handlers never fire, so the link stays at its rest style.
257
+ */
258
+
259
+ /** Theme-resolved colours a link needs, computed once by `NavBar`. */
260
+ interface NavBarLinkColors {
261
+ /** Muted link text at rest. */
262
+ rest: string;
263
+ /** Brightened link text on hover. */
264
+ hoverText: string;
265
+ /** Subtle hover-pill background. */
266
+ hoverBg: string;
267
+ /** Active-pill background (accent). */
268
+ activeBg: string;
269
+ /** Focus-ring colour. */
270
+ ring: string;
271
+ }
272
+
273
+ /**
274
+ * NavOverflowMenu — the "More ▾" trigger + dropdown that holds the `NavBar` items
275
+ * which don't fit inline (the priority+ overflow set).
276
+ *
277
+ * It REUSES the shared `@dloizides/ui-layout` `ModalDropdown` — the exact component
278
+ * every other menu in the app uses — so the overflow menu is portalled, keyboard
279
+ * navigable, dismissed on outside-click / Escape, and opens on click, looking
280
+ * identical to the rest of the app's menus. The overflow entries are the same
281
+ * `NavItem`s (label + route + active state); selecting one navigates via the same
282
+ * `onNavigate` the inline links use.
283
+ *
284
+ * The trigger is a rounded pill matching a `NavBarLink`: muted at rest, and — when
285
+ * the ACTIVE route lives in the overflow set — it wears the accent pill so the
286
+ * active item stays visibly reachable behind the "…". The dropdown marks that same
287
+ * item selected (its `value` is the active overflow route). a11y: `ModalDropdown`
288
+ * owns the trigger's `role="button"` + `aria-expanded` + caller-supplied label/hint,
289
+ * and gives the options keyboard navigation.
290
+ *
291
+ * Contract discipline (as the rest of `@dloizides/ui-nav`): no FM / router / icon
292
+ * set / store; the label + hint are pre-localized strings from the caller and every
293
+ * colour comes from the theme (via the `NavBarLinkColors` the `NavBar` computed).
294
+ */
295
+
296
+ interface NavOverflowMenuProps {
297
+ /** The items that spilled out of the inline row. */
298
+ items: NavItem[];
299
+ /** Current active route/path — decides the active-pill + selected option. */
300
+ pathname: string;
301
+ /** Navigation callback — receives the chosen item's `route`. */
302
+ onNavigate: (route: string) => void;
303
+ /** Theme-resolved link colours (same set the inline links use). */
304
+ colors: NavBarLinkColors;
305
+ /** Localized visible + accessible label for the trigger (e.g. "More"). */
306
+ label: string;
307
+ /** Localized accessibility hint for the trigger. */
308
+ hint: string;
309
+ /** testID for the trigger (options default to `` `${testID}-option-${route}` ``). */
310
+ testID: string;
311
+ /** Force a dropdown variant (default: responsive — inline menu on desktop). */
312
+ variant?: DropdownVariant;
313
+ }
314
+ declare const NavOverflowMenu: ({ items, pathname, onNavigate, colors, label, hint, testID, variant, }: NavOverflowMenuProps) => React.ReactElement;
197
315
 
198
316
  /**
199
317
  * MobileDrawer — the below-the-breakpoint face of `AppShell`'s back-office
@@ -334,6 +452,8 @@ declare const NAV_TEST_IDS: {
334
452
  readonly navBarToggle: "navbar-toggle";
335
453
  /** links container in the horizontal NavBar. */
336
454
  readonly navBarLinks: "navbar-links";
455
+ /** the "More ▾" priority+ overflow trigger in the horizontal NavBar (shown when items spill). */
456
+ readonly navBarOverflow: "navbar-overflow";
337
457
  };
338
458
  /** Suffixes appended to an `AppShell`'s required `testID` to name its regions. */
339
459
  declare const APP_SHELL_SUFFIX: {
@@ -399,6 +519,13 @@ declare function roleRoutesToNavItems(routes: RoleRoute[], translate: (key: stri
399
519
  declare function accessibleNavItems(user: NavUser, table: RoleRouteTable, translate: (key: string) => string): NavItem[];
400
520
 
401
521
  declare const ACTIVE_BORDER_RADIUS = 4;
522
+ /**
523
+ * Horizontal gap (px) between adjacent inline items in the `NavBar` links row.
524
+ * Single source of truth: it is both the row's `columnGap` AND the gap the
525
+ * priority+ overflow fit ({@link computeVisibleCount}) reasons about, so the two
526
+ * can never drift apart.
527
+ */
528
+ declare const NAV_LINK_GAP = 4;
402
529
  declare const navStyles: {
403
530
  sidebarContainer: {
404
531
  width: number;
@@ -521,4 +648,4 @@ declare const NAV_ICON_SIZE = 14;
521
648
  /** Chevron icon size for expandable sections. */
522
649
  declare const CHEVRON_ICON_SIZE = 12;
523
650
 
524
- export { ACTIVE_BORDER_RADIUS, APP_SHELL_SUFFIX, type AccountPlan, type AccountState, AppShell, type AppShellProps, type AppShellWidth, BASE_INDENT, CHEVRON_ICON_SIZE, NAV_ICON_SIZE, NAV_TEST_IDS, NavBar, type NavBarProps, NavExpandableItem, type NavExpandableItemProps, type NavItem, type NavUser, type ShellMessage, Sidebar, type SidebarProps, Topbar, type TopbarAction, type TopbarProps, type TopbarUser, accessibleNavItems, expandableStyles, isRouteActive, navStyles, resolveContentMaxWidth, roleRoutesToNavItems, useContentMaxWidth };
651
+ export { ACTIVE_BORDER_RADIUS, APP_SHELL_SUFFIX, type AccountPlan, type AccountState, AppShell, type AppShellProps, type AppShellWidth, BASE_INDENT, CHEVRON_ICON_SIZE, NAV_ICON_SIZE, NAV_LINK_GAP, NAV_TEST_IDS, Nav, NavBar, type NavBarProps, NavExpandableItem, type NavExpandableItemProps, type NavItem, type NavOrientation, NavOverflowMenu, type NavOverflowMenuProps, type NavProps, type NavUser, type ShellMessage, Sidebar, type SidebarProps, Topbar, type TopbarAction, type TopbarProps, type TopbarUser, accessibleNavItems, expandableStyles, isRouteActive, navStyles, resolveContentMaxWidth, roleRoutesToNavItems, useContentMaxWidth };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import React from 'react';
2
2
  import { ViewStyle } from 'react-native';
3
+ import { DropdownVariant } from '@dloizides/ui-layout';
3
4
  import { resolveAccessibleRoutes, RoleRouteTable, RoleRoute } from '@dloizides/auth-web';
4
5
 
5
6
  /**
@@ -161,10 +162,19 @@ declare const Topbar: ({ left, language, notificationSlot, user, account, logout
161
162
  * hover bg=`surfaceElevated`, active=`palette.primary`, ring=`palette.primary`).
162
163
  * Hover/focus are web-only (react-native-web); native stays at the rest style.
163
164
  *
165
+ * Overflow ("…" priority+ menu). Above the `collapseBelow` breakpoint the links sit
166
+ * on ONE row; when they don't all fit, a measure pass ({@link useNavOverflow} +
167
+ * {@link computeVisibleCount}) keeps the leading items that fit inline and collapses
168
+ * the rest behind a "More ▾" trigger — the shared `ModalDropdown` (see
169
+ * {@link NavOverflowMenu}) — so no item is ever cut off. BELOW `collapseBelow` the
170
+ * whole set still collapses into the responsive hamburger drawer (all items stacked),
171
+ * so the two mechanisms don't overlap: hamburger for true mobile, "…" for the
172
+ * in-between desktop case where only SOME items spill.
173
+ *
164
174
  * Contract discipline (same as the rest of `@dloizides/ui-nav`): NO FM, router,
165
175
  * icon set, or store imports. Labels are pre-localized strings, icons are render
166
176
  * slots, and every colour is read from the theme. This is purely additive —
167
- * `Sidebar`/`Topbar` are unchanged.
177
+ * `Sidebar`/`Topbar` are unchanged and the new overflow labels are optional.
168
178
  */
169
179
 
170
180
  interface NavBarProps {
@@ -190,10 +200,118 @@ interface NavBarProps {
190
200
  renderMenuIcon?: (color: string, open: boolean) => React.ReactNode;
191
201
  /** Collapse the links behind a hamburger below this viewport width (default 760). */
192
202
  collapseBelow?: number;
203
+ /**
204
+ * Localized visible + accessible label for the "More ▾" overflow trigger shown when
205
+ * the inline links don't all fit on one row (priority+ menu). Defaults to `"More"`.
206
+ */
207
+ overflowLabel?: string;
208
+ /** Localized accessibility hint for the overflow trigger. */
209
+ overflowHint?: string;
193
210
  /** Extra container style overrides. */
194
211
  containerStyle?: ViewStyle | ViewStyle[];
195
212
  }
196
- declare const NavBar: ({ items, pathname, onNavigate, regionLabel, navigateHint, brand, right, menuLabel, menuHint, renderMenuIcon, collapseBelow, containerStyle, }: NavBarProps) => React.ReactElement;
213
+ declare const NavBar: ({ items, pathname, onNavigate, regionLabel, navigateHint, brand, right, menuLabel, menuHint, renderMenuIcon, collapseBelow, overflowLabel, overflowHint, containerStyle, }: NavBarProps) => React.ReactElement;
214
+
215
+ /**
216
+ * `Nav` — ONE component that renders EITHER the horizontal top bar (`NavBar`) or the
217
+ * vertical rail (`Sidebar`) from the same `NavItem[]`, chosen by a single
218
+ * `orientation` prop. It lets an app flip its navigation between a top bar and a side
219
+ * menu without swapping components or re-plumbing props:
220
+ *
221
+ * <Nav orientation="horizontal" items={items} pathname={p} onNavigate={go} .../> // NavBar
222
+ * <Nav orientation="vertical" items={items} pathname={p} onNavigate={go} .../> // Sidebar
223
+ *
224
+ * `orientation` defaults to `"horizontal"`. The remaining props are exactly the
225
+ * delegate's props — a discriminated union — so TypeScript enforces the right prop
226
+ * set per orientation (e.g. `title` is required only for the vertical/Sidebar side,
227
+ * `collapseBelow` / `overflowLabel` only for the horizontal/NavBar side).
228
+ *
229
+ * `Nav` is purely additive: `NavBar` and `Sidebar` remain exported and unchanged, so
230
+ * every existing consumer (erevna / katalogos / kefi / agora) is untouched. Vertical
231
+ * navigation is the existing Sidebar — stacked links that scroll vertically, so it
232
+ * needs no overflow menu; the priority+ "…" overflow is a horizontal-bar concern.
233
+ */
234
+
235
+ /** Which axis the navigation renders on. */
236
+ type NavOrientation = 'horizontal' | 'vertical';
237
+ type NavProps = ({
238
+ orientation?: 'horizontal';
239
+ } & NavBarProps) | ({
240
+ orientation: 'vertical';
241
+ } & SidebarProps);
242
+ declare const Nav: (props: NavProps) => React.ReactElement;
243
+
244
+ /**
245
+ * NavBarLink — a SINGLE horizontal `NavBar` link, extracted so each link owns its
246
+ * own hover/focus state (React hooks can't be per-item inside a map otherwise).
247
+ *
248
+ * It renders the v1 `.gn-links a` affordance, THEME-DRIVEN (no hard-coded colours
249
+ * beyond white ink on the accent pill):
250
+ * - rest → muted text (`colors.rest`), no background
251
+ * - hover → text brightens to full contrast (`colors.hoverText`) + a subtle
252
+ * rounded-pill background (`colors.hoverBg`, e.g. v1 `#16223a`)
253
+ * - active → solid accent pill (`colors.activeBg`) with white ink + aria-current
254
+ * - focus → a themed keyboard focus ring (`colors.ring`)
255
+ * Hover/focus are WEB-only (react-native-web fires `onHoverIn/Out`); on native
256
+ * those handlers never fire, so the link stays at its rest style.
257
+ */
258
+
259
+ /** Theme-resolved colours a link needs, computed once by `NavBar`. */
260
+ interface NavBarLinkColors {
261
+ /** Muted link text at rest. */
262
+ rest: string;
263
+ /** Brightened link text on hover. */
264
+ hoverText: string;
265
+ /** Subtle hover-pill background. */
266
+ hoverBg: string;
267
+ /** Active-pill background (accent). */
268
+ activeBg: string;
269
+ /** Focus-ring colour. */
270
+ ring: string;
271
+ }
272
+
273
+ /**
274
+ * NavOverflowMenu — the "More ▾" trigger + dropdown that holds the `NavBar` items
275
+ * which don't fit inline (the priority+ overflow set).
276
+ *
277
+ * It REUSES the shared `@dloizides/ui-layout` `ModalDropdown` — the exact component
278
+ * every other menu in the app uses — so the overflow menu is portalled, keyboard
279
+ * navigable, dismissed on outside-click / Escape, and opens on click, looking
280
+ * identical to the rest of the app's menus. The overflow entries are the same
281
+ * `NavItem`s (label + route + active state); selecting one navigates via the same
282
+ * `onNavigate` the inline links use.
283
+ *
284
+ * The trigger is a rounded pill matching a `NavBarLink`: muted at rest, and — when
285
+ * the ACTIVE route lives in the overflow set — it wears the accent pill so the
286
+ * active item stays visibly reachable behind the "…". The dropdown marks that same
287
+ * item selected (its `value` is the active overflow route). a11y: `ModalDropdown`
288
+ * owns the trigger's `role="button"` + `aria-expanded` + caller-supplied label/hint,
289
+ * and gives the options keyboard navigation.
290
+ *
291
+ * Contract discipline (as the rest of `@dloizides/ui-nav`): no FM / router / icon
292
+ * set / store; the label + hint are pre-localized strings from the caller and every
293
+ * colour comes from the theme (via the `NavBarLinkColors` the `NavBar` computed).
294
+ */
295
+
296
+ interface NavOverflowMenuProps {
297
+ /** The items that spilled out of the inline row. */
298
+ items: NavItem[];
299
+ /** Current active route/path — decides the active-pill + selected option. */
300
+ pathname: string;
301
+ /** Navigation callback — receives the chosen item's `route`. */
302
+ onNavigate: (route: string) => void;
303
+ /** Theme-resolved link colours (same set the inline links use). */
304
+ colors: NavBarLinkColors;
305
+ /** Localized visible + accessible label for the trigger (e.g. "More"). */
306
+ label: string;
307
+ /** Localized accessibility hint for the trigger. */
308
+ hint: string;
309
+ /** testID for the trigger (options default to `` `${testID}-option-${route}` ``). */
310
+ testID: string;
311
+ /** Force a dropdown variant (default: responsive — inline menu on desktop). */
312
+ variant?: DropdownVariant;
313
+ }
314
+ declare const NavOverflowMenu: ({ items, pathname, onNavigate, colors, label, hint, testID, variant, }: NavOverflowMenuProps) => React.ReactElement;
197
315
 
198
316
  /**
199
317
  * MobileDrawer — the below-the-breakpoint face of `AppShell`'s back-office
@@ -334,6 +452,8 @@ declare const NAV_TEST_IDS: {
334
452
  readonly navBarToggle: "navbar-toggle";
335
453
  /** links container in the horizontal NavBar. */
336
454
  readonly navBarLinks: "navbar-links";
455
+ /** the "More ▾" priority+ overflow trigger in the horizontal NavBar (shown when items spill). */
456
+ readonly navBarOverflow: "navbar-overflow";
337
457
  };
338
458
  /** Suffixes appended to an `AppShell`'s required `testID` to name its regions. */
339
459
  declare const APP_SHELL_SUFFIX: {
@@ -399,6 +519,13 @@ declare function roleRoutesToNavItems(routes: RoleRoute[], translate: (key: stri
399
519
  declare function accessibleNavItems(user: NavUser, table: RoleRouteTable, translate: (key: string) => string): NavItem[];
400
520
 
401
521
  declare const ACTIVE_BORDER_RADIUS = 4;
522
+ /**
523
+ * Horizontal gap (px) between adjacent inline items in the `NavBar` links row.
524
+ * Single source of truth: it is both the row's `columnGap` AND the gap the
525
+ * priority+ overflow fit ({@link computeVisibleCount}) reasons about, so the two
526
+ * can never drift apart.
527
+ */
528
+ declare const NAV_LINK_GAP = 4;
402
529
  declare const navStyles: {
403
530
  sidebarContainer: {
404
531
  width: number;
@@ -521,4 +648,4 @@ declare const NAV_ICON_SIZE = 14;
521
648
  /** Chevron icon size for expandable sections. */
522
649
  declare const CHEVRON_ICON_SIZE = 12;
523
650
 
524
- export { ACTIVE_BORDER_RADIUS, APP_SHELL_SUFFIX, type AccountPlan, type AccountState, AppShell, type AppShellProps, type AppShellWidth, BASE_INDENT, CHEVRON_ICON_SIZE, NAV_ICON_SIZE, NAV_TEST_IDS, NavBar, type NavBarProps, NavExpandableItem, type NavExpandableItemProps, type NavItem, type NavUser, type ShellMessage, Sidebar, type SidebarProps, Topbar, type TopbarAction, type TopbarProps, type TopbarUser, accessibleNavItems, expandableStyles, isRouteActive, navStyles, resolveContentMaxWidth, roleRoutesToNavItems, useContentMaxWidth };
651
+ export { ACTIVE_BORDER_RADIUS, APP_SHELL_SUFFIX, type AccountPlan, type AccountState, AppShell, type AppShellProps, type AppShellWidth, BASE_INDENT, CHEVRON_ICON_SIZE, NAV_ICON_SIZE, NAV_LINK_GAP, NAV_TEST_IDS, Nav, NavBar, type NavBarProps, NavExpandableItem, type NavExpandableItemProps, type NavItem, type NavOrientation, NavOverflowMenu, type NavOverflowMenuProps, type NavProps, type NavUser, type ShellMessage, Sidebar, type SidebarProps, Topbar, type TopbarAction, type TopbarProps, type TopbarUser, accessibleNavItems, expandableStyles, isRouteActive, navStyles, resolveContentMaxWidth, roleRoutesToNavItems, useContentMaxWidth };