@dloizides/ui-nav 1.2.0 → 1.3.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 +23 -0
- package/README.md +15 -2
- package/dist/index.d.mts +9 -2
- package/dist/index.d.ts +9 -2
- package/dist/index.js +190 -47
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +191 -48
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,28 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.3.0
|
|
4
|
+
|
|
5
|
+
Give the `NavBar` the v1 `.gn-links` look + a full accessibility pass (and extend the
|
|
6
|
+
same a11y bar to `Sidebar`/`Topbar`).
|
|
7
|
+
|
|
8
|
+
**Look & hover (NavBar).** Each link is a rounded-pill target with the v1 hover
|
|
9
|
+
affordance — muted at rest, and on HOVER the text brightens to full contrast while a
|
|
10
|
+
subtle rounded-pill background appears; the ACTIVE link is a solid accent pill. All
|
|
11
|
+
theme-driven (rest = `textSecondary`, hover text = `text`, hover bg = `surfaceElevated`,
|
|
12
|
+
active/ring = `palette.primary`). Hover/focus are web-only (`onHoverIn/Out`, `onFocus/Blur`
|
|
13
|
+
via `Pressable`); native stays at the rest style. The hover cross-fade is cheap CSS and is
|
|
14
|
+
disabled under `prefers-reduced-motion: reduce` (new `useReducedMotion`). Backwards
|
|
15
|
+
compatible — the hover pill is the default (aml-v2 is the only `NavBar` consumer) and the
|
|
16
|
+
brand/right slots, responsive hamburger collapse, and `containerStyle` width behaviour are
|
|
17
|
+
unchanged.
|
|
18
|
+
|
|
19
|
+
**Accessibility.** NavBar toggle now carries `aria-controls` pointing at the collapsible
|
|
20
|
+
links region (id via `nativeID`), keeps `aria-expanded`, and gains a themed keyboard focus
|
|
21
|
+
ring; links keep `aria-current="page"` + `role="link"` and gain a focus ring; link/toggle
|
|
22
|
+
touch targets are now ≥44×44. `Sidebar` leaves gain `aria-current="page"` + a focus ring;
|
|
23
|
+
`Topbar` action buttons gain a focus ring (new internal `FocusableTouchable`). Focus rings
|
|
24
|
+
never remove the outline without a themed replacement.
|
|
25
|
+
|
|
3
26
|
## 1.2.0
|
|
4
27
|
|
|
5
28
|
Add `NavBar` — a config-driven **horizontal top navigation bar**, the top-bar
|
package/README.md
CHANGED
|
@@ -74,8 +74,21 @@ import { NavBar, type NavItem } from '@dloizides/ui-nav';
|
|
|
74
74
|
An app that renders a `Sidebar` today switches to a top bar by swapping the
|
|
75
75
|
`Sidebar` element for `NavBar` (same `items` / `pathname` / `onNavigate`) — e.g.
|
|
76
76
|
the AML v2 app drops `NavBar` into the `AppShell` `header` (or `nav`) slot so its
|
|
77
|
-
links sit across the top like the rest of the console.
|
|
78
|
-
|
|
77
|
+
links sit across the top like the rest of the console.
|
|
78
|
+
|
|
79
|
+
Each link is a rounded-pill target with the v1 `.gn-links` affordance: muted at
|
|
80
|
+
rest, brightening its text + showing a subtle pill background on **hover**, and a
|
|
81
|
+
solid accent pill when **active**. All colours are theme-driven (rest =
|
|
82
|
+
`textSecondary`, hover text = `text`, hover bg = `surfaceElevated`, active/ring =
|
|
83
|
+
`palette.primary`); hover/focus are web-only and the hover cross-fade honours
|
|
84
|
+
`prefers-reduced-motion`.
|
|
85
|
+
|
|
86
|
+
**Accessibility.** The container is a `role="navigation"` landmark named by
|
|
87
|
+
`regionLabel`; active links expose `aria-current="page"` + `role="link"`; the
|
|
88
|
+
responsive hamburger carries `role="button"`, `aria-expanded`, and `aria-controls`
|
|
89
|
+
pointing at the collapsible links region; every link/toggle is keyboard-operable
|
|
90
|
+
(Enter/Space) with a themed focus ring and a ≥44×44 touch target. `Sidebar` leaves
|
|
91
|
+
carry `aria-current="page"` and `Topbar` buttons a focus ring under the same bar.
|
|
79
92
|
|
|
80
93
|
### Role gating
|
|
81
94
|
|
package/dist/index.d.mts
CHANGED
|
@@ -154,10 +154,17 @@ declare const Topbar: ({ left, language, notificationSlot, user, account, logout
|
|
|
154
154
|
* collapses the links behind a hamburger toggle — mirroring the v1 AML console's
|
|
155
155
|
* responsive `.global-nav`.
|
|
156
156
|
*
|
|
157
|
+
* Look + feel: each link is a rounded-pill target with the v1 `.gn-links a`
|
|
158
|
+
* affordance — muted at rest, brightening text + a subtle pill background on
|
|
159
|
+
* HOVER, and a solid accent pill when ACTIVE. All colours are THEME-DRIVEN from
|
|
160
|
+
* the `@dloizides/ui-feedback` UiProvider (rest=`textSecondary`, hover text=`text`,
|
|
161
|
+
* hover bg=`surfaceElevated`, active=`palette.primary`, ring=`palette.primary`).
|
|
162
|
+
* Hover/focus are web-only (react-native-web); native stays at the rest style.
|
|
163
|
+
*
|
|
157
164
|
* Contract discipline (same as the rest of `@dloizides/ui-nav`): NO FM, router,
|
|
158
165
|
* icon set, or store imports. Labels are pre-localized strings, icons are render
|
|
159
|
-
* slots, and every colour is read from the
|
|
160
|
-
*
|
|
166
|
+
* slots, and every colour is read from the theme. This is purely additive —
|
|
167
|
+
* `Sidebar`/`Topbar` are unchanged.
|
|
161
168
|
*/
|
|
162
169
|
|
|
163
170
|
interface NavBarProps {
|
package/dist/index.d.ts
CHANGED
|
@@ -154,10 +154,17 @@ declare const Topbar: ({ left, language, notificationSlot, user, account, logout
|
|
|
154
154
|
* collapses the links behind a hamburger toggle — mirroring the v1 AML console's
|
|
155
155
|
* responsive `.global-nav`.
|
|
156
156
|
*
|
|
157
|
+
* Look + feel: each link is a rounded-pill target with the v1 `.gn-links a`
|
|
158
|
+
* affordance — muted at rest, brightening text + a subtle pill background on
|
|
159
|
+
* HOVER, and a solid accent pill when ACTIVE. All colours are THEME-DRIVEN from
|
|
160
|
+
* the `@dloizides/ui-feedback` UiProvider (rest=`textSecondary`, hover text=`text`,
|
|
161
|
+
* hover bg=`surfaceElevated`, active=`palette.primary`, ring=`palette.primary`).
|
|
162
|
+
* Hover/focus are web-only (react-native-web); native stays at the rest style.
|
|
163
|
+
*
|
|
157
164
|
* Contract discipline (same as the rest of `@dloizides/ui-nav`): NO FM, router,
|
|
158
165
|
* icon set, or store imports. Labels are pre-localized strings, icons are render
|
|
159
|
-
* slots, and every colour is read from the
|
|
160
|
-
*
|
|
166
|
+
* slots, and every colour is read from the theme. This is purely additive —
|
|
167
|
+
* `Sidebar`/`Topbar` are unchanged.
|
|
161
168
|
*/
|
|
162
169
|
|
|
163
170
|
interface NavBarProps {
|
package/dist/index.js
CHANGED
|
@@ -81,7 +81,12 @@ var navBarStyles = reactNative.StyleSheet.create({
|
|
|
81
81
|
marginLeft: "auto",
|
|
82
82
|
paddingHorizontal: 10,
|
|
83
83
|
paddingVertical: 6,
|
|
84
|
-
borderRadius: 4
|
|
84
|
+
borderRadius: 4,
|
|
85
|
+
// a11y: keep the hamburger a comfortable ≥44×44 touch target.
|
|
86
|
+
minWidth: 44,
|
|
87
|
+
minHeight: 44,
|
|
88
|
+
alignItems: "center",
|
|
89
|
+
justifyContent: "center"
|
|
85
90
|
},
|
|
86
91
|
toggleGlyph: { fontSize: 20, fontWeight: "700" },
|
|
87
92
|
links: {
|
|
@@ -106,7 +111,10 @@ var navBarStyles = reactNative.StyleSheet.create({
|
|
|
106
111
|
paddingHorizontal: 14,
|
|
107
112
|
borderRadius: 8,
|
|
108
113
|
flexDirection: "row",
|
|
109
|
-
alignItems: "center"
|
|
114
|
+
alignItems: "center",
|
|
115
|
+
// a11y: comfortable ≥44 tall touch target (rounded-pill hover/active affordance).
|
|
116
|
+
minHeight: 44,
|
|
117
|
+
justifyContent: "center"
|
|
110
118
|
},
|
|
111
119
|
linkStacked: { paddingVertical: 10 },
|
|
112
120
|
linkIcon: { marginRight: 6 },
|
|
@@ -149,6 +157,31 @@ var expandableStyles = reactNative.StyleSheet.create({
|
|
|
149
157
|
var BASE_INDENT = 12;
|
|
150
158
|
var NAV_ICON_SIZE = 14;
|
|
151
159
|
var CHEVRON_ICON_SIZE = 12;
|
|
160
|
+
var IS_WEB = reactNative.Platform.OS === "web";
|
|
161
|
+
var FOCUS_RING_WIDTH = 2;
|
|
162
|
+
var FOCUS_RING_OFFSET = 2;
|
|
163
|
+
var HOVER_TRANSITION_MS = 150;
|
|
164
|
+
function focusRingStyle(focused, ringColor) {
|
|
165
|
+
if (!IS_WEB || !focused) return void 0;
|
|
166
|
+
const ring = {
|
|
167
|
+
outlineWidth: FOCUS_RING_WIDTH,
|
|
168
|
+
outlineStyle: "solid",
|
|
169
|
+
outlineColor: ringColor,
|
|
170
|
+
outlineOffset: FOCUS_RING_OFFSET
|
|
171
|
+
};
|
|
172
|
+
return ring;
|
|
173
|
+
}
|
|
174
|
+
function hoverTransitionStyle(reducedMotion) {
|
|
175
|
+
if (!IS_WEB) return void 0;
|
|
176
|
+
const transition = {
|
|
177
|
+
transitionProperty: "color, background-color",
|
|
178
|
+
transitionDuration: reducedMotion ? "0ms" : `${HOVER_TRANSITION_MS}ms`
|
|
179
|
+
};
|
|
180
|
+
return transition;
|
|
181
|
+
}
|
|
182
|
+
function ariaCurrentProps(isActive) {
|
|
183
|
+
return isActive ? { "aria-current": "page" } : {};
|
|
184
|
+
}
|
|
152
185
|
var NavExpandableItem = ({
|
|
153
186
|
item,
|
|
154
187
|
pathname,
|
|
@@ -160,6 +193,7 @@ var NavExpandableItem = ({
|
|
|
160
193
|
depth = 0
|
|
161
194
|
}) => {
|
|
162
195
|
const [expanded, setExpanded] = react.useState(false);
|
|
196
|
+
const [focused, setFocused] = react.useState(false);
|
|
163
197
|
const { theme } = uiFeedback.useUi();
|
|
164
198
|
const colors = theme.colors;
|
|
165
199
|
const primaryColor = theme.palette.primary["500"];
|
|
@@ -180,9 +214,18 @@ var NavExpandableItem = ({
|
|
|
180
214
|
accessibilityHint: navigateHint(item.label),
|
|
181
215
|
accessibilityLabel: item.label,
|
|
182
216
|
accessibilityRole: "button",
|
|
183
|
-
|
|
217
|
+
accessibilityState: { selected: isActive },
|
|
218
|
+
style: [
|
|
219
|
+
expandableStyles.childItem,
|
|
220
|
+
paddingStyle,
|
|
221
|
+
isActive ? activeItemStyle : void 0,
|
|
222
|
+
focusRingStyle(focused, primaryColor)
|
|
223
|
+
],
|
|
184
224
|
testID: item.testID ?? item.key,
|
|
225
|
+
onBlur: () => setFocused(false),
|
|
226
|
+
onFocus: () => setFocused(true),
|
|
185
227
|
onPress: () => onNavigate(item.route),
|
|
228
|
+
...ariaCurrentProps(isActive),
|
|
186
229
|
children: [
|
|
187
230
|
typeof item.renderIcon === "function" ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: expandableStyles.iconWrapper, children: item.renderIcon(colors.textSecondary, NAV_ICON_SIZE) }) : null,
|
|
188
231
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -206,8 +249,10 @@ var NavExpandableItem = ({
|
|
|
206
249
|
accessibilityLabel: item.label,
|
|
207
250
|
accessibilityRole: "button",
|
|
208
251
|
accessibilityState: { expanded },
|
|
209
|
-
style: [expandableStyles.header, headerPaddingStyle],
|
|
252
|
+
style: [expandableStyles.header, headerPaddingStyle, focusRingStyle(focused, primaryColor)],
|
|
210
253
|
testID: item.testID ?? item.key,
|
|
254
|
+
onBlur: () => setFocused(false),
|
|
255
|
+
onFocus: () => setFocused(true),
|
|
211
256
|
onPress: toggle,
|
|
212
257
|
children: [
|
|
213
258
|
typeof item.renderIcon === "function" ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: expandableStyles.iconWrapper, children: item.renderIcon(colors.text, NAV_ICON_SIZE) }) : null,
|
|
@@ -307,6 +352,32 @@ var APP_SHELL_SUFFIX = {
|
|
|
307
352
|
error: "-error",
|
|
308
353
|
forbidden: "-forbidden"
|
|
309
354
|
};
|
|
355
|
+
var FocusableTouchable = ({
|
|
356
|
+
onPress,
|
|
357
|
+
accessibilityLabel,
|
|
358
|
+
accessibilityHint,
|
|
359
|
+
accessibilityRole = "button",
|
|
360
|
+
ringColor,
|
|
361
|
+
style,
|
|
362
|
+
testID,
|
|
363
|
+
children
|
|
364
|
+
}) => {
|
|
365
|
+
const [focused, setFocused] = react.useState(false);
|
|
366
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
367
|
+
reactNative.Pressable,
|
|
368
|
+
{
|
|
369
|
+
accessibilityHint,
|
|
370
|
+
accessibilityLabel,
|
|
371
|
+
accessibilityRole,
|
|
372
|
+
style: [style, focusRingStyle(focused, ringColor)],
|
|
373
|
+
testID,
|
|
374
|
+
onBlur: () => setFocused(false),
|
|
375
|
+
onFocus: () => setFocused(true),
|
|
376
|
+
onPress,
|
|
377
|
+
children
|
|
378
|
+
}
|
|
379
|
+
);
|
|
380
|
+
};
|
|
310
381
|
var TEXT_ON_PRIMARY = "#ffffff";
|
|
311
382
|
function isAccountState(account) {
|
|
312
383
|
return "displayName" in account;
|
|
@@ -344,11 +415,11 @@ var Topbar = ({
|
|
|
344
415
|
/* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: navStyles.topbarLeft, children: left }),
|
|
345
416
|
/* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: navStyles.topbarRight, children: [
|
|
346
417
|
language ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
347
|
-
|
|
418
|
+
FocusableTouchable,
|
|
348
419
|
{
|
|
349
420
|
accessibilityHint: language.hint,
|
|
350
421
|
accessibilityLabel: language.label,
|
|
351
|
-
|
|
422
|
+
ringColor: primaryColor,
|
|
352
423
|
style: navStyles.topbarRowItem,
|
|
353
424
|
onPress: language.onPress,
|
|
354
425
|
children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [navStyles.topbarLabel, { color: colors.text }], children: language.label })
|
|
@@ -361,11 +432,11 @@ var Topbar = ({
|
|
|
361
432
|
] }) : null,
|
|
362
433
|
richAccount ? /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: navStyles.userBlock, children: [
|
|
363
434
|
richAccount.onAccount ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
364
|
-
|
|
435
|
+
FocusableTouchable,
|
|
365
436
|
{
|
|
366
437
|
accessibilityHint: richAccount.displayName,
|
|
367
438
|
accessibilityLabel: richAccount.displayName,
|
|
368
|
-
|
|
439
|
+
ringColor: primaryColor,
|
|
369
440
|
testID: NAV_TEST_IDS.accountName,
|
|
370
441
|
onPress: richAccount.onAccount,
|
|
371
442
|
children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [navStyles.userName, { color: colors.text }], children: richAccount.displayName })
|
|
@@ -389,11 +460,11 @@ var Topbar = ({
|
|
|
389
460
|
) : null
|
|
390
461
|
] }) : null,
|
|
391
462
|
legacyAccount ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
392
|
-
|
|
463
|
+
FocusableTouchable,
|
|
393
464
|
{
|
|
394
465
|
accessibilityHint: legacyAccount.hint,
|
|
395
466
|
accessibilityLabel: legacyAccount.label,
|
|
396
|
-
|
|
467
|
+
ringColor: primaryColor,
|
|
397
468
|
style: [navStyles.accountBtn, { backgroundColor: primaryColor }],
|
|
398
469
|
testID: legacyAccount.testID,
|
|
399
470
|
onPress: legacyAccount.onPress,
|
|
@@ -401,11 +472,11 @@ var Topbar = ({
|
|
|
401
472
|
}
|
|
402
473
|
) : null,
|
|
403
474
|
upgrade ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
404
|
-
|
|
475
|
+
FocusableTouchable,
|
|
405
476
|
{
|
|
406
477
|
accessibilityHint: upgrade.hint,
|
|
407
478
|
accessibilityLabel: upgrade.label,
|
|
408
|
-
|
|
479
|
+
ringColor: primaryColor,
|
|
409
480
|
style: [navStyles.accountBtn, { backgroundColor: primaryColor }],
|
|
410
481
|
testID: upgrade.testID ?? NAV_TEST_IDS.accountUpgrade,
|
|
411
482
|
onPress: upgrade.onPress,
|
|
@@ -413,12 +484,11 @@ var Topbar = ({
|
|
|
413
484
|
}
|
|
414
485
|
) : null,
|
|
415
486
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
416
|
-
|
|
487
|
+
FocusableTouchable,
|
|
417
488
|
{
|
|
418
|
-
accessible: true,
|
|
419
489
|
accessibilityHint: logout.hint,
|
|
420
490
|
accessibilityLabel: logout.label,
|
|
421
|
-
|
|
491
|
+
ringColor: primaryColor,
|
|
422
492
|
style: [navStyles.accountBtn, { backgroundColor: primaryColor }],
|
|
423
493
|
testID: logout.testID,
|
|
424
494
|
onPress: richAccount ? richAccount.onLogout : logout.onPress,
|
|
@@ -431,10 +501,75 @@ var Topbar = ({
|
|
|
431
501
|
);
|
|
432
502
|
};
|
|
433
503
|
var TEXT_ON_PRIMARY2 = "#ffffff";
|
|
504
|
+
function ariaCurrentProps2(isActive) {
|
|
505
|
+
return isActive ? { "aria-current": "page" } : {};
|
|
506
|
+
}
|
|
507
|
+
var NavBarLink = ({
|
|
508
|
+
item,
|
|
509
|
+
isActive,
|
|
510
|
+
collapsed,
|
|
511
|
+
iconSize,
|
|
512
|
+
reducedMotion,
|
|
513
|
+
colors,
|
|
514
|
+
navigateHint,
|
|
515
|
+
onPress
|
|
516
|
+
}) => {
|
|
517
|
+
const [hovered, setHovered] = react.useState(false);
|
|
518
|
+
const [focused, setFocused] = react.useState(false);
|
|
519
|
+
const isHovered = hovered && !isActive;
|
|
520
|
+
const textColor = isActive ? TEXT_ON_PRIMARY2 : isHovered ? colors.hoverText : colors.rest;
|
|
521
|
+
const pillStyle = isActive ? { backgroundColor: colors.activeBg } : isHovered ? { backgroundColor: colors.hoverBg } : void 0;
|
|
522
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
523
|
+
reactNative.Pressable,
|
|
524
|
+
{
|
|
525
|
+
accessibilityHint: navigateHint(item.label),
|
|
526
|
+
accessibilityLabel: item.label,
|
|
527
|
+
accessibilityRole: "link",
|
|
528
|
+
accessibilityState: { selected: isActive },
|
|
529
|
+
style: [
|
|
530
|
+
navBarStyles.link,
|
|
531
|
+
collapsed ? navBarStyles.linkStacked : void 0,
|
|
532
|
+
pillStyle,
|
|
533
|
+
hoverTransitionStyle(reducedMotion),
|
|
534
|
+
focusRingStyle(focused, colors.ring)
|
|
535
|
+
],
|
|
536
|
+
testID: item.testID ?? item.key,
|
|
537
|
+
onBlur: () => setFocused(false),
|
|
538
|
+
onFocus: () => setFocused(true),
|
|
539
|
+
onHoverIn: () => setHovered(true),
|
|
540
|
+
onHoverOut: () => setHovered(false),
|
|
541
|
+
onPress: () => onPress(item.route),
|
|
542
|
+
...ariaCurrentProps2(isActive),
|
|
543
|
+
children: [
|
|
544
|
+
typeof item.renderIcon === "function" ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: navBarStyles.linkIcon, children: item.renderIcon(textColor, iconSize) }) : null,
|
|
545
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [navBarStyles.linkText, { color: textColor }], children: item.label })
|
|
546
|
+
]
|
|
547
|
+
}
|
|
548
|
+
);
|
|
549
|
+
};
|
|
550
|
+
var REDUCED_MOTION_QUERY = "(prefers-reduced-motion: reduce)";
|
|
551
|
+
function getReducedMotionQuery() {
|
|
552
|
+
if (typeof window === "undefined" || typeof window.matchMedia !== "function") return void 0;
|
|
553
|
+
return window.matchMedia(REDUCED_MOTION_QUERY);
|
|
554
|
+
}
|
|
555
|
+
function useReducedMotion() {
|
|
556
|
+
const [reduced, setReduced] = react.useState(() => getReducedMotionQuery()?.matches ?? false);
|
|
557
|
+
react.useEffect(() => {
|
|
558
|
+
const mql = getReducedMotionQuery();
|
|
559
|
+
if (mql === void 0 || mql.addEventListener === void 0) return void 0;
|
|
560
|
+
const onChange = () => setReduced(mql.matches);
|
|
561
|
+
mql.addEventListener("change", onChange);
|
|
562
|
+
return () => {
|
|
563
|
+
mql.removeEventListener?.("change", onChange);
|
|
564
|
+
};
|
|
565
|
+
}, []);
|
|
566
|
+
return reduced;
|
|
567
|
+
}
|
|
568
|
+
var LINKS_REGION_ID = "navbar-links-region";
|
|
434
569
|
var DEFAULT_COLLAPSE_BELOW = 760;
|
|
435
570
|
var MENU_GLYPH = "\u2630";
|
|
436
|
-
function
|
|
437
|
-
return
|
|
571
|
+
function ariaControlsProps(id) {
|
|
572
|
+
return { "aria-controls": id };
|
|
438
573
|
}
|
|
439
574
|
var NavBar = ({
|
|
440
575
|
items,
|
|
@@ -454,7 +589,9 @@ var NavBar = ({
|
|
|
454
589
|
const colors = theme.colors;
|
|
455
590
|
const primaryColor = theme.palette.primary["500"];
|
|
456
591
|
const { width } = reactNative.useWindowDimensions();
|
|
592
|
+
const reducedMotion = useReducedMotion();
|
|
457
593
|
const [open, setOpen] = react.useState(false);
|
|
594
|
+
const [toggleFocused, setToggleFocused] = react.useState(false);
|
|
458
595
|
const collapsed = width < collapseBelow;
|
|
459
596
|
const showLinks = !collapsed || open;
|
|
460
597
|
const toggleMenu = react.useCallback(() => setOpen((v) => !v), []);
|
|
@@ -465,33 +602,16 @@ var NavBar = ({
|
|
|
465
602
|
},
|
|
466
603
|
[onNavigate]
|
|
467
604
|
);
|
|
468
|
-
const
|
|
469
|
-
() => ({
|
|
470
|
-
|
|
605
|
+
const linkColors = react.useMemo(
|
|
606
|
+
() => ({
|
|
607
|
+
rest: colors.textSecondary,
|
|
608
|
+
hoverText: colors.text,
|
|
609
|
+
hoverBg: colors.surfaceElevated,
|
|
610
|
+
activeBg: primaryColor,
|
|
611
|
+
ring: primaryColor
|
|
612
|
+
}),
|
|
613
|
+
[colors.textSecondary, colors.text, colors.surfaceElevated, primaryColor]
|
|
471
614
|
);
|
|
472
|
-
const renderLink = (item) => {
|
|
473
|
-
const isActive = isRouteActive(pathname, item.route);
|
|
474
|
-
const textColor = isActive ? TEXT_ON_PRIMARY2 : colors.textSecondary;
|
|
475
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
476
|
-
reactNative.TouchableOpacity,
|
|
477
|
-
{
|
|
478
|
-
accessible: true,
|
|
479
|
-
accessibilityHint: navigateHint(item.label),
|
|
480
|
-
accessibilityLabel: item.label,
|
|
481
|
-
accessibilityRole: "link",
|
|
482
|
-
accessibilityState: { selected: isActive },
|
|
483
|
-
style: [navBarStyles.link, collapsed ? navBarStyles.linkStacked : void 0, isActive ? activeLinkStyle : void 0],
|
|
484
|
-
testID: item.testID ?? item.key,
|
|
485
|
-
onPress: () => handlePress(item.route),
|
|
486
|
-
...activeAriaProps(isActive),
|
|
487
|
-
children: [
|
|
488
|
-
typeof item.renderIcon === "function" ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: navBarStyles.linkIcon, children: item.renderIcon(textColor, NAV_ICON_SIZE) }) : null,
|
|
489
|
-
/* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [navBarStyles.linkText, { color: textColor }], children: item.label })
|
|
490
|
-
]
|
|
491
|
-
},
|
|
492
|
-
item.key
|
|
493
|
-
);
|
|
494
|
-
};
|
|
495
615
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
496
616
|
reactNative.View,
|
|
497
617
|
{
|
|
@@ -506,21 +626,44 @@ var NavBar = ({
|
|
|
506
626
|
children: /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: navBarStyles.inner, children: [
|
|
507
627
|
brand !== void 0 ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: navBarStyles.brand, children: brand }) : null,
|
|
508
628
|
collapsed ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
509
|
-
reactNative.
|
|
629
|
+
reactNative.Pressable,
|
|
510
630
|
{
|
|
511
|
-
accessible: true,
|
|
512
631
|
accessibilityHint: menuHint,
|
|
513
632
|
accessibilityLabel: menuLabel,
|
|
514
633
|
accessibilityRole: "button",
|
|
515
634
|
accessibilityState: { expanded: open },
|
|
516
635
|
"aria-expanded": open,
|
|
517
|
-
style: navBarStyles.toggle,
|
|
636
|
+
style: [navBarStyles.toggle, focusRingStyle(toggleFocused, primaryColor)],
|
|
518
637
|
testID: NAV_TEST_IDS.navBarToggle,
|
|
638
|
+
onBlur: () => setToggleFocused(false),
|
|
639
|
+
onFocus: () => setToggleFocused(true),
|
|
519
640
|
onPress: toggleMenu,
|
|
641
|
+
...ariaControlsProps(LINKS_REGION_ID),
|
|
520
642
|
children: typeof renderMenuIcon === "function" ? renderMenuIcon(colors.text, open) : /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [navBarStyles.toggleGlyph, { color: colors.text }], children: MENU_GLYPH })
|
|
521
643
|
}
|
|
522
644
|
) : null,
|
|
523
|
-
showLinks ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
645
|
+
showLinks ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
646
|
+
reactNative.View,
|
|
647
|
+
{
|
|
648
|
+
nativeID: LINKS_REGION_ID,
|
|
649
|
+
style: collapsed ? navBarStyles.linksStacked : navBarStyles.links,
|
|
650
|
+
testID: NAV_TEST_IDS.navBarLinks,
|
|
651
|
+
children: items.map((item) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
652
|
+
NavBarLink,
|
|
653
|
+
{
|
|
654
|
+
collapsed,
|
|
655
|
+
colors: linkColors,
|
|
656
|
+
iconSize: NAV_ICON_SIZE,
|
|
657
|
+
isActive: isRouteActive(pathname, item.route),
|
|
658
|
+
item,
|
|
659
|
+
navigateHint,
|
|
660
|
+
reducedMotion,
|
|
661
|
+
onPress: handlePress
|
|
662
|
+
},
|
|
663
|
+
item.key
|
|
664
|
+
))
|
|
665
|
+
}
|
|
666
|
+
) : null,
|
|
524
667
|
showLinks && right !== void 0 ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: collapsed ? navBarStyles.rightStacked : navBarStyles.right, children: right }) : null
|
|
525
668
|
] })
|
|
526
669
|
}
|