@dloizides/ui-nav 1.7.0 → 1.8.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,5 +1,5 @@
1
- import { StyleSheet, Platform, TouchableOpacity, View, Text, useWindowDimensions, Pressable, ActivityIndicator, ScrollView } from 'react-native';
2
- import { useUi } from '@dloizides/ui-feedback';
1
+ import { StyleSheet, Platform, TouchableOpacity, View, Text, useWindowDimensions, ActivityIndicator, ScrollView, Pressable } from 'react-native';
2
+ import { useUi, UiProvider } from '@dloizides/ui-feedback';
3
3
  import React5, { useState, useCallback, useMemo, useEffect, useRef } from 'react';
4
4
  import { jsxs, jsx } from 'react/jsx-runtime';
5
5
  import { ModalDropdown } from '@dloizides/ui-layout';
@@ -155,6 +155,86 @@ var navBarStyles = StyleSheet.create({
155
155
  columnGap: 8
156
156
  }
157
157
  });
158
+ var pillNavStyles = StyleSheet.create({
159
+ // Cross-nav pill row (kefi coral dashboard switcher): fully-rounded pills, wraps.
160
+ container: {
161
+ flexDirection: "row",
162
+ flexWrap: "wrap",
163
+ alignItems: "center",
164
+ columnGap: 8,
165
+ rowGap: 8,
166
+ paddingVertical: 8
167
+ },
168
+ pill: {
169
+ paddingHorizontal: 16,
170
+ paddingVertical: 8,
171
+ borderRadius: 999,
172
+ minHeight: 36,
173
+ flexDirection: "row",
174
+ alignItems: "center",
175
+ columnGap: 6,
176
+ justifyContent: "center"
177
+ },
178
+ pillText: { fontSize: 14, fontWeight: "600" }
179
+ });
180
+ var darkModeStyles = StyleSheet.create({
181
+ // Segmented Light / Dark / System control.
182
+ segmentRow: {
183
+ flexDirection: "row",
184
+ alignItems: "center",
185
+ borderWidth: 1,
186
+ borderRadius: 8,
187
+ overflow: "hidden",
188
+ alignSelf: "flex-start"
189
+ },
190
+ segment: {
191
+ paddingHorizontal: 12,
192
+ paddingVertical: 8,
193
+ minHeight: 36,
194
+ flexDirection: "row",
195
+ alignItems: "center",
196
+ columnGap: 6,
197
+ justifyContent: "center"
198
+ },
199
+ segmentText: { fontSize: 13, fontWeight: "600" },
200
+ // Cycle-button variant.
201
+ cycle: {
202
+ paddingHorizontal: 12,
203
+ paddingVertical: 8,
204
+ minHeight: 44,
205
+ minWidth: 44,
206
+ borderRadius: 8,
207
+ flexDirection: "row",
208
+ alignItems: "center",
209
+ columnGap: 6,
210
+ justifyContent: "center"
211
+ },
212
+ cycleText: { fontSize: 13, fontWeight: "600" }
213
+ });
214
+ var collapsedRailStyles = StyleSheet.create({
215
+ // Intermediate (tablet) icon-only rail: a narrow vertical strip of icon buttons.
216
+ container: {
217
+ width: 64,
218
+ paddingTop: 16,
219
+ paddingHorizontal: 8,
220
+ borderRightWidth: 1,
221
+ height: "100%",
222
+ alignItems: "center"
223
+ },
224
+ // Each item is a ≥44×44 centred touch target with a rounded active/hover pill.
225
+ item: {
226
+ width: 48,
227
+ height: 48,
228
+ marginVertical: 4,
229
+ borderRadius: 8,
230
+ alignItems: "center",
231
+ justifyContent: "center"
232
+ },
233
+ // Glyph fallback (the label's first character) when an item has no renderIcon.
234
+ glyph: { fontSize: 18, fontWeight: "700" },
235
+ spacer: { flex: 1 },
236
+ slot: { alignItems: "center" }
237
+ });
158
238
  var expandableStyles = StyleSheet.create({
159
239
  childItem: {
160
240
  borderRadius: 4,
@@ -371,6 +451,8 @@ var APP_SHELL_SUFFIX = {
371
451
  nav: "-nav",
372
452
  /** The persistent left rail of the back-office layout (only when `sidebar` is supplied). */
373
453
  sidebar: "-sidebar",
454
+ /** The intermediate persistent COLLAPSED (icon-only) rail (3-tier layout, tablet band). */
455
+ collapsedSidebar: "-collapsed-sidebar",
374
456
  /** Mobile hamburger that opens the nav drawer (back-office layout, below the breakpoint). */
375
457
  menuToggle: "-menu-toggle",
376
458
  /** The mobile nav drawer panel (holds the `sidebar` slot when opened on a narrow viewport). */
@@ -704,7 +786,7 @@ var MENU_GLYPH = "\u2630";
704
786
  function ariaControlsProps(id) {
705
787
  return { "aria-controls": id };
706
788
  }
707
- var NavBar = ({
789
+ var NavBarInner = ({
708
790
  items,
709
791
  pathname,
710
792
  onNavigate,
@@ -842,13 +924,57 @@ var NavBar = ({
842
924
  }
843
925
  );
844
926
  };
927
+ var NavBar = ({ barTheme, ...props }) => {
928
+ const { t, navigate } = useUi();
929
+ if (barTheme === void 0) return /* @__PURE__ */ jsx(NavBarInner, { ...props });
930
+ return /* @__PURE__ */ jsx(UiProvider, { navigate, t, theme: barTheme, children: /* @__PURE__ */ jsx(NavBarInner, { ...props }) });
931
+ };
845
932
  var Nav = (props) => {
846
933
  if (props.orientation === "vertical") {
847
934
  return /* @__PURE__ */ jsx(Sidebar, { ...props });
848
935
  }
849
936
  return /* @__PURE__ */ jsx(NavBar, { ...props });
850
937
  };
851
- var MOBILE_BREAKPOINT = 768;
938
+ var CARD_PADDING = 20;
939
+ var CARD_BORDER_RADIUS = 12;
940
+ var CARD_BORDER_WIDTH = 1;
941
+ var CARD_TITLE_FONT_SIZE = 16;
942
+ var CARD_MESSAGE_FONT_SIZE = 14;
943
+ var CARD_TITLE_MARGIN_BOTTOM = 8;
944
+ var styles = StyleSheet.create({
945
+ centerFill: { flex: 1, justifyContent: "center", alignItems: "center" },
946
+ card: {
947
+ padding: CARD_PADDING,
948
+ borderRadius: CARD_BORDER_RADIUS,
949
+ borderWidth: CARD_BORDER_WIDTH
950
+ },
951
+ cardTitle: { fontSize: CARD_TITLE_FONT_SIZE, fontWeight: "700", marginBottom: CARD_TITLE_MARGIN_BOTTOM },
952
+ cardMessage: { fontSize: CARD_MESSAGE_FONT_SIZE }
953
+ });
954
+ function MessageCard({
955
+ message,
956
+ accentColor,
957
+ testID
958
+ }) {
959
+ const { theme } = useUi();
960
+ const colors = theme.colors;
961
+ return /* @__PURE__ */ jsxs(View, { style: [styles.card, { backgroundColor: colors.surface, borderColor: accentColor }], testID, children: [
962
+ /* @__PURE__ */ jsx(Text, { style: [styles.cardTitle, { color: accentColor }], children: message.titleText }),
963
+ /* @__PURE__ */ jsx(Text, { style: [styles.cardMessage, { color: colors.textSecondary }], children: message.messageText })
964
+ ] });
965
+ }
966
+ function useContentBody(state, children, testID) {
967
+ const { theme } = useUi();
968
+ const primary = theme.palette.primary["500"];
969
+ const errorColor = theme.semantic.error["500"];
970
+ if (state?.forbidden)
971
+ return /* @__PURE__ */ jsx(MessageCard, { accentColor: errorColor, message: state.forbidden, testID: `${testID}${APP_SHELL_SUFFIX.forbidden}` });
972
+ if (state?.error)
973
+ return /* @__PURE__ */ jsx(MessageCard, { accentColor: errorColor, message: state.error, testID: `${testID}${APP_SHELL_SUFFIX.error}` });
974
+ if (state?.loading === true)
975
+ return /* @__PURE__ */ jsx(View, { style: styles.centerFill, testID: `${testID}${APP_SHELL_SUFFIX.loading}`, children: /* @__PURE__ */ jsx(ActivityIndicator, { color: primary, size: "large" }) });
976
+ return children;
977
+ }
852
978
  var SCRIM_COLOR = "rgba(0, 0, 0, 0.5)";
853
979
  var MENU_GLYPH2 = "\u2630";
854
980
  var MENU_MIN_TARGET = 44;
@@ -862,7 +988,7 @@ var DEFAULT_DRAWER_LABELS = {
862
988
  closeLabel: "Close menu",
863
989
  closeHint: "Close the navigation menu"
864
990
  };
865
- var styles = StyleSheet.create({
991
+ var styles2 = StyleSheet.create({
866
992
  overlay: { ...StyleSheet.absoluteFillObject, zIndex: DRAWER_Z_INDEX },
867
993
  scrim: { ...StyleSheet.absoluteFillObject, backgroundColor: SCRIM_COLOR },
868
994
  panel: { position: "absolute", top: 0, bottom: 0, left: 0 },
@@ -883,10 +1009,10 @@ var MenuToggle = ({ label, hint, onOpen, testID }) => {
883
1009
  accessibilityHint: hint,
884
1010
  accessibilityLabel: label,
885
1011
  ringColor: theme.palette.primary["500"],
886
- style: styles.menuToggle,
1012
+ style: styles2.menuToggle,
887
1013
  testID,
888
1014
  onPress: onOpen,
889
- children: /* @__PURE__ */ jsx(Text, { style: [styles.menuToggleGlyph, { color: theme.colors.text }], children: MENU_GLYPH2 })
1015
+ children: /* @__PURE__ */ jsx(Text, { style: [styles2.menuToggleGlyph, { color: theme.colors.text }], children: MENU_GLYPH2 })
890
1016
  }
891
1017
  );
892
1018
  };
@@ -909,21 +1035,34 @@ var MobileDrawer = ({
909
1035
  node.addEventListener("click", handleClick);
910
1036
  return () => node.removeEventListener("click", handleClick);
911
1037
  }, [onClose]);
912
- return /* @__PURE__ */ jsxs(View, { style: styles.overlay, children: [
1038
+ return /* @__PURE__ */ jsxs(View, { style: styles2.overlay, children: [
913
1039
  /* @__PURE__ */ jsx(
914
1040
  Pressable,
915
1041
  {
916
1042
  accessibilityHint: labels.closeHint,
917
1043
  accessibilityLabel: labels.closeLabel,
918
1044
  accessibilityRole: "button",
919
- style: styles.scrim,
1045
+ style: styles2.scrim,
920
1046
  testID: scrimTestID,
921
1047
  onPress: onClose
922
1048
  }
923
1049
  ),
924
- /* @__PURE__ */ jsx(View, { ref: panelRef, "aria-modal": true, role: "dialog", style: styles.panel, testID: drawerTestID, children: sidebar })
1050
+ /* @__PURE__ */ jsx(View, { ref: panelRef, "aria-modal": true, role: "dialog", style: styles2.panel, testID: drawerTestID, children: sidebar })
925
1051
  ] });
926
1052
  };
1053
+
1054
+ // src/railMode.ts
1055
+ var RAIL_FULL_BREAKPOINT = 768;
1056
+ var DEFAULT_COLLAPSED_RAIL_MAX = 1024;
1057
+ function resolveRailMode({ viewport, hasSidebar, hasCollapsed, range }) {
1058
+ if (!hasSidebar) return "none";
1059
+ if (hasCollapsed) {
1060
+ if (viewport >= range.max) return "full";
1061
+ if (viewport >= range.min) return "collapsed";
1062
+ return "drawer";
1063
+ }
1064
+ return viewport >= RAIL_FULL_BREAKPOINT ? "full" : "drawer";
1065
+ }
927
1066
  function resolveContentMaxWidth(width, viewport) {
928
1067
  if (width === "full") return "full";
929
1068
  const wideMinViewport = width.wideMinViewport ?? Number.POSITIVE_INFINITY;
@@ -935,13 +1074,7 @@ function useContentMaxWidth(width) {
935
1074
  return resolveContentMaxWidth(width, viewport);
936
1075
  }
937
1076
  var DEFAULT_CONTENT_PADDING = 24;
938
- var CARD_PADDING = 20;
939
- var CARD_BORDER_RADIUS = 12;
940
- var CARD_BORDER_WIDTH = 1;
941
- var CARD_TITLE_FONT_SIZE = 16;
942
- var CARD_MESSAGE_FONT_SIZE = 14;
943
- var CARD_TITLE_MARGIN_BOTTOM = 8;
944
- var styles2 = StyleSheet.create({
1077
+ var styles3 = StyleSheet.create({
945
1078
  root: { flex: 1 },
946
1079
  centerFill: { flex: 1, justifyContent: "center", alignItems: "center" },
947
1080
  scroll: { flex: 1 },
@@ -955,50 +1088,14 @@ var styles2 = StyleSheet.create({
955
1088
  columnCapped: { flex: 1, width: "100%", alignSelf: "center" },
956
1089
  // Non-flex variant used to cap the nav strip's inner content to the content
957
1090
  // column so the nav aligns with the page content instead of running full-bleed.
958
- chromeCapped: { width: "100%", alignSelf: "center" },
959
- card: {
960
- padding: CARD_PADDING,
961
- borderRadius: CARD_BORDER_RADIUS,
962
- borderWidth: CARD_BORDER_WIDTH
963
- },
964
- cardTitle: { fontSize: CARD_TITLE_FONT_SIZE, fontWeight: "700", marginBottom: CARD_TITLE_MARGIN_BOTTOM },
965
- cardMessage: { fontSize: CARD_MESSAGE_FONT_SIZE }
1091
+ chromeCapped: { width: "100%", alignSelf: "center" }
966
1092
  });
967
- function MessageCard({
968
- message,
969
- accentColor,
970
- testID
971
- }) {
972
- const { theme } = useUi();
973
- const colors = theme.colors;
974
- return /* @__PURE__ */ jsxs(
975
- View,
976
- {
977
- style: [styles2.card, { backgroundColor: colors.surface, borderColor: accentColor }],
978
- testID,
979
- children: [
980
- /* @__PURE__ */ jsx(Text, { style: [styles2.cardTitle, { color: accentColor }], children: message.titleText }),
981
- /* @__PURE__ */ jsx(Text, { style: [styles2.cardMessage, { color: colors.textSecondary }], children: message.messageText })
982
- ]
983
- }
984
- );
985
- }
986
- function useContentBody(state, children, testID) {
987
- const { theme } = useUi();
988
- const primary = theme.palette.primary["500"];
989
- const errorColor = theme.semantic.error["500"];
990
- if (state?.forbidden)
991
- return /* @__PURE__ */ jsx(MessageCard, { accentColor: errorColor, message: state.forbidden, testID: `${testID}${APP_SHELL_SUFFIX.forbidden}` });
992
- if (state?.error)
993
- return /* @__PURE__ */ jsx(MessageCard, { accentColor: errorColor, message: state.error, testID: `${testID}${APP_SHELL_SUFFIX.error}` });
994
- if (state?.loading === true)
995
- return /* @__PURE__ */ jsx(View, { style: styles2.centerFill, testID: `${testID}${APP_SHELL_SUFFIX.loading}`, children: /* @__PURE__ */ jsx(ActivityIndicator, { color: primary, size: "large" }) });
996
- return children;
997
- }
998
1093
  var AppShell = ({
999
1094
  header,
1000
1095
  nav,
1001
1096
  sidebar,
1097
+ collapsedSidebar,
1098
+ collapsedRailRange,
1002
1099
  mobileMenu,
1003
1100
  banner,
1004
1101
  width = "full",
@@ -1014,34 +1111,40 @@ var AppShell = ({
1014
1111
  const maxWidth = useContentMaxWidth(width);
1015
1112
  const body = useContentBody(state, children, testID);
1016
1113
  const { width: viewportWidth } = useWindowDimensions();
1017
- const isNarrow = viewportWidth < MOBILE_BREAKPOINT;
1018
- const useDrawer = sidebar !== void 0 && isNarrow;
1114
+ const railRange = collapsedRailRange ?? { min: RAIL_FULL_BREAKPOINT, max: DEFAULT_COLLAPSED_RAIL_MAX };
1115
+ const railMode = resolveRailMode({
1116
+ viewport: viewportWidth,
1117
+ hasSidebar: sidebar !== void 0,
1118
+ hasCollapsed: collapsedSidebar !== void 0,
1119
+ range: railRange
1120
+ });
1121
+ const useDrawer = railMode === "drawer";
1019
1122
  const [drawerOpen, setDrawerOpen] = useState(false);
1020
1123
  const openDrawer = useCallback(() => setDrawerOpen(true), []);
1021
1124
  const closeDrawer = useCallback(() => setDrawerOpen(false), []);
1022
1125
  useEffect(() => {
1023
- if (!isNarrow && drawerOpen) setDrawerOpen(false);
1024
- }, [isNarrow, drawerOpen]);
1126
+ if (!useDrawer && drawerOpen) setDrawerOpen(false);
1127
+ }, [useDrawer, drawerOpen]);
1025
1128
  const drawerLabels = { ...DEFAULT_DRAWER_LABELS, ...mobileMenu };
1026
1129
  const isUnauthenticated = gate !== void 0 && !gate.pending && !gate.authenticated;
1027
1130
  useEffect(() => {
1028
1131
  if (isUnauthenticated && gate !== void 0) gate.onRedirect();
1029
1132
  }, [isUnauthenticated, gate]);
1030
1133
  if (gate?.pending === true)
1031
- return /* @__PURE__ */ jsx(View, { style: [styles2.root, styles2.centerFill, { backgroundColor: theme.colors.background }], testID: `${testID}${APP_SHELL_SUFFIX.pending}`, children: /* @__PURE__ */ jsx(ActivityIndicator, { color: primary, size: "large" }) });
1134
+ 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" }) });
1032
1135
  if (isUnauthenticated) return null;
1033
- const columnStyle = maxWidth === "full" ? styles2.columnFull : [styles2.columnCapped, { maxWidth }];
1034
- const navInnerStyle = chromeAlignment === "content" && maxWidth !== "full" ? [styles2.chromeCapped, { maxWidth, paddingHorizontal: contentPadding }] : void 0;
1136
+ const columnStyle = maxWidth === "full" ? styles3.columnFull : [styles3.columnCapped, { maxWidth }];
1137
+ const navInnerStyle = chromeAlignment === "content" && maxWidth !== "full" ? [styles3.chromeCapped, { maxWidth, paddingHorizontal: contentPadding }] : void 0;
1035
1138
  const scroller = /* @__PURE__ */ jsx(
1036
1139
  ScrollView,
1037
1140
  {
1038
- contentContainerStyle: [styles2.scrollContent, { padding: contentPadding }],
1039
- style: styles2.scroll,
1141
+ contentContainerStyle: [styles3.scrollContent, { padding: contentPadding }],
1142
+ style: styles3.scroll,
1040
1143
  testID: `${testID}${APP_SHELL_SUFFIX.content}`,
1041
1144
  children: /* @__PURE__ */ jsx(View, { style: columnStyle, children: body })
1042
1145
  }
1043
1146
  );
1044
- const headerRegion = useDrawer ? /* @__PURE__ */ jsxs(View, { style: styles2.headerRow, testID: `${testID}${APP_SHELL_SUFFIX.header}`, children: [
1147
+ const headerRegion = useDrawer ? /* @__PURE__ */ jsxs(View, { style: styles3.headerRow, testID: `${testID}${APP_SHELL_SUFFIX.header}`, children: [
1045
1148
  /* @__PURE__ */ jsx(
1046
1149
  MenuToggle,
1047
1150
  {
@@ -1051,15 +1154,15 @@ var AppShell = ({
1051
1154
  onOpen: openDrawer
1052
1155
  }
1053
1156
  ),
1054
- /* @__PURE__ */ jsx(View, { style: styles2.headerFill, children: header })
1157
+ /* @__PURE__ */ jsx(View, { style: styles3.headerFill, children: header })
1055
1158
  ] }) : /* @__PURE__ */ jsx(View, { testID: `${testID}${APP_SHELL_SUFFIX.header}`, children: header });
1056
- const showRail = sidebar !== void 0 && !isNarrow;
1057
- return /* @__PURE__ */ jsxs(View, { style: [styles2.root, { backgroundColor: theme.colors.background }], testID, children: [
1159
+ const railRegion = railMode === "full" ? /* @__PURE__ */ jsx(View, { testID: `${testID}${APP_SHELL_SUFFIX.sidebar}`, children: sidebar }) : railMode === "collapsed" ? /* @__PURE__ */ jsx(View, { testID: `${testID}${APP_SHELL_SUFFIX.collapsedSidebar}`, children: collapsedSidebar }) : null;
1160
+ return /* @__PURE__ */ jsxs(View, { style: [styles3.root, { backgroundColor: theme.colors.background }], testID, children: [
1058
1161
  headerRegion,
1059
1162
  nav !== void 0 ? /* @__PURE__ */ jsx(View, { testID: `${testID}${APP_SHELL_SUFFIX.nav}`, children: navInnerStyle !== void 0 ? /* @__PURE__ */ jsx(View, { style: navInnerStyle, children: nav }) : nav }) : null,
1060
1163
  banner !== void 0 ? /* @__PURE__ */ jsx(View, { testID: `${testID}${APP_SHELL_SUFFIX.banner}`, children: banner }) : null,
1061
- showRail ? /* @__PURE__ */ jsxs(View, { style: styles2.bodyRow, children: [
1062
- /* @__PURE__ */ jsx(View, { testID: `${testID}${APP_SHELL_SUFFIX.sidebar}`, children: sidebar }),
1164
+ railRegion !== null ? /* @__PURE__ */ jsxs(View, { style: styles3.bodyRow, children: [
1165
+ railRegion,
1063
1166
  scroller
1064
1167
  ] }) : scroller,
1065
1168
  useDrawer && drawerOpen ? /* @__PURE__ */ jsx(
@@ -1074,6 +1177,261 @@ var AppShell = ({
1074
1177
  ) : null
1075
1178
  ] });
1076
1179
  };
1180
+ var COLLAPSED_ICON_SIZE = 22;
1181
+ function glyphFor(label) {
1182
+ return label.trim().charAt(0).toUpperCase();
1183
+ }
1184
+ var CollapsedRail = ({
1185
+ items,
1186
+ pathname,
1187
+ onNavigate,
1188
+ regionLabel,
1189
+ navigateHint = (label) => label,
1190
+ header,
1191
+ footer,
1192
+ containerStyle
1193
+ }) => {
1194
+ const { theme } = useUi();
1195
+ const colors = theme.colors;
1196
+ const primaryColor = theme.palette.primary["500"];
1197
+ return /* @__PURE__ */ jsxs(
1198
+ View,
1199
+ {
1200
+ accessibilityRole: "none",
1201
+ "aria-label": regionLabel,
1202
+ role: "navigation",
1203
+ style: [
1204
+ collapsedRailStyles.container,
1205
+ { backgroundColor: colors.surface, borderRightColor: colors.border },
1206
+ containerStyle
1207
+ ],
1208
+ children: [
1209
+ header !== void 0 ? /* @__PURE__ */ jsx(View, { style: collapsedRailStyles.slot, children: header }) : null,
1210
+ items.map((item) => {
1211
+ const active = isRouteActive(pathname, item.route);
1212
+ const iconColor = active ? primaryColor : colors.textSecondary;
1213
+ return /* @__PURE__ */ jsx(
1214
+ FocusableTouchable,
1215
+ {
1216
+ accessibilityHint: navigateHint(item.label),
1217
+ accessibilityLabel: item.label,
1218
+ ringColor: primaryColor,
1219
+ style: [collapsedRailStyles.item, active ? { backgroundColor: colors.border } : void 0],
1220
+ testID: item.testID ?? item.key,
1221
+ onPress: () => onNavigate(item.route),
1222
+ children: typeof item.renderIcon === "function" ? item.renderIcon(iconColor, COLLAPSED_ICON_SIZE) : /* @__PURE__ */ jsx(Text, { style: [collapsedRailStyles.glyph, { color: iconColor }], children: glyphFor(item.label) })
1223
+ },
1224
+ item.key
1225
+ );
1226
+ }),
1227
+ /* @__PURE__ */ jsx(View, { style: collapsedRailStyles.spacer }),
1228
+ footer !== void 0 ? /* @__PURE__ */ jsx(View, { style: collapsedRailStyles.slot, children: footer }) : null
1229
+ ]
1230
+ }
1231
+ );
1232
+ };
1233
+ function wantsTopBar(layout, hasHeader, hasTopBar) {
1234
+ return layout === "top" || layout === "both" || hasHeader || layout === "side" && hasTopBar;
1235
+ }
1236
+ function wantsSideRail(layout, hasSideRail) {
1237
+ return (layout === "side" || layout === "both") && hasSideRail;
1238
+ }
1239
+ var NavShell = ({
1240
+ layout,
1241
+ pathname,
1242
+ onNavigate,
1243
+ regionLabel,
1244
+ navigateHint,
1245
+ topBar,
1246
+ header,
1247
+ sideRail,
1248
+ children,
1249
+ collapsedSidebar: collapsedSidebarProp,
1250
+ collapsedRailRange: collapsedRailRangeProp,
1251
+ ...shellProps
1252
+ }) => {
1253
+ const showTopBar = wantsTopBar(layout, header !== void 0, topBar !== void 0);
1254
+ const showSideRail = wantsSideRail(layout, sideRail !== void 0);
1255
+ const headerNode = header !== void 0 ? header : showTopBar && topBar !== void 0 ? /* @__PURE__ */ jsx(
1256
+ NavBar,
1257
+ {
1258
+ barTheme: topBar.barTheme,
1259
+ brand: topBar.brand,
1260
+ collapseBelow: topBar.collapseBelow,
1261
+ containerStyle: topBar.containerStyle,
1262
+ items: topBar.items,
1263
+ menuHint: topBar.menuHint,
1264
+ menuLabel: topBar.menuLabel,
1265
+ navigateHint,
1266
+ overflowHint: topBar.overflowHint,
1267
+ overflowLabel: topBar.overflowLabel,
1268
+ pathname,
1269
+ regionLabel,
1270
+ renderMenuIcon: topBar.renderMenuIcon,
1271
+ right: topBar.right,
1272
+ onNavigate
1273
+ }
1274
+ ) : null;
1275
+ const sidebarNode = showSideRail && sideRail !== void 0 ? /* @__PURE__ */ jsx(
1276
+ Sidebar,
1277
+ {
1278
+ collapseHint: sideRail.collapseHint,
1279
+ containerStyle: sideRail.containerStyle,
1280
+ expandHint: sideRail.expandHint,
1281
+ footer: sideRail.footer,
1282
+ header: sideRail.header,
1283
+ items: sideRail.items,
1284
+ navigateHint,
1285
+ pathname,
1286
+ regionLabel,
1287
+ renderChevron: sideRail.renderChevron,
1288
+ title: sideRail.title,
1289
+ onNavigate
1290
+ }
1291
+ ) : void 0;
1292
+ const collapsedCfg = showSideRail ? sideRail?.collapsed : void 0;
1293
+ const collapsedNode = collapsedCfg !== void 0 && sideRail !== void 0 ? /* @__PURE__ */ jsx(
1294
+ CollapsedRail,
1295
+ {
1296
+ footer: collapsedCfg.footer,
1297
+ header: collapsedCfg.header,
1298
+ items: collapsedCfg.items ?? sideRail.items,
1299
+ navigateHint,
1300
+ pathname,
1301
+ regionLabel,
1302
+ onNavigate
1303
+ }
1304
+ ) : collapsedSidebarProp;
1305
+ const collapsedRange = collapsedCfg?.range ?? collapsedRailRangeProp;
1306
+ return /* @__PURE__ */ jsx(
1307
+ AppShell,
1308
+ {
1309
+ ...shellProps,
1310
+ collapsedRailRange: collapsedRange,
1311
+ collapsedSidebar: collapsedNode,
1312
+ header: headerNode,
1313
+ sidebar: sidebarNode,
1314
+ children
1315
+ }
1316
+ );
1317
+ };
1318
+ var TEXT_ON_PRIMARY4 = "#ffffff";
1319
+ var DEFAULT_MIN_ITEMS = 1;
1320
+ var PillNav = ({
1321
+ items,
1322
+ pathname,
1323
+ onNavigate,
1324
+ regionLabel,
1325
+ navigateHint = (label) => label,
1326
+ minItems = DEFAULT_MIN_ITEMS,
1327
+ containerStyle
1328
+ }) => {
1329
+ const { theme } = useUi();
1330
+ const colors = theme.colors;
1331
+ const primaryColor = theme.palette.primary["500"];
1332
+ if (items.length < minItems) return null;
1333
+ return /* @__PURE__ */ jsx(
1334
+ View,
1335
+ {
1336
+ accessibilityRole: "none",
1337
+ "aria-label": regionLabel,
1338
+ role: "navigation",
1339
+ style: [pillNavStyles.container, containerStyle],
1340
+ children: items.map((item) => {
1341
+ const active = isRouteActive(pathname, item.route);
1342
+ const textColor = active ? TEXT_ON_PRIMARY4 : colors.textSecondary;
1343
+ const pillStyle = active ? { backgroundColor: primaryColor } : { backgroundColor: colors.surfaceElevated };
1344
+ return /* @__PURE__ */ jsxs(
1345
+ FocusableTouchable,
1346
+ {
1347
+ accessibilityHint: navigateHint(item.label),
1348
+ accessibilityLabel: item.label,
1349
+ accessibilityRole: "link",
1350
+ ringColor: primaryColor,
1351
+ style: [pillNavStyles.pill, pillStyle],
1352
+ testID: item.testID ?? item.key,
1353
+ onPress: () => onNavigate(item.route),
1354
+ children: [
1355
+ typeof item.renderIcon === "function" ? item.renderIcon(textColor, NAV_ICON_SIZE) : null,
1356
+ /* @__PURE__ */ jsx(Text, { style: [pillNavStyles.pillText, { color: textColor }], children: item.label })
1357
+ ]
1358
+ },
1359
+ item.key
1360
+ );
1361
+ })
1362
+ }
1363
+ );
1364
+ };
1365
+ var TEXT_ON_PRIMARY5 = "#ffffff";
1366
+ var DARK_MODE_ICON_SIZE = 16;
1367
+ var DarkModeControl = ({
1368
+ value,
1369
+ options,
1370
+ onChange,
1371
+ regionLabel,
1372
+ variant = "segmented",
1373
+ testID,
1374
+ containerStyle
1375
+ }) => {
1376
+ const { theme } = useUi();
1377
+ const colors = theme.colors;
1378
+ const primaryColor = theme.palette.primary["500"];
1379
+ const currentIndex = options.findIndex((option) => option.value === value);
1380
+ const current = currentIndex >= 0 ? options[currentIndex] : options[0];
1381
+ const advance = useCallback(() => {
1382
+ if (options.length === 0) return;
1383
+ const from = currentIndex >= 0 ? currentIndex : 0;
1384
+ const next = options[(from + 1) % options.length];
1385
+ if (next !== void 0) onChange(next.value);
1386
+ }, [options, currentIndex, onChange]);
1387
+ if (options.length === 0 || current === void 0) return null;
1388
+ if (variant === "cycle") {
1389
+ const iconColor = colors.text;
1390
+ return /* @__PURE__ */ jsx(View, { "aria-label": regionLabel, style: containerStyle, children: /* @__PURE__ */ jsxs(
1391
+ FocusableTouchable,
1392
+ {
1393
+ accessibilityHint: current.hint,
1394
+ accessibilityLabel: current.label,
1395
+ ringColor: primaryColor,
1396
+ style: [darkModeStyles.cycle, { backgroundColor: colors.surfaceElevated }],
1397
+ testID,
1398
+ onPress: advance,
1399
+ children: [
1400
+ typeof current.renderIcon === "function" ? current.renderIcon(iconColor, DARK_MODE_ICON_SIZE) : null,
1401
+ /* @__PURE__ */ jsx(Text, { style: [darkModeStyles.cycleText, { color: colors.text }], children: current.label })
1402
+ ]
1403
+ }
1404
+ ) });
1405
+ }
1406
+ return /* @__PURE__ */ jsx(
1407
+ View,
1408
+ {
1409
+ "aria-label": regionLabel,
1410
+ role: "group",
1411
+ style: [darkModeStyles.segmentRow, { borderColor: colors.border }, containerStyle],
1412
+ children: options.map((option) => {
1413
+ const active = option.value === value;
1414
+ const textColor = active ? TEXT_ON_PRIMARY5 : colors.textSecondary;
1415
+ return /* @__PURE__ */ jsxs(
1416
+ FocusableTouchable,
1417
+ {
1418
+ accessibilityHint: option.hint,
1419
+ accessibilityLabel: option.label,
1420
+ ringColor: primaryColor,
1421
+ style: [darkModeStyles.segment, active ? { backgroundColor: primaryColor } : void 0],
1422
+ testID: testID !== void 0 ? `${testID}-${option.value}` : void 0,
1423
+ onPress: () => onChange(option.value),
1424
+ children: [
1425
+ typeof option.renderIcon === "function" ? option.renderIcon(textColor, DARK_MODE_ICON_SIZE) : null,
1426
+ /* @__PURE__ */ jsx(Text, { style: [darkModeStyles.segmentText, { color: textColor }], children: option.label })
1427
+ ]
1428
+ },
1429
+ option.value
1430
+ );
1431
+ })
1432
+ }
1433
+ );
1434
+ };
1077
1435
  function roleRoutesToNavItems(routes, translate) {
1078
1436
  return routes.map((entry) => ({
1079
1437
  key: entry.role,
@@ -1085,6 +1443,6 @@ function accessibleNavItems(user, table, translate) {
1085
1443
  return roleRoutesToNavItems(resolveAccessibleRoutes(user, table), translate);
1086
1444
  }
1087
1445
 
1088
- export { ACTIVE_BORDER_RADIUS, APP_SHELL_SUFFIX, AppShell, BASE_INDENT, CHEVRON_ICON_SIZE, NAV_ICON_SIZE, NAV_LINK_GAP, NAV_TEST_IDS, Nav, NavBar, NavExpandableItem, NavOverflowMenu, Sidebar, Topbar, accessibleNavItems, expandableStyles, isRouteActive, navStyles, resolveContentMaxWidth, roleRoutesToNavItems, useContentMaxWidth };
1446
+ 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 };
1089
1447
  //# sourceMappingURL=index.mjs.map
1090
1448
  //# sourceMappingURL=index.mjs.map