@dloizides/ui-nav 1.18.0 → 1.20.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 +41 -0
- package/dist/index.d.mts +10 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +45 -9
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +46 -10
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,46 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.20.0
|
|
4
|
+
|
|
5
|
+
**The auth-pending and page-loading spinners now OVERLAY stably-mounted children instead of
|
|
6
|
+
replacing them — closing a latent deep-link-collapse trap in the shared shell.**
|
|
7
|
+
|
|
8
|
+
`AppShell`'s `gate.pending` branch and `appShellContent`'s `state.loading` branch each `return`ed a
|
|
9
|
+
full-screen spinner *instead of* `children`. Replacing the subtree unmounts whatever `children` host
|
|
10
|
+
— in a portal that is a navigator/`<Slot/>` — and on remount React Navigation re-derives its default
|
|
11
|
+
route, so a deep-linked sub-route silently collapses to its default on every reload. (This is the
|
|
12
|
+
exact class of bug that hit kefi-web's organizer portal, fixed there in its `OnboardingGate`; the
|
|
13
|
+
same shape lived dormant here because no consumer passes `gate=`/`state=` yet.)
|
|
14
|
+
|
|
15
|
+
- **`AppShell` (`gate.pending`)** now renders the shell tree normally and paints the pending spinner
|
|
16
|
+
as an opaque `position:absolute` overlay on top, so `children` mount once and are never torn down
|
|
17
|
+
across `pending → authenticated` (proven by a "same child instance, no remount" test).
|
|
18
|
+
- **`appShellContent` (`state.loading`)** likewise overlays the loading spinner over mounted
|
|
19
|
+
`children` rather than swapping them out. The `loadingHost` wrapper is gated on `state` being
|
|
20
|
+
DEFINED, not on `loading`'s live value — so the element type at the mount point is invariant across
|
|
21
|
+
a `loading` true↔false toggle and `children` are reconciled in place, never remounted on the edge
|
|
22
|
+
(also proven by a "same child instance, no remount" test). Consumers that pass no `state` render
|
|
23
|
+
`children` bare, byte-identical to 1.19.0. Error / forbidden cards are TERMINAL and still replace;
|
|
24
|
+
unauthenticated still returns null + `onRedirect`.
|
|
25
|
+
- **Rule encoded in the docblock:** a *transitional* status overlays; only a *terminal* state
|
|
26
|
+
replaces. Corollary for consumers — `children` mount beneath the pending overlay, so gate data
|
|
27
|
+
FETCHES on auth state (`enabled: isAuthenticated`), not on mount. No public API changed; behavior
|
|
28
|
+
is unchanged for any consumer not passing `gate`/`state`.
|
|
29
|
+
|
|
30
|
+
## 1.19.0
|
|
31
|
+
|
|
32
|
+
**The "More ▾" overflow menu now opens at a readable width instead of cramping onto the trigger's tiny footprint.**
|
|
33
|
+
|
|
34
|
+
`NavOverflowMenu` rendered the shared `@dloizides/ui-layout` `ModalDropdown` without a `menuMinWidth`,
|
|
35
|
+
so the popover matched the width of the compact "More ▾" pill (~its own label). Option labels
|
|
36
|
+
("Settings", "API docs") wrapped onto two lines, and the resulting tall rows overflowed the menu's
|
|
37
|
+
max-height — so the lower items scrolled out of view and read as missing.
|
|
38
|
+
|
|
39
|
+
- **`NavOverflowMenu`** now passes a 200px `menuMinWidth` floor, so the overflow menu opens wide
|
|
40
|
+
enough to show each label on one line. `ModalDropdown`/`InlineMenu` clamp a floored menu inside the
|
|
41
|
+
viewport (ui-layout ≥ 1.24.0, now the peer floor), so the extra width never pushes it off the right
|
|
42
|
+
edge. No public API changed.
|
|
43
|
+
|
|
3
44
|
## 1.18.0
|
|
4
45
|
|
|
5
46
|
Root-cause fix for sidebar leaf co-activation. Behavioral/additive — no breaking API.
|
package/dist/index.d.mts
CHANGED
|
@@ -595,6 +595,16 @@ declare function useContentMaxWidth(width: AppShellWidth): number | 'full';
|
|
|
595
595
|
* (pending spinner / redirect-when-unauthenticated) and page state cards
|
|
596
596
|
* (loading / error / forbidden).
|
|
597
597
|
*
|
|
598
|
+
* OVERLAY DISCIPLINE (do not regress): a TRANSITIONAL status — `gate.pending` and
|
|
599
|
+
* `state.loading` — renders as an absolute overlay OVER stably-mounted `children`,
|
|
600
|
+
* never as a replacement. Replacing the subtree unmounts whatever `children` host
|
|
601
|
+
* (typically a navigator/`<Slot/>`); on remount React Navigation re-derives its
|
|
602
|
+
* default route, so a deep-linked sub-route silently collapses to its default on
|
|
603
|
+
* every reload. Only TERMINAL states replace: unauthenticated returns null (we are
|
|
604
|
+
* navigating away) and error/forbidden cards win inside the content column.
|
|
605
|
+
* Corollary for consumers: `children` mount BENEATH the pending overlay, so gate
|
|
606
|
+
* your data FETCHES on auth state (`enabled: isAuthenticated`), not on mount.
|
|
607
|
+
*
|
|
598
608
|
* A BACK-OFFICE variant is supported via the optional `sidebar` slot (added for
|
|
599
609
|
* Agora's merchant admin): supply a `<Sidebar>` and the body below the header /
|
|
600
610
|
* nav / banner becomes a ROW — a persistent left rail beside the scrolling
|
package/dist/index.d.ts
CHANGED
|
@@ -595,6 +595,16 @@ declare function useContentMaxWidth(width: AppShellWidth): number | 'full';
|
|
|
595
595
|
* (pending spinner / redirect-when-unauthenticated) and page state cards
|
|
596
596
|
* (loading / error / forbidden).
|
|
597
597
|
*
|
|
598
|
+
* OVERLAY DISCIPLINE (do not regress): a TRANSITIONAL status — `gate.pending` and
|
|
599
|
+
* `state.loading` — renders as an absolute overlay OVER stably-mounted `children`,
|
|
600
|
+
* never as a replacement. Replacing the subtree unmounts whatever `children` host
|
|
601
|
+
* (typically a navigator/`<Slot/>`); on remount React Navigation re-derives its
|
|
602
|
+
* default route, so a deep-linked sub-route silently collapses to its default on
|
|
603
|
+
* every reload. Only TERMINAL states replace: unauthenticated returns null (we are
|
|
604
|
+
* navigating away) and error/forbidden cards win inside the content column.
|
|
605
|
+
* Corollary for consumers: `children` mount BENEATH the pending overlay, so gate
|
|
606
|
+
* your data FETCHES on auth state (`enabled: isAuthenticated`), not on mount.
|
|
607
|
+
*
|
|
598
608
|
* A BACK-OFFICE variant is supported via the optional `sidebar` slot (added for
|
|
599
609
|
* Agora's merchant admin): supply a `<Sidebar>` and the body below the header /
|
|
600
610
|
* nav / banner becomes a ROW — a persistent left rail beside the scrolling
|
package/dist/index.js
CHANGED
|
@@ -1074,6 +1074,7 @@ var NavBarLink = ({
|
|
|
1074
1074
|
};
|
|
1075
1075
|
var TEXT_ON_PRIMARY3 = "#ffffff";
|
|
1076
1076
|
var NO_ACTIVE_ROUTE = "";
|
|
1077
|
+
var OVERFLOW_MENU_MIN_WIDTH = 200;
|
|
1077
1078
|
var NavOverflowMenu = ({
|
|
1078
1079
|
items,
|
|
1079
1080
|
pathname,
|
|
@@ -1101,6 +1102,7 @@ var NavOverflowMenu = ({
|
|
|
1101
1102
|
{
|
|
1102
1103
|
accessibilityHint: hint,
|
|
1103
1104
|
accessibilityLabel: label,
|
|
1105
|
+
menuMinWidth: OVERFLOW_MENU_MIN_WIDTH,
|
|
1104
1106
|
optionTestID,
|
|
1105
1107
|
options,
|
|
1106
1108
|
testID,
|
|
@@ -1342,7 +1344,22 @@ var CARD_TITLE_FONT_SIZE = 16;
|
|
|
1342
1344
|
var CARD_MESSAGE_FONT_SIZE = 14;
|
|
1343
1345
|
var CARD_TITLE_MARGIN_BOTTOM = 8;
|
|
1344
1346
|
var styles3 = reactNative.StyleSheet.create({
|
|
1345
|
-
|
|
1347
|
+
// Loading is a TRANSITIONAL state: the spinner is an absolute overlay ON TOP of
|
|
1348
|
+
// stably-mounted children, never a replacement for them. Replacing children here
|
|
1349
|
+
// would unmount whatever they host (e.g. a navigator), and on remount React
|
|
1350
|
+
// Navigation re-derives its default route — silently discarding a deep link.
|
|
1351
|
+
// The host wraps children whenever a `state` is passed (not only while loading),
|
|
1352
|
+
// so its element type is invariant across the loading toggle — see `useContentBody`.
|
|
1353
|
+
loadingHost: { flex: 1 },
|
|
1354
|
+
loadingOverlay: {
|
|
1355
|
+
alignItems: "center",
|
|
1356
|
+
bottom: 0,
|
|
1357
|
+
justifyContent: "center",
|
|
1358
|
+
left: 0,
|
|
1359
|
+
position: "absolute",
|
|
1360
|
+
right: 0,
|
|
1361
|
+
top: 0
|
|
1362
|
+
},
|
|
1346
1363
|
card: {
|
|
1347
1364
|
padding: CARD_PADDING,
|
|
1348
1365
|
borderRadius: CARD_BORDER_RADIUS,
|
|
@@ -1367,13 +1384,22 @@ function useContentBody(state, children, testID) {
|
|
|
1367
1384
|
const { theme } = uiFeedback.useUi();
|
|
1368
1385
|
const primary = theme.palette.primary["500"];
|
|
1369
1386
|
const errorColor = theme.semantic.error["500"];
|
|
1370
|
-
if (state
|
|
1387
|
+
if (!state) return children;
|
|
1388
|
+
if (state.forbidden)
|
|
1371
1389
|
return /* @__PURE__ */ jsxRuntime.jsx(MessageCard, { accentColor: errorColor, message: state.forbidden, testID: `${testID}${APP_SHELL_SUFFIX.forbidden}` });
|
|
1372
|
-
if (state
|
|
1390
|
+
if (state.error)
|
|
1373
1391
|
return /* @__PURE__ */ jsxRuntime.jsx(MessageCard, { accentColor: errorColor, message: state.error, testID: `${testID}${APP_SHELL_SUFFIX.error}` });
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1392
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles3.loadingHost, children: [
|
|
1393
|
+
children,
|
|
1394
|
+
state.loading === true ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
1395
|
+
reactNative.View,
|
|
1396
|
+
{
|
|
1397
|
+
style: [styles3.loadingOverlay, { backgroundColor: theme.colors.background }],
|
|
1398
|
+
testID: `${testID}${APP_SHELL_SUFFIX.loading}`,
|
|
1399
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.ActivityIndicator, { color: primary, size: "large" })
|
|
1400
|
+
}
|
|
1401
|
+
) : null
|
|
1402
|
+
] });
|
|
1377
1403
|
}
|
|
1378
1404
|
var SCRIM_COLOR = "rgba(0, 0, 0, 0.5)";
|
|
1379
1405
|
var MENU_GLYPH2 = "\u2630";
|
|
@@ -1511,7 +1537,10 @@ function useContentMaxWidth(width) {
|
|
|
1511
1537
|
var DEFAULT_CONTENT_PADDING = 24;
|
|
1512
1538
|
var styles5 = reactNative.StyleSheet.create({
|
|
1513
1539
|
root: { flex: 1 },
|
|
1514
|
-
|
|
1540
|
+
// Auth-pending spinner: an opaque absolute overlay over the mounted shell, so
|
|
1541
|
+
// `children` (and any navigator they host) are never unmounted while the gate
|
|
1542
|
+
// resolves. See the `gatePending` note in the render body.
|
|
1543
|
+
pendingOverlay: { alignItems: "center", bottom: 0, justifyContent: "center", left: 0, position: "absolute", right: 0, top: 0 },
|
|
1515
1544
|
scroll: { flex: 1 },
|
|
1516
1545
|
scrollContent: { flexGrow: 1 },
|
|
1517
1546
|
// Mobile back-office header: the hamburger sits inline before the header slot.
|
|
@@ -1565,8 +1594,7 @@ var AppShell = ({
|
|
|
1565
1594
|
React.useEffect(() => {
|
|
1566
1595
|
if (isUnauthenticated && gate !== void 0) gate.onRedirect();
|
|
1567
1596
|
}, [isUnauthenticated, gate]);
|
|
1568
|
-
|
|
1569
|
-
return /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [styles5.root, styles5.centerFill, { backgroundColor: theme.colors.background }], testID: `${testID}${APP_SHELL_SUFFIX.pending}`, children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.ActivityIndicator, { color: primary, size: "large" }) });
|
|
1597
|
+
const gatePending = gate?.pending === true;
|
|
1570
1598
|
if (isUnauthenticated) return null;
|
|
1571
1599
|
const columnStyle = maxWidth === "full" ? styles5.columnFull : [styles5.columnCapped, { maxWidth }];
|
|
1572
1600
|
const navInnerStyle = chromeAlignment === "content" && maxWidth !== "full" ? [styles5.chromeCapped, { maxWidth, paddingHorizontal: contentPadding }] : void 0;
|
|
@@ -1609,6 +1637,14 @@ var AppShell = ({
|
|
|
1609
1637
|
sidebar,
|
|
1610
1638
|
onClose: closeDrawer
|
|
1611
1639
|
}
|
|
1640
|
+
) : null,
|
|
1641
|
+
gatePending ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
1642
|
+
reactNative.View,
|
|
1643
|
+
{
|
|
1644
|
+
style: [styles5.pendingOverlay, { backgroundColor: theme.colors.background }],
|
|
1645
|
+
testID: `${testID}${APP_SHELL_SUFFIX.pending}`,
|
|
1646
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.ActivityIndicator, { color: primary, size: "large" })
|
|
1647
|
+
}
|
|
1612
1648
|
) : null
|
|
1613
1649
|
] });
|
|
1614
1650
|
};
|