@dloizides/ui-nav 1.11.0 → 1.13.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,4 +1,4 @@
1
- import { StyleSheet, Platform, TouchableOpacity, View, Text, useWindowDimensions, ActivityIndicator, ScrollView, Pressable } from 'react-native';
1
+ import { StyleSheet, Platform, TouchableOpacity, View, Text, useWindowDimensions, ActivityIndicator, ScrollView, Pressable, TextInput } from 'react-native';
2
2
  import { useUi, UiProvider } from '@dloizides/ui-feedback';
3
3
  import React2, { useState, useCallback, useMemo, useEffect, useRef } from 'react';
4
4
  import { jsxs, jsx } from 'react/jsx-runtime';
@@ -13,6 +13,7 @@ function isRouteActive(pathname, route) {
13
13
  return pathname === route || pathname.startsWith(`${route}/`);
14
14
  }
15
15
  var ACTIVE_BORDER_RADIUS = 4;
16
+ var ACTIVE_ACCENT_WIDTH = 3;
16
17
  var NAV_LINK_GAP = 4;
17
18
  var navStyles = StyleSheet.create({
18
19
  // --- Sidebar ---
@@ -236,23 +237,39 @@ var collapsedRailStyles = StyleSheet.create({
236
237
  slot: { alignItems: "center" }
237
238
  });
238
239
  var expandableStyles = StyleSheet.create({
240
+ // Every leaf reserves the accent-bar gutter (a TRANSPARENT left border of the
241
+ // same width the active item colours in) so activation never shifts the row.
239
242
  childItem: {
240
- borderRadius: 4,
243
+ borderRadius: 6,
241
244
  flexDirection: "row",
242
245
  alignItems: "center",
243
- paddingVertical: 6
246
+ paddingVertical: 9,
247
+ borderLeftWidth: ACTIVE_ACCENT_WIDTH,
248
+ borderLeftColor: "transparent"
244
249
  },
245
250
  childItemText: { fontSize: 14 },
246
251
  childItemTextWithIcon: { fontSize: 14, marginLeft: 6 },
247
252
  chevron: { marginLeft: "auto" },
248
- childrenContainer: { overflow: "hidden" },
253
+ childrenContainer: { overflow: "hidden", marginBottom: 4 },
249
254
  header: {
250
- borderRadius: 4,
255
+ borderRadius: 6,
251
256
  flexDirection: "row",
252
257
  alignItems: "center",
253
258
  paddingVertical: 8
254
259
  },
260
+ // Depth-0 module groups render as docs-style SECTION HEADERS: extra top rhythm
261
+ // to separate one module from the next.
262
+ sectionHeader: { marginTop: 12 },
255
263
  headerText: { fontSize: 15, fontWeight: "600", marginLeft: 6 },
264
+ // The section-label look: small, uppercased, tracked, muted — the aml-screening
265
+ // docs treatment ("START HERE" / "REFERENCE"), kept on an interactive toggle.
266
+ sectionHeaderText: {
267
+ fontSize: 12,
268
+ fontWeight: "700",
269
+ marginLeft: 6,
270
+ textTransform: "uppercase",
271
+ letterSpacing: 0.8
272
+ },
256
273
  iconWrapper: { width: 20, alignItems: "center" }
257
274
  });
258
275
  var BASE_INDENT = 12;
@@ -283,6 +300,9 @@ function hoverTransitionStyle(reducedMotion) {
283
300
  function ariaCurrentProps(isActive) {
284
301
  return isActive ? { "aria-current": "page" } : {};
285
302
  }
303
+ function ariaExpandedProps(expanded) {
304
+ return { "aria-expanded": expanded };
305
+ }
286
306
  function hasActiveDescendant(item, pathname) {
287
307
  if (isRouteActive(pathname, item.route)) return true;
288
308
  return (item.children ?? []).some((child) => hasActiveDescendant(child, pathname));
@@ -306,9 +326,15 @@ var NavExpandableItem = ({
306
326
  const indent = depth * BASE_INDENT;
307
327
  const hasChildren = Array.isArray(item.children) && item.children.length > 0;
308
328
  const isActive = isRouteActive(pathname, item.route);
329
+ const isSectionHeader = hasChildren && depth === 0;
309
330
  const activeItemStyle = useMemo(
310
- () => ({ backgroundColor: colors.border, borderRadius: ACTIVE_BORDER_RADIUS }),
311
- [colors.border]
331
+ () => ({
332
+ backgroundColor: colors.border,
333
+ borderRadius: ACTIVE_BORDER_RADIUS,
334
+ borderLeftWidth: ACTIVE_ACCENT_WIDTH,
335
+ borderLeftColor: primaryColor
336
+ }),
337
+ [colors.border, primaryColor]
312
338
  );
313
339
  const paddingStyle = useMemo(() => ({ paddingLeft: indent + BASE_INDENT }), [indent]);
314
340
  const headerPaddingStyle = useMemo(() => ({ paddingLeft: indent }), [indent]);
@@ -354,14 +380,29 @@ var NavExpandableItem = ({
354
380
  accessibilityLabel: item.label,
355
381
  accessibilityRole: "button",
356
382
  accessibilityState: { expanded },
357
- style: [expandableStyles.header, headerPaddingStyle, focusRingStyle(focused, primaryColor)],
383
+ style: [
384
+ expandableStyles.header,
385
+ isSectionHeader ? expandableStyles.sectionHeader : void 0,
386
+ headerPaddingStyle,
387
+ focusRingStyle(focused, primaryColor)
388
+ ],
358
389
  testID: item.testID ?? item.key,
359
390
  onBlur: () => setFocused(false),
360
391
  onFocus: () => setFocused(true),
361
392
  onPress: toggle,
393
+ ...ariaExpandedProps(expanded),
362
394
  children: [
363
- typeof item.renderIcon === "function" ? /* @__PURE__ */ jsx(View, { style: expandableStyles.iconWrapper, children: item.renderIcon(colors.text, NAV_ICON_SIZE) }) : null,
364
- /* @__PURE__ */ jsx(Text, { style: [expandableStyles.headerText, { color: colors.text }], children: item.label }),
395
+ typeof item.renderIcon === "function" ? /* @__PURE__ */ jsx(View, { style: expandableStyles.iconWrapper, children: item.renderIcon(isSectionHeader ? colors.textSecondary : colors.text, NAV_ICON_SIZE) }) : null,
396
+ /* @__PURE__ */ jsx(
397
+ Text,
398
+ {
399
+ style: [
400
+ isSectionHeader ? expandableStyles.sectionHeaderText : expandableStyles.headerText,
401
+ { color: isSectionHeader ? colors.textSecondary : colors.text }
402
+ ],
403
+ children: item.label
404
+ }
405
+ ),
365
406
  typeof renderChevron === "function" ? /* @__PURE__ */ jsx(View, { style: expandableStyles.chevron, children: renderChevron(expanded, colors.textSecondary, CHEVRON_ICON_SIZE) }) : null
366
407
  ]
367
408
  }
@@ -1006,6 +1047,7 @@ var DRAWER_WIDTH = 280;
1006
1047
  var SCRIM_Z_INDEX = 1;
1007
1048
  var PANEL_Z_INDEX = 2;
1008
1049
  var NAV_ACTIVATABLE_SELECTOR = 'a, [role="button"], [role="link"]';
1050
+ var NAV_TOGGLE_SELECTOR = "[aria-expanded]";
1009
1051
  var DEFAULT_DRAWER_LABELS = {
1010
1052
  openLabel: "Menu",
1011
1053
  openHint: "Open the navigation menu",
@@ -1068,8 +1110,10 @@ var MobileDrawer = ({
1068
1110
  if (node === null || typeof node.addEventListener !== "function") return void 0;
1069
1111
  const handleClick = (event) => {
1070
1112
  const target = event.target;
1071
- const onNavItem = target !== null && target.closest(NAV_ACTIVATABLE_SELECTOR) !== null;
1072
- if (onNavItem) onClose();
1113
+ if (target === null) return;
1114
+ const activatable = target.closest(NAV_ACTIVATABLE_SELECTOR);
1115
+ const isToggle = activatable !== null && activatable.closest(NAV_TOGGLE_SELECTOR) !== null;
1116
+ if (activatable !== null && !isToggle) onClose();
1073
1117
  };
1074
1118
  node.addEventListener("click", handleClick);
1075
1119
  return () => node.removeEventListener("click", handleClick);
@@ -1482,6 +1526,208 @@ var DarkModeControl = ({
1482
1526
  }
1483
1527
  );
1484
1528
  };
1529
+
1530
+ // src/commandPaletteFilter.ts
1531
+ function haystack(item) {
1532
+ return [item.label, item.hint ?? "", ...item.keywords ?? []].join(" ").toLowerCase();
1533
+ }
1534
+ function filterCommands(items, query) {
1535
+ const tokens = query.trim().toLowerCase().split(/\s+/).filter(Boolean);
1536
+ if (tokens.length === 0) return [...items];
1537
+ return items.filter((item) => {
1538
+ const hay = haystack(item);
1539
+ return tokens.every((token) => hay.includes(token));
1540
+ });
1541
+ }
1542
+ var SCRIM_COLOR2 = "rgba(0, 0, 0, 0.5)";
1543
+ var OVERLAY_Z_INDEX = 60;
1544
+ var PANEL_MAX_WIDTH = 560;
1545
+ var PANEL_TOP_OFFSET = 88;
1546
+ var LIST_MAX_HEIGHT = 360;
1547
+ var KEY_DOWN = "ArrowDown";
1548
+ var KEY_UP = "ArrowUp";
1549
+ var KEY_ENTER = "Enter";
1550
+ var KEY_ESCAPE = "Escape";
1551
+ var styles4 = StyleSheet.create({
1552
+ overlay: { ...StyleSheet.absoluteFillObject, zIndex: OVERLAY_Z_INDEX, alignItems: "center" },
1553
+ scrim: { ...StyleSheet.absoluteFillObject, backgroundColor: SCRIM_COLOR2 },
1554
+ panel: {
1555
+ marginTop: PANEL_TOP_OFFSET,
1556
+ width: "92%",
1557
+ maxWidth: PANEL_MAX_WIDTH,
1558
+ borderRadius: 12,
1559
+ borderWidth: 1,
1560
+ overflow: "hidden"
1561
+ },
1562
+ input: { fontSize: 16, paddingHorizontal: 16, paddingVertical: 14, borderBottomWidth: 1 },
1563
+ list: { maxHeight: LIST_MAX_HEIGHT },
1564
+ row: { flexDirection: "row", alignItems: "center", paddingHorizontal: 16, paddingVertical: 12, columnGap: 10 },
1565
+ rowText: { flex: 1 },
1566
+ rowLabel: { fontSize: 15, fontWeight: "600" },
1567
+ rowHint: { fontSize: 12, marginTop: 2 },
1568
+ empty: { paddingHorizontal: 16, paddingVertical: 20, fontSize: 14 }
1569
+ });
1570
+ var PaletteRow = ({ item, selected, onChoose }) => {
1571
+ const { theme } = useUi();
1572
+ const colors = theme.colors;
1573
+ return /* @__PURE__ */ jsxs(
1574
+ Pressable,
1575
+ {
1576
+ accessibilityHint: item.hint,
1577
+ accessibilityLabel: item.label,
1578
+ accessibilityRole: "button",
1579
+ accessibilityState: { selected },
1580
+ style: [styles4.row, selected ? { backgroundColor: colors.border } : void 0],
1581
+ testID: item.testID ?? item.key,
1582
+ onPress: () => onChoose(item),
1583
+ children: [
1584
+ typeof item.renderIcon === "function" ? item.renderIcon(colors.textSecondary, NAV_ICON_SIZE) : null,
1585
+ /* @__PURE__ */ jsxs(View, { style: styles4.rowText, children: [
1586
+ /* @__PURE__ */ jsx(Text, { style: [styles4.rowLabel, { color: colors.text }], children: item.label }),
1587
+ item.hint ? /* @__PURE__ */ jsx(Text, { style: [styles4.rowHint, { color: colors.textSecondary }], children: item.hint }) : null
1588
+ ] })
1589
+ ]
1590
+ }
1591
+ );
1592
+ };
1593
+ var CommandPalette = ({ open, items, onClose, labels, testID }) => {
1594
+ const { theme } = useUi();
1595
+ const colors = theme.colors;
1596
+ const [query, setQuery] = useState("");
1597
+ const [selected, setSelected] = useState(0);
1598
+ const inputRef = useRef(null);
1599
+ const results = useMemo(() => filterCommands(items, query), [items, query]);
1600
+ useEffect(() => {
1601
+ if (!open) return;
1602
+ setQuery("");
1603
+ setSelected(0);
1604
+ inputRef.current?.focus();
1605
+ }, [open]);
1606
+ useEffect(() => {
1607
+ setSelected((prev) => Math.min(prev, Math.max(0, results.length - 1)));
1608
+ }, [results.length]);
1609
+ const choose = useCallback(
1610
+ (item) => {
1611
+ item.onSelect();
1612
+ onClose();
1613
+ },
1614
+ [onClose]
1615
+ );
1616
+ const onKeyPress = useCallback(
1617
+ (event) => {
1618
+ const key = event.nativeEvent.key;
1619
+ if (key === KEY_DOWN) setSelected((i) => Math.min(i + 1, results.length - 1));
1620
+ else if (key === KEY_UP) setSelected((i) => Math.max(i - 1, 0));
1621
+ else if (key === KEY_ESCAPE) onClose();
1622
+ else if (key === KEY_ENTER && results[selected]) choose(results[selected]);
1623
+ },
1624
+ [results, selected, choose, onClose]
1625
+ );
1626
+ if (!open) return null;
1627
+ return /* @__PURE__ */ jsxs(View, { style: styles4.overlay, children: [
1628
+ /* @__PURE__ */ jsx(
1629
+ Pressable,
1630
+ {
1631
+ accessibilityHint: labels.closeHint,
1632
+ accessibilityLabel: labels.closeLabel,
1633
+ accessibilityRole: "button",
1634
+ style: styles4.scrim,
1635
+ testID: `${testID ?? "command-palette"}-scrim`,
1636
+ onPress: onClose
1637
+ }
1638
+ ),
1639
+ /* @__PURE__ */ jsxs(
1640
+ View,
1641
+ {
1642
+ "aria-modal": true,
1643
+ "aria-label": labels.regionLabel,
1644
+ role: "dialog",
1645
+ style: [styles4.panel, { backgroundColor: colors.surface, borderColor: colors.border }],
1646
+ testID: testID ?? "command-palette",
1647
+ children: [
1648
+ /* @__PURE__ */ jsx(
1649
+ TextInput,
1650
+ {
1651
+ ref: inputRef,
1652
+ accessibilityLabel: labels.placeholder,
1653
+ placeholder: labels.placeholder,
1654
+ placeholderTextColor: colors.textSecondary,
1655
+ style: [styles4.input, { color: colors.text, borderBottomColor: colors.border }],
1656
+ testID: `${testID ?? "command-palette"}-input`,
1657
+ value: query,
1658
+ onChangeText: setQuery,
1659
+ onKeyPress
1660
+ }
1661
+ ),
1662
+ 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)) })
1663
+ ]
1664
+ }
1665
+ )
1666
+ ] });
1667
+ };
1668
+ var MIN_TARGET = 36;
1669
+ var styles5 = StyleSheet.create({
1670
+ trigger: {
1671
+ flexDirection: "row",
1672
+ alignItems: "center",
1673
+ justifyContent: "space-between",
1674
+ columnGap: 8,
1675
+ paddingHorizontal: 12,
1676
+ minHeight: MIN_TARGET,
1677
+ borderWidth: 1,
1678
+ borderRadius: 8
1679
+ },
1680
+ label: { fontSize: 14 },
1681
+ badge: { paddingHorizontal: 6, paddingVertical: 2, borderRadius: 4, borderWidth: 1 },
1682
+ badgeText: { fontSize: 11, fontWeight: "700" }
1683
+ });
1684
+ var CommandPaletteTrigger = ({
1685
+ label,
1686
+ hint,
1687
+ shortcut,
1688
+ onPress,
1689
+ testID
1690
+ }) => {
1691
+ const { theme } = useUi();
1692
+ const colors = theme.colors;
1693
+ const primary = theme.palette.primary["500"];
1694
+ const [focused, setFocused] = React2.useState(false);
1695
+ return /* @__PURE__ */ jsxs(
1696
+ Pressable,
1697
+ {
1698
+ accessibilityHint: hint,
1699
+ accessibilityLabel: label,
1700
+ accessibilityRole: "button",
1701
+ style: [
1702
+ styles5.trigger,
1703
+ { backgroundColor: colors.surface, borderColor: colors.border },
1704
+ focusRingStyle(focused, primary)
1705
+ ],
1706
+ testID,
1707
+ onBlur: () => setFocused(false),
1708
+ onFocus: () => setFocused(true),
1709
+ onPress,
1710
+ children: [
1711
+ /* @__PURE__ */ jsx(Text, { style: [styles5.label, { color: colors.textSecondary }], children: label }),
1712
+ /* @__PURE__ */ jsx(View, { style: [styles5.badge, { borderColor: colors.border }], children: /* @__PURE__ */ jsx(Text, { style: [styles5.badgeText, { color: colors.textSecondary }], children: shortcut }) })
1713
+ ]
1714
+ }
1715
+ );
1716
+ };
1717
+ var LAUNCH_KEY = "k";
1718
+ function useCommandPaletteHotkey(onOpen, enabled = true) {
1719
+ useEffect(() => {
1720
+ if (!enabled || typeof document === "undefined") return void 0;
1721
+ const handler = (event) => {
1722
+ const isLauncher = event.key.toLowerCase() === LAUNCH_KEY && (event.metaKey || event.ctrlKey);
1723
+ if (!isLauncher) return;
1724
+ event.preventDefault();
1725
+ onOpen();
1726
+ };
1727
+ document.addEventListener("keydown", handler);
1728
+ return () => document.removeEventListener("keydown", handler);
1729
+ }, [onOpen, enabled]);
1730
+ }
1485
1731
  function roleRoutesToNavItems(routes, translate) {
1486
1732
  return routes.map((entry) => ({
1487
1733
  key: entry.role,
@@ -1493,6 +1739,6 @@ function accessibleNavItems(user, table, translate) {
1493
1739
  return roleRoutesToNavItems(resolveAccessibleRoutes(user, table), translate);
1494
1740
  }
1495
1741
 
1496
- export { ACTIVE_BORDER_RADIUS, APP_SHELL_SUFFIX, AppShell, BASE_INDENT, CHEVRON_ICON_SIZE, CollapsedRail, 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, isRouteActive, navStyles, pillNavStyles, resolveContentMaxWidth, resolveRailMode, roleRoutesToNavItems, useContentMaxWidth };
1742
+ 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 };
1497
1743
  //# sourceMappingURL=index.mjs.map
1498
1744
  //# sourceMappingURL=index.mjs.map