@dloizides/ui-nav 1.21.0 → 1.23.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/CHANGELOG.md +12 -0
- package/dist/index.d.mts +6 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +216 -160
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +217 -161
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.23.0
|
|
4
|
+
|
|
5
|
+
**The NavBar's stacked mobile menu now scrolls within the viewport (SHOWCASE-1).**
|
|
6
|
+
|
|
7
|
+
Below `collapseBelow` the open hamburger menu grew to its natural height (734px with ~15 items on
|
|
8
|
+
ui.dloizides.com at 360x640). Portals set `body { overflow: hidden }`, so the last items sat below
|
|
9
|
+
the fold and neither wheel nor touch could reach them — in every portal with more than ~10 items.
|
|
10
|
+
|
|
11
|
+
- The stacked links region is now a `ScrollView` capped to `windowHeight - closedBarHeight -
|
|
12
|
+
stackedRightSlot - chrome` (pure `computeStackedMenuMaxHeight`, unit-tested; floor of three 44px
|
|
13
|
+
rows). Rows keep their 44px minimum; nothing depends on hover.
|
|
14
|
+
|
|
3
15
|
## 1.20.0
|
|
4
16
|
|
|
5
17
|
**The auth-pending and page-loading spinners now OVERLAY stably-mounted children instead of
|
package/dist/index.d.mts
CHANGED
|
@@ -1359,6 +1359,9 @@ declare const navStyles: {
|
|
|
1359
1359
|
paddingHorizontal: number;
|
|
1360
1360
|
paddingVertical: number;
|
|
1361
1361
|
borderRadius: number;
|
|
1362
|
+
minHeight: number;
|
|
1363
|
+
minWidth: number;
|
|
1364
|
+
justifyContent: "center";
|
|
1362
1365
|
};
|
|
1363
1366
|
accountText: {
|
|
1364
1367
|
fontWeight: "600";
|
|
@@ -1415,6 +1418,7 @@ declare const darkModeStyles: {
|
|
|
1415
1418
|
paddingHorizontal: number;
|
|
1416
1419
|
paddingVertical: number;
|
|
1417
1420
|
minHeight: number;
|
|
1421
|
+
minWidth: number;
|
|
1418
1422
|
flexDirection: "row";
|
|
1419
1423
|
alignItems: "center";
|
|
1420
1424
|
columnGap: number;
|
|
@@ -1476,6 +1480,7 @@ declare const expandableStyles: {
|
|
|
1476
1480
|
flexDirection: "row";
|
|
1477
1481
|
alignItems: "center";
|
|
1478
1482
|
paddingVertical: number;
|
|
1483
|
+
minHeight: number;
|
|
1479
1484
|
borderLeftWidth: number;
|
|
1480
1485
|
borderLeftColor: string;
|
|
1481
1486
|
};
|
|
@@ -1509,6 +1514,7 @@ declare const expandableStyles: {
|
|
|
1509
1514
|
flexDirection: "row";
|
|
1510
1515
|
alignItems: "center";
|
|
1511
1516
|
paddingVertical: number;
|
|
1517
|
+
minHeight: number;
|
|
1512
1518
|
};
|
|
1513
1519
|
sectionHeader: {
|
|
1514
1520
|
marginTop: number;
|
package/dist/index.d.ts
CHANGED
|
@@ -1359,6 +1359,9 @@ declare const navStyles: {
|
|
|
1359
1359
|
paddingHorizontal: number;
|
|
1360
1360
|
paddingVertical: number;
|
|
1361
1361
|
borderRadius: number;
|
|
1362
|
+
minHeight: number;
|
|
1363
|
+
minWidth: number;
|
|
1364
|
+
justifyContent: "center";
|
|
1362
1365
|
};
|
|
1363
1366
|
accountText: {
|
|
1364
1367
|
fontWeight: "600";
|
|
@@ -1415,6 +1418,7 @@ declare const darkModeStyles: {
|
|
|
1415
1418
|
paddingHorizontal: number;
|
|
1416
1419
|
paddingVertical: number;
|
|
1417
1420
|
minHeight: number;
|
|
1421
|
+
minWidth: number;
|
|
1418
1422
|
flexDirection: "row";
|
|
1419
1423
|
alignItems: "center";
|
|
1420
1424
|
columnGap: number;
|
|
@@ -1476,6 +1480,7 @@ declare const expandableStyles: {
|
|
|
1476
1480
|
flexDirection: "row";
|
|
1477
1481
|
alignItems: "center";
|
|
1478
1482
|
paddingVertical: number;
|
|
1483
|
+
minHeight: number;
|
|
1479
1484
|
borderLeftWidth: number;
|
|
1480
1485
|
borderLeftColor: string;
|
|
1481
1486
|
};
|
|
@@ -1509,6 +1514,7 @@ declare const expandableStyles: {
|
|
|
1509
1514
|
flexDirection: "row";
|
|
1510
1515
|
alignItems: "center";
|
|
1511
1516
|
paddingVertical: number;
|
|
1517
|
+
minHeight: number;
|
|
1512
1518
|
};
|
|
1513
1519
|
sectionHeader: {
|
|
1514
1520
|
marginTop: number;
|
package/dist/index.js
CHANGED
|
@@ -13,146 +13,7 @@ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
|
13
13
|
var React__default = /*#__PURE__*/_interopDefault(React);
|
|
14
14
|
|
|
15
15
|
// src/Sidebar.tsx
|
|
16
|
-
var
|
|
17
|
-
var FOCUS_RING_WIDTH = 2;
|
|
18
|
-
var FOCUS_RING_OFFSET = 2;
|
|
19
|
-
var HOVER_TRANSITION_MS = 150;
|
|
20
|
-
var TINT_TRANSITION_MS = 160;
|
|
21
|
-
function focusRingStyle(focused, ringColor) {
|
|
22
|
-
if (!IS_WEB || !focused) return void 0;
|
|
23
|
-
const ring = {
|
|
24
|
-
outlineWidth: FOCUS_RING_WIDTH,
|
|
25
|
-
outlineStyle: "solid",
|
|
26
|
-
outlineColor: ringColor,
|
|
27
|
-
outlineOffset: FOCUS_RING_OFFSET
|
|
28
|
-
};
|
|
29
|
-
return ring;
|
|
30
|
-
}
|
|
31
|
-
function webTransition(properties, durationMs, reducedMotion) {
|
|
32
|
-
if (!IS_WEB) return void 0;
|
|
33
|
-
const transition = {
|
|
34
|
-
transitionProperty: properties,
|
|
35
|
-
transitionDuration: reducedMotion ? "0ms" : `${durationMs}ms`
|
|
36
|
-
};
|
|
37
|
-
return transition;
|
|
38
|
-
}
|
|
39
|
-
function hoverTransitionStyle(reducedMotion) {
|
|
40
|
-
return webTransition("color, background-color", HOVER_TRANSITION_MS, reducedMotion);
|
|
41
|
-
}
|
|
42
|
-
function tintTransitionStyle(reducedMotion) {
|
|
43
|
-
return webTransition("opacity", TINT_TRANSITION_MS, reducedMotion);
|
|
44
|
-
}
|
|
45
|
-
function rotateTransitionStyle(reducedMotion) {
|
|
46
|
-
return webTransition("transform", TINT_TRANSITION_MS, reducedMotion);
|
|
47
|
-
}
|
|
48
|
-
var MIN_TARGET = 36;
|
|
49
|
-
var styles = reactNative.StyleSheet.create({
|
|
50
|
-
trigger: {
|
|
51
|
-
flexDirection: "row",
|
|
52
|
-
alignItems: "center",
|
|
53
|
-
justifyContent: "space-between",
|
|
54
|
-
columnGap: 8,
|
|
55
|
-
paddingHorizontal: 12,
|
|
56
|
-
minHeight: MIN_TARGET,
|
|
57
|
-
borderWidth: 1,
|
|
58
|
-
borderRadius: 8
|
|
59
|
-
},
|
|
60
|
-
label: { fontSize: 14 },
|
|
61
|
-
badge: { paddingHorizontal: 6, paddingVertical: 2, borderRadius: 4, borderWidth: 1 },
|
|
62
|
-
badgeText: { fontSize: 11, fontWeight: "700" }
|
|
63
|
-
});
|
|
64
|
-
var CommandPaletteTrigger = ({
|
|
65
|
-
label,
|
|
66
|
-
hint,
|
|
67
|
-
shortcut,
|
|
68
|
-
onPress,
|
|
69
|
-
testID
|
|
70
|
-
}) => {
|
|
71
|
-
const { theme } = uiFeedback.useUi();
|
|
72
|
-
const colors = theme.colors;
|
|
73
|
-
const primary = theme.palette.primary["500"];
|
|
74
|
-
const [focused, setFocused] = React__default.default.useState(false);
|
|
75
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
76
|
-
reactNative.Pressable,
|
|
77
|
-
{
|
|
78
|
-
accessibilityHint: hint,
|
|
79
|
-
accessibilityLabel: label,
|
|
80
|
-
accessibilityRole: "button",
|
|
81
|
-
style: [
|
|
82
|
-
styles.trigger,
|
|
83
|
-
{ backgroundColor: colors.surface, borderColor: colors.border },
|
|
84
|
-
focusRingStyle(focused, primary)
|
|
85
|
-
],
|
|
86
|
-
testID,
|
|
87
|
-
onBlur: () => setFocused(false),
|
|
88
|
-
onFocus: () => setFocused(true),
|
|
89
|
-
onPress,
|
|
90
|
-
children: [
|
|
91
|
-
/* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles.label, { color: colors.textSecondary }], children: label }),
|
|
92
|
-
/* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [styles.badge, { borderColor: colors.border }], children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles.badgeText, { color: colors.textSecondary }], children: shortcut }) })
|
|
93
|
-
]
|
|
94
|
-
}
|
|
95
|
-
);
|
|
96
|
-
};
|
|
97
|
-
|
|
98
|
-
// src/sidebarFilter.ts
|
|
99
|
-
function searchTokens(query) {
|
|
100
|
-
return query.trim().toLowerCase().split(/\s+/).filter(Boolean);
|
|
101
|
-
}
|
|
102
|
-
function labelMatches(item, tokens) {
|
|
103
|
-
const label = item.label.toLowerCase();
|
|
104
|
-
return tokens.every((token) => label.includes(token));
|
|
105
|
-
}
|
|
106
|
-
function filterNavItems(items, query) {
|
|
107
|
-
const tokens = searchTokens(query);
|
|
108
|
-
if (tokens.length === 0) return [...items];
|
|
109
|
-
return filterByTokens(items, tokens);
|
|
110
|
-
}
|
|
111
|
-
function filterByTokens(items, tokens) {
|
|
112
|
-
const kept = [];
|
|
113
|
-
for (const item of items) {
|
|
114
|
-
const selfMatch = labelMatches(item, tokens);
|
|
115
|
-
const children = item.children ?? [];
|
|
116
|
-
if (selfMatch) {
|
|
117
|
-
kept.push(item);
|
|
118
|
-
continue;
|
|
119
|
-
}
|
|
120
|
-
if (children.length === 0) continue;
|
|
121
|
-
const prunedChildren = filterByTokens(children, tokens);
|
|
122
|
-
if (prunedChildren.length > 0) kept.push({ ...item, children: prunedChildren });
|
|
123
|
-
}
|
|
124
|
-
return kept;
|
|
125
|
-
}
|
|
126
|
-
function isFilterActive(query) {
|
|
127
|
-
return searchTokens(query).length > 0;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
// src/isRouteActive.ts
|
|
131
|
-
function isRouteActive(pathname, route, exact = false) {
|
|
132
|
-
if (exact) return pathname === route;
|
|
133
|
-
if (route === "/") return pathname === "/";
|
|
134
|
-
return pathname === route || pathname.startsWith(`${route}/`);
|
|
135
|
-
}
|
|
136
|
-
function leafRoutes(items) {
|
|
137
|
-
return items.flatMap((item) => {
|
|
138
|
-
const children = item.children ?? [];
|
|
139
|
-
const isGroup = children.length > 0;
|
|
140
|
-
return isGroup ? leafRoutes(children) : [{ route: item.route, exact: item.exact === true }];
|
|
141
|
-
});
|
|
142
|
-
}
|
|
143
|
-
function resolveActiveRoute(items, pathname) {
|
|
144
|
-
let winner;
|
|
145
|
-
let winnerLength = -1;
|
|
146
|
-
for (const leaf of leafRoutes(items)) {
|
|
147
|
-
const matches = isRouteActive(pathname, leaf.route, leaf.exact);
|
|
148
|
-
const isLonger = leaf.route.length > winnerLength;
|
|
149
|
-
if (matches && isLonger) {
|
|
150
|
-
winner = leaf.route;
|
|
151
|
-
winnerLength = leaf.route.length;
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
return winner;
|
|
155
|
-
}
|
|
16
|
+
var MIN_TOUCH_TARGET = 44;
|
|
156
17
|
var ACTIVE_BORDER_RADIUS = 4;
|
|
157
18
|
var ACTIVE_ACCENT_WIDTH = 3;
|
|
158
19
|
var NAV_ROW_RADIUS = 8;
|
|
@@ -215,7 +76,15 @@ var navStyles = reactNative.StyleSheet.create({
|
|
|
215
76
|
userBlock: { marginHorizontal: 12, alignItems: "flex-end" },
|
|
216
77
|
userName: { fontWeight: "600" },
|
|
217
78
|
userEmail: { fontSize: 12 },
|
|
218
|
-
accountBtn: {
|
|
79
|
+
accountBtn: {
|
|
80
|
+
marginLeft: 8,
|
|
81
|
+
paddingHorizontal: 10,
|
|
82
|
+
paddingVertical: 6,
|
|
83
|
+
borderRadius: 4,
|
|
84
|
+
minHeight: MIN_TOUCH_TARGET,
|
|
85
|
+
minWidth: MIN_TOUCH_TARGET,
|
|
86
|
+
justifyContent: "center"
|
|
87
|
+
},
|
|
219
88
|
accountText: { fontWeight: "600" },
|
|
220
89
|
// --- Rich account-state header (Move 1c) ---
|
|
221
90
|
accountTenant: { fontSize: 12 },
|
|
@@ -251,8 +120,8 @@ var navBarStyles = reactNative.StyleSheet.create({
|
|
|
251
120
|
paddingVertical: 6,
|
|
252
121
|
borderRadius: 4,
|
|
253
122
|
// a11y: keep the hamburger a comfortable ≥44×44 touch target.
|
|
254
|
-
minWidth:
|
|
255
|
-
minHeight:
|
|
123
|
+
minWidth: MIN_TOUCH_TARGET,
|
|
124
|
+
minHeight: MIN_TOUCH_TARGET,
|
|
256
125
|
alignItems: "center",
|
|
257
126
|
justifyContent: "center"
|
|
258
127
|
},
|
|
@@ -279,13 +148,19 @@ var navBarStyles = reactNative.StyleSheet.create({
|
|
|
279
148
|
overflowChevron: { fontSize: 12, fontWeight: "700" },
|
|
280
149
|
// Collapsed (below breakpoint): links drop onto their own full-width line,
|
|
281
150
|
// stacked vertically — mirrors the v1 `.gn-links` responsive behaviour.
|
|
151
|
+
// SHOWCASE-1: this is a ScrollView — `maxHeight` (viewport minus bar) is applied at render time,
|
|
152
|
+
// so a long menu scrolls within the viewport instead of running off a body:overflow:hidden page.
|
|
282
153
|
linksStacked: {
|
|
283
154
|
width: "100%",
|
|
284
155
|
flexBasis: "100%",
|
|
156
|
+
flexGrow: 0,
|
|
157
|
+
flexShrink: 0,
|
|
158
|
+
marginTop: 8
|
|
159
|
+
},
|
|
160
|
+
linksStackedContent: {
|
|
285
161
|
flexDirection: "column",
|
|
286
162
|
alignItems: "stretch",
|
|
287
|
-
rowGap: 2
|
|
288
|
-
marginTop: 8
|
|
163
|
+
rowGap: 2
|
|
289
164
|
},
|
|
290
165
|
link: {
|
|
291
166
|
paddingVertical: 8,
|
|
@@ -294,7 +169,7 @@ var navBarStyles = reactNative.StyleSheet.create({
|
|
|
294
169
|
flexDirection: "row",
|
|
295
170
|
alignItems: "center",
|
|
296
171
|
// a11y: comfortable ≥44 tall touch target (rounded-pill hover/active affordance).
|
|
297
|
-
minHeight:
|
|
172
|
+
minHeight: MIN_TOUCH_TARGET,
|
|
298
173
|
justifyContent: "center"
|
|
299
174
|
},
|
|
300
175
|
linkStacked: { paddingVertical: 10 },
|
|
@@ -330,7 +205,7 @@ var pillNavStyles = reactNative.StyleSheet.create({
|
|
|
330
205
|
paddingHorizontal: 16,
|
|
331
206
|
paddingVertical: 8,
|
|
332
207
|
borderRadius: 999,
|
|
333
|
-
minHeight:
|
|
208
|
+
minHeight: MIN_TOUCH_TARGET,
|
|
334
209
|
flexDirection: "row",
|
|
335
210
|
alignItems: "center",
|
|
336
211
|
columnGap: 6,
|
|
@@ -351,7 +226,8 @@ var darkModeStyles = reactNative.StyleSheet.create({
|
|
|
351
226
|
segment: {
|
|
352
227
|
paddingHorizontal: 12,
|
|
353
228
|
paddingVertical: 8,
|
|
354
|
-
minHeight:
|
|
229
|
+
minHeight: MIN_TOUCH_TARGET,
|
|
230
|
+
minWidth: MIN_TOUCH_TARGET,
|
|
355
231
|
flexDirection: "row",
|
|
356
232
|
alignItems: "center",
|
|
357
233
|
columnGap: 6,
|
|
@@ -362,8 +238,8 @@ var darkModeStyles = reactNative.StyleSheet.create({
|
|
|
362
238
|
cycle: {
|
|
363
239
|
paddingHorizontal: 12,
|
|
364
240
|
paddingVertical: 8,
|
|
365
|
-
minHeight:
|
|
366
|
-
minWidth:
|
|
241
|
+
minHeight: MIN_TOUCH_TARGET,
|
|
242
|
+
minWidth: MIN_TOUCH_TARGET,
|
|
367
243
|
borderRadius: 8,
|
|
368
244
|
flexDirection: "row",
|
|
369
245
|
alignItems: "center",
|
|
@@ -408,6 +284,7 @@ var expandableStyles = reactNative.StyleSheet.create({
|
|
|
408
284
|
flexDirection: "row",
|
|
409
285
|
alignItems: "center",
|
|
410
286
|
paddingVertical: 9,
|
|
287
|
+
minHeight: MIN_TOUCH_TARGET,
|
|
411
288
|
borderLeftWidth: ACTIVE_ACCENT_WIDTH,
|
|
412
289
|
borderLeftColor: "transparent"
|
|
413
290
|
},
|
|
@@ -435,7 +312,8 @@ var expandableStyles = reactNative.StyleSheet.create({
|
|
|
435
312
|
borderRadius: NAV_ROW_RADIUS,
|
|
436
313
|
flexDirection: "row",
|
|
437
314
|
alignItems: "center",
|
|
438
|
-
paddingVertical: 8
|
|
315
|
+
paddingVertical: 8,
|
|
316
|
+
minHeight: MIN_TOUCH_TARGET
|
|
439
317
|
},
|
|
440
318
|
// Depth-0 module groups render as docs-style SECTION HEADERS: extra top rhythm
|
|
441
319
|
// to separate one module from the next.
|
|
@@ -455,6 +333,145 @@ var expandableStyles = reactNative.StyleSheet.create({
|
|
|
455
333
|
var BASE_INDENT = 12;
|
|
456
334
|
var NAV_ICON_SIZE = 14;
|
|
457
335
|
var CHEVRON_ICON_SIZE = 12;
|
|
336
|
+
var IS_WEB = reactNative.Platform.OS === "web";
|
|
337
|
+
var FOCUS_RING_WIDTH = 2;
|
|
338
|
+
var FOCUS_RING_OFFSET = 2;
|
|
339
|
+
var HOVER_TRANSITION_MS = 150;
|
|
340
|
+
var TINT_TRANSITION_MS = 160;
|
|
341
|
+
function focusRingStyle(focused, ringColor) {
|
|
342
|
+
if (!IS_WEB || !focused) return void 0;
|
|
343
|
+
const ring = {
|
|
344
|
+
outlineWidth: FOCUS_RING_WIDTH,
|
|
345
|
+
outlineStyle: "solid",
|
|
346
|
+
outlineColor: ringColor,
|
|
347
|
+
outlineOffset: FOCUS_RING_OFFSET
|
|
348
|
+
};
|
|
349
|
+
return ring;
|
|
350
|
+
}
|
|
351
|
+
function webTransition(properties, durationMs, reducedMotion) {
|
|
352
|
+
if (!IS_WEB) return void 0;
|
|
353
|
+
const transition = {
|
|
354
|
+
transitionProperty: properties,
|
|
355
|
+
transitionDuration: reducedMotion ? "0ms" : `${durationMs}ms`
|
|
356
|
+
};
|
|
357
|
+
return transition;
|
|
358
|
+
}
|
|
359
|
+
function hoverTransitionStyle(reducedMotion) {
|
|
360
|
+
return webTransition("color, background-color", HOVER_TRANSITION_MS, reducedMotion);
|
|
361
|
+
}
|
|
362
|
+
function tintTransitionStyle(reducedMotion) {
|
|
363
|
+
return webTransition("opacity", TINT_TRANSITION_MS, reducedMotion);
|
|
364
|
+
}
|
|
365
|
+
function rotateTransitionStyle(reducedMotion) {
|
|
366
|
+
return webTransition("transform", TINT_TRANSITION_MS, reducedMotion);
|
|
367
|
+
}
|
|
368
|
+
var styles = reactNative.StyleSheet.create({
|
|
369
|
+
trigger: {
|
|
370
|
+
flexDirection: "row",
|
|
371
|
+
alignItems: "center",
|
|
372
|
+
justifyContent: "space-between",
|
|
373
|
+
columnGap: 8,
|
|
374
|
+
paddingHorizontal: 12,
|
|
375
|
+
minHeight: MIN_TOUCH_TARGET,
|
|
376
|
+
borderWidth: 1,
|
|
377
|
+
borderRadius: 8
|
|
378
|
+
},
|
|
379
|
+
label: { fontSize: 14 },
|
|
380
|
+
badge: { paddingHorizontal: 6, paddingVertical: 2, borderRadius: 4, borderWidth: 1 },
|
|
381
|
+
badgeText: { fontSize: 11, fontWeight: "700" }
|
|
382
|
+
});
|
|
383
|
+
var CommandPaletteTrigger = ({
|
|
384
|
+
label,
|
|
385
|
+
hint,
|
|
386
|
+
shortcut,
|
|
387
|
+
onPress,
|
|
388
|
+
testID
|
|
389
|
+
}) => {
|
|
390
|
+
const { theme } = uiFeedback.useUi();
|
|
391
|
+
const colors = theme.colors;
|
|
392
|
+
const primary = theme.palette.primary["500"];
|
|
393
|
+
const [focused, setFocused] = React__default.default.useState(false);
|
|
394
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
395
|
+
reactNative.Pressable,
|
|
396
|
+
{
|
|
397
|
+
accessibilityHint: hint,
|
|
398
|
+
accessibilityLabel: label,
|
|
399
|
+
accessibilityRole: "button",
|
|
400
|
+
style: [
|
|
401
|
+
styles.trigger,
|
|
402
|
+
{ backgroundColor: colors.surface, borderColor: colors.border },
|
|
403
|
+
focusRingStyle(focused, primary)
|
|
404
|
+
],
|
|
405
|
+
testID,
|
|
406
|
+
onBlur: () => setFocused(false),
|
|
407
|
+
onFocus: () => setFocused(true),
|
|
408
|
+
onPress,
|
|
409
|
+
children: [
|
|
410
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles.label, { color: colors.textSecondary }], children: label }),
|
|
411
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [styles.badge, { borderColor: colors.border }], children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles.badgeText, { color: colors.textSecondary }], children: shortcut }) })
|
|
412
|
+
]
|
|
413
|
+
}
|
|
414
|
+
);
|
|
415
|
+
};
|
|
416
|
+
|
|
417
|
+
// src/sidebarFilter.ts
|
|
418
|
+
function searchTokens(query) {
|
|
419
|
+
return query.trim().toLowerCase().split(/\s+/).filter(Boolean);
|
|
420
|
+
}
|
|
421
|
+
function labelMatches(item, tokens) {
|
|
422
|
+
const label = item.label.toLowerCase();
|
|
423
|
+
return tokens.every((token) => label.includes(token));
|
|
424
|
+
}
|
|
425
|
+
function filterNavItems(items, query) {
|
|
426
|
+
const tokens = searchTokens(query);
|
|
427
|
+
if (tokens.length === 0) return [...items];
|
|
428
|
+
return filterByTokens(items, tokens);
|
|
429
|
+
}
|
|
430
|
+
function filterByTokens(items, tokens) {
|
|
431
|
+
const kept = [];
|
|
432
|
+
for (const item of items) {
|
|
433
|
+
const selfMatch = labelMatches(item, tokens);
|
|
434
|
+
const children = item.children ?? [];
|
|
435
|
+
if (selfMatch) {
|
|
436
|
+
kept.push(item);
|
|
437
|
+
continue;
|
|
438
|
+
}
|
|
439
|
+
if (children.length === 0) continue;
|
|
440
|
+
const prunedChildren = filterByTokens(children, tokens);
|
|
441
|
+
if (prunedChildren.length > 0) kept.push({ ...item, children: prunedChildren });
|
|
442
|
+
}
|
|
443
|
+
return kept;
|
|
444
|
+
}
|
|
445
|
+
function isFilterActive(query) {
|
|
446
|
+
return searchTokens(query).length > 0;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
// src/isRouteActive.ts
|
|
450
|
+
function isRouteActive(pathname, route, exact = false) {
|
|
451
|
+
if (exact) return pathname === route;
|
|
452
|
+
if (route === "/") return pathname === "/";
|
|
453
|
+
return pathname === route || pathname.startsWith(`${route}/`);
|
|
454
|
+
}
|
|
455
|
+
function leafRoutes(items) {
|
|
456
|
+
return items.flatMap((item) => {
|
|
457
|
+
const children = item.children ?? [];
|
|
458
|
+
const isGroup = children.length > 0;
|
|
459
|
+
return isGroup ? leafRoutes(children) : [{ route: item.route, exact: item.exact === true }];
|
|
460
|
+
});
|
|
461
|
+
}
|
|
462
|
+
function resolveActiveRoute(items, pathname) {
|
|
463
|
+
let winner;
|
|
464
|
+
let winnerLength = -1;
|
|
465
|
+
for (const leaf of leafRoutes(items)) {
|
|
466
|
+
const matches = isRouteActive(pathname, leaf.route, leaf.exact);
|
|
467
|
+
const isLonger = leaf.route.length > winnerLength;
|
|
468
|
+
if (matches && isLonger) {
|
|
469
|
+
winner = leaf.route;
|
|
470
|
+
winnerLength = leaf.route.length;
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
return winner;
|
|
474
|
+
}
|
|
458
475
|
var REDUCED_MOTION_QUERY = "(prefers-reduced-motion: reduce)";
|
|
459
476
|
function getReducedMotionQuery() {
|
|
460
477
|
if (typeof window === "undefined" || typeof window.matchMedia !== "function") return void 0;
|
|
@@ -652,7 +669,6 @@ function resolveSearchAffordance(viewport, breakpoint = DEFAULT_SEARCH_BREAKPOIN
|
|
|
652
669
|
}
|
|
653
670
|
var FIELD_MIN_HEIGHT = 38;
|
|
654
671
|
var CLEAR_GLYPH = "\u2715";
|
|
655
|
-
var CLEAR_MIN_TARGET = 28;
|
|
656
672
|
var styles2 = reactNative.StyleSheet.create({
|
|
657
673
|
wrap: {
|
|
658
674
|
flexDirection: "row",
|
|
@@ -670,8 +686,8 @@ var styles2 = reactNative.StyleSheet.create({
|
|
|
670
686
|
minHeight: FIELD_MIN_HEIGHT
|
|
671
687
|
},
|
|
672
688
|
clear: {
|
|
673
|
-
minWidth:
|
|
674
|
-
minHeight:
|
|
689
|
+
minWidth: MIN_TOUCH_TARGET,
|
|
690
|
+
minHeight: MIN_TOUCH_TARGET,
|
|
675
691
|
alignItems: "center",
|
|
676
692
|
justifyContent: "center",
|
|
677
693
|
borderRadius: 6
|
|
@@ -1174,9 +1190,24 @@ function useNavOverflow(itemCount, gap) {
|
|
|
1174
1190
|
);
|
|
1175
1191
|
return { visibleCount, setAvailableWidth, setItemWidth, setMoreWidth };
|
|
1176
1192
|
}
|
|
1193
|
+
|
|
1194
|
+
// src/stackedMenuMaxHeight.ts
|
|
1195
|
+
var DEFAULT_BAR_HEIGHT = 64;
|
|
1196
|
+
var MIN_STACKED_MENU_HEIGHT = 3 * 44;
|
|
1197
|
+
function computeStackedMenuMaxHeight({
|
|
1198
|
+
windowHeight,
|
|
1199
|
+
barHeight,
|
|
1200
|
+
reservedBelow
|
|
1201
|
+
}) {
|
|
1202
|
+
const bar = barHeight > 0 ? barHeight : DEFAULT_BAR_HEIGHT;
|
|
1203
|
+
const reserved = Math.max(0, reservedBelow);
|
|
1204
|
+
return Math.max(MIN_STACKED_MENU_HEIGHT, windowHeight - bar - reserved);
|
|
1205
|
+
}
|
|
1177
1206
|
var LINKS_REGION_ID = "navbar-links-region";
|
|
1178
1207
|
var DEFAULT_COLLAPSE_BELOW = 760;
|
|
1179
1208
|
var MENU_GLYPH = "\u2630";
|
|
1209
|
+
var STACKED_MENU_CHROME = 12;
|
|
1210
|
+
var STACKED_RIGHT_CHROME = 8;
|
|
1180
1211
|
function ariaControlsProps(id) {
|
|
1181
1212
|
return { "aria-controls": id };
|
|
1182
1213
|
}
|
|
@@ -1200,13 +1231,22 @@ var NavBarInner = ({
|
|
|
1200
1231
|
const { theme } = uiFeedback.useUi();
|
|
1201
1232
|
const colors = theme.colors;
|
|
1202
1233
|
const primaryColor = theme.palette.primary["500"];
|
|
1203
|
-
const { width } = reactNative.useWindowDimensions();
|
|
1234
|
+
const { width, height } = reactNative.useWindowDimensions();
|
|
1204
1235
|
const reducedMotion = useReducedMotion();
|
|
1205
1236
|
const [open, setOpen] = React.useState(false);
|
|
1206
1237
|
const [toggleFocused, setToggleFocused] = React.useState(false);
|
|
1207
1238
|
const { visibleCount, setAvailableWidth, setItemWidth, setMoreWidth } = useNavOverflow(items.length, NAV_LINK_GAP);
|
|
1208
1239
|
const collapsed = width < collapseBelow;
|
|
1209
1240
|
const showLinks = !collapsed || open;
|
|
1241
|
+
const [barHeight, setBarHeight] = React.useState(0);
|
|
1242
|
+
const [rightHeight, setRightHeight] = React.useState(0);
|
|
1243
|
+
const hasRight = right !== void 0;
|
|
1244
|
+
const stackedMaxHeight = computeStackedMenuMaxHeight({
|
|
1245
|
+
windowHeight: height,
|
|
1246
|
+
barHeight,
|
|
1247
|
+
reservedBelow: STACKED_MENU_CHROME + (hasRight ? STACKED_RIGHT_CHROME + rightHeight : 0)
|
|
1248
|
+
});
|
|
1249
|
+
const measureBar = collapsed && !open;
|
|
1210
1250
|
const innerCapStyle = React.useMemo(
|
|
1211
1251
|
() => contentMaxWidth === void 0 ? null : { maxWidth: contentMaxWidth, width: "100%", alignSelf: "center" },
|
|
1212
1252
|
[contentMaxWidth]
|
|
@@ -1241,6 +1281,7 @@ var NavBarInner = ({
|
|
|
1241
1281
|
{ backgroundColor: colors.surface, borderBottomColor: colors.border },
|
|
1242
1282
|
containerStyle
|
|
1243
1283
|
],
|
|
1284
|
+
onLayout: measureBar ? (event) => setBarHeight(event.nativeEvent.layout.height) : void 0,
|
|
1244
1285
|
children: /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: [navBarStyles.inner, collapsed ? null : navBarStyles.innerNoWrap, innerCapStyle], children: [
|
|
1245
1286
|
brand !== void 0 ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: navBarStyles.brand, children: renderTextSlot(brand, { color: colors.text }) }) : null,
|
|
1246
1287
|
collapsed ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -1275,7 +1316,16 @@ var NavBarInner = ({
|
|
|
1275
1316
|
}
|
|
1276
1317
|
);
|
|
1277
1318
|
if (collapsed) {
|
|
1278
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1319
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1320
|
+
reactNative.ScrollView,
|
|
1321
|
+
{
|
|
1322
|
+
contentContainerStyle: navBarStyles.linksStackedContent,
|
|
1323
|
+
nativeID: LINKS_REGION_ID,
|
|
1324
|
+
style: [navBarStyles.linksStacked, { maxHeight: stackedMaxHeight }],
|
|
1325
|
+
testID: NAV_TEST_IDS.navBarLinks,
|
|
1326
|
+
children: items.map((item) => /* @__PURE__ */ jsxRuntime.jsx(React__default.default.Fragment, { children: renderLink(item) }, item.key))
|
|
1327
|
+
}
|
|
1328
|
+
);
|
|
1279
1329
|
}
|
|
1280
1330
|
const visibleItems = items.slice(0, visibleCount);
|
|
1281
1331
|
const overflowItems = items.slice(visibleCount);
|
|
@@ -1319,7 +1369,14 @@ var NavBarInner = ({
|
|
|
1319
1369
|
}
|
|
1320
1370
|
);
|
|
1321
1371
|
})() : null,
|
|
1322
|
-
showLinks &&
|
|
1372
|
+
showLinks && hasRight ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
1373
|
+
reactNative.View,
|
|
1374
|
+
{
|
|
1375
|
+
style: collapsed ? navBarStyles.rightStacked : navBarStyles.right,
|
|
1376
|
+
onLayout: collapsed ? (event) => setRightHeight(event.nativeEvent.layout.height) : void 0,
|
|
1377
|
+
children: renderTextSlot(right, { color: colors.text })
|
|
1378
|
+
}
|
|
1379
|
+
) : null
|
|
1323
1380
|
] })
|
|
1324
1381
|
}
|
|
1325
1382
|
);
|
|
@@ -1401,7 +1458,6 @@ function useContentBody(state, children, testID) {
|
|
|
1401
1458
|
}
|
|
1402
1459
|
var SCRIM_COLOR = "rgba(0, 0, 0, 0.5)";
|
|
1403
1460
|
var MENU_GLYPH2 = "\u2630";
|
|
1404
|
-
var MENU_MIN_TARGET = 44;
|
|
1405
1461
|
var MENU_GLYPH_FONT_SIZE = 20;
|
|
1406
1462
|
var MENU_TOGGLE_PADDING = 12;
|
|
1407
1463
|
var DRAWER_Z_INDEX = 50;
|
|
@@ -1439,8 +1495,8 @@ var styles4 = reactNative.StyleSheet.create({
|
|
|
1439
1495
|
// and receive presses instead of the scrim swallowing them.
|
|
1440
1496
|
panel: { position: "absolute", top: 0, bottom: 0, left: 0, width: DRAWER_WIDTH, zIndex: PANEL_Z_INDEX },
|
|
1441
1497
|
menuToggle: {
|
|
1442
|
-
minWidth:
|
|
1443
|
-
minHeight:
|
|
1498
|
+
minWidth: MIN_TOUCH_TARGET,
|
|
1499
|
+
minHeight: MIN_TOUCH_TARGET,
|
|
1444
1500
|
alignItems: "center",
|
|
1445
1501
|
justifyContent: "center",
|
|
1446
1502
|
paddingHorizontal: MENU_TOGGLE_PADDING
|