@dloizides/ui-nav 1.15.0 → 1.16.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.mjs CHANGED
@@ -1,13 +1,45 @@
1
- import { StyleSheet, Platform, TouchableOpacity, View, Text, useWindowDimensions, ActivityIndicator, ScrollView, Pressable, TextInput } from 'react-native';
1
+ import React3, { useState, useCallback, useMemo, useEffect, useRef } from 'react';
2
+ import { StyleSheet, Platform, Text, TouchableOpacity, View, TextInput, Pressable, useWindowDimensions, ActivityIndicator, ScrollView } from 'react-native';
2
3
  import { useUi, UiProvider } from '@dloizides/ui-feedback';
3
- import React2, { useState, useCallback, useMemo, useEffect, useRef } from 'react';
4
4
  import { Collapse } from '@dloizides/ui-motion';
5
- import { jsxs, jsx } from 'react/jsx-runtime';
5
+ import { jsx, jsxs } from 'react/jsx-runtime';
6
6
  import { ModalDropdown } from '@dloizides/ui-layout';
7
7
  import { resolveAccessibleRoutes } from '@dloizides/auth-web';
8
8
 
9
9
  // src/Sidebar.tsx
10
10
 
11
+ // src/sidebarFilter.ts
12
+ function searchTokens(query) {
13
+ return query.trim().toLowerCase().split(/\s+/).filter(Boolean);
14
+ }
15
+ function labelMatches(item, tokens) {
16
+ const label = item.label.toLowerCase();
17
+ return tokens.every((token) => label.includes(token));
18
+ }
19
+ function filterNavItems(items, query) {
20
+ const tokens = searchTokens(query);
21
+ if (tokens.length === 0) return [...items];
22
+ return filterByTokens(items, tokens);
23
+ }
24
+ function filterByTokens(items, tokens) {
25
+ const kept = [];
26
+ for (const item of items) {
27
+ const selfMatch = labelMatches(item, tokens);
28
+ const children = item.children ?? [];
29
+ if (selfMatch) {
30
+ kept.push(item);
31
+ continue;
32
+ }
33
+ if (children.length === 0) continue;
34
+ const prunedChildren = filterByTokens(children, tokens);
35
+ if (prunedChildren.length > 0) kept.push({ ...item, children: prunedChildren });
36
+ }
37
+ return kept;
38
+ }
39
+ function isFilterActive(query) {
40
+ return searchTokens(query).length > 0;
41
+ }
42
+
11
43
  // src/isRouteActive.ts
12
44
  function isRouteActive(pathname, route) {
13
45
  if (route === "/") return pathname === "/";
@@ -15,6 +47,9 @@ function isRouteActive(pathname, route) {
15
47
  }
16
48
  var ACTIVE_BORDER_RADIUS = 4;
17
49
  var ACTIVE_ACCENT_WIDTH = 3;
50
+ var NAV_ROW_RADIUS = 8;
51
+ var ACTIVE_TINT_OPACITY = 0.14;
52
+ var HOVER_TINT_OPACITY = 0.06;
18
53
  var NAV_LINK_GAP = 4;
19
54
  var navStyles = StyleSheet.create({
20
55
  // --- Sidebar ---
@@ -40,6 +75,12 @@ var navStyles = StyleSheet.create({
40
75
  sidebarSpacer: {
41
76
  flex: 1
42
77
  },
78
+ // Inline-search no-match text — sits where the nav list would, muted + padded.
79
+ sidebarEmpty: {
80
+ fontSize: 13,
81
+ paddingVertical: 10,
82
+ paddingHorizontal: 12
83
+ },
43
84
  // --- Topbar ---
44
85
  topbarContainer: {
45
86
  height: 64,
@@ -240,8 +281,12 @@ var collapsedRailStyles = StyleSheet.create({
240
281
  var expandableStyles = StyleSheet.create({
241
282
  // Every leaf reserves the accent-bar gutter (a TRANSPARENT left border of the
242
283
  // same width the active item colours in) so activation never shifts the row.
284
+ // `overflow: hidden` clips the rounded {@link tintOverlay} to the row's corners;
285
+ // `position: relative` anchors that absolutely-filled overlay to the row.
243
286
  childItem: {
244
- borderRadius: 6,
287
+ position: "relative",
288
+ overflow: "hidden",
289
+ borderRadius: NAV_ROW_RADIUS,
245
290
  flexDirection: "row",
246
291
  alignItems: "center",
247
292
  paddingVertical: 9,
@@ -252,8 +297,24 @@ var expandableStyles = StyleSheet.create({
252
297
  childItemTextWithIcon: { fontSize: 14, marginLeft: 6 },
253
298
  chevron: { marginLeft: "auto" },
254
299
  childrenContainer: { overflow: "hidden", marginBottom: 4 },
300
+ // The hover/active brand wash: a rounded fill behind the row's icon + label,
301
+ // its opacity eased between 0 / hover / active. Colour + opacity are applied at
302
+ // render time (theme primary); this only positions and rounds it.
303
+ tintOverlay: {
304
+ position: "absolute",
305
+ top: 0,
306
+ left: 0,
307
+ right: 0,
308
+ bottom: 0,
309
+ borderRadius: NAV_ROW_RADIUS,
310
+ // `style.pointerEvents` (not the deprecated prop) so the overlay never
311
+ // intercepts the row's press/hover — RNW forwards this style key to the DOM.
312
+ pointerEvents: "none"
313
+ },
255
314
  header: {
256
- borderRadius: 6,
315
+ position: "relative",
316
+ overflow: "hidden",
317
+ borderRadius: NAV_ROW_RADIUS,
257
318
  flexDirection: "row",
258
319
  alignItems: "center",
259
320
  paddingVertical: 8
@@ -280,6 +341,7 @@ var IS_WEB = Platform.OS === "web";
280
341
  var FOCUS_RING_WIDTH = 2;
281
342
  var FOCUS_RING_OFFSET = 2;
282
343
  var HOVER_TRANSITION_MS = 150;
344
+ var TINT_TRANSITION_MS = 160;
283
345
  function focusRingStyle(focused, ringColor) {
284
346
  if (!IS_WEB || !focused) return void 0;
285
347
  const ring = {
@@ -290,24 +352,80 @@ function focusRingStyle(focused, ringColor) {
290
352
  };
291
353
  return ring;
292
354
  }
293
- function hoverTransitionStyle(reducedMotion) {
355
+ function webTransition(properties, durationMs, reducedMotion) {
294
356
  if (!IS_WEB) return void 0;
295
357
  const transition = {
296
- transitionProperty: "color, background-color",
297
- transitionDuration: reducedMotion ? "0ms" : `${HOVER_TRANSITION_MS}ms`
358
+ transitionProperty: properties,
359
+ transitionDuration: reducedMotion ? "0ms" : `${durationMs}ms`
298
360
  };
299
361
  return transition;
300
362
  }
363
+ function hoverTransitionStyle(reducedMotion) {
364
+ return webTransition("color, background-color", HOVER_TRANSITION_MS, reducedMotion);
365
+ }
366
+ function tintTransitionStyle(reducedMotion) {
367
+ return webTransition("opacity", TINT_TRANSITION_MS, reducedMotion);
368
+ }
369
+ function rotateTransitionStyle(reducedMotion) {
370
+ return webTransition("transform", TINT_TRANSITION_MS, reducedMotion);
371
+ }
372
+ var REDUCED_MOTION_QUERY = "(prefers-reduced-motion: reduce)";
373
+ function getReducedMotionQuery() {
374
+ if (typeof window === "undefined" || typeof window.matchMedia !== "function") return void 0;
375
+ return window.matchMedia(REDUCED_MOTION_QUERY);
376
+ }
377
+ function useReducedMotion() {
378
+ const [reduced, setReduced] = useState(() => getReducedMotionQuery()?.matches ?? false);
379
+ useEffect(() => {
380
+ const mql = getReducedMotionQuery();
381
+ if (mql === void 0 || mql.addEventListener === void 0) return void 0;
382
+ const onChange = () => setReduced(mql.matches);
383
+ mql.addEventListener("change", onChange);
384
+ return () => {
385
+ mql.removeEventListener?.("change", onChange);
386
+ };
387
+ }, []);
388
+ return reduced;
389
+ }
390
+ var CHEVRON_GLYPH = "\u203A";
391
+ var EXPANDED_ROTATION = "90deg";
392
+ var COLLAPSED_ROTATION = "0deg";
393
+ var CHEVRON_FONT_WEIGHT = "700";
394
+ var SidebarChevron = ({ expanded, color, size }) => {
395
+ const reducedMotion = useReducedMotion();
396
+ const style = {
397
+ color,
398
+ fontSize: size,
399
+ fontWeight: CHEVRON_FONT_WEIGHT,
400
+ transform: [{ rotate: expanded ? EXPANDED_ROTATION : COLLAPSED_ROTATION }]
401
+ };
402
+ return /* @__PURE__ */ jsx(
403
+ Text,
404
+ {
405
+ accessibilityElementsHidden: true,
406
+ importantForAccessibility: "no",
407
+ style: [style, rotateTransitionStyle(reducedMotion)],
408
+ children: CHEVRON_GLYPH
409
+ }
410
+ );
411
+ };
301
412
  function ariaCurrentProps(isActive) {
302
413
  return isActive ? { "aria-current": "page" } : {};
303
414
  }
304
415
  function ariaExpandedProps(expanded) {
305
416
  return { "aria-expanded": expanded };
306
417
  }
418
+ function hoverHandlerProps(onIn, onOut) {
419
+ return IS_WEB ? { onHoverIn: onIn, onHoverOut: onOut } : {};
420
+ }
307
421
  function hasActiveDescendant(item, pathname) {
308
422
  if (isRouteActive(pathname, item.route)) return true;
309
423
  return (item.children ?? []).some((child) => hasActiveDescendant(child, pathname));
310
424
  }
425
+ function TintOverlay({ opacity, color }) {
426
+ const reducedMotion = useReducedMotion();
427
+ return /* @__PURE__ */ jsx(View, { style: [expandableStyles.tintOverlay, { backgroundColor: color, opacity }, tintTransitionStyle(reducedMotion)] });
428
+ }
311
429
  var NavExpandableItem = ({
312
430
  item,
313
431
  pathname,
@@ -320,25 +438,25 @@ var NavExpandableItem = ({
320
438
  }) => {
321
439
  const [expanded, setExpanded] = useState(() => hasActiveDescendant(item, pathname));
322
440
  const [focused, setFocused] = useState(false);
441
+ const [hovered, setHovered] = useState(false);
323
442
  const { theme } = useUi();
324
443
  const colors = theme.colors;
325
444
  const primaryColor = theme.palette.primary["500"];
326
445
  const toggle = useCallback(() => setExpanded((v) => !v), []);
446
+ const onHoverIn = useCallback(() => setHovered(true), []);
447
+ const onHoverOut = useCallback(() => setHovered(false), []);
327
448
  const indent = depth * BASE_INDENT;
328
449
  const hasChildren = Array.isArray(item.children) && item.children.length > 0;
329
450
  const isActive = isRouteActive(pathname, item.route);
330
451
  const isSectionHeader = hasChildren && depth === 0;
331
- const activeItemStyle = useMemo(
332
- () => ({
333
- backgroundColor: colors.border,
334
- borderRadius: ACTIVE_BORDER_RADIUS,
335
- borderLeftWidth: ACTIVE_ACCENT_WIDTH,
336
- borderLeftColor: primaryColor
337
- }),
338
- [colors.border, primaryColor]
452
+ const activeAccentStyle = useMemo(
453
+ () => ({ borderLeftWidth: ACTIVE_ACCENT_WIDTH, borderLeftColor: primaryColor }),
454
+ [primaryColor]
339
455
  );
340
456
  const paddingStyle = useMemo(() => ({ paddingLeft: indent + BASE_INDENT }), [indent]);
341
457
  const headerPaddingStyle = useMemo(() => ({ paddingLeft: indent }), [indent]);
458
+ const leafTintOpacity = isActive ? ACTIVE_TINT_OPACITY : hovered ? HOVER_TINT_OPACITY : 0;
459
+ const headerTintOpacity = hovered ? HOVER_TINT_OPACITY : 0;
342
460
  if (!hasChildren)
343
461
  return /* @__PURE__ */ jsxs(
344
462
  TouchableOpacity,
@@ -350,15 +468,17 @@ var NavExpandableItem = ({
350
468
  style: [
351
469
  expandableStyles.childItem,
352
470
  paddingStyle,
353
- isActive ? activeItemStyle : void 0,
471
+ isActive ? activeAccentStyle : void 0,
354
472
  focusRingStyle(focused, primaryColor)
355
473
  ],
356
474
  testID: item.testID ?? item.key,
357
475
  onBlur: () => setFocused(false),
358
476
  onFocus: () => setFocused(true),
359
477
  onPress: () => onNavigate(item.route),
478
+ ...hoverHandlerProps(onHoverIn, onHoverOut),
360
479
  ...ariaCurrentProps(isActive),
361
480
  children: [
481
+ /* @__PURE__ */ jsx(TintOverlay, { color: primaryColor, opacity: leafTintOpacity }),
362
482
  typeof item.renderIcon === "function" ? /* @__PURE__ */ jsx(View, { style: expandableStyles.iconWrapper, children: item.renderIcon(colors.textSecondary, NAV_ICON_SIZE) }) : null,
363
483
  /* @__PURE__ */ jsx(
364
484
  Text,
@@ -373,6 +493,7 @@ var NavExpandableItem = ({
373
493
  ]
374
494
  }
375
495
  );
496
+ const chevronColor = colors.textSecondary;
376
497
  return /* @__PURE__ */ jsxs(View, { children: [
377
498
  /* @__PURE__ */ jsxs(
378
499
  TouchableOpacity,
@@ -391,8 +512,10 @@ var NavExpandableItem = ({
391
512
  onBlur: () => setFocused(false),
392
513
  onFocus: () => setFocused(true),
393
514
  onPress: toggle,
515
+ ...hoverHandlerProps(onHoverIn, onHoverOut),
394
516
  ...ariaExpandedProps(expanded),
395
517
  children: [
518
+ /* @__PURE__ */ jsx(TintOverlay, { color: primaryColor, opacity: headerTintOpacity }),
396
519
  typeof item.renderIcon === "function" ? /* @__PURE__ */ jsx(View, { style: expandableStyles.iconWrapper, children: item.renderIcon(isSectionHeader ? colors.textSecondary : colors.text, NAV_ICON_SIZE) }) : null,
397
520
  /* @__PURE__ */ jsx(
398
521
  Text,
@@ -404,7 +527,7 @@ var NavExpandableItem = ({
404
527
  children: item.label
405
528
  }
406
529
  ),
407
- typeof renderChevron === "function" ? /* @__PURE__ */ jsx(View, { style: expandableStyles.chevron, children: renderChevron(expanded, colors.textSecondary, CHEVRON_ICON_SIZE) }) : null
530
+ /* @__PURE__ */ jsx(View, { style: expandableStyles.chevron, children: typeof renderChevron === "function" ? renderChevron(expanded, chevronColor, CHEVRON_ICON_SIZE) : /* @__PURE__ */ jsx(SidebarChevron, { color: chevronColor, expanded, size: CHEVRON_ICON_SIZE }) })
408
531
  ]
409
532
  }
410
533
  ),
@@ -424,13 +547,93 @@ var NavExpandableItem = ({
424
547
  )) }) })
425
548
  ] });
426
549
  };
550
+ var FIELD_MIN_HEIGHT = 38;
551
+ var CLEAR_GLYPH = "\u2715";
552
+ var CLEAR_MIN_TARGET = 28;
553
+ var styles = StyleSheet.create({
554
+ wrap: {
555
+ flexDirection: "row",
556
+ alignItems: "center",
557
+ borderWidth: 1,
558
+ borderRadius: 8,
559
+ paddingLeft: 12,
560
+ paddingRight: 6,
561
+ marginBottom: 12
562
+ },
563
+ input: {
564
+ flex: 1,
565
+ fontSize: 14,
566
+ paddingVertical: 8,
567
+ minHeight: FIELD_MIN_HEIGHT
568
+ },
569
+ clear: {
570
+ minWidth: CLEAR_MIN_TARGET,
571
+ minHeight: CLEAR_MIN_TARGET,
572
+ alignItems: "center",
573
+ justifyContent: "center",
574
+ borderRadius: 6
575
+ },
576
+ clearGlyph: { fontSize: 13, fontWeight: "700" }
577
+ });
578
+ var SidebarSearch = ({
579
+ value,
580
+ onChangeText,
581
+ onClear,
582
+ labels,
583
+ testID
584
+ }) => {
585
+ const { theme } = useUi();
586
+ const colors = theme.colors;
587
+ const primary = theme.palette.primary["500"];
588
+ const [focused, setFocused] = useState(false);
589
+ const hasQuery = value.length > 0;
590
+ return /* @__PURE__ */ jsxs(
591
+ View,
592
+ {
593
+ style: [
594
+ styles.wrap,
595
+ { backgroundColor: colors.surface, borderColor: focused ? primary : colors.border },
596
+ focusRingStyle(focused, primary)
597
+ ],
598
+ children: [
599
+ /* @__PURE__ */ jsx(
600
+ TextInput,
601
+ {
602
+ accessibilityHint: labels.hint,
603
+ accessibilityLabel: labels.placeholder,
604
+ placeholder: labels.placeholder,
605
+ placeholderTextColor: colors.textSecondary,
606
+ style: [styles.input, { color: colors.text }],
607
+ testID,
608
+ value,
609
+ onBlur: () => setFocused(false),
610
+ onChangeText,
611
+ onFocus: () => setFocused(true)
612
+ }
613
+ ),
614
+ hasQuery ? /* @__PURE__ */ jsx(
615
+ Pressable,
616
+ {
617
+ accessibilityHint: labels.clearHint,
618
+ accessibilityLabel: labels.clearLabel,
619
+ accessibilityRole: "button",
620
+ style: styles.clear,
621
+ testID: `${testID}-clear`,
622
+ onPress: onClear,
623
+ children: /* @__PURE__ */ jsx(Text, { style: [styles.clearGlyph, { color: colors.textSecondary }], children: CLEAR_GLYPH })
624
+ }
625
+ ) : null
626
+ ]
627
+ }
628
+ );
629
+ };
427
630
  function isBareText(node) {
428
631
  return typeof node === "string" || typeof node === "number";
429
632
  }
430
633
  function renderTextSlot(node, style) {
431
634
  if (node === void 0 || node === null) return node;
432
635
  if (isBareText(node)) return /* @__PURE__ */ jsx(Text, { style, children: node });
433
- return React2.Children.map(
636
+ return React3.Children.map(
434
637
  node,
435
638
  (child) => isBareText(child) ? /* @__PURE__ */ jsx(Text, { style, children: child }) : child
436
639
  );
@@ -445,12 +648,30 @@ var Sidebar = ({
445
648
  expandHint = "",
446
649
  collapseHint = "",
447
650
  renderChevron,
651
+ enableInlineSearch = false,
652
+ search,
653
+ searchQuery,
654
+ onSearchChange,
448
655
  header,
449
656
  footer,
450
657
  containerStyle
451
658
  }) => {
452
659
  const { theme } = useUi();
453
660
  const colors = theme.colors;
661
+ const [internalQuery, setInternalQuery] = useState("");
662
+ const controlled = searchQuery !== void 0;
663
+ const query = controlled ? searchQuery : internalQuery;
664
+ const setQuery = useCallback(
665
+ (next) => {
666
+ if (!controlled) setInternalQuery(next);
667
+ onSearchChange?.(next);
668
+ },
669
+ [controlled, onSearchChange]
670
+ );
671
+ const clearQuery = useCallback(() => setQuery(""), [setQuery]);
672
+ const searchOn = enableInlineSearch && search !== void 0;
673
+ const visibleItems = searchOn ? filterNavItems(items, query) : items;
674
+ const showEmpty = searchOn && isFilterActive(query) && visibleItems.length === 0;
454
675
  return /* @__PURE__ */ jsxs(
455
676
  View,
456
677
  {
@@ -464,8 +685,18 @@ var Sidebar = ({
464
685
  ],
465
686
  children: [
466
687
  /* @__PURE__ */ jsx(Text, { accessibilityRole: "header", style: [navStyles.sidebarTitle, { color: colors.text }], children: title }),
688
+ searchOn && search !== void 0 ? /* @__PURE__ */ jsx(
689
+ SidebarSearch,
690
+ {
691
+ labels: search,
692
+ testID: "sidebar-search",
693
+ value: query,
694
+ onChangeText: setQuery,
695
+ onClear: clearQuery
696
+ }
697
+ ) : null,
467
698
  renderTextSlot(header, { color: colors.text }),
468
- items.map((item) => /* @__PURE__ */ jsx(
699
+ showEmpty && search !== void 0 ? /* @__PURE__ */ jsx(Text, { style: [navStyles.sidebarEmpty, { color: colors.textSecondary }], testID: "sidebar-search-empty", children: search.emptyText }) : visibleItems.map((item) => /* @__PURE__ */ jsx(
469
700
  NavExpandableItem,
470
701
  {
471
702
  collapseHint,
@@ -485,6 +716,12 @@ var Sidebar = ({
485
716
  );
486
717
  };
487
718
 
719
+ // src/searchAffordance.ts
720
+ var DEFAULT_SEARCH_BREAKPOINT = 768;
721
+ function resolveSearchAffordance(viewport, breakpoint = DEFAULT_SEARCH_BREAKPOINT) {
722
+ return viewport >= breakpoint ? "inline" : "palette";
723
+ }
724
+
488
725
  // src/constants.ts
489
726
  var NAV_TEST_IDS = {
490
727
  /** displayName line in the rich account header (tappable when `onAccount` set). */
@@ -820,24 +1057,6 @@ function useNavOverflow(itemCount, gap) {
820
1057
  );
821
1058
  return { visibleCount, setAvailableWidth, setItemWidth, setMoreWidth };
822
1059
  }
823
- var REDUCED_MOTION_QUERY = "(prefers-reduced-motion: reduce)";
824
- function getReducedMotionQuery() {
825
- if (typeof window === "undefined" || typeof window.matchMedia !== "function") return void 0;
826
- return window.matchMedia(REDUCED_MOTION_QUERY);
827
- }
828
- function useReducedMotion() {
829
- const [reduced, setReduced] = useState(() => getReducedMotionQuery()?.matches ?? false);
830
- useEffect(() => {
831
- const mql = getReducedMotionQuery();
832
- if (mql === void 0 || mql.addEventListener === void 0) return void 0;
833
- const onChange = () => setReduced(mql.matches);
834
- mql.addEventListener("change", onChange);
835
- return () => {
836
- mql.removeEventListener?.("change", onChange);
837
- };
838
- }, []);
839
- return reduced;
840
- }
841
1060
  var LINKS_REGION_ID = "navbar-links-region";
842
1061
  var DEFAULT_COLLAPSE_BELOW = 760;
843
1062
  var MENU_GLYPH = "\u2630";
@@ -938,7 +1157,7 @@ var NavBarInner = ({
938
1157
  }
939
1158
  );
940
1159
  if (collapsed) {
941
- return /* @__PURE__ */ jsx(View, { nativeID: LINKS_REGION_ID, style: navBarStyles.linksStacked, testID: NAV_TEST_IDS.navBarLinks, children: items.map((item) => /* @__PURE__ */ jsx(React2.Fragment, { children: renderLink(item) }, item.key)) });
1160
+ return /* @__PURE__ */ jsx(View, { nativeID: LINKS_REGION_ID, style: navBarStyles.linksStacked, testID: NAV_TEST_IDS.navBarLinks, children: items.map((item) => /* @__PURE__ */ jsx(React3.Fragment, { children: renderLink(item) }, item.key)) });
942
1161
  }
943
1162
  const visibleItems = items.slice(0, visibleCount);
944
1163
  const overflowItems = items.slice(visibleCount);
@@ -1004,7 +1223,7 @@ var CARD_BORDER_WIDTH = 1;
1004
1223
  var CARD_TITLE_FONT_SIZE = 16;
1005
1224
  var CARD_MESSAGE_FONT_SIZE = 14;
1006
1225
  var CARD_TITLE_MARGIN_BOTTOM = 8;
1007
- var styles = StyleSheet.create({
1226
+ var styles2 = StyleSheet.create({
1008
1227
  centerFill: { flex: 1, justifyContent: "center", alignItems: "center" },
1009
1228
  card: {
1010
1229
  padding: CARD_PADDING,
@@ -1021,9 +1240,9 @@ function MessageCard({
1021
1240
  }) {
1022
1241
  const { theme } = useUi();
1023
1242
  const colors = theme.colors;
1024
- return /* @__PURE__ */ jsxs(View, { style: [styles.card, { backgroundColor: colors.surface, borderColor: accentColor }], testID, children: [
1025
- /* @__PURE__ */ jsx(Text, { style: [styles.cardTitle, { color: accentColor }], children: message.titleText }),
1026
- /* @__PURE__ */ jsx(Text, { style: [styles.cardMessage, { color: colors.textSecondary }], children: message.messageText })
1243
+ return /* @__PURE__ */ jsxs(View, { style: [styles2.card, { backgroundColor: colors.surface, borderColor: accentColor }], testID, children: [
1244
+ /* @__PURE__ */ jsx(Text, { style: [styles2.cardTitle, { color: accentColor }], children: message.titleText }),
1245
+ /* @__PURE__ */ jsx(Text, { style: [styles2.cardMessage, { color: colors.textSecondary }], children: message.messageText })
1027
1246
  ] });
1028
1247
  }
1029
1248
  function useContentBody(state, children, testID) {
@@ -1035,7 +1254,7 @@ function useContentBody(state, children, testID) {
1035
1254
  if (state?.error)
1036
1255
  return /* @__PURE__ */ jsx(MessageCard, { accentColor: errorColor, message: state.error, testID: `${testID}${APP_SHELL_SUFFIX.error}` });
1037
1256
  if (state?.loading === true)
1038
- return /* @__PURE__ */ jsx(View, { style: styles.centerFill, testID: `${testID}${APP_SHELL_SUFFIX.loading}`, children: /* @__PURE__ */ jsx(ActivityIndicator, { color: primary, size: "large" }) });
1257
+ return /* @__PURE__ */ jsx(View, { style: styles2.centerFill, testID: `${testID}${APP_SHELL_SUFFIX.loading}`, children: /* @__PURE__ */ jsx(ActivityIndicator, { color: primary, size: "large" }) });
1039
1258
  return children;
1040
1259
  }
1041
1260
  var SCRIM_COLOR = "rgba(0, 0, 0, 0.5)";
@@ -1059,7 +1278,7 @@ var DEFAULT_DRAWER_LABELS = {
1059
1278
  closeLabel: "Close menu",
1060
1279
  closeHint: "Close the navigation menu"
1061
1280
  };
1062
- var styles2 = StyleSheet.create({
1281
+ var styles3 = StyleSheet.create({
1063
1282
  overlay: { ...StyleSheet.absoluteFillObject, zIndex: DRAWER_Z_INDEX },
1064
1283
  // The scrim's tap-to-close hit area is anchored to the RIGHT of the panel
1065
1284
  // (`left: DRAWER_WIDTH`), so it NEVER overlaps the panel: nav-item taps always
@@ -1094,10 +1313,10 @@ var MenuToggle = ({ label, hint, onOpen, testID }) => {
1094
1313
  accessibilityHint: hint,
1095
1314
  accessibilityLabel: label,
1096
1315
  ringColor: theme.palette.primary["500"],
1097
- style: styles2.menuToggle,
1316
+ style: styles3.menuToggle,
1098
1317
  testID,
1099
1318
  onPress: onOpen,
1100
- children: /* @__PURE__ */ jsx(Text, { style: [styles2.menuToggleGlyph, { color: theme.colors.text }], children: MENU_GLYPH2 })
1319
+ children: /* @__PURE__ */ jsx(Text, { style: [styles3.menuToggleGlyph, { color: theme.colors.text }], children: MENU_GLYPH2 })
1101
1320
  }
1102
1321
  );
1103
1322
  };
@@ -1123,14 +1342,14 @@ var MobileDrawer = ({
1123
1342
  node.addEventListener("click", handleClick);
1124
1343
  return () => node.removeEventListener("click", handleClick);
1125
1344
  }, [onClose]);
1126
- return /* @__PURE__ */ jsxs(View, { style: styles2.overlay, children: [
1345
+ return /* @__PURE__ */ jsxs(View, { style: styles3.overlay, children: [
1127
1346
  /* @__PURE__ */ jsx(
1128
1347
  Pressable,
1129
1348
  {
1130
1349
  accessibilityHint: labels.closeHint,
1131
1350
  accessibilityLabel: labels.closeLabel,
1132
1351
  accessibilityRole: "button",
1133
- style: styles2.scrim,
1352
+ style: styles3.scrim,
1134
1353
  testID: scrimTestID,
1135
1354
  onPress: onClose
1136
1355
  }
@@ -1141,7 +1360,7 @@ var MobileDrawer = ({
1141
1360
  ref: panelRef,
1142
1361
  "aria-modal": true,
1143
1362
  role: "dialog",
1144
- style: [styles2.panel, { backgroundColor: theme.colors.surface }],
1363
+ style: [styles3.panel, { backgroundColor: theme.colors.surface }],
1145
1364
  testID: drawerTestID,
1146
1365
  children: renderTextSlot(sidebar, { color: theme.colors.text })
1147
1366
  }
@@ -1172,7 +1391,7 @@ function useContentMaxWidth(width) {
1172
1391
  return resolveContentMaxWidth(width, viewport);
1173
1392
  }
1174
1393
  var DEFAULT_CONTENT_PADDING = 24;
1175
- var styles3 = StyleSheet.create({
1394
+ var styles4 = StyleSheet.create({
1176
1395
  root: { flex: 1 },
1177
1396
  centerFill: { flex: 1, justifyContent: "center", alignItems: "center" },
1178
1397
  scroll: { flex: 1 },
@@ -1229,20 +1448,20 @@ var AppShell = ({
1229
1448
  if (isUnauthenticated && gate !== void 0) gate.onRedirect();
1230
1449
  }, [isUnauthenticated, gate]);
1231
1450
  if (gate?.pending === true)
1232
- return /* @__PURE__ */ jsx(View, { style: [styles3.root, styles3.centerFill, { backgroundColor: theme.colors.background }], testID: `${testID}${APP_SHELL_SUFFIX.pending}`, children: /* @__PURE__ */ jsx(ActivityIndicator, { color: primary, size: "large" }) });
1451
+ return /* @__PURE__ */ jsx(View, { style: [styles4.root, styles4.centerFill, { backgroundColor: theme.colors.background }], testID: `${testID}${APP_SHELL_SUFFIX.pending}`, children: /* @__PURE__ */ jsx(ActivityIndicator, { color: primary, size: "large" }) });
1233
1452
  if (isUnauthenticated) return null;
1234
- const columnStyle = maxWidth === "full" ? styles3.columnFull : [styles3.columnCapped, { maxWidth }];
1235
- const navInnerStyle = chromeAlignment === "content" && maxWidth !== "full" ? [styles3.chromeCapped, { maxWidth, paddingHorizontal: contentPadding }] : void 0;
1453
+ const columnStyle = maxWidth === "full" ? styles4.columnFull : [styles4.columnCapped, { maxWidth }];
1454
+ const navInnerStyle = chromeAlignment === "content" && maxWidth !== "full" ? [styles4.chromeCapped, { maxWidth, paddingHorizontal: contentPadding }] : void 0;
1236
1455
  const scroller = /* @__PURE__ */ jsx(
1237
1456
  ScrollView,
1238
1457
  {
1239
- contentContainerStyle: [styles3.scrollContent, { padding: contentPadding }],
1240
- style: styles3.scroll,
1458
+ contentContainerStyle: [styles4.scrollContent, { padding: contentPadding }],
1459
+ style: styles4.scroll,
1241
1460
  testID: `${testID}${APP_SHELL_SUFFIX.content}`,
1242
1461
  children: /* @__PURE__ */ jsx(View, { style: columnStyle, children: body })
1243
1462
  }
1244
1463
  );
1245
- const headerRegion = useDrawer ? /* @__PURE__ */ jsxs(View, { style: styles3.headerRow, testID: `${testID}${APP_SHELL_SUFFIX.header}`, children: [
1464
+ const headerRegion = useDrawer ? /* @__PURE__ */ jsxs(View, { style: styles4.headerRow, testID: `${testID}${APP_SHELL_SUFFIX.header}`, children: [
1246
1465
  /* @__PURE__ */ jsx(
1247
1466
  MenuToggle,
1248
1467
  {
@@ -1252,14 +1471,14 @@ var AppShell = ({
1252
1471
  onOpen: openDrawer
1253
1472
  }
1254
1473
  ),
1255
- /* @__PURE__ */ jsx(View, { style: styles3.headerFill, children: renderTextSlot(header, { color: theme.colors.text }) })
1474
+ /* @__PURE__ */ jsx(View, { style: styles4.headerFill, children: renderTextSlot(header, { color: theme.colors.text }) })
1256
1475
  ] }) : /* @__PURE__ */ jsx(View, { testID: `${testID}${APP_SHELL_SUFFIX.header}`, children: renderTextSlot(header, { color: theme.colors.text }) });
1257
1476
  const railRegion = railMode === "full" ? /* @__PURE__ */ jsx(View, { testID: `${testID}${APP_SHELL_SUFFIX.sidebar}`, children: renderTextSlot(sidebar, { color: theme.colors.text }) }) : railMode === "collapsed" ? /* @__PURE__ */ jsx(View, { testID: `${testID}${APP_SHELL_SUFFIX.collapsedSidebar}`, children: renderTextSlot(collapsedSidebar, { color: theme.colors.text }) }) : null;
1258
- return /* @__PURE__ */ jsxs(View, { style: [styles3.root, { backgroundColor: theme.colors.background }], testID, children: [
1477
+ return /* @__PURE__ */ jsxs(View, { style: [styles4.root, { backgroundColor: theme.colors.background }], testID, children: [
1259
1478
  headerRegion,
1260
1479
  nav !== void 0 ? /* @__PURE__ */ jsx(View, { testID: `${testID}${APP_SHELL_SUFFIX.nav}`, children: navInnerStyle !== void 0 ? /* @__PURE__ */ jsx(View, { style: navInnerStyle, children: renderTextSlot(nav, { color: theme.colors.text }) }) : renderTextSlot(nav, { color: theme.colors.text }) }) : null,
1261
1480
  banner !== void 0 ? /* @__PURE__ */ jsx(View, { testID: `${testID}${APP_SHELL_SUFFIX.banner}`, children: renderTextSlot(banner, { color: theme.colors.text }) }) : null,
1262
- railRegion !== null ? /* @__PURE__ */ jsxs(View, { style: styles3.bodyRow, children: [
1481
+ railRegion !== null ? /* @__PURE__ */ jsxs(View, { style: styles4.bodyRow, children: [
1263
1482
  railRegion,
1264
1483
  scroller
1265
1484
  ] }) : scroller,
@@ -1553,7 +1772,7 @@ var KEY_DOWN = "ArrowDown";
1553
1772
  var KEY_UP = "ArrowUp";
1554
1773
  var KEY_ENTER = "Enter";
1555
1774
  var KEY_ESCAPE = "Escape";
1556
- var styles4 = StyleSheet.create({
1775
+ var styles5 = StyleSheet.create({
1557
1776
  overlay: { ...StyleSheet.absoluteFillObject, zIndex: OVERLAY_Z_INDEX, alignItems: "center" },
1558
1777
  scrim: { ...StyleSheet.absoluteFillObject, backgroundColor: SCRIM_COLOR2 },
1559
1778
  panel: {
@@ -1582,14 +1801,14 @@ var PaletteRow = ({ item, selected, onChoose }) => {
1582
1801
  accessibilityLabel: item.label,
1583
1802
  accessibilityRole: "button",
1584
1803
  accessibilityState: { selected },
1585
- style: [styles4.row, selected ? { backgroundColor: colors.border } : void 0],
1804
+ style: [styles5.row, selected ? { backgroundColor: colors.border } : void 0],
1586
1805
  testID: item.testID ?? item.key,
1587
1806
  onPress: () => onChoose(item),
1588
1807
  children: [
1589
1808
  typeof item.renderIcon === "function" ? item.renderIcon(colors.textSecondary, NAV_ICON_SIZE) : null,
1590
- /* @__PURE__ */ jsxs(View, { style: styles4.rowText, children: [
1591
- /* @__PURE__ */ jsx(Text, { style: [styles4.rowLabel, { color: colors.text }], children: item.label }),
1592
- item.hint ? /* @__PURE__ */ jsx(Text, { style: [styles4.rowHint, { color: colors.textSecondary }], children: item.hint }) : null
1809
+ /* @__PURE__ */ jsxs(View, { style: styles5.rowText, children: [
1810
+ /* @__PURE__ */ jsx(Text, { style: [styles5.rowLabel, { color: colors.text }], children: item.label }),
1811
+ item.hint ? /* @__PURE__ */ jsx(Text, { style: [styles5.rowHint, { color: colors.textSecondary }], children: item.hint }) : null
1593
1812
  ] })
1594
1813
  ]
1595
1814
  }
@@ -1629,14 +1848,14 @@ var CommandPalette = ({ open, items, onClose, labels, testID }) => {
1629
1848
  [results, selected, choose, onClose]
1630
1849
  );
1631
1850
  if (!open) return null;
1632
- return /* @__PURE__ */ jsxs(View, { style: styles4.overlay, children: [
1851
+ return /* @__PURE__ */ jsxs(View, { style: styles5.overlay, children: [
1633
1852
  /* @__PURE__ */ jsx(
1634
1853
  Pressable,
1635
1854
  {
1636
1855
  accessibilityHint: labels.closeHint,
1637
1856
  accessibilityLabel: labels.closeLabel,
1638
1857
  accessibilityRole: "button",
1639
- style: styles4.scrim,
1858
+ style: styles5.scrim,
1640
1859
  testID: `${testID ?? "command-palette"}-scrim`,
1641
1860
  onPress: onClose
1642
1861
  }
@@ -1647,7 +1866,7 @@ var CommandPalette = ({ open, items, onClose, labels, testID }) => {
1647
1866
  "aria-modal": true,
1648
1867
  "aria-label": labels.regionLabel,
1649
1868
  role: "dialog",
1650
- style: [styles4.panel, { backgroundColor: colors.surface, borderColor: colors.border }],
1869
+ style: [styles5.panel, { backgroundColor: colors.surface, borderColor: colors.border }],
1651
1870
  testID: testID ?? "command-palette",
1652
1871
  children: [
1653
1872
  /* @__PURE__ */ jsx(
@@ -1657,21 +1876,21 @@ var CommandPalette = ({ open, items, onClose, labels, testID }) => {
1657
1876
  accessibilityLabel: labels.placeholder,
1658
1877
  placeholder: labels.placeholder,
1659
1878
  placeholderTextColor: colors.textSecondary,
1660
- style: [styles4.input, { color: colors.text, borderBottomColor: colors.border }],
1879
+ style: [styles5.input, { color: colors.text, borderBottomColor: colors.border }],
1661
1880
  testID: `${testID ?? "command-palette"}-input`,
1662
1881
  value: query,
1663
1882
  onChangeText: setQuery,
1664
1883
  onKeyPress
1665
1884
  }
1666
1885
  ),
1667
- results.length === 0 ? /* @__PURE__ */ jsx(Text, { style: [styles4.empty, { color: colors.textSecondary }], children: labels.emptyText }) : /* @__PURE__ */ jsx(ScrollView, { keyboardShouldPersistTaps: "handled", style: styles4.list, children: results.map((item, index) => /* @__PURE__ */ jsx(PaletteRow, { item, selected: index === selected, onChoose: choose }, item.key)) })
1886
+ results.length === 0 ? /* @__PURE__ */ jsx(Text, { style: [styles5.empty, { color: colors.textSecondary }], children: labels.emptyText }) : /* @__PURE__ */ jsx(ScrollView, { keyboardShouldPersistTaps: "handled", style: styles5.list, children: results.map((item, index) => /* @__PURE__ */ jsx(PaletteRow, { item, selected: index === selected, onChoose: choose }, item.key)) })
1668
1887
  ]
1669
1888
  }
1670
1889
  )
1671
1890
  ] });
1672
1891
  };
1673
1892
  var MIN_TARGET = 36;
1674
- var styles5 = StyleSheet.create({
1893
+ var styles6 = StyleSheet.create({
1675
1894
  trigger: {
1676
1895
  flexDirection: "row",
1677
1896
  alignItems: "center",
@@ -1696,7 +1915,7 @@ var CommandPaletteTrigger = ({
1696
1915
  const { theme } = useUi();
1697
1916
  const colors = theme.colors;
1698
1917
  const primary = theme.palette.primary["500"];
1699
- const [focused, setFocused] = React2.useState(false);
1918
+ const [focused, setFocused] = React3.useState(false);
1700
1919
  return /* @__PURE__ */ jsxs(
1701
1920
  Pressable,
1702
1921
  {
@@ -1704,7 +1923,7 @@ var CommandPaletteTrigger = ({
1704
1923
  accessibilityLabel: label,
1705
1924
  accessibilityRole: "button",
1706
1925
  style: [
1707
- styles5.trigger,
1926
+ styles6.trigger,
1708
1927
  { backgroundColor: colors.surface, borderColor: colors.border },
1709
1928
  focusRingStyle(focused, primary)
1710
1929
  ],
@@ -1713,8 +1932,8 @@ var CommandPaletteTrigger = ({
1713
1932
  onFocus: () => setFocused(true),
1714
1933
  onPress,
1715
1934
  children: [
1716
- /* @__PURE__ */ jsx(Text, { style: [styles5.label, { color: colors.textSecondary }], children: label }),
1717
- /* @__PURE__ */ jsx(View, { style: [styles5.badge, { borderColor: colors.border }], children: /* @__PURE__ */ jsx(Text, { style: [styles5.badgeText, { color: colors.textSecondary }], children: shortcut }) })
1935
+ /* @__PURE__ */ jsx(Text, { style: [styles6.label, { color: colors.textSecondary }], children: label }),
1936
+ /* @__PURE__ */ jsx(View, { style: [styles6.badge, { borderColor: colors.border }], children: /* @__PURE__ */ jsx(Text, { style: [styles6.badgeText, { color: colors.textSecondary }], children: shortcut }) })
1718
1937
  ]
1719
1938
  }
1720
1939
  );
@@ -1744,6 +1963,6 @@ function accessibleNavItems(user, table, translate) {
1744
1963
  return roleRoutesToNavItems(resolveAccessibleRoutes(user, table), translate);
1745
1964
  }
1746
1965
 
1747
- export { ACTIVE_BORDER_RADIUS, APP_SHELL_SUFFIX, AppShell, BASE_INDENT, CHEVRON_ICON_SIZE, CollapsedRail, CommandPalette, CommandPaletteTrigger, DEFAULT_COLLAPSED_RAIL_MAX, DarkModeControl, NAV_ICON_SIZE, NAV_LINK_GAP, NAV_TEST_IDS, Nav, NavBar, NavExpandableItem, NavOverflowMenu, NavShell, PillNav, RAIL_FULL_BREAKPOINT, Sidebar, Topbar, accessibleNavItems, collapsedRailStyles, darkModeStyles, expandableStyles, filterCommands, isRouteActive, navStyles, pillNavStyles, resolveContentMaxWidth, resolveRailMode, roleRoutesToNavItems, useCommandPaletteHotkey, useContentMaxWidth };
1966
+ export { ACTIVE_BORDER_RADIUS, ACTIVE_TINT_OPACITY, APP_SHELL_SUFFIX, AppShell, BASE_INDENT, CHEVRON_ICON_SIZE, CollapsedRail, CommandPalette, CommandPaletteTrigger, DEFAULT_COLLAPSED_RAIL_MAX, DEFAULT_SEARCH_BREAKPOINT, DarkModeControl, HOVER_TINT_OPACITY, NAV_ICON_SIZE, NAV_LINK_GAP, NAV_ROW_RADIUS, NAV_TEST_IDS, Nav, NavBar, NavExpandableItem, NavOverflowMenu, NavShell, PillNav, RAIL_FULL_BREAKPOINT, Sidebar, SidebarChevron, SidebarSearch, Topbar, accessibleNavItems, collapsedRailStyles, darkModeStyles, expandableStyles, filterCommands, filterNavItems, isFilterActive, isRouteActive, navStyles, pillNavStyles, resolveContentMaxWidth, resolveRailMode, resolveSearchAffordance, roleRoutesToNavItems, searchTokens, useCommandPaletteHotkey, useContentMaxWidth };
1748
1967
  //# sourceMappingURL=index.mjs.map
1749
1968
  //# sourceMappingURL=index.mjs.map