@dloizides/ui-nav 1.30.0 → 1.32.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.d.mts CHANGED
@@ -155,7 +155,7 @@ interface SidebarProps {
155
155
  /** Navigation callback — receives a `NavItem.route`. */
156
156
  onNavigate: (route: string) => void;
157
157
  /** Localized menu title (heading). */
158
- title: string;
158
+ title: React.ReactNode;
159
159
  /** Localized accessibility label for the navigation landmark. */
160
160
  regionLabel: string;
161
161
  /** a11y hint for a leaf item, given its label. Defaults to the label. */
@@ -711,6 +711,13 @@ interface AppShellProps {
711
711
  }
712
712
  declare const AppShell: ({ header, nav, sidebar, collapsedSidebar, collapsedRailRange, mobileMenu, banner, width, contentPadding, chromeAlignment, gate, state, testID, children, }: AppShellProps) => React.ReactElement | null;
713
713
 
714
+ interface NavShellCollapsedRail {
715
+ items?: NavItem[];
716
+ header?: React.ReactNode;
717
+ footer?: React.ReactNode;
718
+ range?: CollapsedRailRange;
719
+ }
720
+
714
721
  /**
715
722
  * NavShell — the ONE first-class navigation shell that renders a **top bar**, a
716
723
  * **side rail**, or **BOTH together**, chosen by a single `layout` prop
@@ -786,22 +793,7 @@ interface NavShellTopBar {
786
793
  */
787
794
  barTheme?: UiTheme;
788
795
  }
789
- /**
790
- * Config for the intermediate COLLAPSED (icon-only) rail — opts the side layout
791
- * into a THREE-tier responsive scheme (full rail → collapsed rail → drawer). When
792
- * present, NavShell builds a `<CollapsedRail>` and hands it to `AppShell` for the
793
- * tablet band; omit it and the layout stays two-tier.
794
- */
795
- interface NavShellCollapsedRail {
796
- /** Items for the icon rail. Defaults to the full rail's `items`. */
797
- items?: NavItem[];
798
- /** Optional header slot (e.g. a home shortcut). */
799
- header?: React.ReactNode;
800
- /** Optional footer slot (dark-mode toggle, logout). */
801
- footer?: React.ReactNode;
802
- /** The `[min, max)` viewport band for the collapsed rail (default `{ min: 768, max: 1024 }`). */
803
- range?: CollapsedRailRange;
804
- }
796
+
805
797
  /**
806
798
  * Config for the SIDE rail (rendered as a `Sidebar` in `AppShell`'s sidebar
807
799
  * slot). Used when `layout` is `'side'` or `'both'`.
@@ -809,6 +801,7 @@ interface NavShellCollapsedRail {
809
801
  interface NavShellSideRail {
810
802
  /** Side-rail nav entries (sections — leaf + expandable). */
811
803
  items: NavItem[];
804
+ brand?: React.ReactNode;
812
805
  /** Localized menu title (heading). */
813
806
  title: string;
814
807
  /** Optional header slot rendered above the items (e.g. a Home shortcut). */
@@ -1372,6 +1365,9 @@ declare const navStyles: {
1372
1365
  borderRightWidth: number;
1373
1366
  height: "100%";
1374
1367
  };
1368
+ sidebarBrand: {
1369
+ marginBottom: number;
1370
+ };
1375
1371
  sidebarTitle: {
1376
1372
  fontWeight: "700";
1377
1373
  marginBottom: number;
@@ -1396,7 +1392,7 @@ declare const navStyles: {
1396
1392
  paddingHorizontal: number;
1397
1393
  };
1398
1394
  topbarContainer: {
1399
- height: number;
1395
+ minHeight: number;
1400
1396
  paddingHorizontal: number;
1401
1397
  borderBottomWidth: number;
1402
1398
  flexDirection: "row";
@@ -1450,6 +1446,7 @@ declare const navStyles: {
1450
1446
  };
1451
1447
  userEmail: {
1452
1448
  fontSize: number;
1449
+ maxWidth: "100%";
1453
1450
  };
1454
1451
  accountBtn: {
1455
1452
  marginLeft: number;
package/dist/index.d.ts CHANGED
@@ -155,7 +155,7 @@ interface SidebarProps {
155
155
  /** Navigation callback — receives a `NavItem.route`. */
156
156
  onNavigate: (route: string) => void;
157
157
  /** Localized menu title (heading). */
158
- title: string;
158
+ title: React.ReactNode;
159
159
  /** Localized accessibility label for the navigation landmark. */
160
160
  regionLabel: string;
161
161
  /** a11y hint for a leaf item, given its label. Defaults to the label. */
@@ -711,6 +711,13 @@ interface AppShellProps {
711
711
  }
712
712
  declare const AppShell: ({ header, nav, sidebar, collapsedSidebar, collapsedRailRange, mobileMenu, banner, width, contentPadding, chromeAlignment, gate, state, testID, children, }: AppShellProps) => React.ReactElement | null;
713
713
 
714
+ interface NavShellCollapsedRail {
715
+ items?: NavItem[];
716
+ header?: React.ReactNode;
717
+ footer?: React.ReactNode;
718
+ range?: CollapsedRailRange;
719
+ }
720
+
714
721
  /**
715
722
  * NavShell — the ONE first-class navigation shell that renders a **top bar**, a
716
723
  * **side rail**, or **BOTH together**, chosen by a single `layout` prop
@@ -786,22 +793,7 @@ interface NavShellTopBar {
786
793
  */
787
794
  barTheme?: UiTheme;
788
795
  }
789
- /**
790
- * Config for the intermediate COLLAPSED (icon-only) rail — opts the side layout
791
- * into a THREE-tier responsive scheme (full rail → collapsed rail → drawer). When
792
- * present, NavShell builds a `<CollapsedRail>` and hands it to `AppShell` for the
793
- * tablet band; omit it and the layout stays two-tier.
794
- */
795
- interface NavShellCollapsedRail {
796
- /** Items for the icon rail. Defaults to the full rail's `items`. */
797
- items?: NavItem[];
798
- /** Optional header slot (e.g. a home shortcut). */
799
- header?: React.ReactNode;
800
- /** Optional footer slot (dark-mode toggle, logout). */
801
- footer?: React.ReactNode;
802
- /** The `[min, max)` viewport band for the collapsed rail (default `{ min: 768, max: 1024 }`). */
803
- range?: CollapsedRailRange;
804
- }
796
+
805
797
  /**
806
798
  * Config for the SIDE rail (rendered as a `Sidebar` in `AppShell`'s sidebar
807
799
  * slot). Used when `layout` is `'side'` or `'both'`.
@@ -809,6 +801,7 @@ interface NavShellCollapsedRail {
809
801
  interface NavShellSideRail {
810
802
  /** Side-rail nav entries (sections — leaf + expandable). */
811
803
  items: NavItem[];
804
+ brand?: React.ReactNode;
812
805
  /** Localized menu title (heading). */
813
806
  title: string;
814
807
  /** Optional header slot rendered above the items (e.g. a Home shortcut). */
@@ -1372,6 +1365,9 @@ declare const navStyles: {
1372
1365
  borderRightWidth: number;
1373
1366
  height: "100%";
1374
1367
  };
1368
+ sidebarBrand: {
1369
+ marginBottom: number;
1370
+ };
1375
1371
  sidebarTitle: {
1376
1372
  fontWeight: "700";
1377
1373
  marginBottom: number;
@@ -1396,7 +1392,7 @@ declare const navStyles: {
1396
1392
  paddingHorizontal: number;
1397
1393
  };
1398
1394
  topbarContainer: {
1399
- height: number;
1395
+ minHeight: number;
1400
1396
  paddingHorizontal: number;
1401
1397
  borderBottomWidth: number;
1402
1398
  flexDirection: "row";
@@ -1450,6 +1446,7 @@ declare const navStyles: {
1450
1446
  };
1451
1447
  userEmail: {
1452
1448
  fontSize: number;
1449
+ maxWidth: "100%";
1453
1450
  };
1454
1451
  accountBtn: {
1455
1452
  marginLeft: number;
package/dist/index.js CHANGED
@@ -34,6 +34,9 @@ var navStyles = reactNative.StyleSheet.create({
34
34
  borderRightWidth: 1,
35
35
  height: "100%"
36
36
  },
37
+ sidebarBrand: {
38
+ marginBottom: 12
39
+ },
37
40
  sidebarTitle: {
38
41
  fontWeight: "700",
39
42
  marginBottom: 12
@@ -63,7 +66,7 @@ var navStyles = reactNative.StyleSheet.create({
63
66
  },
64
67
  // --- Topbar ---
65
68
  topbarContainer: {
66
- height: 64,
69
+ minHeight: 64,
67
70
  paddingHorizontal: 12,
68
71
  borderBottomWidth: 1,
69
72
  flexDirection: "row",
@@ -84,7 +87,7 @@ var navStyles = reactNative.StyleSheet.create({
84
87
  userBlock: { marginHorizontal: 12, alignItems: "flex-end", flexShrink: 1, minWidth: 0 },
85
88
  userName: { fontWeight: "600", maxWidth: "100%" },
86
89
  accountNameBtn: { flexShrink: 1, maxWidth: "100%", minHeight: MIN_TOUCH_TARGET, minWidth: MIN_TOUCH_TARGET, justifyContent: "center", alignItems: "flex-end" },
87
- userEmail: { fontSize: 12 },
90
+ userEmail: { fontSize: 12, maxWidth: "100%" },
88
91
  accountBtn: {
89
92
  marginLeft: 8,
90
93
  paddingHorizontal: 10,
@@ -821,7 +824,7 @@ var Sidebar = ({
821
824
  containerStyle
822
825
  ],
823
826
  children: [
824
- /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { accessibilityRole: "header", style: [navStyles.sidebarTitle, { color: colors.text }], children: title }),
827
+ typeof title === "string" ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { accessibilityRole: "header", style: [navStyles.sidebarTitle, { color: colors.text }], children: title }) : /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: navStyles.sidebarBrand, children: title }),
825
828
  showInlineSearch && search !== void 0 ? /* @__PURE__ */ jsxRuntime.jsx(
826
829
  SidebarSearch,
827
830
  {
@@ -935,7 +938,22 @@ var FocusableTouchable = ({
935
938
  }
936
939
  );
937
940
  };
941
+ var RAIL_FULL_BREAKPOINT = uiLayout.LAYOUT_COLLAPSE_BREAKPOINT;
942
+ var DEFAULT_COLLAPSED_RAIL_MAX = 1024;
943
+ function resolveRailMode({ viewport, hasSidebar, hasCollapsed, range }) {
944
+ if (!hasSidebar) return "none";
945
+ if (hasCollapsed) {
946
+ if (viewport >= range.max) return "full";
947
+ if (viewport >= range.min) return "collapsed";
948
+ return "drawer";
949
+ }
950
+ return viewport >= RAIL_FULL_BREAKPOINT ? "full" : "drawer";
951
+ }
938
952
  var TEXT_ON_PRIMARY = "#ffffff";
953
+ var ON_BRAND_SCALE = "onBrand";
954
+ var USER_BLOCK_COMPACT = { marginHorizontal: 4 };
955
+ var MAIN_STEP = "500";
956
+ var WRAP_ANYWHERE = reactNative.Platform.OS === "web" ? { overflowWrap: "anywhere" } : null;
939
957
  function isAccountState(account) {
940
958
  return "displayName" in account;
941
959
  }
@@ -945,6 +963,14 @@ function planToneColor(theme, tone) {
945
963
  if (tone === "enterprise") return palette.accent?.["500"] ?? palette.primary["500"];
946
964
  return theme.colors.textSecondary;
947
965
  }
966
+ function onBrandInk(palette) {
967
+ const scales = palette;
968
+ return scales[ON_BRAND_SCALE]?.[MAIN_STEP] ?? TEXT_ON_PRIMARY;
969
+ }
970
+ function planBadgeInk(theme, tone) {
971
+ const fillIsPrimary = planToneColor(theme, tone) === theme.palette.primary["500"];
972
+ return fillIsPrimary ? onBrandInk(theme.palette) : TEXT_ON_PRIMARY;
973
+ }
948
974
  var Topbar = ({
949
975
  left,
950
976
  center,
@@ -958,6 +984,9 @@ var Topbar = ({
958
984
  const { theme } = uiFeedback.useUi();
959
985
  const colors = theme.colors;
960
986
  const primaryColor = theme.palette.primary["500"];
987
+ const onPrimaryInk = onBrandInk(theme.palette);
988
+ const { width: viewportWidth } = reactNative.useWindowDimensions();
989
+ const userBlockStyle = [navStyles.userBlock, viewportWidth < RAIL_FULL_BREAKPOINT ? USER_BLOCK_COMPACT : null];
961
990
  const richAccount = account !== void 0 && isAccountState(account) ? account : void 0;
962
991
  const legacyAccount = account !== void 0 && !isAccountState(account) ? account : void 0;
963
992
  const upgrade = richAccount?.upgrade;
@@ -986,11 +1015,11 @@ var Topbar = ({
986
1015
  }
987
1016
  ) : null,
988
1017
  renderTextSlot(notificationSlot, { color: colors.text }),
989
- user ? /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: navStyles.userBlock, children: [
990
- /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [navStyles.userName, { color: colors.text }], children: user.name }),
991
- /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [navStyles.userEmail, { color: colors.textSecondary }], children: user.email })
1018
+ user ? /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { accessible: true, accessibilityLabel: `${user.name}, ${user.email}`, style: userBlockStyle, children: [
1019
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [navStyles.userName, WRAP_ANYWHERE, { color: colors.text }], children: user.name }),
1020
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [navStyles.userEmail, WRAP_ANYWHERE, { color: colors.textSecondary }], children: user.email })
992
1021
  ] }) : null,
993
- richAccount ? /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: navStyles.userBlock, children: [
1022
+ richAccount ? /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: userBlockStyle, children: [
994
1023
  richAccount.onAccount ? /* @__PURE__ */ jsxRuntime.jsx(
995
1024
  FocusableTouchable,
996
1025
  {
@@ -1016,7 +1045,7 @@ var Topbar = ({
1016
1045
  {
1017
1046
  style: [navStyles.planBadge, { backgroundColor: planToneColor(theme, richAccount.plan.tone) }],
1018
1047
  testID: NAV_TEST_IDS.accountPlan,
1019
- children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [navStyles.planBadgeText, { color: TEXT_ON_PRIMARY }], children: richAccount.plan.label })
1048
+ children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [navStyles.planBadgeText, { color: planBadgeInk(theme, richAccount.plan.tone) }], children: richAccount.plan.label })
1020
1049
  }
1021
1050
  ) : null
1022
1051
  ] }) : null,
@@ -1029,7 +1058,7 @@ var Topbar = ({
1029
1058
  style: [navStyles.accountBtn, { backgroundColor: primaryColor }],
1030
1059
  testID: legacyAccount.testID,
1031
1060
  onPress: legacyAccount.onPress,
1032
- children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [navStyles.accountText, { color: TEXT_ON_PRIMARY }], children: legacyAccount.label })
1061
+ children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [navStyles.accountText, { color: onPrimaryInk }], children: legacyAccount.label })
1033
1062
  }
1034
1063
  ) : null,
1035
1064
  upgrade ? /* @__PURE__ */ jsxRuntime.jsx(
@@ -1041,7 +1070,7 @@ var Topbar = ({
1041
1070
  style: [navStyles.accountBtn, { backgroundColor: primaryColor }],
1042
1071
  testID: upgrade.testID ?? NAV_TEST_IDS.accountUpgrade,
1043
1072
  onPress: upgrade.onPress,
1044
- children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [navStyles.accountText, { color: TEXT_ON_PRIMARY }], children: upgrade.label })
1073
+ children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [navStyles.accountText, { color: onPrimaryInk }], children: upgrade.label })
1045
1074
  }
1046
1075
  ) : null,
1047
1076
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -1053,7 +1082,7 @@ var Topbar = ({
1053
1082
  style: [navStyles.accountBtn, { backgroundColor: primaryColor }],
1054
1083
  testID: logout.testID,
1055
1084
  onPress: richAccount ? richAccount.onLogout : logout.onPress,
1056
- children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [navStyles.accountText, { color: TEXT_ON_PRIMARY }], children: logout.label })
1085
+ children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [navStyles.accountText, { color: onPrimaryInk }], children: logout.label })
1057
1086
  }
1058
1087
  )
1059
1088
  ] })
@@ -1618,17 +1647,6 @@ var Nav = (props) => {
1618
1647
  }
1619
1648
  return /* @__PURE__ */ jsxRuntime.jsx(NavBar, { ...props });
1620
1649
  };
1621
- var RAIL_FULL_BREAKPOINT = uiLayout.LAYOUT_COLLAPSE_BREAKPOINT;
1622
- var DEFAULT_COLLAPSED_RAIL_MAX = 1024;
1623
- function resolveRailMode({ viewport, hasSidebar, hasCollapsed, range }) {
1624
- if (!hasSidebar) return "none";
1625
- if (hasCollapsed) {
1626
- if (viewport >= range.max) return "full";
1627
- if (viewport >= range.min) return "collapsed";
1628
- return "drawer";
1629
- }
1630
- return viewport >= RAIL_FULL_BREAKPOINT ? "full" : "drawer";
1631
- }
1632
1650
  var styles5 = reactNative.StyleSheet.create({
1633
1651
  band: { left: 0, position: "absolute", right: 0, top: 0 }
1634
1652
  });
@@ -2073,7 +2091,7 @@ var NavShell = ({
2073
2091
  renderChevron: sideRail.renderChevron,
2074
2092
  search: sideRail.search,
2075
2093
  searchQuery: sideRail.searchQuery,
2076
- title: sideRail.title,
2094
+ title: sideRail.brand ?? sideRail.title,
2077
2095
  onNavigate,
2078
2096
  onSearchChange: sideRail.onSearchChange
2079
2097
  }