@dloizides/ui-nav 1.6.1 → 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/ADOPTION.md +83 -0
- package/CHANGELOG.md +100 -0
- package/README.md +113 -0
- package/dist/index.d.mts +595 -14
- package/dist/index.d.ts +595 -14
- package/dist/index.js +640 -115
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +603 -95
- package/dist/index.mjs.map +1 -1
- package/package.json +5 -2
package/dist/index.js
CHANGED
|
@@ -2,10 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
var reactNative = require('react-native');
|
|
4
4
|
var uiFeedback = require('@dloizides/ui-feedback');
|
|
5
|
-
var
|
|
5
|
+
var React5 = require('react');
|
|
6
6
|
var jsxRuntime = require('react/jsx-runtime');
|
|
7
|
+
var uiLayout = require('@dloizides/ui-layout');
|
|
7
8
|
var authWeb = require('@dloizides/auth-web');
|
|
8
9
|
|
|
10
|
+
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
11
|
+
|
|
12
|
+
var React5__default = /*#__PURE__*/_interopDefault(React5);
|
|
13
|
+
|
|
9
14
|
// src/Sidebar.tsx
|
|
10
15
|
|
|
11
16
|
// src/isRouteActive.ts
|
|
@@ -14,6 +19,7 @@ function isRouteActive(pathname, route) {
|
|
|
14
19
|
return pathname === route || pathname.startsWith(`${route}/`);
|
|
15
20
|
}
|
|
16
21
|
var ACTIVE_BORDER_RADIUS = 4;
|
|
22
|
+
var NAV_LINK_GAP = 4;
|
|
17
23
|
var navStyles = reactNative.StyleSheet.create({
|
|
18
24
|
// --- Sidebar ---
|
|
19
25
|
sidebarContainer: {
|
|
@@ -96,22 +102,26 @@ var navBarStyles = reactNative.StyleSheet.create({
|
|
|
96
102
|
justifyContent: "center"
|
|
97
103
|
},
|
|
98
104
|
toggleGlyph: { fontSize: 20, fontWeight: "700" },
|
|
99
|
-
// Expanded links live
|
|
100
|
-
//
|
|
101
|
-
//
|
|
102
|
-
//
|
|
103
|
-
|
|
105
|
+
// Expanded links live on ONE ROW that never wraps: it is a shrinkable/growable flex child that
|
|
106
|
+
// fills the space between the pinned brand (left) and right slot, and CLIPS overflow. A measure
|
|
107
|
+
// pass (priority+) then keeps only the leading items that fit inline and collapses the rest behind
|
|
108
|
+
// the "More ▾" overflow trigger — so no item is ever cut off; the surplus moves into a dropdown.
|
|
109
|
+
// `overflow:'hidden'` only masks the single unmeasured first frame before the fit resolves.
|
|
110
|
+
linksRow: {
|
|
104
111
|
flexGrow: 1,
|
|
105
112
|
flexShrink: 1,
|
|
106
|
-
minWidth: 0
|
|
107
|
-
},
|
|
108
|
-
links: {
|
|
113
|
+
minWidth: 0,
|
|
109
114
|
flexDirection: "row",
|
|
110
115
|
alignItems: "center",
|
|
111
116
|
flexWrap: "nowrap",
|
|
112
|
-
columnGap:
|
|
113
|
-
|
|
117
|
+
columnGap: NAV_LINK_GAP,
|
|
118
|
+
overflow: "hidden"
|
|
114
119
|
},
|
|
120
|
+
// Each inline link + the overflow trigger sit in a natural-width cell that never shrinks, so their
|
|
121
|
+
// measured widths are their true content widths (what the priority+ fit computation reasons about).
|
|
122
|
+
linkCell: { flexShrink: 0 },
|
|
123
|
+
overflowTrigger: { columnGap: 4 },
|
|
124
|
+
overflowChevron: { fontSize: 12, fontWeight: "700" },
|
|
115
125
|
// Collapsed (below breakpoint): links drop onto their own full-width line,
|
|
116
126
|
// stacked vertically — mirrors the v1 `.gn-links` responsive behaviour.
|
|
117
127
|
linksStacked: {
|
|
@@ -151,6 +161,86 @@ var navBarStyles = reactNative.StyleSheet.create({
|
|
|
151
161
|
columnGap: 8
|
|
152
162
|
}
|
|
153
163
|
});
|
|
164
|
+
var pillNavStyles = reactNative.StyleSheet.create({
|
|
165
|
+
// Cross-nav pill row (kefi coral dashboard switcher): fully-rounded pills, wraps.
|
|
166
|
+
container: {
|
|
167
|
+
flexDirection: "row",
|
|
168
|
+
flexWrap: "wrap",
|
|
169
|
+
alignItems: "center",
|
|
170
|
+
columnGap: 8,
|
|
171
|
+
rowGap: 8,
|
|
172
|
+
paddingVertical: 8
|
|
173
|
+
},
|
|
174
|
+
pill: {
|
|
175
|
+
paddingHorizontal: 16,
|
|
176
|
+
paddingVertical: 8,
|
|
177
|
+
borderRadius: 999,
|
|
178
|
+
minHeight: 36,
|
|
179
|
+
flexDirection: "row",
|
|
180
|
+
alignItems: "center",
|
|
181
|
+
columnGap: 6,
|
|
182
|
+
justifyContent: "center"
|
|
183
|
+
},
|
|
184
|
+
pillText: { fontSize: 14, fontWeight: "600" }
|
|
185
|
+
});
|
|
186
|
+
var darkModeStyles = reactNative.StyleSheet.create({
|
|
187
|
+
// Segmented Light / Dark / System control.
|
|
188
|
+
segmentRow: {
|
|
189
|
+
flexDirection: "row",
|
|
190
|
+
alignItems: "center",
|
|
191
|
+
borderWidth: 1,
|
|
192
|
+
borderRadius: 8,
|
|
193
|
+
overflow: "hidden",
|
|
194
|
+
alignSelf: "flex-start"
|
|
195
|
+
},
|
|
196
|
+
segment: {
|
|
197
|
+
paddingHorizontal: 12,
|
|
198
|
+
paddingVertical: 8,
|
|
199
|
+
minHeight: 36,
|
|
200
|
+
flexDirection: "row",
|
|
201
|
+
alignItems: "center",
|
|
202
|
+
columnGap: 6,
|
|
203
|
+
justifyContent: "center"
|
|
204
|
+
},
|
|
205
|
+
segmentText: { fontSize: 13, fontWeight: "600" },
|
|
206
|
+
// Cycle-button variant.
|
|
207
|
+
cycle: {
|
|
208
|
+
paddingHorizontal: 12,
|
|
209
|
+
paddingVertical: 8,
|
|
210
|
+
minHeight: 44,
|
|
211
|
+
minWidth: 44,
|
|
212
|
+
borderRadius: 8,
|
|
213
|
+
flexDirection: "row",
|
|
214
|
+
alignItems: "center",
|
|
215
|
+
columnGap: 6,
|
|
216
|
+
justifyContent: "center"
|
|
217
|
+
},
|
|
218
|
+
cycleText: { fontSize: 13, fontWeight: "600" }
|
|
219
|
+
});
|
|
220
|
+
var collapsedRailStyles = reactNative.StyleSheet.create({
|
|
221
|
+
// Intermediate (tablet) icon-only rail: a narrow vertical strip of icon buttons.
|
|
222
|
+
container: {
|
|
223
|
+
width: 64,
|
|
224
|
+
paddingTop: 16,
|
|
225
|
+
paddingHorizontal: 8,
|
|
226
|
+
borderRightWidth: 1,
|
|
227
|
+
height: "100%",
|
|
228
|
+
alignItems: "center"
|
|
229
|
+
},
|
|
230
|
+
// Each item is a ≥44×44 centred touch target with a rounded active/hover pill.
|
|
231
|
+
item: {
|
|
232
|
+
width: 48,
|
|
233
|
+
height: 48,
|
|
234
|
+
marginVertical: 4,
|
|
235
|
+
borderRadius: 8,
|
|
236
|
+
alignItems: "center",
|
|
237
|
+
justifyContent: "center"
|
|
238
|
+
},
|
|
239
|
+
// Glyph fallback (the label's first character) when an item has no renderIcon.
|
|
240
|
+
glyph: { fontSize: 18, fontWeight: "700" },
|
|
241
|
+
spacer: { flex: 1 },
|
|
242
|
+
slot: { alignItems: "center" }
|
|
243
|
+
});
|
|
154
244
|
var expandableStyles = reactNative.StyleSheet.create({
|
|
155
245
|
childItem: {
|
|
156
246
|
borderRadius: 4,
|
|
@@ -209,21 +299,21 @@ var NavExpandableItem = ({
|
|
|
209
299
|
renderChevron,
|
|
210
300
|
depth = 0
|
|
211
301
|
}) => {
|
|
212
|
-
const [expanded, setExpanded] =
|
|
213
|
-
const [focused, setFocused] =
|
|
302
|
+
const [expanded, setExpanded] = React5.useState(false);
|
|
303
|
+
const [focused, setFocused] = React5.useState(false);
|
|
214
304
|
const { theme } = uiFeedback.useUi();
|
|
215
305
|
const colors = theme.colors;
|
|
216
306
|
const primaryColor = theme.palette.primary["500"];
|
|
217
|
-
const toggle =
|
|
307
|
+
const toggle = React5.useCallback(() => setExpanded((v) => !v), []);
|
|
218
308
|
const indent = depth * BASE_INDENT;
|
|
219
309
|
const hasChildren = Array.isArray(item.children) && item.children.length > 0;
|
|
220
310
|
const isActive = isRouteActive(pathname, item.route);
|
|
221
|
-
const activeItemStyle =
|
|
311
|
+
const activeItemStyle = React5.useMemo(
|
|
222
312
|
() => ({ backgroundColor: colors.border, borderRadius: ACTIVE_BORDER_RADIUS }),
|
|
223
313
|
[colors.border]
|
|
224
314
|
);
|
|
225
|
-
const paddingStyle =
|
|
226
|
-
const headerPaddingStyle =
|
|
315
|
+
const paddingStyle = React5.useMemo(() => ({ paddingLeft: indent + BASE_INDENT }), [indent]);
|
|
316
|
+
const headerPaddingStyle = React5.useMemo(() => ({ paddingLeft: indent }), [indent]);
|
|
227
317
|
if (!hasChildren)
|
|
228
318
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
229
319
|
reactNative.TouchableOpacity,
|
|
@@ -357,7 +447,9 @@ var NAV_TEST_IDS = {
|
|
|
357
447
|
/** responsive hamburger toggle in the horizontal NavBar (shown below the breakpoint). */
|
|
358
448
|
navBarToggle: "navbar-toggle",
|
|
359
449
|
/** links container in the horizontal NavBar. */
|
|
360
|
-
navBarLinks: "navbar-links"
|
|
450
|
+
navBarLinks: "navbar-links",
|
|
451
|
+
/** the "More ▾" priority+ overflow trigger in the horizontal NavBar (shown when items spill). */
|
|
452
|
+
navBarOverflow: "navbar-overflow"
|
|
361
453
|
};
|
|
362
454
|
var APP_SHELL_SUFFIX = {
|
|
363
455
|
content: "-content",
|
|
@@ -365,6 +457,8 @@ var APP_SHELL_SUFFIX = {
|
|
|
365
457
|
nav: "-nav",
|
|
366
458
|
/** The persistent left rail of the back-office layout (only when `sidebar` is supplied). */
|
|
367
459
|
sidebar: "-sidebar",
|
|
460
|
+
/** The intermediate persistent COLLAPSED (icon-only) rail (3-tier layout, tablet band). */
|
|
461
|
+
collapsedSidebar: "-collapsed-sidebar",
|
|
368
462
|
/** Mobile hamburger that opens the nav drawer (back-office layout, below the breakpoint). */
|
|
369
463
|
menuToggle: "-menu-toggle",
|
|
370
464
|
/** The mobile nav drawer panel (holds the `sidebar` slot when opened on a narrow viewport). */
|
|
@@ -387,7 +481,7 @@ var FocusableTouchable = ({
|
|
|
387
481
|
testID,
|
|
388
482
|
children
|
|
389
483
|
}) => {
|
|
390
|
-
const [focused, setFocused] =
|
|
484
|
+
const [focused, setFocused] = React5.useState(false);
|
|
391
485
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
392
486
|
reactNative.Pressable,
|
|
393
487
|
{
|
|
@@ -539,8 +633,8 @@ var NavBarLink = ({
|
|
|
539
633
|
navigateHint,
|
|
540
634
|
onPress
|
|
541
635
|
}) => {
|
|
542
|
-
const [hovered, setHovered] =
|
|
543
|
-
const [focused, setFocused] =
|
|
636
|
+
const [hovered, setHovered] = React5.useState(false);
|
|
637
|
+
const [focused, setFocused] = React5.useState(false);
|
|
544
638
|
const isHovered = hovered && !isActive;
|
|
545
639
|
const textColor = isActive ? TEXT_ON_PRIMARY2 : isHovered ? colors.hoverText : colors.rest;
|
|
546
640
|
const pillStyle = isActive ? { backgroundColor: colors.activeBg } : isHovered ? { backgroundColor: colors.hoverBg } : void 0;
|
|
@@ -572,14 +666,116 @@ var NavBarLink = ({
|
|
|
572
666
|
}
|
|
573
667
|
);
|
|
574
668
|
};
|
|
669
|
+
var TEXT_ON_PRIMARY3 = "#ffffff";
|
|
670
|
+
var NO_ACTIVE_ROUTE = "";
|
|
671
|
+
var NavOverflowMenu = ({
|
|
672
|
+
items,
|
|
673
|
+
pathname,
|
|
674
|
+
onNavigate,
|
|
675
|
+
colors,
|
|
676
|
+
label,
|
|
677
|
+
hint,
|
|
678
|
+
testID,
|
|
679
|
+
variant
|
|
680
|
+
}) => {
|
|
681
|
+
const options = React5.useMemo(() => items.map((item) => ({ label: item.label, value: item.route })), [items]);
|
|
682
|
+
const activeRoute = React5.useMemo(
|
|
683
|
+
() => items.find((item) => isRouteActive(pathname, item.route))?.route ?? NO_ACTIVE_ROUTE,
|
|
684
|
+
[items, pathname]
|
|
685
|
+
);
|
|
686
|
+
const optionTestID = React5.useMemo(() => {
|
|
687
|
+
const byRoute = new Map(items.map((item) => [item.route, item.testID ?? item.key]));
|
|
688
|
+
return (route) => byRoute.get(route) ?? `${testID}-option-${route}`;
|
|
689
|
+
}, [items, testID]);
|
|
690
|
+
const hasActive = activeRoute !== NO_ACTIVE_ROUTE;
|
|
691
|
+
const textColor = hasActive ? TEXT_ON_PRIMARY3 : colors.rest;
|
|
692
|
+
const pillStyle = hasActive ? { backgroundColor: colors.activeBg } : void 0;
|
|
693
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
694
|
+
uiLayout.ModalDropdown,
|
|
695
|
+
{
|
|
696
|
+
accessibilityHint: hint,
|
|
697
|
+
accessibilityLabel: label,
|
|
698
|
+
optionTestID,
|
|
699
|
+
options,
|
|
700
|
+
testID,
|
|
701
|
+
value: activeRoute,
|
|
702
|
+
variant,
|
|
703
|
+
onChange: onNavigate,
|
|
704
|
+
renderTrigger: ({ isOpen }) => /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: [navBarStyles.link, navBarStyles.overflowTrigger, pillStyle], children: [
|
|
705
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [navBarStyles.linkText, { color: textColor }], children: label }),
|
|
706
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [navBarStyles.overflowChevron, { color: textColor }], children: isOpen ? "\u25B4" : "\u25BE" })
|
|
707
|
+
] })
|
|
708
|
+
}
|
|
709
|
+
);
|
|
710
|
+
};
|
|
711
|
+
|
|
712
|
+
// src/computeVisibleCount.ts
|
|
713
|
+
function sum(values) {
|
|
714
|
+
return values.reduce((acc, value) => acc + value, 0);
|
|
715
|
+
}
|
|
716
|
+
function computeVisibleCount({ availableWidth, itemWidths, moreWidth, gap }) {
|
|
717
|
+
const total = itemWidths.length;
|
|
718
|
+
if (total === 0) return 0;
|
|
719
|
+
if (availableWidth <= 0 || itemWidths.some((width) => width <= 0)) return total;
|
|
720
|
+
const fullRowWidth = sum(itemWidths) + gap * (total - 1);
|
|
721
|
+
if (fullRowWidth <= availableWidth) return total;
|
|
722
|
+
const reservedForMore = moreWidth + gap;
|
|
723
|
+
let used = 0;
|
|
724
|
+
let count = 0;
|
|
725
|
+
for (let index = 0; index < total; index += 1) {
|
|
726
|
+
const width = itemWidths[index] ?? 0;
|
|
727
|
+
const withGap = used + (count > 0 ? gap : 0) + width;
|
|
728
|
+
if (withGap + reservedForMore > availableWidth) break;
|
|
729
|
+
used = withGap;
|
|
730
|
+
count += 1;
|
|
731
|
+
}
|
|
732
|
+
return count;
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
// src/useNavOverflow.ts
|
|
736
|
+
function resizeWidthBuffer(previous, count) {
|
|
737
|
+
const next = new Array(count).fill(0);
|
|
738
|
+
const shared = Math.min(previous.length, count);
|
|
739
|
+
for (let index = 0; index < shared; index += 1) next[index] = previous[index] ?? 0;
|
|
740
|
+
return next;
|
|
741
|
+
}
|
|
742
|
+
function useNavOverflow(itemCount, gap) {
|
|
743
|
+
const [availableWidth, setAvailableWidthState] = React5.useState(0);
|
|
744
|
+
const [moreWidth, setMoreWidthState] = React5.useState(0);
|
|
745
|
+
const [itemWidths, setItemWidths] = React5.useState(() => new Array(itemCount).fill(0));
|
|
746
|
+
React5.useEffect(() => {
|
|
747
|
+
setItemWidths((previous) => previous.length === itemCount ? previous : resizeWidthBuffer(previous, itemCount));
|
|
748
|
+
}, [itemCount]);
|
|
749
|
+
const setItemWidth = React5.useCallback((index, width) => {
|
|
750
|
+
setItemWidths((previous) => {
|
|
751
|
+
if (index < 0 || index >= previous.length || previous[index] === width) return previous;
|
|
752
|
+
const next = previous.slice();
|
|
753
|
+
next[index] = width;
|
|
754
|
+
return next;
|
|
755
|
+
});
|
|
756
|
+
}, []);
|
|
757
|
+
const setAvailableWidth = React5.useCallback(
|
|
758
|
+
(width) => setAvailableWidthState((previous) => previous === width ? previous : width),
|
|
759
|
+
[]
|
|
760
|
+
);
|
|
761
|
+
const setMoreWidth = React5.useCallback(
|
|
762
|
+
(width) => setMoreWidthState((previous) => previous === width ? previous : width),
|
|
763
|
+
[]
|
|
764
|
+
);
|
|
765
|
+
const visibleCount = React5.useMemo(
|
|
766
|
+
() => computeVisibleCount({ availableWidth, itemWidths, moreWidth, gap }),
|
|
767
|
+
[availableWidth, itemWidths, moreWidth, gap]
|
|
768
|
+
);
|
|
769
|
+
return { visibleCount, setAvailableWidth, setItemWidth, setMoreWidth };
|
|
770
|
+
}
|
|
575
771
|
var REDUCED_MOTION_QUERY = "(prefers-reduced-motion: reduce)";
|
|
576
772
|
function getReducedMotionQuery() {
|
|
577
773
|
if (typeof window === "undefined" || typeof window.matchMedia !== "function") return void 0;
|
|
578
774
|
return window.matchMedia(REDUCED_MOTION_QUERY);
|
|
579
775
|
}
|
|
580
776
|
function useReducedMotion() {
|
|
581
|
-
const [reduced, setReduced] =
|
|
582
|
-
|
|
777
|
+
const [reduced, setReduced] = React5.useState(() => getReducedMotionQuery()?.matches ?? false);
|
|
778
|
+
React5.useEffect(() => {
|
|
583
779
|
const mql = getReducedMotionQuery();
|
|
584
780
|
if (mql === void 0 || mql.addEventListener === void 0) return void 0;
|
|
585
781
|
const onChange = () => setReduced(mql.matches);
|
|
@@ -596,7 +792,7 @@ var MENU_GLYPH = "\u2630";
|
|
|
596
792
|
function ariaControlsProps(id) {
|
|
597
793
|
return { "aria-controls": id };
|
|
598
794
|
}
|
|
599
|
-
var
|
|
795
|
+
var NavBarInner = ({
|
|
600
796
|
items,
|
|
601
797
|
pathname,
|
|
602
798
|
onNavigate,
|
|
@@ -608,6 +804,8 @@ var NavBar = ({
|
|
|
608
804
|
menuHint = "",
|
|
609
805
|
renderMenuIcon,
|
|
610
806
|
collapseBelow = DEFAULT_COLLAPSE_BELOW,
|
|
807
|
+
overflowLabel = "More",
|
|
808
|
+
overflowHint = "",
|
|
611
809
|
containerStyle
|
|
612
810
|
}) => {
|
|
613
811
|
const { theme } = uiFeedback.useUi();
|
|
@@ -615,19 +813,20 @@ var NavBar = ({
|
|
|
615
813
|
const primaryColor = theme.palette.primary["500"];
|
|
616
814
|
const { width } = reactNative.useWindowDimensions();
|
|
617
815
|
const reducedMotion = useReducedMotion();
|
|
618
|
-
const [open, setOpen] =
|
|
619
|
-
const [toggleFocused, setToggleFocused] =
|
|
816
|
+
const [open, setOpen] = React5.useState(false);
|
|
817
|
+
const [toggleFocused, setToggleFocused] = React5.useState(false);
|
|
818
|
+
const { visibleCount, setAvailableWidth, setItemWidth, setMoreWidth } = useNavOverflow(items.length, NAV_LINK_GAP);
|
|
620
819
|
const collapsed = width < collapseBelow;
|
|
621
820
|
const showLinks = !collapsed || open;
|
|
622
|
-
const toggleMenu =
|
|
623
|
-
const handlePress =
|
|
821
|
+
const toggleMenu = React5.useCallback(() => setOpen((v) => !v), []);
|
|
822
|
+
const handlePress = React5.useCallback(
|
|
624
823
|
(route) => {
|
|
625
824
|
setOpen(false);
|
|
626
825
|
onNavigate(route);
|
|
627
826
|
},
|
|
628
827
|
[onNavigate]
|
|
629
828
|
);
|
|
630
|
-
const linkColors =
|
|
829
|
+
const linkColors = React5.useMemo(
|
|
631
830
|
() => ({
|
|
632
831
|
rest: colors.textSecondary,
|
|
633
832
|
hoverText: colors.text,
|
|
@@ -668,7 +867,7 @@ var NavBar = ({
|
|
|
668
867
|
}
|
|
669
868
|
) : null,
|
|
670
869
|
showLinks ? (() => {
|
|
671
|
-
const
|
|
870
|
+
const renderLink = (item) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
672
871
|
NavBarLink,
|
|
673
872
|
{
|
|
674
873
|
collapsed,
|
|
@@ -679,19 +878,50 @@ var NavBar = ({
|
|
|
679
878
|
navigateHint,
|
|
680
879
|
reducedMotion,
|
|
681
880
|
onPress: handlePress
|
|
682
|
-
}
|
|
683
|
-
|
|
684
|
-
)
|
|
685
|
-
|
|
686
|
-
|
|
881
|
+
}
|
|
882
|
+
);
|
|
883
|
+
if (collapsed) {
|
|
884
|
+
return /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { nativeID: LINKS_REGION_ID, style: navBarStyles.linksStacked, testID: NAV_TEST_IDS.navBarLinks, children: items.map((item) => /* @__PURE__ */ jsxRuntime.jsx(React5__default.default.Fragment, { children: renderLink(item) }, item.key)) });
|
|
885
|
+
}
|
|
886
|
+
const visibleItems = items.slice(0, visibleCount);
|
|
887
|
+
const overflowItems = items.slice(visibleCount);
|
|
888
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
889
|
+
reactNative.View,
|
|
687
890
|
{
|
|
688
|
-
horizontal: true,
|
|
689
|
-
showsHorizontalScrollIndicator: false,
|
|
690
891
|
nativeID: LINKS_REGION_ID,
|
|
691
|
-
style: navBarStyles.
|
|
692
|
-
contentContainerStyle: navBarStyles.links,
|
|
892
|
+
style: navBarStyles.linksRow,
|
|
693
893
|
testID: NAV_TEST_IDS.navBarLinks,
|
|
694
|
-
|
|
894
|
+
onLayout: (event) => setAvailableWidth(event.nativeEvent.layout.width),
|
|
895
|
+
children: [
|
|
896
|
+
visibleItems.map((item, index) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
897
|
+
reactNative.View,
|
|
898
|
+
{
|
|
899
|
+
style: navBarStyles.linkCell,
|
|
900
|
+
onLayout: (event) => setItemWidth(index, event.nativeEvent.layout.width),
|
|
901
|
+
children: renderLink(item)
|
|
902
|
+
},
|
|
903
|
+
item.key
|
|
904
|
+
)),
|
|
905
|
+
overflowItems.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
906
|
+
reactNative.View,
|
|
907
|
+
{
|
|
908
|
+
style: navBarStyles.linkCell,
|
|
909
|
+
onLayout: (event) => setMoreWidth(event.nativeEvent.layout.width),
|
|
910
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
911
|
+
NavOverflowMenu,
|
|
912
|
+
{
|
|
913
|
+
colors: linkColors,
|
|
914
|
+
hint: overflowHint,
|
|
915
|
+
items: overflowItems,
|
|
916
|
+
label: overflowLabel,
|
|
917
|
+
pathname,
|
|
918
|
+
testID: NAV_TEST_IDS.navBarOverflow,
|
|
919
|
+
onNavigate: handlePress
|
|
920
|
+
}
|
|
921
|
+
)
|
|
922
|
+
}
|
|
923
|
+
) : null
|
|
924
|
+
]
|
|
695
925
|
}
|
|
696
926
|
);
|
|
697
927
|
})() : null,
|
|
@@ -700,7 +930,57 @@ var NavBar = ({
|
|
|
700
930
|
}
|
|
701
931
|
);
|
|
702
932
|
};
|
|
703
|
-
var
|
|
933
|
+
var NavBar = ({ barTheme, ...props }) => {
|
|
934
|
+
const { t, navigate } = uiFeedback.useUi();
|
|
935
|
+
if (barTheme === void 0) return /* @__PURE__ */ jsxRuntime.jsx(NavBarInner, { ...props });
|
|
936
|
+
return /* @__PURE__ */ jsxRuntime.jsx(uiFeedback.UiProvider, { navigate, t, theme: barTheme, children: /* @__PURE__ */ jsxRuntime.jsx(NavBarInner, { ...props }) });
|
|
937
|
+
};
|
|
938
|
+
var Nav = (props) => {
|
|
939
|
+
if (props.orientation === "vertical") {
|
|
940
|
+
return /* @__PURE__ */ jsxRuntime.jsx(Sidebar, { ...props });
|
|
941
|
+
}
|
|
942
|
+
return /* @__PURE__ */ jsxRuntime.jsx(NavBar, { ...props });
|
|
943
|
+
};
|
|
944
|
+
var CARD_PADDING = 20;
|
|
945
|
+
var CARD_BORDER_RADIUS = 12;
|
|
946
|
+
var CARD_BORDER_WIDTH = 1;
|
|
947
|
+
var CARD_TITLE_FONT_SIZE = 16;
|
|
948
|
+
var CARD_MESSAGE_FONT_SIZE = 14;
|
|
949
|
+
var CARD_TITLE_MARGIN_BOTTOM = 8;
|
|
950
|
+
var styles = reactNative.StyleSheet.create({
|
|
951
|
+
centerFill: { flex: 1, justifyContent: "center", alignItems: "center" },
|
|
952
|
+
card: {
|
|
953
|
+
padding: CARD_PADDING,
|
|
954
|
+
borderRadius: CARD_BORDER_RADIUS,
|
|
955
|
+
borderWidth: CARD_BORDER_WIDTH
|
|
956
|
+
},
|
|
957
|
+
cardTitle: { fontSize: CARD_TITLE_FONT_SIZE, fontWeight: "700", marginBottom: CARD_TITLE_MARGIN_BOTTOM },
|
|
958
|
+
cardMessage: { fontSize: CARD_MESSAGE_FONT_SIZE }
|
|
959
|
+
});
|
|
960
|
+
function MessageCard({
|
|
961
|
+
message,
|
|
962
|
+
accentColor,
|
|
963
|
+
testID
|
|
964
|
+
}) {
|
|
965
|
+
const { theme } = uiFeedback.useUi();
|
|
966
|
+
const colors = theme.colors;
|
|
967
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: [styles.card, { backgroundColor: colors.surface, borderColor: accentColor }], testID, children: [
|
|
968
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles.cardTitle, { color: accentColor }], children: message.titleText }),
|
|
969
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles.cardMessage, { color: colors.textSecondary }], children: message.messageText })
|
|
970
|
+
] });
|
|
971
|
+
}
|
|
972
|
+
function useContentBody(state, children, testID) {
|
|
973
|
+
const { theme } = uiFeedback.useUi();
|
|
974
|
+
const primary = theme.palette.primary["500"];
|
|
975
|
+
const errorColor = theme.semantic.error["500"];
|
|
976
|
+
if (state?.forbidden)
|
|
977
|
+
return /* @__PURE__ */ jsxRuntime.jsx(MessageCard, { accentColor: errorColor, message: state.forbidden, testID: `${testID}${APP_SHELL_SUFFIX.forbidden}` });
|
|
978
|
+
if (state?.error)
|
|
979
|
+
return /* @__PURE__ */ jsxRuntime.jsx(MessageCard, { accentColor: errorColor, message: state.error, testID: `${testID}${APP_SHELL_SUFFIX.error}` });
|
|
980
|
+
if (state?.loading === true)
|
|
981
|
+
return /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: styles.centerFill, testID: `${testID}${APP_SHELL_SUFFIX.loading}`, children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.ActivityIndicator, { color: primary, size: "large" }) });
|
|
982
|
+
return children;
|
|
983
|
+
}
|
|
704
984
|
var SCRIM_COLOR = "rgba(0, 0, 0, 0.5)";
|
|
705
985
|
var MENU_GLYPH2 = "\u2630";
|
|
706
986
|
var MENU_MIN_TARGET = 44;
|
|
@@ -714,7 +994,7 @@ var DEFAULT_DRAWER_LABELS = {
|
|
|
714
994
|
closeLabel: "Close menu",
|
|
715
995
|
closeHint: "Close the navigation menu"
|
|
716
996
|
};
|
|
717
|
-
var
|
|
997
|
+
var styles2 = reactNative.StyleSheet.create({
|
|
718
998
|
overlay: { ...reactNative.StyleSheet.absoluteFillObject, zIndex: DRAWER_Z_INDEX },
|
|
719
999
|
scrim: { ...reactNative.StyleSheet.absoluteFillObject, backgroundColor: SCRIM_COLOR },
|
|
720
1000
|
panel: { position: "absolute", top: 0, bottom: 0, left: 0 },
|
|
@@ -735,10 +1015,10 @@ var MenuToggle = ({ label, hint, onOpen, testID }) => {
|
|
|
735
1015
|
accessibilityHint: hint,
|
|
736
1016
|
accessibilityLabel: label,
|
|
737
1017
|
ringColor: theme.palette.primary["500"],
|
|
738
|
-
style:
|
|
1018
|
+
style: styles2.menuToggle,
|
|
739
1019
|
testID,
|
|
740
1020
|
onPress: onOpen,
|
|
741
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [
|
|
1021
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles2.menuToggleGlyph, { color: theme.colors.text }], children: MENU_GLYPH2 })
|
|
742
1022
|
}
|
|
743
1023
|
);
|
|
744
1024
|
};
|
|
@@ -749,8 +1029,8 @@ var MobileDrawer = ({
|
|
|
749
1029
|
drawerTestID,
|
|
750
1030
|
scrimTestID
|
|
751
1031
|
}) => {
|
|
752
|
-
const panelRef =
|
|
753
|
-
|
|
1032
|
+
const panelRef = React5.useRef(null);
|
|
1033
|
+
React5.useEffect(() => {
|
|
754
1034
|
const node = panelRef.current;
|
|
755
1035
|
if (node === null || typeof node.addEventListener !== "function") return void 0;
|
|
756
1036
|
const handleClick = (event) => {
|
|
@@ -761,21 +1041,34 @@ var MobileDrawer = ({
|
|
|
761
1041
|
node.addEventListener("click", handleClick);
|
|
762
1042
|
return () => node.removeEventListener("click", handleClick);
|
|
763
1043
|
}, [onClose]);
|
|
764
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style:
|
|
1044
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles2.overlay, children: [
|
|
765
1045
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
766
1046
|
reactNative.Pressable,
|
|
767
1047
|
{
|
|
768
1048
|
accessibilityHint: labels.closeHint,
|
|
769
1049
|
accessibilityLabel: labels.closeLabel,
|
|
770
1050
|
accessibilityRole: "button",
|
|
771
|
-
style:
|
|
1051
|
+
style: styles2.scrim,
|
|
772
1052
|
testID: scrimTestID,
|
|
773
1053
|
onPress: onClose
|
|
774
1054
|
}
|
|
775
1055
|
),
|
|
776
|
-
/* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { ref: panelRef, "aria-modal": true, role: "dialog", style:
|
|
1056
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { ref: panelRef, "aria-modal": true, role: "dialog", style: styles2.panel, testID: drawerTestID, children: sidebar })
|
|
777
1057
|
] });
|
|
778
1058
|
};
|
|
1059
|
+
|
|
1060
|
+
// src/railMode.ts
|
|
1061
|
+
var RAIL_FULL_BREAKPOINT = 768;
|
|
1062
|
+
var DEFAULT_COLLAPSED_RAIL_MAX = 1024;
|
|
1063
|
+
function resolveRailMode({ viewport, hasSidebar, hasCollapsed, range }) {
|
|
1064
|
+
if (!hasSidebar) return "none";
|
|
1065
|
+
if (hasCollapsed) {
|
|
1066
|
+
if (viewport >= range.max) return "full";
|
|
1067
|
+
if (viewport >= range.min) return "collapsed";
|
|
1068
|
+
return "drawer";
|
|
1069
|
+
}
|
|
1070
|
+
return viewport >= RAIL_FULL_BREAKPOINT ? "full" : "drawer";
|
|
1071
|
+
}
|
|
779
1072
|
function resolveContentMaxWidth(width, viewport) {
|
|
780
1073
|
if (width === "full") return "full";
|
|
781
1074
|
const wideMinViewport = width.wideMinViewport ?? Number.POSITIVE_INFINITY;
|
|
@@ -787,13 +1080,7 @@ function useContentMaxWidth(width) {
|
|
|
787
1080
|
return resolveContentMaxWidth(width, viewport);
|
|
788
1081
|
}
|
|
789
1082
|
var DEFAULT_CONTENT_PADDING = 24;
|
|
790
|
-
var
|
|
791
|
-
var CARD_BORDER_RADIUS = 12;
|
|
792
|
-
var CARD_BORDER_WIDTH = 1;
|
|
793
|
-
var CARD_TITLE_FONT_SIZE = 16;
|
|
794
|
-
var CARD_MESSAGE_FONT_SIZE = 14;
|
|
795
|
-
var CARD_TITLE_MARGIN_BOTTOM = 8;
|
|
796
|
-
var styles2 = reactNative.StyleSheet.create({
|
|
1083
|
+
var styles3 = reactNative.StyleSheet.create({
|
|
797
1084
|
root: { flex: 1 },
|
|
798
1085
|
centerFill: { flex: 1, justifyContent: "center", alignItems: "center" },
|
|
799
1086
|
scroll: { flex: 1 },
|
|
@@ -807,50 +1094,14 @@ var styles2 = reactNative.StyleSheet.create({
|
|
|
807
1094
|
columnCapped: { flex: 1, width: "100%", alignSelf: "center" },
|
|
808
1095
|
// Non-flex variant used to cap the nav strip's inner content to the content
|
|
809
1096
|
// column so the nav aligns with the page content instead of running full-bleed.
|
|
810
|
-
chromeCapped: { width: "100%", alignSelf: "center" }
|
|
811
|
-
card: {
|
|
812
|
-
padding: CARD_PADDING,
|
|
813
|
-
borderRadius: CARD_BORDER_RADIUS,
|
|
814
|
-
borderWidth: CARD_BORDER_WIDTH
|
|
815
|
-
},
|
|
816
|
-
cardTitle: { fontSize: CARD_TITLE_FONT_SIZE, fontWeight: "700", marginBottom: CARD_TITLE_MARGIN_BOTTOM },
|
|
817
|
-
cardMessage: { fontSize: CARD_MESSAGE_FONT_SIZE }
|
|
1097
|
+
chromeCapped: { width: "100%", alignSelf: "center" }
|
|
818
1098
|
});
|
|
819
|
-
function MessageCard({
|
|
820
|
-
message,
|
|
821
|
-
accentColor,
|
|
822
|
-
testID
|
|
823
|
-
}) {
|
|
824
|
-
const { theme } = uiFeedback.useUi();
|
|
825
|
-
const colors = theme.colors;
|
|
826
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
827
|
-
reactNative.View,
|
|
828
|
-
{
|
|
829
|
-
style: [styles2.card, { backgroundColor: colors.surface, borderColor: accentColor }],
|
|
830
|
-
testID,
|
|
831
|
-
children: [
|
|
832
|
-
/* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles2.cardTitle, { color: accentColor }], children: message.titleText }),
|
|
833
|
-
/* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles2.cardMessage, { color: colors.textSecondary }], children: message.messageText })
|
|
834
|
-
]
|
|
835
|
-
}
|
|
836
|
-
);
|
|
837
|
-
}
|
|
838
|
-
function useContentBody(state, children, testID) {
|
|
839
|
-
const { theme } = uiFeedback.useUi();
|
|
840
|
-
const primary = theme.palette.primary["500"];
|
|
841
|
-
const errorColor = theme.semantic.error["500"];
|
|
842
|
-
if (state?.forbidden)
|
|
843
|
-
return /* @__PURE__ */ jsxRuntime.jsx(MessageCard, { accentColor: errorColor, message: state.forbidden, testID: `${testID}${APP_SHELL_SUFFIX.forbidden}` });
|
|
844
|
-
if (state?.error)
|
|
845
|
-
return /* @__PURE__ */ jsxRuntime.jsx(MessageCard, { accentColor: errorColor, message: state.error, testID: `${testID}${APP_SHELL_SUFFIX.error}` });
|
|
846
|
-
if (state?.loading === true)
|
|
847
|
-
return /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: styles2.centerFill, testID: `${testID}${APP_SHELL_SUFFIX.loading}`, children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.ActivityIndicator, { color: primary, size: "large" }) });
|
|
848
|
-
return children;
|
|
849
|
-
}
|
|
850
1099
|
var AppShell = ({
|
|
851
1100
|
header,
|
|
852
1101
|
nav,
|
|
853
1102
|
sidebar,
|
|
1103
|
+
collapsedSidebar,
|
|
1104
|
+
collapsedRailRange,
|
|
854
1105
|
mobileMenu,
|
|
855
1106
|
banner,
|
|
856
1107
|
width = "full",
|
|
@@ -866,34 +1117,40 @@ var AppShell = ({
|
|
|
866
1117
|
const maxWidth = useContentMaxWidth(width);
|
|
867
1118
|
const body = useContentBody(state, children, testID);
|
|
868
1119
|
const { width: viewportWidth } = reactNative.useWindowDimensions();
|
|
869
|
-
const
|
|
870
|
-
const
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
1120
|
+
const railRange = collapsedRailRange ?? { min: RAIL_FULL_BREAKPOINT, max: DEFAULT_COLLAPSED_RAIL_MAX };
|
|
1121
|
+
const railMode = resolveRailMode({
|
|
1122
|
+
viewport: viewportWidth,
|
|
1123
|
+
hasSidebar: sidebar !== void 0,
|
|
1124
|
+
hasCollapsed: collapsedSidebar !== void 0,
|
|
1125
|
+
range: railRange
|
|
1126
|
+
});
|
|
1127
|
+
const useDrawer = railMode === "drawer";
|
|
1128
|
+
const [drawerOpen, setDrawerOpen] = React5.useState(false);
|
|
1129
|
+
const openDrawer = React5.useCallback(() => setDrawerOpen(true), []);
|
|
1130
|
+
const closeDrawer = React5.useCallback(() => setDrawerOpen(false), []);
|
|
1131
|
+
React5.useEffect(() => {
|
|
1132
|
+
if (!useDrawer && drawerOpen) setDrawerOpen(false);
|
|
1133
|
+
}, [useDrawer, drawerOpen]);
|
|
877
1134
|
const drawerLabels = { ...DEFAULT_DRAWER_LABELS, ...mobileMenu };
|
|
878
1135
|
const isUnauthenticated = gate !== void 0 && !gate.pending && !gate.authenticated;
|
|
879
|
-
|
|
1136
|
+
React5.useEffect(() => {
|
|
880
1137
|
if (isUnauthenticated && gate !== void 0) gate.onRedirect();
|
|
881
1138
|
}, [isUnauthenticated, gate]);
|
|
882
1139
|
if (gate?.pending === true)
|
|
883
|
-
return /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [
|
|
1140
|
+
return /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [styles3.root, styles3.centerFill, { backgroundColor: theme.colors.background }], testID: `${testID}${APP_SHELL_SUFFIX.pending}`, children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.ActivityIndicator, { color: primary, size: "large" }) });
|
|
884
1141
|
if (isUnauthenticated) return null;
|
|
885
|
-
const columnStyle = maxWidth === "full" ?
|
|
886
|
-
const navInnerStyle = chromeAlignment === "content" && maxWidth !== "full" ? [
|
|
1142
|
+
const columnStyle = maxWidth === "full" ? styles3.columnFull : [styles3.columnCapped, { maxWidth }];
|
|
1143
|
+
const navInnerStyle = chromeAlignment === "content" && maxWidth !== "full" ? [styles3.chromeCapped, { maxWidth, paddingHorizontal: contentPadding }] : void 0;
|
|
887
1144
|
const scroller = /* @__PURE__ */ jsxRuntime.jsx(
|
|
888
1145
|
reactNative.ScrollView,
|
|
889
1146
|
{
|
|
890
|
-
contentContainerStyle: [
|
|
891
|
-
style:
|
|
1147
|
+
contentContainerStyle: [styles3.scrollContent, { padding: contentPadding }],
|
|
1148
|
+
style: styles3.scroll,
|
|
892
1149
|
testID: `${testID}${APP_SHELL_SUFFIX.content}`,
|
|
893
1150
|
children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: columnStyle, children: body })
|
|
894
1151
|
}
|
|
895
1152
|
);
|
|
896
|
-
const headerRegion = useDrawer ? /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style:
|
|
1153
|
+
const headerRegion = useDrawer ? /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles3.headerRow, testID: `${testID}${APP_SHELL_SUFFIX.header}`, children: [
|
|
897
1154
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
898
1155
|
MenuToggle,
|
|
899
1156
|
{
|
|
@@ -903,15 +1160,15 @@ var AppShell = ({
|
|
|
903
1160
|
onOpen: openDrawer
|
|
904
1161
|
}
|
|
905
1162
|
),
|
|
906
|
-
/* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style:
|
|
1163
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: styles3.headerFill, children: header })
|
|
907
1164
|
] }) : /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { testID: `${testID}${APP_SHELL_SUFFIX.header}`, children: header });
|
|
908
|
-
const
|
|
909
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: [
|
|
1165
|
+
const railRegion = railMode === "full" ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { testID: `${testID}${APP_SHELL_SUFFIX.sidebar}`, children: sidebar }) : railMode === "collapsed" ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { testID: `${testID}${APP_SHELL_SUFFIX.collapsedSidebar}`, children: collapsedSidebar }) : null;
|
|
1166
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: [styles3.root, { backgroundColor: theme.colors.background }], testID, children: [
|
|
910
1167
|
headerRegion,
|
|
911
1168
|
nav !== void 0 ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { testID: `${testID}${APP_SHELL_SUFFIX.nav}`, children: navInnerStyle !== void 0 ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: navInnerStyle, children: nav }) : nav }) : null,
|
|
912
1169
|
banner !== void 0 ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { testID: `${testID}${APP_SHELL_SUFFIX.banner}`, children: banner }) : null,
|
|
913
|
-
|
|
914
|
-
|
|
1170
|
+
railRegion !== null ? /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles3.bodyRow, children: [
|
|
1171
|
+
railRegion,
|
|
915
1172
|
scroller
|
|
916
1173
|
] }) : scroller,
|
|
917
1174
|
useDrawer && drawerOpen ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -926,6 +1183,261 @@ var AppShell = ({
|
|
|
926
1183
|
) : null
|
|
927
1184
|
] });
|
|
928
1185
|
};
|
|
1186
|
+
var COLLAPSED_ICON_SIZE = 22;
|
|
1187
|
+
function glyphFor(label) {
|
|
1188
|
+
return label.trim().charAt(0).toUpperCase();
|
|
1189
|
+
}
|
|
1190
|
+
var CollapsedRail = ({
|
|
1191
|
+
items,
|
|
1192
|
+
pathname,
|
|
1193
|
+
onNavigate,
|
|
1194
|
+
regionLabel,
|
|
1195
|
+
navigateHint = (label) => label,
|
|
1196
|
+
header,
|
|
1197
|
+
footer,
|
|
1198
|
+
containerStyle
|
|
1199
|
+
}) => {
|
|
1200
|
+
const { theme } = uiFeedback.useUi();
|
|
1201
|
+
const colors = theme.colors;
|
|
1202
|
+
const primaryColor = theme.palette.primary["500"];
|
|
1203
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1204
|
+
reactNative.View,
|
|
1205
|
+
{
|
|
1206
|
+
accessibilityRole: "none",
|
|
1207
|
+
"aria-label": regionLabel,
|
|
1208
|
+
role: "navigation",
|
|
1209
|
+
style: [
|
|
1210
|
+
collapsedRailStyles.container,
|
|
1211
|
+
{ backgroundColor: colors.surface, borderRightColor: colors.border },
|
|
1212
|
+
containerStyle
|
|
1213
|
+
],
|
|
1214
|
+
children: [
|
|
1215
|
+
header !== void 0 ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: collapsedRailStyles.slot, children: header }) : null,
|
|
1216
|
+
items.map((item) => {
|
|
1217
|
+
const active = isRouteActive(pathname, item.route);
|
|
1218
|
+
const iconColor = active ? primaryColor : colors.textSecondary;
|
|
1219
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1220
|
+
FocusableTouchable,
|
|
1221
|
+
{
|
|
1222
|
+
accessibilityHint: navigateHint(item.label),
|
|
1223
|
+
accessibilityLabel: item.label,
|
|
1224
|
+
ringColor: primaryColor,
|
|
1225
|
+
style: [collapsedRailStyles.item, active ? { backgroundColor: colors.border } : void 0],
|
|
1226
|
+
testID: item.testID ?? item.key,
|
|
1227
|
+
onPress: () => onNavigate(item.route),
|
|
1228
|
+
children: typeof item.renderIcon === "function" ? item.renderIcon(iconColor, COLLAPSED_ICON_SIZE) : /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [collapsedRailStyles.glyph, { color: iconColor }], children: glyphFor(item.label) })
|
|
1229
|
+
},
|
|
1230
|
+
item.key
|
|
1231
|
+
);
|
|
1232
|
+
}),
|
|
1233
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: collapsedRailStyles.spacer }),
|
|
1234
|
+
footer !== void 0 ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: collapsedRailStyles.slot, children: footer }) : null
|
|
1235
|
+
]
|
|
1236
|
+
}
|
|
1237
|
+
);
|
|
1238
|
+
};
|
|
1239
|
+
function wantsTopBar(layout, hasHeader, hasTopBar) {
|
|
1240
|
+
return layout === "top" || layout === "both" || hasHeader || layout === "side" && hasTopBar;
|
|
1241
|
+
}
|
|
1242
|
+
function wantsSideRail(layout, hasSideRail) {
|
|
1243
|
+
return (layout === "side" || layout === "both") && hasSideRail;
|
|
1244
|
+
}
|
|
1245
|
+
var NavShell = ({
|
|
1246
|
+
layout,
|
|
1247
|
+
pathname,
|
|
1248
|
+
onNavigate,
|
|
1249
|
+
regionLabel,
|
|
1250
|
+
navigateHint,
|
|
1251
|
+
topBar,
|
|
1252
|
+
header,
|
|
1253
|
+
sideRail,
|
|
1254
|
+
children,
|
|
1255
|
+
collapsedSidebar: collapsedSidebarProp,
|
|
1256
|
+
collapsedRailRange: collapsedRailRangeProp,
|
|
1257
|
+
...shellProps
|
|
1258
|
+
}) => {
|
|
1259
|
+
const showTopBar = wantsTopBar(layout, header !== void 0, topBar !== void 0);
|
|
1260
|
+
const showSideRail = wantsSideRail(layout, sideRail !== void 0);
|
|
1261
|
+
const headerNode = header !== void 0 ? header : showTopBar && topBar !== void 0 ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
1262
|
+
NavBar,
|
|
1263
|
+
{
|
|
1264
|
+
barTheme: topBar.barTheme,
|
|
1265
|
+
brand: topBar.brand,
|
|
1266
|
+
collapseBelow: topBar.collapseBelow,
|
|
1267
|
+
containerStyle: topBar.containerStyle,
|
|
1268
|
+
items: topBar.items,
|
|
1269
|
+
menuHint: topBar.menuHint,
|
|
1270
|
+
menuLabel: topBar.menuLabel,
|
|
1271
|
+
navigateHint,
|
|
1272
|
+
overflowHint: topBar.overflowHint,
|
|
1273
|
+
overflowLabel: topBar.overflowLabel,
|
|
1274
|
+
pathname,
|
|
1275
|
+
regionLabel,
|
|
1276
|
+
renderMenuIcon: topBar.renderMenuIcon,
|
|
1277
|
+
right: topBar.right,
|
|
1278
|
+
onNavigate
|
|
1279
|
+
}
|
|
1280
|
+
) : null;
|
|
1281
|
+
const sidebarNode = showSideRail && sideRail !== void 0 ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
1282
|
+
Sidebar,
|
|
1283
|
+
{
|
|
1284
|
+
collapseHint: sideRail.collapseHint,
|
|
1285
|
+
containerStyle: sideRail.containerStyle,
|
|
1286
|
+
expandHint: sideRail.expandHint,
|
|
1287
|
+
footer: sideRail.footer,
|
|
1288
|
+
header: sideRail.header,
|
|
1289
|
+
items: sideRail.items,
|
|
1290
|
+
navigateHint,
|
|
1291
|
+
pathname,
|
|
1292
|
+
regionLabel,
|
|
1293
|
+
renderChevron: sideRail.renderChevron,
|
|
1294
|
+
title: sideRail.title,
|
|
1295
|
+
onNavigate
|
|
1296
|
+
}
|
|
1297
|
+
) : void 0;
|
|
1298
|
+
const collapsedCfg = showSideRail ? sideRail?.collapsed : void 0;
|
|
1299
|
+
const collapsedNode = collapsedCfg !== void 0 && sideRail !== void 0 ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
1300
|
+
CollapsedRail,
|
|
1301
|
+
{
|
|
1302
|
+
footer: collapsedCfg.footer,
|
|
1303
|
+
header: collapsedCfg.header,
|
|
1304
|
+
items: collapsedCfg.items ?? sideRail.items,
|
|
1305
|
+
navigateHint,
|
|
1306
|
+
pathname,
|
|
1307
|
+
regionLabel,
|
|
1308
|
+
onNavigate
|
|
1309
|
+
}
|
|
1310
|
+
) : collapsedSidebarProp;
|
|
1311
|
+
const collapsedRange = collapsedCfg?.range ?? collapsedRailRangeProp;
|
|
1312
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1313
|
+
AppShell,
|
|
1314
|
+
{
|
|
1315
|
+
...shellProps,
|
|
1316
|
+
collapsedRailRange: collapsedRange,
|
|
1317
|
+
collapsedSidebar: collapsedNode,
|
|
1318
|
+
header: headerNode,
|
|
1319
|
+
sidebar: sidebarNode,
|
|
1320
|
+
children
|
|
1321
|
+
}
|
|
1322
|
+
);
|
|
1323
|
+
};
|
|
1324
|
+
var TEXT_ON_PRIMARY4 = "#ffffff";
|
|
1325
|
+
var DEFAULT_MIN_ITEMS = 1;
|
|
1326
|
+
var PillNav = ({
|
|
1327
|
+
items,
|
|
1328
|
+
pathname,
|
|
1329
|
+
onNavigate,
|
|
1330
|
+
regionLabel,
|
|
1331
|
+
navigateHint = (label) => label,
|
|
1332
|
+
minItems = DEFAULT_MIN_ITEMS,
|
|
1333
|
+
containerStyle
|
|
1334
|
+
}) => {
|
|
1335
|
+
const { theme } = uiFeedback.useUi();
|
|
1336
|
+
const colors = theme.colors;
|
|
1337
|
+
const primaryColor = theme.palette.primary["500"];
|
|
1338
|
+
if (items.length < minItems) return null;
|
|
1339
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1340
|
+
reactNative.View,
|
|
1341
|
+
{
|
|
1342
|
+
accessibilityRole: "none",
|
|
1343
|
+
"aria-label": regionLabel,
|
|
1344
|
+
role: "navigation",
|
|
1345
|
+
style: [pillNavStyles.container, containerStyle],
|
|
1346
|
+
children: items.map((item) => {
|
|
1347
|
+
const active = isRouteActive(pathname, item.route);
|
|
1348
|
+
const textColor = active ? TEXT_ON_PRIMARY4 : colors.textSecondary;
|
|
1349
|
+
const pillStyle = active ? { backgroundColor: primaryColor } : { backgroundColor: colors.surfaceElevated };
|
|
1350
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1351
|
+
FocusableTouchable,
|
|
1352
|
+
{
|
|
1353
|
+
accessibilityHint: navigateHint(item.label),
|
|
1354
|
+
accessibilityLabel: item.label,
|
|
1355
|
+
accessibilityRole: "link",
|
|
1356
|
+
ringColor: primaryColor,
|
|
1357
|
+
style: [pillNavStyles.pill, pillStyle],
|
|
1358
|
+
testID: item.testID ?? item.key,
|
|
1359
|
+
onPress: () => onNavigate(item.route),
|
|
1360
|
+
children: [
|
|
1361
|
+
typeof item.renderIcon === "function" ? item.renderIcon(textColor, NAV_ICON_SIZE) : null,
|
|
1362
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [pillNavStyles.pillText, { color: textColor }], children: item.label })
|
|
1363
|
+
]
|
|
1364
|
+
},
|
|
1365
|
+
item.key
|
|
1366
|
+
);
|
|
1367
|
+
})
|
|
1368
|
+
}
|
|
1369
|
+
);
|
|
1370
|
+
};
|
|
1371
|
+
var TEXT_ON_PRIMARY5 = "#ffffff";
|
|
1372
|
+
var DARK_MODE_ICON_SIZE = 16;
|
|
1373
|
+
var DarkModeControl = ({
|
|
1374
|
+
value,
|
|
1375
|
+
options,
|
|
1376
|
+
onChange,
|
|
1377
|
+
regionLabel,
|
|
1378
|
+
variant = "segmented",
|
|
1379
|
+
testID,
|
|
1380
|
+
containerStyle
|
|
1381
|
+
}) => {
|
|
1382
|
+
const { theme } = uiFeedback.useUi();
|
|
1383
|
+
const colors = theme.colors;
|
|
1384
|
+
const primaryColor = theme.palette.primary["500"];
|
|
1385
|
+
const currentIndex = options.findIndex((option) => option.value === value);
|
|
1386
|
+
const current = currentIndex >= 0 ? options[currentIndex] : options[0];
|
|
1387
|
+
const advance = React5.useCallback(() => {
|
|
1388
|
+
if (options.length === 0) return;
|
|
1389
|
+
const from = currentIndex >= 0 ? currentIndex : 0;
|
|
1390
|
+
const next = options[(from + 1) % options.length];
|
|
1391
|
+
if (next !== void 0) onChange(next.value);
|
|
1392
|
+
}, [options, currentIndex, onChange]);
|
|
1393
|
+
if (options.length === 0 || current === void 0) return null;
|
|
1394
|
+
if (variant === "cycle") {
|
|
1395
|
+
const iconColor = colors.text;
|
|
1396
|
+
return /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { "aria-label": regionLabel, style: containerStyle, children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1397
|
+
FocusableTouchable,
|
|
1398
|
+
{
|
|
1399
|
+
accessibilityHint: current.hint,
|
|
1400
|
+
accessibilityLabel: current.label,
|
|
1401
|
+
ringColor: primaryColor,
|
|
1402
|
+
style: [darkModeStyles.cycle, { backgroundColor: colors.surfaceElevated }],
|
|
1403
|
+
testID,
|
|
1404
|
+
onPress: advance,
|
|
1405
|
+
children: [
|
|
1406
|
+
typeof current.renderIcon === "function" ? current.renderIcon(iconColor, DARK_MODE_ICON_SIZE) : null,
|
|
1407
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [darkModeStyles.cycleText, { color: colors.text }], children: current.label })
|
|
1408
|
+
]
|
|
1409
|
+
}
|
|
1410
|
+
) });
|
|
1411
|
+
}
|
|
1412
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1413
|
+
reactNative.View,
|
|
1414
|
+
{
|
|
1415
|
+
"aria-label": regionLabel,
|
|
1416
|
+
role: "group",
|
|
1417
|
+
style: [darkModeStyles.segmentRow, { borderColor: colors.border }, containerStyle],
|
|
1418
|
+
children: options.map((option) => {
|
|
1419
|
+
const active = option.value === value;
|
|
1420
|
+
const textColor = active ? TEXT_ON_PRIMARY5 : colors.textSecondary;
|
|
1421
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1422
|
+
FocusableTouchable,
|
|
1423
|
+
{
|
|
1424
|
+
accessibilityHint: option.hint,
|
|
1425
|
+
accessibilityLabel: option.label,
|
|
1426
|
+
ringColor: primaryColor,
|
|
1427
|
+
style: [darkModeStyles.segment, active ? { backgroundColor: primaryColor } : void 0],
|
|
1428
|
+
testID: testID !== void 0 ? `${testID}-${option.value}` : void 0,
|
|
1429
|
+
onPress: () => onChange(option.value),
|
|
1430
|
+
children: [
|
|
1431
|
+
typeof option.renderIcon === "function" ? option.renderIcon(textColor, DARK_MODE_ICON_SIZE) : null,
|
|
1432
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [darkModeStyles.segmentText, { color: textColor }], children: option.label })
|
|
1433
|
+
]
|
|
1434
|
+
},
|
|
1435
|
+
option.value
|
|
1436
|
+
);
|
|
1437
|
+
})
|
|
1438
|
+
}
|
|
1439
|
+
);
|
|
1440
|
+
};
|
|
929
1441
|
function roleRoutesToNavItems(routes, translate) {
|
|
930
1442
|
return routes.map((entry) => ({
|
|
931
1443
|
key: entry.role,
|
|
@@ -942,17 +1454,30 @@ exports.APP_SHELL_SUFFIX = APP_SHELL_SUFFIX;
|
|
|
942
1454
|
exports.AppShell = AppShell;
|
|
943
1455
|
exports.BASE_INDENT = BASE_INDENT;
|
|
944
1456
|
exports.CHEVRON_ICON_SIZE = CHEVRON_ICON_SIZE;
|
|
1457
|
+
exports.CollapsedRail = CollapsedRail;
|
|
1458
|
+
exports.DEFAULT_COLLAPSED_RAIL_MAX = DEFAULT_COLLAPSED_RAIL_MAX;
|
|
1459
|
+
exports.DarkModeControl = DarkModeControl;
|
|
945
1460
|
exports.NAV_ICON_SIZE = NAV_ICON_SIZE;
|
|
1461
|
+
exports.NAV_LINK_GAP = NAV_LINK_GAP;
|
|
946
1462
|
exports.NAV_TEST_IDS = NAV_TEST_IDS;
|
|
1463
|
+
exports.Nav = Nav;
|
|
947
1464
|
exports.NavBar = NavBar;
|
|
948
1465
|
exports.NavExpandableItem = NavExpandableItem;
|
|
1466
|
+
exports.NavOverflowMenu = NavOverflowMenu;
|
|
1467
|
+
exports.NavShell = NavShell;
|
|
1468
|
+
exports.PillNav = PillNav;
|
|
1469
|
+
exports.RAIL_FULL_BREAKPOINT = RAIL_FULL_BREAKPOINT;
|
|
949
1470
|
exports.Sidebar = Sidebar;
|
|
950
1471
|
exports.Topbar = Topbar;
|
|
951
1472
|
exports.accessibleNavItems = accessibleNavItems;
|
|
1473
|
+
exports.collapsedRailStyles = collapsedRailStyles;
|
|
1474
|
+
exports.darkModeStyles = darkModeStyles;
|
|
952
1475
|
exports.expandableStyles = expandableStyles;
|
|
953
1476
|
exports.isRouteActive = isRouteActive;
|
|
954
1477
|
exports.navStyles = navStyles;
|
|
1478
|
+
exports.pillNavStyles = pillNavStyles;
|
|
955
1479
|
exports.resolveContentMaxWidth = resolveContentMaxWidth;
|
|
1480
|
+
exports.resolveRailMode = resolveRailMode;
|
|
956
1481
|
exports.roleRoutesToNavItems = roleRoutesToNavItems;
|
|
957
1482
|
exports.useContentMaxWidth = useContentMaxWidth;
|
|
958
1483
|
//# sourceMappingURL=index.js.map
|