@dloizides/ui-nav 1.9.0 → 1.10.1
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 +57 -0
- package/dist/index.d.mts +22 -3
- package/dist/index.d.ts +22 -3
- package/dist/index.js +71 -58
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +34 -21
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,62 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.10.1
|
|
4
|
+
|
|
5
|
+
- **Fix (a plain-string slot logged `console.error` on EVERY render, in all seven portals).**
|
|
6
|
+
Every caller-supplied slot in the kit is typed `React.ReactNode`, which legitimately includes
|
|
7
|
+
`string` — and callers pass exactly that (`brand: FM('app.name')`, a plain localized string).
|
|
8
|
+
The slot was dropped straight into a `<View>`, and react-native-web refuses to render a bare
|
|
9
|
+
text node there: `Unexpected text node: <s>. A text node cannot be a child of a <View>.`
|
|
10
|
+
Nothing in the type system objected, so the defect only ever surfaced as browser console
|
|
11
|
+
noise — which is why it survived to 1.10.0 across the whole fleet.
|
|
12
|
+
- Fixed once, in the kit, via the new internal `renderTextSlot` helper: a `string`/`number`
|
|
13
|
+
slot is wrapped in `<Text>` (routed through the theme's `colors.text`, so it honours dark
|
|
14
|
+
mode rather than defaulting to black); an element slot passes through **untouched**, so
|
|
15
|
+
every existing caller renders byte-identically. Array slots have each string member wrapped
|
|
16
|
+
individually, keys preserved.
|
|
17
|
+
- Applied to **all 12 slot render sites**, not just the reported one — `NavBar` (`brand`,
|
|
18
|
+
`right`), `Topbar` (`left`, `notificationSlot`), `Sidebar` (`header`, `footer`),
|
|
19
|
+
`CollapsedRail` (`header`, `footer`), `AppShell` (`header`, `nav`, `sidebar`,
|
|
20
|
+
`collapsedSidebar`, `banner`) and `MobileDrawer` (`sidebar`).
|
|
21
|
+
- Guarded by `src/rawTextSlots.test.tsx`, which spies on `console.error` and asserts silence.
|
|
22
|
+
Unit tests that only assert "the slot rendered" pass against the bug; this one does not.
|
|
23
|
+
- **No API change.** Consumers need no edit — the `<Text>` wrapper they were expected to
|
|
24
|
+
remember is now the kit's job.
|
|
25
|
+
|
|
26
|
+
## 1.10.0
|
|
27
|
+
|
|
28
|
+
- **Fix (`layout` was not authoritative — `layout="side"` rendered a top bar anyway).**
|
|
29
|
+
`wantsTopBar` carried an extra `(layout === 'side' && hasTopBar)` clause, so passing a
|
|
30
|
+
`topBar` **config** with `layout="side"` mounted the bar regardless — making `'side'` and
|
|
31
|
+
`'both'` render **pixel-identically** and `layout` unobservable. `layout` now decides which
|
|
32
|
+
REGIONS exist; `topBar` is only the CONTENT of the top region. A `'side'` app that also wants
|
|
33
|
+
a bar asks for `layout="both"`, which is what that combination always meant.
|
|
34
|
+
- Found by `apps/ui-showcase`, which renders every orientation side by side — a product app
|
|
35
|
+
picks ONE orientation and never sees the other, which is why this survived to 1.9.0.
|
|
36
|
+
- **Not breaking in this fleet:** no app passed `layout="side"` + `topBar`. The four `'side'`
|
|
37
|
+
portals (erevna, katalogos, zygos, agora) pass a `header` NODE, not a `topBar` config.
|
|
38
|
+
- The `hasHeader` clause is **deliberately kept**: `header` is the documented escape hatch by
|
|
39
|
+
which a `'side'` app supplies its own ready-made header (the structured `Topbar` with
|
|
40
|
+
language / notification / user slots). It is an explicit "render THIS node", not a config
|
|
41
|
+
whose presence is silently read as an orientation change.
|
|
42
|
+
- **a11y: `AccountState.accountHint`** — the account-name button's `accessibilityHint` was
|
|
43
|
+
hardcoded to `displayName`, byte-identical to its `accessibilityLabel`, so a screen reader
|
|
44
|
+
announced the name twice and never said what pressing it does — and the consumer had no way
|
|
45
|
+
to correct it. The sibling `upgrade?: TopbarAction` on the same interface has always carried
|
|
46
|
+
a `hint`; this closes that inconsistency. Falls back to `displayName`, so existing callers
|
|
47
|
+
are unaffected.
|
|
48
|
+
- **a11y: `AccountState.accountTestID`** — the account-name button's testID was hardcoded, with
|
|
49
|
+
no override, unlike `TopbarAction.testID?` / `NavItem.testID?` elsewhere in the package.
|
|
50
|
+
- **a11y: `Topbar.language.testID`** — the inline `language` prop type declared no `testID`, so
|
|
51
|
+
the repo's testID+label+hint standard was **structurally unsatisfiable** for that one button
|
|
52
|
+
while every other Topbar action (all `TopbarAction`) accepted one.
|
|
53
|
+
|
|
54
|
+
> Note for consumers: this react-native-web build does **not** forward `accessibilityHint` to
|
|
55
|
+
> the DOM at all — rendered buttons carry `aria-label` but no hint attribute. The hint is a
|
|
56
|
+
> NATIVE-only guarantee today. That is pre-existing and unchanged here, but it means DOM-level
|
|
57
|
+
> tests cannot distinguish a threaded hint from a dropped one; `topbarA11y.test.tsx` therefore
|
|
58
|
+
> pins the prop threading directly.
|
|
59
|
+
|
|
3
60
|
## 1.9.0
|
|
4
61
|
|
|
5
62
|
**`NavBar.contentMaxWidth` — a full-bleed bar with a centred content column.**
|
package/dist/index.d.mts
CHANGED
|
@@ -120,16 +120,34 @@ interface AccountState {
|
|
|
120
120
|
plan?: AccountPlan;
|
|
121
121
|
onLogout: () => void;
|
|
122
122
|
onAccount?: () => void;
|
|
123
|
+
/**
|
|
124
|
+
* a11y hint for the account-name button (what pressing it DOES).
|
|
125
|
+
*
|
|
126
|
+
* Without this the hint fell back to `displayName` — byte-identical to the
|
|
127
|
+
* accessibility LABEL, so a screen reader announced the name twice and never
|
|
128
|
+
* said where the button goes. The sibling `upgrade?: TopbarAction` on this very
|
|
129
|
+
* interface has always carried a `hint`; this closes that inconsistency. The
|
|
130
|
+
* `displayName` fallback is retained so existing callers are unaffected.
|
|
131
|
+
*/
|
|
132
|
+
accountHint?: string;
|
|
133
|
+
/** testID for the account-name button. Defaults to `NAV_TEST_IDS.accountName`. */
|
|
134
|
+
accountTestID?: string;
|
|
123
135
|
upgrade?: TopbarAction;
|
|
124
136
|
}
|
|
125
137
|
interface TopbarProps {
|
|
126
138
|
/** Left slot — typically the tenant logo. */
|
|
127
139
|
left?: React.ReactNode;
|
|
128
|
-
/**
|
|
140
|
+
/**
|
|
141
|
+
* Optional language toggle. `testID` is optional but the repo standard wants
|
|
142
|
+
* one — before 1.10.0 this inline type declared none, so a testID was
|
|
143
|
+
* structurally unreachable for this button while every other Topbar action
|
|
144
|
+
* (which use `TopbarAction`) accepted one.
|
|
145
|
+
*/
|
|
129
146
|
language?: {
|
|
130
147
|
label: string;
|
|
131
148
|
hint: string;
|
|
132
149
|
onPress: () => void;
|
|
150
|
+
testID?: string;
|
|
133
151
|
};
|
|
134
152
|
/** Optional notification slot (e.g. a notification bell). */
|
|
135
153
|
notificationSlot?: React.ReactNode;
|
|
@@ -591,8 +609,9 @@ interface NavShellProps extends ForwardedShellProps {
|
|
|
591
609
|
navigateHint?: (label: string) => string;
|
|
592
610
|
/**
|
|
593
611
|
* Top-bar config → a `NavBar` in the header slot. Required for `'top'` /
|
|
594
|
-
* `'both'`; ignored for `'side'
|
|
595
|
-
* a `
|
|
612
|
+
* `'both'`; **ignored for `'side'`** — `layout` decides which regions exist, so
|
|
613
|
+
* a `'side'` app that also wants a bar asks for `layout="both"`. A `'side'` app
|
|
614
|
+
* that wants its OWN header node uses the `header` escape hatch instead.
|
|
596
615
|
*/
|
|
597
616
|
topBar?: NavShellTopBar;
|
|
598
617
|
/**
|
package/dist/index.d.ts
CHANGED
|
@@ -120,16 +120,34 @@ interface AccountState {
|
|
|
120
120
|
plan?: AccountPlan;
|
|
121
121
|
onLogout: () => void;
|
|
122
122
|
onAccount?: () => void;
|
|
123
|
+
/**
|
|
124
|
+
* a11y hint for the account-name button (what pressing it DOES).
|
|
125
|
+
*
|
|
126
|
+
* Without this the hint fell back to `displayName` — byte-identical to the
|
|
127
|
+
* accessibility LABEL, so a screen reader announced the name twice and never
|
|
128
|
+
* said where the button goes. The sibling `upgrade?: TopbarAction` on this very
|
|
129
|
+
* interface has always carried a `hint`; this closes that inconsistency. The
|
|
130
|
+
* `displayName` fallback is retained so existing callers are unaffected.
|
|
131
|
+
*/
|
|
132
|
+
accountHint?: string;
|
|
133
|
+
/** testID for the account-name button. Defaults to `NAV_TEST_IDS.accountName`. */
|
|
134
|
+
accountTestID?: string;
|
|
123
135
|
upgrade?: TopbarAction;
|
|
124
136
|
}
|
|
125
137
|
interface TopbarProps {
|
|
126
138
|
/** Left slot — typically the tenant logo. */
|
|
127
139
|
left?: React.ReactNode;
|
|
128
|
-
/**
|
|
140
|
+
/**
|
|
141
|
+
* Optional language toggle. `testID` is optional but the repo standard wants
|
|
142
|
+
* one — before 1.10.0 this inline type declared none, so a testID was
|
|
143
|
+
* structurally unreachable for this button while every other Topbar action
|
|
144
|
+
* (which use `TopbarAction`) accepted one.
|
|
145
|
+
*/
|
|
129
146
|
language?: {
|
|
130
147
|
label: string;
|
|
131
148
|
hint: string;
|
|
132
149
|
onPress: () => void;
|
|
150
|
+
testID?: string;
|
|
133
151
|
};
|
|
134
152
|
/** Optional notification slot (e.g. a notification bell). */
|
|
135
153
|
notificationSlot?: React.ReactNode;
|
|
@@ -591,8 +609,9 @@ interface NavShellProps extends ForwardedShellProps {
|
|
|
591
609
|
navigateHint?: (label: string) => string;
|
|
592
610
|
/**
|
|
593
611
|
* Top-bar config → a `NavBar` in the header slot. Required for `'top'` /
|
|
594
|
-
* `'both'`; ignored for `'side'
|
|
595
|
-
* a `
|
|
612
|
+
* `'both'`; **ignored for `'side'`** — `layout` decides which regions exist, so
|
|
613
|
+
* a `'side'` app that also wants a bar asks for `layout="both"`. A `'side'` app
|
|
614
|
+
* that wants its OWN header node uses the `header` escape hatch instead.
|
|
596
615
|
*/
|
|
597
616
|
topBar?: NavShellTopBar;
|
|
598
617
|
/**
|
package/dist/index.js
CHANGED
|
@@ -2,14 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
var reactNative = require('react-native');
|
|
4
4
|
var uiFeedback = require('@dloizides/ui-feedback');
|
|
5
|
-
var
|
|
5
|
+
var React2 = require('react');
|
|
6
6
|
var jsxRuntime = require('react/jsx-runtime');
|
|
7
7
|
var uiLayout = require('@dloizides/ui-layout');
|
|
8
8
|
var authWeb = require('@dloizides/auth-web');
|
|
9
9
|
|
|
10
10
|
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
11
11
|
|
|
12
|
-
var
|
|
12
|
+
var React2__default = /*#__PURE__*/_interopDefault(React2);
|
|
13
13
|
|
|
14
14
|
// src/Sidebar.tsx
|
|
15
15
|
|
|
@@ -299,21 +299,21 @@ var NavExpandableItem = ({
|
|
|
299
299
|
renderChevron,
|
|
300
300
|
depth = 0
|
|
301
301
|
}) => {
|
|
302
|
-
const [expanded, setExpanded] =
|
|
303
|
-
const [focused, setFocused] =
|
|
302
|
+
const [expanded, setExpanded] = React2.useState(false);
|
|
303
|
+
const [focused, setFocused] = React2.useState(false);
|
|
304
304
|
const { theme } = uiFeedback.useUi();
|
|
305
305
|
const colors = theme.colors;
|
|
306
306
|
const primaryColor = theme.palette.primary["500"];
|
|
307
|
-
const toggle =
|
|
307
|
+
const toggle = React2.useCallback(() => setExpanded((v) => !v), []);
|
|
308
308
|
const indent = depth * BASE_INDENT;
|
|
309
309
|
const hasChildren = Array.isArray(item.children) && item.children.length > 0;
|
|
310
310
|
const isActive = isRouteActive(pathname, item.route);
|
|
311
|
-
const activeItemStyle =
|
|
311
|
+
const activeItemStyle = React2.useMemo(
|
|
312
312
|
() => ({ backgroundColor: colors.border, borderRadius: ACTIVE_BORDER_RADIUS }),
|
|
313
313
|
[colors.border]
|
|
314
314
|
);
|
|
315
|
-
const paddingStyle =
|
|
316
|
-
const headerPaddingStyle =
|
|
315
|
+
const paddingStyle = React2.useMemo(() => ({ paddingLeft: indent + BASE_INDENT }), [indent]);
|
|
316
|
+
const headerPaddingStyle = React2.useMemo(() => ({ paddingLeft: indent }), [indent]);
|
|
317
317
|
if (!hasChildren)
|
|
318
318
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
319
319
|
reactNative.TouchableOpacity,
|
|
@@ -384,6 +384,17 @@ var NavExpandableItem = ({
|
|
|
384
384
|
)) }) : null
|
|
385
385
|
] });
|
|
386
386
|
};
|
|
387
|
+
function isBareText(node) {
|
|
388
|
+
return typeof node === "string" || typeof node === "number";
|
|
389
|
+
}
|
|
390
|
+
function renderTextSlot(node, style) {
|
|
391
|
+
if (node === void 0 || node === null) return node;
|
|
392
|
+
if (isBareText(node)) return /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style, children: node });
|
|
393
|
+
return React2__default.default.Children.map(
|
|
394
|
+
node,
|
|
395
|
+
(child) => isBareText(child) ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style, children: child }) : child
|
|
396
|
+
);
|
|
397
|
+
}
|
|
387
398
|
var Sidebar = ({
|
|
388
399
|
items,
|
|
389
400
|
pathname,
|
|
@@ -413,7 +424,7 @@ var Sidebar = ({
|
|
|
413
424
|
],
|
|
414
425
|
children: [
|
|
415
426
|
/* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { accessibilityRole: "header", style: [navStyles.sidebarTitle, { color: colors.text }], children: title }),
|
|
416
|
-
header,
|
|
427
|
+
renderTextSlot(header, { color: colors.text }),
|
|
417
428
|
items.map((item) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
418
429
|
NavExpandableItem,
|
|
419
430
|
{
|
|
@@ -428,7 +439,7 @@ var Sidebar = ({
|
|
|
428
439
|
item.key
|
|
429
440
|
)),
|
|
430
441
|
/* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: navStyles.sidebarSpacer }),
|
|
431
|
-
footer
|
|
442
|
+
renderTextSlot(footer, { color: colors.text })
|
|
432
443
|
]
|
|
433
444
|
}
|
|
434
445
|
);
|
|
@@ -481,7 +492,7 @@ var FocusableTouchable = ({
|
|
|
481
492
|
testID,
|
|
482
493
|
children
|
|
483
494
|
}) => {
|
|
484
|
-
const [focused, setFocused] =
|
|
495
|
+
const [focused, setFocused] = React2.useState(false);
|
|
485
496
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
486
497
|
reactNative.Pressable,
|
|
487
498
|
{
|
|
@@ -531,7 +542,7 @@ var Topbar = ({
|
|
|
531
542
|
containerStyle
|
|
532
543
|
],
|
|
533
544
|
children: [
|
|
534
|
-
/* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: navStyles.topbarLeft, children: left }),
|
|
545
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: navStyles.topbarLeft, children: renderTextSlot(left, { color: colors.text }) }),
|
|
535
546
|
/* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: navStyles.topbarRight, children: [
|
|
536
547
|
language ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
537
548
|
FocusableTouchable,
|
|
@@ -540,11 +551,12 @@ var Topbar = ({
|
|
|
540
551
|
accessibilityLabel: language.label,
|
|
541
552
|
ringColor: primaryColor,
|
|
542
553
|
style: navStyles.topbarRowItem,
|
|
554
|
+
testID: language.testID,
|
|
543
555
|
onPress: language.onPress,
|
|
544
556
|
children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [navStyles.topbarLabel, { color: colors.text }], children: language.label })
|
|
545
557
|
}
|
|
546
558
|
) : null,
|
|
547
|
-
notificationSlot,
|
|
559
|
+
renderTextSlot(notificationSlot, { color: colors.text }),
|
|
548
560
|
user ? /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: navStyles.userBlock, children: [
|
|
549
561
|
/* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [navStyles.userName, { color: colors.text }], children: user.name }),
|
|
550
562
|
/* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [navStyles.userEmail, { color: colors.textSecondary }], children: user.email })
|
|
@@ -553,10 +565,10 @@ var Topbar = ({
|
|
|
553
565
|
richAccount.onAccount ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
554
566
|
FocusableTouchable,
|
|
555
567
|
{
|
|
556
|
-
accessibilityHint: richAccount.displayName,
|
|
568
|
+
accessibilityHint: richAccount.accountHint ?? richAccount.displayName,
|
|
557
569
|
accessibilityLabel: richAccount.displayName,
|
|
558
570
|
ringColor: primaryColor,
|
|
559
|
-
testID: NAV_TEST_IDS.accountName,
|
|
571
|
+
testID: richAccount.accountTestID ?? NAV_TEST_IDS.accountName,
|
|
560
572
|
onPress: richAccount.onAccount,
|
|
561
573
|
children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [navStyles.userName, { color: colors.text }], children: richAccount.displayName })
|
|
562
574
|
}
|
|
@@ -633,8 +645,8 @@ var NavBarLink = ({
|
|
|
633
645
|
navigateHint,
|
|
634
646
|
onPress
|
|
635
647
|
}) => {
|
|
636
|
-
const [hovered, setHovered] =
|
|
637
|
-
const [focused, setFocused] =
|
|
648
|
+
const [hovered, setHovered] = React2.useState(false);
|
|
649
|
+
const [focused, setFocused] = React2.useState(false);
|
|
638
650
|
const isHovered = hovered && !isActive;
|
|
639
651
|
const textColor = isActive ? TEXT_ON_PRIMARY2 : isHovered ? colors.hoverText : colors.rest;
|
|
640
652
|
const pillStyle = isActive ? { backgroundColor: colors.activeBg } : isHovered ? { backgroundColor: colors.hoverBg } : void 0;
|
|
@@ -678,12 +690,12 @@ var NavOverflowMenu = ({
|
|
|
678
690
|
testID,
|
|
679
691
|
variant
|
|
680
692
|
}) => {
|
|
681
|
-
const options =
|
|
682
|
-
const activeRoute =
|
|
693
|
+
const options = React2.useMemo(() => items.map((item) => ({ label: item.label, value: item.route })), [items]);
|
|
694
|
+
const activeRoute = React2.useMemo(
|
|
683
695
|
() => items.find((item) => isRouteActive(pathname, item.route))?.route ?? NO_ACTIVE_ROUTE,
|
|
684
696
|
[items, pathname]
|
|
685
697
|
);
|
|
686
|
-
const optionTestID =
|
|
698
|
+
const optionTestID = React2.useMemo(() => {
|
|
687
699
|
const byRoute = new Map(items.map((item) => [item.route, item.testID ?? item.key]));
|
|
688
700
|
return (route) => byRoute.get(route) ?? `${testID}-option-${route}`;
|
|
689
701
|
}, [items, testID]);
|
|
@@ -740,13 +752,13 @@ function resizeWidthBuffer(previous, count) {
|
|
|
740
752
|
return next;
|
|
741
753
|
}
|
|
742
754
|
function useNavOverflow(itemCount, gap) {
|
|
743
|
-
const [availableWidth, setAvailableWidthState] =
|
|
744
|
-
const [moreWidth, setMoreWidthState] =
|
|
745
|
-
const [itemWidths, setItemWidths] =
|
|
746
|
-
|
|
755
|
+
const [availableWidth, setAvailableWidthState] = React2.useState(0);
|
|
756
|
+
const [moreWidth, setMoreWidthState] = React2.useState(0);
|
|
757
|
+
const [itemWidths, setItemWidths] = React2.useState(() => new Array(itemCount).fill(0));
|
|
758
|
+
React2.useEffect(() => {
|
|
747
759
|
setItemWidths((previous) => previous.length === itemCount ? previous : resizeWidthBuffer(previous, itemCount));
|
|
748
760
|
}, [itemCount]);
|
|
749
|
-
const setItemWidth =
|
|
761
|
+
const setItemWidth = React2.useCallback((index, width) => {
|
|
750
762
|
setItemWidths((previous) => {
|
|
751
763
|
if (index < 0 || index >= previous.length || previous[index] === width) return previous;
|
|
752
764
|
const next = previous.slice();
|
|
@@ -754,15 +766,15 @@ function useNavOverflow(itemCount, gap) {
|
|
|
754
766
|
return next;
|
|
755
767
|
});
|
|
756
768
|
}, []);
|
|
757
|
-
const setAvailableWidth =
|
|
769
|
+
const setAvailableWidth = React2.useCallback(
|
|
758
770
|
(width) => setAvailableWidthState((previous) => previous === width ? previous : width),
|
|
759
771
|
[]
|
|
760
772
|
);
|
|
761
|
-
const setMoreWidth =
|
|
773
|
+
const setMoreWidth = React2.useCallback(
|
|
762
774
|
(width) => setMoreWidthState((previous) => previous === width ? previous : width),
|
|
763
775
|
[]
|
|
764
776
|
);
|
|
765
|
-
const visibleCount =
|
|
777
|
+
const visibleCount = React2.useMemo(
|
|
766
778
|
() => computeVisibleCount({ availableWidth, itemWidths, moreWidth, gap }),
|
|
767
779
|
[availableWidth, itemWidths, moreWidth, gap]
|
|
768
780
|
);
|
|
@@ -774,8 +786,8 @@ function getReducedMotionQuery() {
|
|
|
774
786
|
return window.matchMedia(REDUCED_MOTION_QUERY);
|
|
775
787
|
}
|
|
776
788
|
function useReducedMotion() {
|
|
777
|
-
const [reduced, setReduced] =
|
|
778
|
-
|
|
789
|
+
const [reduced, setReduced] = React2.useState(() => getReducedMotionQuery()?.matches ?? false);
|
|
790
|
+
React2.useEffect(() => {
|
|
779
791
|
const mql = getReducedMotionQuery();
|
|
780
792
|
if (mql === void 0 || mql.addEventListener === void 0) return void 0;
|
|
781
793
|
const onChange = () => setReduced(mql.matches);
|
|
@@ -814,24 +826,24 @@ var NavBarInner = ({
|
|
|
814
826
|
const primaryColor = theme.palette.primary["500"];
|
|
815
827
|
const { width } = reactNative.useWindowDimensions();
|
|
816
828
|
const reducedMotion = useReducedMotion();
|
|
817
|
-
const [open, setOpen] =
|
|
818
|
-
const [toggleFocused, setToggleFocused] =
|
|
829
|
+
const [open, setOpen] = React2.useState(false);
|
|
830
|
+
const [toggleFocused, setToggleFocused] = React2.useState(false);
|
|
819
831
|
const { visibleCount, setAvailableWidth, setItemWidth, setMoreWidth } = useNavOverflow(items.length, NAV_LINK_GAP);
|
|
820
832
|
const collapsed = width < collapseBelow;
|
|
821
833
|
const showLinks = !collapsed || open;
|
|
822
|
-
const innerCapStyle =
|
|
834
|
+
const innerCapStyle = React2.useMemo(
|
|
823
835
|
() => contentMaxWidth === void 0 ? null : { maxWidth: contentMaxWidth, width: "100%", alignSelf: "center" },
|
|
824
836
|
[contentMaxWidth]
|
|
825
837
|
);
|
|
826
|
-
const toggleMenu =
|
|
827
|
-
const handlePress =
|
|
838
|
+
const toggleMenu = React2.useCallback(() => setOpen((v) => !v), []);
|
|
839
|
+
const handlePress = React2.useCallback(
|
|
828
840
|
(route) => {
|
|
829
841
|
setOpen(false);
|
|
830
842
|
onNavigate(route);
|
|
831
843
|
},
|
|
832
844
|
[onNavigate]
|
|
833
845
|
);
|
|
834
|
-
const linkColors =
|
|
846
|
+
const linkColors = React2.useMemo(
|
|
835
847
|
() => ({
|
|
836
848
|
rest: colors.textSecondary,
|
|
837
849
|
hoverText: colors.text,
|
|
@@ -853,7 +865,7 @@ var NavBarInner = ({
|
|
|
853
865
|
containerStyle
|
|
854
866
|
],
|
|
855
867
|
children: /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: [navBarStyles.inner, collapsed ? null : navBarStyles.innerNoWrap, innerCapStyle], children: [
|
|
856
|
-
brand !== void 0 ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: navBarStyles.brand, children: brand }) : null,
|
|
868
|
+
brand !== void 0 ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: navBarStyles.brand, children: renderTextSlot(brand, { color: colors.text }) }) : null,
|
|
857
869
|
collapsed ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
858
870
|
reactNative.Pressable,
|
|
859
871
|
{
|
|
@@ -886,7 +898,7 @@ var NavBarInner = ({
|
|
|
886
898
|
}
|
|
887
899
|
);
|
|
888
900
|
if (collapsed) {
|
|
889
|
-
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(
|
|
901
|
+
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(React2__default.default.Fragment, { children: renderLink(item) }, item.key)) });
|
|
890
902
|
}
|
|
891
903
|
const visibleItems = items.slice(0, visibleCount);
|
|
892
904
|
const overflowItems = items.slice(visibleCount);
|
|
@@ -930,7 +942,7 @@ var NavBarInner = ({
|
|
|
930
942
|
}
|
|
931
943
|
);
|
|
932
944
|
})() : null,
|
|
933
|
-
showLinks && right !== void 0 ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: collapsed ? navBarStyles.rightStacked : navBarStyles.right, children: right }) : null
|
|
945
|
+
showLinks && right !== void 0 ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: collapsed ? navBarStyles.rightStacked : navBarStyles.right, children: renderTextSlot(right, { color: colors.text }) }) : null
|
|
934
946
|
] })
|
|
935
947
|
}
|
|
936
948
|
);
|
|
@@ -1034,8 +1046,9 @@ var MobileDrawer = ({
|
|
|
1034
1046
|
drawerTestID,
|
|
1035
1047
|
scrimTestID
|
|
1036
1048
|
}) => {
|
|
1037
|
-
const
|
|
1038
|
-
|
|
1049
|
+
const { theme } = uiFeedback.useUi();
|
|
1050
|
+
const panelRef = React2.useRef(null);
|
|
1051
|
+
React2.useEffect(() => {
|
|
1039
1052
|
const node = panelRef.current;
|
|
1040
1053
|
if (node === null || typeof node.addEventListener !== "function") return void 0;
|
|
1041
1054
|
const handleClick = (event) => {
|
|
@@ -1058,7 +1071,7 @@ var MobileDrawer = ({
|
|
|
1058
1071
|
onPress: onClose
|
|
1059
1072
|
}
|
|
1060
1073
|
),
|
|
1061
|
-
/* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { ref: panelRef, "aria-modal": true, role: "dialog", style: styles2.panel, testID: drawerTestID, children: sidebar })
|
|
1074
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { ref: panelRef, "aria-modal": true, role: "dialog", style: styles2.panel, testID: drawerTestID, children: renderTextSlot(sidebar, { color: theme.colors.text }) })
|
|
1062
1075
|
] });
|
|
1063
1076
|
};
|
|
1064
1077
|
|
|
@@ -1130,15 +1143,15 @@ var AppShell = ({
|
|
|
1130
1143
|
range: railRange
|
|
1131
1144
|
});
|
|
1132
1145
|
const useDrawer = railMode === "drawer";
|
|
1133
|
-
const [drawerOpen, setDrawerOpen] =
|
|
1134
|
-
const openDrawer =
|
|
1135
|
-
const closeDrawer =
|
|
1136
|
-
|
|
1146
|
+
const [drawerOpen, setDrawerOpen] = React2.useState(false);
|
|
1147
|
+
const openDrawer = React2.useCallback(() => setDrawerOpen(true), []);
|
|
1148
|
+
const closeDrawer = React2.useCallback(() => setDrawerOpen(false), []);
|
|
1149
|
+
React2.useEffect(() => {
|
|
1137
1150
|
if (!useDrawer && drawerOpen) setDrawerOpen(false);
|
|
1138
1151
|
}, [useDrawer, drawerOpen]);
|
|
1139
1152
|
const drawerLabels = { ...DEFAULT_DRAWER_LABELS, ...mobileMenu };
|
|
1140
1153
|
const isUnauthenticated = gate !== void 0 && !gate.pending && !gate.authenticated;
|
|
1141
|
-
|
|
1154
|
+
React2.useEffect(() => {
|
|
1142
1155
|
if (isUnauthenticated && gate !== void 0) gate.onRedirect();
|
|
1143
1156
|
}, [isUnauthenticated, gate]);
|
|
1144
1157
|
if (gate?.pending === true)
|
|
@@ -1165,13 +1178,13 @@ var AppShell = ({
|
|
|
1165
1178
|
onOpen: openDrawer
|
|
1166
1179
|
}
|
|
1167
1180
|
),
|
|
1168
|
-
/* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: styles3.headerFill, children: header })
|
|
1169
|
-
] }) : /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { testID: `${testID}${APP_SHELL_SUFFIX.header}`, children: header });
|
|
1170
|
-
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;
|
|
1181
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: styles3.headerFill, children: renderTextSlot(header, { color: theme.colors.text }) })
|
|
1182
|
+
] }) : /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { testID: `${testID}${APP_SHELL_SUFFIX.header}`, children: renderTextSlot(header, { color: theme.colors.text }) });
|
|
1183
|
+
const railRegion = railMode === "full" ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { testID: `${testID}${APP_SHELL_SUFFIX.sidebar}`, children: renderTextSlot(sidebar, { color: theme.colors.text }) }) : railMode === "collapsed" ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { testID: `${testID}${APP_SHELL_SUFFIX.collapsedSidebar}`, children: renderTextSlot(collapsedSidebar, { color: theme.colors.text }) }) : null;
|
|
1171
1184
|
return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: [styles3.root, { backgroundColor: theme.colors.background }], testID, children: [
|
|
1172
1185
|
headerRegion,
|
|
1173
|
-
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,
|
|
1174
|
-
banner !== void 0 ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { testID: `${testID}${APP_SHELL_SUFFIX.banner}`, children: banner }) : null,
|
|
1186
|
+
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: renderTextSlot(nav, { color: theme.colors.text }) }) : renderTextSlot(nav, { color: theme.colors.text }) }) : null,
|
|
1187
|
+
banner !== void 0 ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { testID: `${testID}${APP_SHELL_SUFFIX.banner}`, children: renderTextSlot(banner, { color: theme.colors.text }) }) : null,
|
|
1175
1188
|
railRegion !== null ? /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles3.bodyRow, children: [
|
|
1176
1189
|
railRegion,
|
|
1177
1190
|
scroller
|
|
@@ -1217,7 +1230,7 @@ var CollapsedRail = ({
|
|
|
1217
1230
|
containerStyle
|
|
1218
1231
|
],
|
|
1219
1232
|
children: [
|
|
1220
|
-
header !== void 0 ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: collapsedRailStyles.slot, children: header }) : null,
|
|
1233
|
+
header !== void 0 ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: collapsedRailStyles.slot, children: renderTextSlot(header, { color: colors.text }) }) : null,
|
|
1221
1234
|
items.map((item) => {
|
|
1222
1235
|
const active = isRouteActive(pathname, item.route);
|
|
1223
1236
|
const iconColor = active ? primaryColor : colors.textSecondary;
|
|
@@ -1236,13 +1249,13 @@ var CollapsedRail = ({
|
|
|
1236
1249
|
);
|
|
1237
1250
|
}),
|
|
1238
1251
|
/* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: collapsedRailStyles.spacer }),
|
|
1239
|
-
footer !== void 0 ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: collapsedRailStyles.slot, children: footer }) : null
|
|
1252
|
+
footer !== void 0 ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: collapsedRailStyles.slot, children: renderTextSlot(footer, { color: colors.text }) }) : null
|
|
1240
1253
|
]
|
|
1241
1254
|
}
|
|
1242
1255
|
);
|
|
1243
1256
|
};
|
|
1244
|
-
function wantsTopBar(layout, hasHeader
|
|
1245
|
-
return layout === "top" || layout === "both" || hasHeader
|
|
1257
|
+
function wantsTopBar(layout, hasHeader) {
|
|
1258
|
+
return layout === "top" || layout === "both" || hasHeader;
|
|
1246
1259
|
}
|
|
1247
1260
|
function wantsSideRail(layout, hasSideRail) {
|
|
1248
1261
|
return (layout === "side" || layout === "both") && hasSideRail;
|
|
@@ -1261,7 +1274,7 @@ var NavShell = ({
|
|
|
1261
1274
|
collapsedRailRange: collapsedRailRangeProp,
|
|
1262
1275
|
...shellProps
|
|
1263
1276
|
}) => {
|
|
1264
|
-
const showTopBar = wantsTopBar(layout, header !== void 0
|
|
1277
|
+
const showTopBar = wantsTopBar(layout, header !== void 0);
|
|
1265
1278
|
const showSideRail = wantsSideRail(layout, sideRail !== void 0);
|
|
1266
1279
|
const headerNode = header !== void 0 ? header : showTopBar && topBar !== void 0 ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
1267
1280
|
NavBar,
|
|
@@ -1390,7 +1403,7 @@ var DarkModeControl = ({
|
|
|
1390
1403
|
const primaryColor = theme.palette.primary["500"];
|
|
1391
1404
|
const currentIndex = options.findIndex((option) => option.value === value);
|
|
1392
1405
|
const current = currentIndex >= 0 ? options[currentIndex] : options[0];
|
|
1393
|
-
const advance =
|
|
1406
|
+
const advance = React2.useCallback(() => {
|
|
1394
1407
|
if (options.length === 0) return;
|
|
1395
1408
|
const from = currentIndex >= 0 ? currentIndex : 0;
|
|
1396
1409
|
const next = options[(from + 1) % options.length];
|