@dloizides/ui-nav 1.2.0 → 1.3.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,28 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.3.0
4
+
5
+ Give the `NavBar` the v1 `.gn-links` look + a full accessibility pass (and extend the
6
+ same a11y bar to `Sidebar`/`Topbar`).
7
+
8
+ **Look & hover (NavBar).** Each link is a rounded-pill target with the v1 hover
9
+ affordance — muted at rest, and on HOVER the text brightens to full contrast while a
10
+ subtle rounded-pill background appears; the ACTIVE link is a solid accent pill. All
11
+ theme-driven (rest = `textSecondary`, hover text = `text`, hover bg = `surfaceElevated`,
12
+ active/ring = `palette.primary`). Hover/focus are web-only (`onHoverIn/Out`, `onFocus/Blur`
13
+ via `Pressable`); native stays at the rest style. The hover cross-fade is cheap CSS and is
14
+ disabled under `prefers-reduced-motion: reduce` (new `useReducedMotion`). Backwards
15
+ compatible — the hover pill is the default (aml-v2 is the only `NavBar` consumer) and the
16
+ brand/right slots, responsive hamburger collapse, and `containerStyle` width behaviour are
17
+ unchanged.
18
+
19
+ **Accessibility.** NavBar toggle now carries `aria-controls` pointing at the collapsible
20
+ links region (id via `nativeID`), keeps `aria-expanded`, and gains a themed keyboard focus
21
+ ring; links keep `aria-current="page"` + `role="link"` and gain a focus ring; link/toggle
22
+ touch targets are now ≥44×44. `Sidebar` leaves gain `aria-current="page"` + a focus ring;
23
+ `Topbar` action buttons gain a focus ring (new internal `FocusableTouchable`). Focus rings
24
+ never remove the outline without a themed replacement.
25
+
3
26
  ## 1.2.0
4
27
 
5
28
  Add `NavBar` — a config-driven **horizontal top navigation bar**, the top-bar
package/README.md CHANGED
@@ -74,8 +74,21 @@ import { NavBar, type NavItem } from '@dloizides/ui-nav';
74
74
  An app that renders a `Sidebar` today switches to a top bar by swapping the
75
75
  `Sidebar` element for `NavBar` (same `items` / `pathname` / `onNavigate`) — e.g.
76
76
  the AML v2 app drops `NavBar` into the `AppShell` `header` (or `nav`) slot so its
77
- links sit across the top like the rest of the console. Active links expose
78
- `aria-current="page"`; the container is a `role="navigation"` landmark.
77
+ links sit across the top like the rest of the console.
78
+
79
+ Each link is a rounded-pill target with the v1 `.gn-links` affordance: muted at
80
+ rest, brightening its text + showing a subtle pill background on **hover**, and a
81
+ solid accent pill when **active**. All colours are theme-driven (rest =
82
+ `textSecondary`, hover text = `text`, hover bg = `surfaceElevated`, active/ring =
83
+ `palette.primary`); hover/focus are web-only and the hover cross-fade honours
84
+ `prefers-reduced-motion`.
85
+
86
+ **Accessibility.** The container is a `role="navigation"` landmark named by
87
+ `regionLabel`; active links expose `aria-current="page"` + `role="link"`; the
88
+ responsive hamburger carries `role="button"`, `aria-expanded`, and `aria-controls`
89
+ pointing at the collapsible links region; every link/toggle is keyboard-operable
90
+ (Enter/Space) with a themed focus ring and a ≥44×44 touch target. `Sidebar` leaves
91
+ carry `aria-current="page"` and `Topbar` buttons a focus ring under the same bar.
79
92
 
80
93
  ### Role gating
81
94
 
package/dist/index.d.mts CHANGED
@@ -154,10 +154,17 @@ declare const Topbar: ({ left, language, notificationSlot, user, account, logout
154
154
  * collapses the links behind a hamburger toggle — mirroring the v1 AML console's
155
155
  * responsive `.global-nav`.
156
156
  *
157
+ * Look + feel: each link is a rounded-pill target with the v1 `.gn-links a`
158
+ * affordance — muted at rest, brightening text + a subtle pill background on
159
+ * HOVER, and a solid accent pill when ACTIVE. All colours are THEME-DRIVEN from
160
+ * the `@dloizides/ui-feedback` UiProvider (rest=`textSecondary`, hover text=`text`,
161
+ * hover bg=`surfaceElevated`, active=`palette.primary`, ring=`palette.primary`).
162
+ * Hover/focus are web-only (react-native-web); native stays at the rest style.
163
+ *
157
164
  * Contract discipline (same as the rest of `@dloizides/ui-nav`): NO FM, router,
158
165
  * icon set, or store imports. Labels are pre-localized strings, icons are render
159
- * slots, and every colour is read from the `@dloizides/ui-feedback` UiProvider
160
- * theme. This is purely additive — `Sidebar`/`Topbar` are unchanged.
166
+ * slots, and every colour is read from the theme. This is purely additive —
167
+ * `Sidebar`/`Topbar` are unchanged.
161
168
  */
162
169
 
163
170
  interface NavBarProps {
package/dist/index.d.ts CHANGED
@@ -154,10 +154,17 @@ declare const Topbar: ({ left, language, notificationSlot, user, account, logout
154
154
  * collapses the links behind a hamburger toggle — mirroring the v1 AML console's
155
155
  * responsive `.global-nav`.
156
156
  *
157
+ * Look + feel: each link is a rounded-pill target with the v1 `.gn-links a`
158
+ * affordance — muted at rest, brightening text + a subtle pill background on
159
+ * HOVER, and a solid accent pill when ACTIVE. All colours are THEME-DRIVEN from
160
+ * the `@dloizides/ui-feedback` UiProvider (rest=`textSecondary`, hover text=`text`,
161
+ * hover bg=`surfaceElevated`, active=`palette.primary`, ring=`palette.primary`).
162
+ * Hover/focus are web-only (react-native-web); native stays at the rest style.
163
+ *
157
164
  * Contract discipline (same as the rest of `@dloizides/ui-nav`): NO FM, router,
158
165
  * icon set, or store imports. Labels are pre-localized strings, icons are render
159
- * slots, and every colour is read from the `@dloizides/ui-feedback` UiProvider
160
- * theme. This is purely additive — `Sidebar`/`Topbar` are unchanged.
166
+ * slots, and every colour is read from the theme. This is purely additive —
167
+ * `Sidebar`/`Topbar` are unchanged.
161
168
  */
162
169
 
163
170
  interface NavBarProps {
package/dist/index.js CHANGED
@@ -81,7 +81,12 @@ var navBarStyles = reactNative.StyleSheet.create({
81
81
  marginLeft: "auto",
82
82
  paddingHorizontal: 10,
83
83
  paddingVertical: 6,
84
- borderRadius: 4
84
+ borderRadius: 4,
85
+ // a11y: keep the hamburger a comfortable ≥44×44 touch target.
86
+ minWidth: 44,
87
+ minHeight: 44,
88
+ alignItems: "center",
89
+ justifyContent: "center"
85
90
  },
86
91
  toggleGlyph: { fontSize: 20, fontWeight: "700" },
87
92
  links: {
@@ -106,7 +111,10 @@ var navBarStyles = reactNative.StyleSheet.create({
106
111
  paddingHorizontal: 14,
107
112
  borderRadius: 8,
108
113
  flexDirection: "row",
109
- alignItems: "center"
114
+ alignItems: "center",
115
+ // a11y: comfortable ≥44 tall touch target (rounded-pill hover/active affordance).
116
+ minHeight: 44,
117
+ justifyContent: "center"
110
118
  },
111
119
  linkStacked: { paddingVertical: 10 },
112
120
  linkIcon: { marginRight: 6 },
@@ -149,6 +157,31 @@ var expandableStyles = reactNative.StyleSheet.create({
149
157
  var BASE_INDENT = 12;
150
158
  var NAV_ICON_SIZE = 14;
151
159
  var CHEVRON_ICON_SIZE = 12;
160
+ var IS_WEB = reactNative.Platform.OS === "web";
161
+ var FOCUS_RING_WIDTH = 2;
162
+ var FOCUS_RING_OFFSET = 2;
163
+ var HOVER_TRANSITION_MS = 150;
164
+ function focusRingStyle(focused, ringColor) {
165
+ if (!IS_WEB || !focused) return void 0;
166
+ const ring = {
167
+ outlineWidth: FOCUS_RING_WIDTH,
168
+ outlineStyle: "solid",
169
+ outlineColor: ringColor,
170
+ outlineOffset: FOCUS_RING_OFFSET
171
+ };
172
+ return ring;
173
+ }
174
+ function hoverTransitionStyle(reducedMotion) {
175
+ if (!IS_WEB) return void 0;
176
+ const transition = {
177
+ transitionProperty: "color, background-color",
178
+ transitionDuration: reducedMotion ? "0ms" : `${HOVER_TRANSITION_MS}ms`
179
+ };
180
+ return transition;
181
+ }
182
+ function ariaCurrentProps(isActive) {
183
+ return isActive ? { "aria-current": "page" } : {};
184
+ }
152
185
  var NavExpandableItem = ({
153
186
  item,
154
187
  pathname,
@@ -160,6 +193,7 @@ var NavExpandableItem = ({
160
193
  depth = 0
161
194
  }) => {
162
195
  const [expanded, setExpanded] = react.useState(false);
196
+ const [focused, setFocused] = react.useState(false);
163
197
  const { theme } = uiFeedback.useUi();
164
198
  const colors = theme.colors;
165
199
  const primaryColor = theme.palette.primary["500"];
@@ -180,9 +214,18 @@ var NavExpandableItem = ({
180
214
  accessibilityHint: navigateHint(item.label),
181
215
  accessibilityLabel: item.label,
182
216
  accessibilityRole: "button",
183
- style: [expandableStyles.childItem, paddingStyle, isActive ? activeItemStyle : void 0],
217
+ accessibilityState: { selected: isActive },
218
+ style: [
219
+ expandableStyles.childItem,
220
+ paddingStyle,
221
+ isActive ? activeItemStyle : void 0,
222
+ focusRingStyle(focused, primaryColor)
223
+ ],
184
224
  testID: item.testID ?? item.key,
225
+ onBlur: () => setFocused(false),
226
+ onFocus: () => setFocused(true),
185
227
  onPress: () => onNavigate(item.route),
228
+ ...ariaCurrentProps(isActive),
186
229
  children: [
187
230
  typeof item.renderIcon === "function" ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: expandableStyles.iconWrapper, children: item.renderIcon(colors.textSecondary, NAV_ICON_SIZE) }) : null,
188
231
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -206,8 +249,10 @@ var NavExpandableItem = ({
206
249
  accessibilityLabel: item.label,
207
250
  accessibilityRole: "button",
208
251
  accessibilityState: { expanded },
209
- style: [expandableStyles.header, headerPaddingStyle],
252
+ style: [expandableStyles.header, headerPaddingStyle, focusRingStyle(focused, primaryColor)],
210
253
  testID: item.testID ?? item.key,
254
+ onBlur: () => setFocused(false),
255
+ onFocus: () => setFocused(true),
211
256
  onPress: toggle,
212
257
  children: [
213
258
  typeof item.renderIcon === "function" ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: expandableStyles.iconWrapper, children: item.renderIcon(colors.text, NAV_ICON_SIZE) }) : null,
@@ -307,6 +352,32 @@ var APP_SHELL_SUFFIX = {
307
352
  error: "-error",
308
353
  forbidden: "-forbidden"
309
354
  };
355
+ var FocusableTouchable = ({
356
+ onPress,
357
+ accessibilityLabel,
358
+ accessibilityHint,
359
+ accessibilityRole = "button",
360
+ ringColor,
361
+ style,
362
+ testID,
363
+ children
364
+ }) => {
365
+ const [focused, setFocused] = react.useState(false);
366
+ return /* @__PURE__ */ jsxRuntime.jsx(
367
+ reactNative.Pressable,
368
+ {
369
+ accessibilityHint,
370
+ accessibilityLabel,
371
+ accessibilityRole,
372
+ style: [style, focusRingStyle(focused, ringColor)],
373
+ testID,
374
+ onBlur: () => setFocused(false),
375
+ onFocus: () => setFocused(true),
376
+ onPress,
377
+ children
378
+ }
379
+ );
380
+ };
310
381
  var TEXT_ON_PRIMARY = "#ffffff";
311
382
  function isAccountState(account) {
312
383
  return "displayName" in account;
@@ -344,11 +415,11 @@ var Topbar = ({
344
415
  /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: navStyles.topbarLeft, children: left }),
345
416
  /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: navStyles.topbarRight, children: [
346
417
  language ? /* @__PURE__ */ jsxRuntime.jsx(
347
- reactNative.TouchableOpacity,
418
+ FocusableTouchable,
348
419
  {
349
420
  accessibilityHint: language.hint,
350
421
  accessibilityLabel: language.label,
351
- accessibilityRole: "button",
422
+ ringColor: primaryColor,
352
423
  style: navStyles.topbarRowItem,
353
424
  onPress: language.onPress,
354
425
  children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [navStyles.topbarLabel, { color: colors.text }], children: language.label })
@@ -361,11 +432,11 @@ var Topbar = ({
361
432
  ] }) : null,
362
433
  richAccount ? /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: navStyles.userBlock, children: [
363
434
  richAccount.onAccount ? /* @__PURE__ */ jsxRuntime.jsx(
364
- reactNative.TouchableOpacity,
435
+ FocusableTouchable,
365
436
  {
366
437
  accessibilityHint: richAccount.displayName,
367
438
  accessibilityLabel: richAccount.displayName,
368
- accessibilityRole: "button",
439
+ ringColor: primaryColor,
369
440
  testID: NAV_TEST_IDS.accountName,
370
441
  onPress: richAccount.onAccount,
371
442
  children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [navStyles.userName, { color: colors.text }], children: richAccount.displayName })
@@ -389,11 +460,11 @@ var Topbar = ({
389
460
  ) : null
390
461
  ] }) : null,
391
462
  legacyAccount ? /* @__PURE__ */ jsxRuntime.jsx(
392
- reactNative.TouchableOpacity,
463
+ FocusableTouchable,
393
464
  {
394
465
  accessibilityHint: legacyAccount.hint,
395
466
  accessibilityLabel: legacyAccount.label,
396
- accessibilityRole: "button",
467
+ ringColor: primaryColor,
397
468
  style: [navStyles.accountBtn, { backgroundColor: primaryColor }],
398
469
  testID: legacyAccount.testID,
399
470
  onPress: legacyAccount.onPress,
@@ -401,11 +472,11 @@ var Topbar = ({
401
472
  }
402
473
  ) : null,
403
474
  upgrade ? /* @__PURE__ */ jsxRuntime.jsx(
404
- reactNative.TouchableOpacity,
475
+ FocusableTouchable,
405
476
  {
406
477
  accessibilityHint: upgrade.hint,
407
478
  accessibilityLabel: upgrade.label,
408
- accessibilityRole: "button",
479
+ ringColor: primaryColor,
409
480
  style: [navStyles.accountBtn, { backgroundColor: primaryColor }],
410
481
  testID: upgrade.testID ?? NAV_TEST_IDS.accountUpgrade,
411
482
  onPress: upgrade.onPress,
@@ -413,12 +484,11 @@ var Topbar = ({
413
484
  }
414
485
  ) : null,
415
486
  /* @__PURE__ */ jsxRuntime.jsx(
416
- reactNative.TouchableOpacity,
487
+ FocusableTouchable,
417
488
  {
418
- accessible: true,
419
489
  accessibilityHint: logout.hint,
420
490
  accessibilityLabel: logout.label,
421
- accessibilityRole: "button",
491
+ ringColor: primaryColor,
422
492
  style: [navStyles.accountBtn, { backgroundColor: primaryColor }],
423
493
  testID: logout.testID,
424
494
  onPress: richAccount ? richAccount.onLogout : logout.onPress,
@@ -431,10 +501,75 @@ var Topbar = ({
431
501
  );
432
502
  };
433
503
  var TEXT_ON_PRIMARY2 = "#ffffff";
504
+ function ariaCurrentProps2(isActive) {
505
+ return isActive ? { "aria-current": "page" } : {};
506
+ }
507
+ var NavBarLink = ({
508
+ item,
509
+ isActive,
510
+ collapsed,
511
+ iconSize,
512
+ reducedMotion,
513
+ colors,
514
+ navigateHint,
515
+ onPress
516
+ }) => {
517
+ const [hovered, setHovered] = react.useState(false);
518
+ const [focused, setFocused] = react.useState(false);
519
+ const isHovered = hovered && !isActive;
520
+ const textColor = isActive ? TEXT_ON_PRIMARY2 : isHovered ? colors.hoverText : colors.rest;
521
+ const pillStyle = isActive ? { backgroundColor: colors.activeBg } : isHovered ? { backgroundColor: colors.hoverBg } : void 0;
522
+ return /* @__PURE__ */ jsxRuntime.jsxs(
523
+ reactNative.Pressable,
524
+ {
525
+ accessibilityHint: navigateHint(item.label),
526
+ accessibilityLabel: item.label,
527
+ accessibilityRole: "link",
528
+ accessibilityState: { selected: isActive },
529
+ style: [
530
+ navBarStyles.link,
531
+ collapsed ? navBarStyles.linkStacked : void 0,
532
+ pillStyle,
533
+ hoverTransitionStyle(reducedMotion),
534
+ focusRingStyle(focused, colors.ring)
535
+ ],
536
+ testID: item.testID ?? item.key,
537
+ onBlur: () => setFocused(false),
538
+ onFocus: () => setFocused(true),
539
+ onHoverIn: () => setHovered(true),
540
+ onHoverOut: () => setHovered(false),
541
+ onPress: () => onPress(item.route),
542
+ ...ariaCurrentProps2(isActive),
543
+ children: [
544
+ typeof item.renderIcon === "function" ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: navBarStyles.linkIcon, children: item.renderIcon(textColor, iconSize) }) : null,
545
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [navBarStyles.linkText, { color: textColor }], children: item.label })
546
+ ]
547
+ }
548
+ );
549
+ };
550
+ var REDUCED_MOTION_QUERY = "(prefers-reduced-motion: reduce)";
551
+ function getReducedMotionQuery() {
552
+ if (typeof window === "undefined" || typeof window.matchMedia !== "function") return void 0;
553
+ return window.matchMedia(REDUCED_MOTION_QUERY);
554
+ }
555
+ function useReducedMotion() {
556
+ const [reduced, setReduced] = react.useState(() => getReducedMotionQuery()?.matches ?? false);
557
+ react.useEffect(() => {
558
+ const mql = getReducedMotionQuery();
559
+ if (mql === void 0 || mql.addEventListener === void 0) return void 0;
560
+ const onChange = () => setReduced(mql.matches);
561
+ mql.addEventListener("change", onChange);
562
+ return () => {
563
+ mql.removeEventListener?.("change", onChange);
564
+ };
565
+ }, []);
566
+ return reduced;
567
+ }
568
+ var LINKS_REGION_ID = "navbar-links-region";
434
569
  var DEFAULT_COLLAPSE_BELOW = 760;
435
570
  var MENU_GLYPH = "\u2630";
436
- function activeAriaProps(isActive) {
437
- return isActive ? { "aria-current": "page" } : {};
571
+ function ariaControlsProps(id) {
572
+ return { "aria-controls": id };
438
573
  }
439
574
  var NavBar = ({
440
575
  items,
@@ -454,7 +589,9 @@ var NavBar = ({
454
589
  const colors = theme.colors;
455
590
  const primaryColor = theme.palette.primary["500"];
456
591
  const { width } = reactNative.useWindowDimensions();
592
+ const reducedMotion = useReducedMotion();
457
593
  const [open, setOpen] = react.useState(false);
594
+ const [toggleFocused, setToggleFocused] = react.useState(false);
458
595
  const collapsed = width < collapseBelow;
459
596
  const showLinks = !collapsed || open;
460
597
  const toggleMenu = react.useCallback(() => setOpen((v) => !v), []);
@@ -465,33 +602,16 @@ var NavBar = ({
465
602
  },
466
603
  [onNavigate]
467
604
  );
468
- const activeLinkStyle = react.useMemo(
469
- () => ({ backgroundColor: primaryColor, borderRadius: ACTIVE_BORDER_RADIUS }),
470
- [primaryColor]
605
+ const linkColors = react.useMemo(
606
+ () => ({
607
+ rest: colors.textSecondary,
608
+ hoverText: colors.text,
609
+ hoverBg: colors.surfaceElevated,
610
+ activeBg: primaryColor,
611
+ ring: primaryColor
612
+ }),
613
+ [colors.textSecondary, colors.text, colors.surfaceElevated, primaryColor]
471
614
  );
472
- const renderLink = (item) => {
473
- const isActive = isRouteActive(pathname, item.route);
474
- const textColor = isActive ? TEXT_ON_PRIMARY2 : colors.textSecondary;
475
- return /* @__PURE__ */ jsxRuntime.jsxs(
476
- reactNative.TouchableOpacity,
477
- {
478
- accessible: true,
479
- accessibilityHint: navigateHint(item.label),
480
- accessibilityLabel: item.label,
481
- accessibilityRole: "link",
482
- accessibilityState: { selected: isActive },
483
- style: [navBarStyles.link, collapsed ? navBarStyles.linkStacked : void 0, isActive ? activeLinkStyle : void 0],
484
- testID: item.testID ?? item.key,
485
- onPress: () => handlePress(item.route),
486
- ...activeAriaProps(isActive),
487
- children: [
488
- typeof item.renderIcon === "function" ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: navBarStyles.linkIcon, children: item.renderIcon(textColor, NAV_ICON_SIZE) }) : null,
489
- /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [navBarStyles.linkText, { color: textColor }], children: item.label })
490
- ]
491
- },
492
- item.key
493
- );
494
- };
495
615
  return /* @__PURE__ */ jsxRuntime.jsx(
496
616
  reactNative.View,
497
617
  {
@@ -506,21 +626,44 @@ var NavBar = ({
506
626
  children: /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: navBarStyles.inner, children: [
507
627
  brand !== void 0 ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: navBarStyles.brand, children: brand }) : null,
508
628
  collapsed ? /* @__PURE__ */ jsxRuntime.jsx(
509
- reactNative.TouchableOpacity,
629
+ reactNative.Pressable,
510
630
  {
511
- accessible: true,
512
631
  accessibilityHint: menuHint,
513
632
  accessibilityLabel: menuLabel,
514
633
  accessibilityRole: "button",
515
634
  accessibilityState: { expanded: open },
516
635
  "aria-expanded": open,
517
- style: navBarStyles.toggle,
636
+ style: [navBarStyles.toggle, focusRingStyle(toggleFocused, primaryColor)],
518
637
  testID: NAV_TEST_IDS.navBarToggle,
638
+ onBlur: () => setToggleFocused(false),
639
+ onFocus: () => setToggleFocused(true),
519
640
  onPress: toggleMenu,
641
+ ...ariaControlsProps(LINKS_REGION_ID),
520
642
  children: typeof renderMenuIcon === "function" ? renderMenuIcon(colors.text, open) : /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [navBarStyles.toggleGlyph, { color: colors.text }], children: MENU_GLYPH })
521
643
  }
522
644
  ) : null,
523
- showLinks ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: collapsed ? navBarStyles.linksStacked : navBarStyles.links, testID: NAV_TEST_IDS.navBarLinks, children: items.map(renderLink) }) : null,
645
+ showLinks ? /* @__PURE__ */ jsxRuntime.jsx(
646
+ reactNative.View,
647
+ {
648
+ nativeID: LINKS_REGION_ID,
649
+ style: collapsed ? navBarStyles.linksStacked : navBarStyles.links,
650
+ testID: NAV_TEST_IDS.navBarLinks,
651
+ children: items.map((item) => /* @__PURE__ */ jsxRuntime.jsx(
652
+ NavBarLink,
653
+ {
654
+ collapsed,
655
+ colors: linkColors,
656
+ iconSize: NAV_ICON_SIZE,
657
+ isActive: isRouteActive(pathname, item.route),
658
+ item,
659
+ navigateHint,
660
+ reducedMotion,
661
+ onPress: handlePress
662
+ },
663
+ item.key
664
+ ))
665
+ }
666
+ ) : null,
524
667
  showLinks && right !== void 0 ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: collapsed ? navBarStyles.rightStacked : navBarStyles.right, children: right }) : null
525
668
  ] })
526
669
  }