@dloizides/ui-nav 1.4.0 → 1.6.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 +27 -0
- package/dist/index.d.mts +44 -1
- package/dist/index.d.ts +44 -1
- package/dist/index.js +173 -38
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +175 -40
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,32 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.5.0
|
|
4
|
+
|
|
5
|
+
Make the `AppShell` back-office `sidebar` layout **responsive** — a mobile drawer.
|
|
6
|
+
|
|
7
|
+
The `sidebar` slot (1.4.0) always rendered a persistent fixed-width left rail in a
|
|
8
|
+
`flexDirection:'row'` body with no width breakpoint, so below ~768px the content column
|
|
9
|
+
starved (headings wrapped a character per line, inputs truncated) — the merchant admin was
|
|
10
|
+
unusable on a phone. Now `AppShell` reads `useWindowDimensions`:
|
|
11
|
+
|
|
12
|
+
- **Desktop (≥768px): IDENTICAL to 1.4.0** — the persistent rail + row layout, untouched.
|
|
13
|
+
Purely additive; no consuming product's desktop layout changes.
|
|
14
|
+
- **Below 768px:** the rail is not rendered beside the content; instead a **hamburger**
|
|
15
|
+
(in the header) opens the same `Sidebar` as an overlay **drawer** (a tap-to-close scrim +
|
|
16
|
+
a left panel) and the content takes the full width. The drawer closes on a scrim tap and
|
|
17
|
+
on a nav-item press (web: a bubbled click on a `role="button"` / link inside the panel).
|
|
18
|
+
|
|
19
|
+
Accessibility: the hamburger has a testID + `accessibilityLabel` + `accessibilityHint` and a
|
|
20
|
+
themed focus ring; the drawer panel is `role="dialog"` `aria-modal`; the scrim is a labelled
|
|
21
|
+
button. New optional prop `mobileMenu?: Partial<DrawerLabels>` supplies pre-localized
|
|
22
|
+
open/close labels (English defaults otherwise — this package has no i18n). New testID
|
|
23
|
+
suffixes: `-menu-toggle`, `-drawer`, `-drawer-scrim`.
|
|
24
|
+
|
|
25
|
+
**Backwards compatible.** No breaking prop changes — consumers pass the same `sidebar` slot.
|
|
26
|
+
Omit `sidebar` and the tree is byte-identical to 1.4.0 (erevna / katalogos / kefi untouched).
|
|
27
|
+
Only a `sidebar` consumer on a narrow viewport sees the drawer. Pinned by tests asserting the
|
|
28
|
+
rail is present on desktop and absent (hamburger present) on a narrow viewport.
|
|
29
|
+
|
|
3
30
|
## 1.4.0
|
|
4
31
|
|
|
5
32
|
Add an optional **`sidebar`** slot to `AppShell` — the back-office layout.
|
package/dist/index.d.mts
CHANGED
|
@@ -195,6 +195,33 @@ interface NavBarProps {
|
|
|
195
195
|
}
|
|
196
196
|
declare const NavBar: ({ items, pathname, onNavigate, regionLabel, navigateHint, brand, right, menuLabel, menuHint, renderMenuIcon, collapseBelow, containerStyle, }: NavBarProps) => React.ReactElement;
|
|
197
197
|
|
|
198
|
+
/**
|
|
199
|
+
* MobileDrawer — the below-the-breakpoint face of `AppShell`'s back-office
|
|
200
|
+
* `sidebar` layout. On a narrow viewport (<768px) the persistent left rail would
|
|
201
|
+
* starve the content column, so instead of the rail we render a hamburger toggle
|
|
202
|
+
* (`MenuToggle`) in the header and slide the same `Sidebar` in as an overlay
|
|
203
|
+
* drawer (a scrim + a left panel). The drawer closes on a scrim tap and on any
|
|
204
|
+
* nav-item press inside the panel (web: a bubbled click on a `role="button"` /
|
|
205
|
+
* link element). Desktop is untouched — AppShell never mounts this above 768px.
|
|
206
|
+
*
|
|
207
|
+
* Contract discipline (same as the rest of `@dloizides/ui-nav`): NO FM / router /
|
|
208
|
+
* store imports. Labels are pre-localized strings (English defaults, overridable
|
|
209
|
+
* by the consumer via `AppShell`'s `mobileMenu` prop); every colour is read from
|
|
210
|
+
* the `@dloizides/ui-feedback` UiProvider theme.
|
|
211
|
+
*/
|
|
212
|
+
|
|
213
|
+
/** Pre-localized labels for the drawer's hamburger + scrim. */
|
|
214
|
+
interface DrawerLabels {
|
|
215
|
+
/** Accessible name of the hamburger that opens the drawer. */
|
|
216
|
+
openLabel: string;
|
|
217
|
+
/** Accessible hint of the hamburger that opens the drawer. */
|
|
218
|
+
openHint: string;
|
|
219
|
+
/** Accessible name of the scrim (tap to close). */
|
|
220
|
+
closeLabel: string;
|
|
221
|
+
/** Accessible hint of the scrim (tap to close). */
|
|
222
|
+
closeHint: string;
|
|
223
|
+
}
|
|
224
|
+
|
|
198
225
|
/** Content-width policy: a capped column, optionally wider past a viewport breakpoint, or full-bleed. */
|
|
199
226
|
type AppShellWidth = {
|
|
200
227
|
max: number;
|
|
@@ -245,8 +272,18 @@ interface AppShellProps {
|
|
|
245
272
|
* supplied, the region below header/nav/banner lays out as a row: the rail on the
|
|
246
273
|
* left, the scrolling content column on the right. Omit for the default vertical
|
|
247
274
|
* stack — the tree is then byte-identical to the pre-`sidebar` AppShell.
|
|
275
|
+
*
|
|
276
|
+
* Responsive: at/under ~768px the persistent rail is REPLACED by a hamburger (in
|
|
277
|
+
* the header) that opens the same `sidebar` as an overlay drawer, so the content
|
|
278
|
+
* takes the full width. Desktop (≥768px) is unchanged. No effect without `sidebar`.
|
|
248
279
|
*/
|
|
249
280
|
sidebar?: React.ReactNode;
|
|
281
|
+
/**
|
|
282
|
+
* Pre-localized labels for the mobile nav drawer (only used in the `sidebar`
|
|
283
|
+
* layout, below the breakpoint): the hamburger's open label/hint and the scrim's
|
|
284
|
+
* close label/hint. Optional — English defaults apply. NO i18n in this package.
|
|
285
|
+
*/
|
|
286
|
+
mobileMenu?: Partial<DrawerLabels>;
|
|
250
287
|
/** Banner slot (e.g. verification-pending). */
|
|
251
288
|
banner?: React.ReactNode;
|
|
252
289
|
/** Content-width policy. Defaults to full-bleed. */
|
|
@@ -275,7 +312,7 @@ interface AppShellProps {
|
|
|
275
312
|
testID: string;
|
|
276
313
|
children: React.ReactNode;
|
|
277
314
|
}
|
|
278
|
-
declare const AppShell: ({ header, nav, sidebar, banner, width, contentPadding, chromeAlignment, gate, state, testID, children, }: AppShellProps) => React.ReactElement | null;
|
|
315
|
+
declare const AppShell: ({ header, nav, sidebar, mobileMenu, banner, width, contentPadding, chromeAlignment, gate, state, testID, children, }: AppShellProps) => React.ReactElement | null;
|
|
279
316
|
|
|
280
317
|
/**
|
|
281
318
|
* Default testIDs for `@dloizides/ui-nav`. Kept as a small central map (mirrors
|
|
@@ -305,6 +342,12 @@ declare const APP_SHELL_SUFFIX: {
|
|
|
305
342
|
readonly nav: "-nav";
|
|
306
343
|
/** The persistent left rail of the back-office layout (only when `sidebar` is supplied). */
|
|
307
344
|
readonly sidebar: "-sidebar";
|
|
345
|
+
/** Mobile hamburger that opens the nav drawer (back-office layout, below the breakpoint). */
|
|
346
|
+
readonly menuToggle: "-menu-toggle";
|
|
347
|
+
/** The mobile nav drawer panel (holds the `sidebar` slot when opened on a narrow viewport). */
|
|
348
|
+
readonly drawer: "-drawer";
|
|
349
|
+
/** The mobile nav drawer scrim (tap to close). */
|
|
350
|
+
readonly scrim: "-drawer-scrim";
|
|
308
351
|
readonly banner: "-banner";
|
|
309
352
|
readonly pending: "-pending";
|
|
310
353
|
readonly loading: "-loading";
|
package/dist/index.d.ts
CHANGED
|
@@ -195,6 +195,33 @@ interface NavBarProps {
|
|
|
195
195
|
}
|
|
196
196
|
declare const NavBar: ({ items, pathname, onNavigate, regionLabel, navigateHint, brand, right, menuLabel, menuHint, renderMenuIcon, collapseBelow, containerStyle, }: NavBarProps) => React.ReactElement;
|
|
197
197
|
|
|
198
|
+
/**
|
|
199
|
+
* MobileDrawer — the below-the-breakpoint face of `AppShell`'s back-office
|
|
200
|
+
* `sidebar` layout. On a narrow viewport (<768px) the persistent left rail would
|
|
201
|
+
* starve the content column, so instead of the rail we render a hamburger toggle
|
|
202
|
+
* (`MenuToggle`) in the header and slide the same `Sidebar` in as an overlay
|
|
203
|
+
* drawer (a scrim + a left panel). The drawer closes on a scrim tap and on any
|
|
204
|
+
* nav-item press inside the panel (web: a bubbled click on a `role="button"` /
|
|
205
|
+
* link element). Desktop is untouched — AppShell never mounts this above 768px.
|
|
206
|
+
*
|
|
207
|
+
* Contract discipline (same as the rest of `@dloizides/ui-nav`): NO FM / router /
|
|
208
|
+
* store imports. Labels are pre-localized strings (English defaults, overridable
|
|
209
|
+
* by the consumer via `AppShell`'s `mobileMenu` prop); every colour is read from
|
|
210
|
+
* the `@dloizides/ui-feedback` UiProvider theme.
|
|
211
|
+
*/
|
|
212
|
+
|
|
213
|
+
/** Pre-localized labels for the drawer's hamburger + scrim. */
|
|
214
|
+
interface DrawerLabels {
|
|
215
|
+
/** Accessible name of the hamburger that opens the drawer. */
|
|
216
|
+
openLabel: string;
|
|
217
|
+
/** Accessible hint of the hamburger that opens the drawer. */
|
|
218
|
+
openHint: string;
|
|
219
|
+
/** Accessible name of the scrim (tap to close). */
|
|
220
|
+
closeLabel: string;
|
|
221
|
+
/** Accessible hint of the scrim (tap to close). */
|
|
222
|
+
closeHint: string;
|
|
223
|
+
}
|
|
224
|
+
|
|
198
225
|
/** Content-width policy: a capped column, optionally wider past a viewport breakpoint, or full-bleed. */
|
|
199
226
|
type AppShellWidth = {
|
|
200
227
|
max: number;
|
|
@@ -245,8 +272,18 @@ interface AppShellProps {
|
|
|
245
272
|
* supplied, the region below header/nav/banner lays out as a row: the rail on the
|
|
246
273
|
* left, the scrolling content column on the right. Omit for the default vertical
|
|
247
274
|
* stack — the tree is then byte-identical to the pre-`sidebar` AppShell.
|
|
275
|
+
*
|
|
276
|
+
* Responsive: at/under ~768px the persistent rail is REPLACED by a hamburger (in
|
|
277
|
+
* the header) that opens the same `sidebar` as an overlay drawer, so the content
|
|
278
|
+
* takes the full width. Desktop (≥768px) is unchanged. No effect without `sidebar`.
|
|
248
279
|
*/
|
|
249
280
|
sidebar?: React.ReactNode;
|
|
281
|
+
/**
|
|
282
|
+
* Pre-localized labels for the mobile nav drawer (only used in the `sidebar`
|
|
283
|
+
* layout, below the breakpoint): the hamburger's open label/hint and the scrim's
|
|
284
|
+
* close label/hint. Optional — English defaults apply. NO i18n in this package.
|
|
285
|
+
*/
|
|
286
|
+
mobileMenu?: Partial<DrawerLabels>;
|
|
250
287
|
/** Banner slot (e.g. verification-pending). */
|
|
251
288
|
banner?: React.ReactNode;
|
|
252
289
|
/** Content-width policy. Defaults to full-bleed. */
|
|
@@ -275,7 +312,7 @@ interface AppShellProps {
|
|
|
275
312
|
testID: string;
|
|
276
313
|
children: React.ReactNode;
|
|
277
314
|
}
|
|
278
|
-
declare const AppShell: ({ header, nav, sidebar, banner, width, contentPadding, chromeAlignment, gate, state, testID, children, }: AppShellProps) => React.ReactElement | null;
|
|
315
|
+
declare const AppShell: ({ header, nav, sidebar, mobileMenu, banner, width, contentPadding, chromeAlignment, gate, state, testID, children, }: AppShellProps) => React.ReactElement | null;
|
|
279
316
|
|
|
280
317
|
/**
|
|
281
318
|
* Default testIDs for `@dloizides/ui-nav`. Kept as a small central map (mirrors
|
|
@@ -305,6 +342,12 @@ declare const APP_SHELL_SUFFIX: {
|
|
|
305
342
|
readonly nav: "-nav";
|
|
306
343
|
/** The persistent left rail of the back-office layout (only when `sidebar` is supplied). */
|
|
307
344
|
readonly sidebar: "-sidebar";
|
|
345
|
+
/** Mobile hamburger that opens the nav drawer (back-office layout, below the breakpoint). */
|
|
346
|
+
readonly menuToggle: "-menu-toggle";
|
|
347
|
+
/** The mobile nav drawer panel (holds the `sidebar` slot when opened on a narrow viewport). */
|
|
348
|
+
readonly drawer: "-drawer";
|
|
349
|
+
/** The mobile nav drawer scrim (tap to close). */
|
|
350
|
+
readonly scrim: "-drawer-scrim";
|
|
308
351
|
readonly banner: "-banner";
|
|
309
352
|
readonly pending: "-pending";
|
|
310
353
|
readonly loading: "-loading";
|
package/dist/index.js
CHANGED
|
@@ -76,7 +76,7 @@ var navBarStyles = reactNative.StyleSheet.create({
|
|
|
76
76
|
rowGap: 4,
|
|
77
77
|
paddingVertical: 8
|
|
78
78
|
},
|
|
79
|
-
brand: { marginRight: 16 },
|
|
79
|
+
brand: { marginRight: 16, flexShrink: 0 },
|
|
80
80
|
toggle: {
|
|
81
81
|
marginLeft: "auto",
|
|
82
82
|
paddingHorizontal: 10,
|
|
@@ -89,10 +89,19 @@ var navBarStyles = reactNative.StyleSheet.create({
|
|
|
89
89
|
justifyContent: "center"
|
|
90
90
|
},
|
|
91
91
|
toggleGlyph: { fontSize: 20, fontWeight: "700" },
|
|
92
|
+
// Expanded links live inside a horizontal ScrollView so the bar stays ONE ROW no matter how many
|
|
93
|
+
// items an app passes: they never wrap onto a second line — any overflow scrolls instead, with the
|
|
94
|
+
// brand (left) and right slot pinned. `linksScroll` is the ScrollView's own box (a shrinkable,
|
|
95
|
+
// growable flex child that may collapse to 0 and scroll); `links` is its non-wrapping content row.
|
|
96
|
+
linksScroll: {
|
|
97
|
+
flexGrow: 1,
|
|
98
|
+
flexShrink: 1,
|
|
99
|
+
minWidth: 0
|
|
100
|
+
},
|
|
92
101
|
links: {
|
|
93
102
|
flexDirection: "row",
|
|
94
103
|
alignItems: "center",
|
|
95
|
-
flexWrap: "
|
|
104
|
+
flexWrap: "nowrap",
|
|
96
105
|
columnGap: 4,
|
|
97
106
|
rowGap: 4
|
|
98
107
|
},
|
|
@@ -121,6 +130,7 @@ var navBarStyles = reactNative.StyleSheet.create({
|
|
|
121
130
|
linkText: { fontSize: 15, fontWeight: "600" },
|
|
122
131
|
right: {
|
|
123
132
|
marginLeft: "auto",
|
|
133
|
+
flexShrink: 0,
|
|
124
134
|
flexDirection: "row",
|
|
125
135
|
alignItems: "center",
|
|
126
136
|
columnGap: 8
|
|
@@ -348,6 +358,12 @@ var APP_SHELL_SUFFIX = {
|
|
|
348
358
|
nav: "-nav",
|
|
349
359
|
/** The persistent left rail of the back-office layout (only when `sidebar` is supplied). */
|
|
350
360
|
sidebar: "-sidebar",
|
|
361
|
+
/** Mobile hamburger that opens the nav drawer (back-office layout, below the breakpoint). */
|
|
362
|
+
menuToggle: "-menu-toggle",
|
|
363
|
+
/** The mobile nav drawer panel (holds the `sidebar` slot when opened on a narrow viewport). */
|
|
364
|
+
drawer: "-drawer",
|
|
365
|
+
/** The mobile nav drawer scrim (tap to close). */
|
|
366
|
+
scrim: "-drawer-scrim",
|
|
351
367
|
banner: "-banner",
|
|
352
368
|
pending: "-pending",
|
|
353
369
|
loading: "-loading",
|
|
@@ -644,33 +660,115 @@ var NavBar = ({
|
|
|
644
660
|
children: typeof renderMenuIcon === "function" ? renderMenuIcon(colors.text, open) : /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [navBarStyles.toggleGlyph, { color: colors.text }], children: MENU_GLYPH })
|
|
645
661
|
}
|
|
646
662
|
) : null,
|
|
647
|
-
showLinks ?
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
663
|
+
showLinks ? (() => {
|
|
664
|
+
const links = items.map((item) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
665
|
+
NavBarLink,
|
|
666
|
+
{
|
|
667
|
+
collapsed,
|
|
668
|
+
colors: linkColors,
|
|
669
|
+
iconSize: NAV_ICON_SIZE,
|
|
670
|
+
isActive: isRouteActive(pathname, item.route),
|
|
671
|
+
item,
|
|
672
|
+
navigateHint,
|
|
673
|
+
reducedMotion,
|
|
674
|
+
onPress: handlePress
|
|
675
|
+
},
|
|
676
|
+
item.key
|
|
677
|
+
));
|
|
678
|
+
return collapsed ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { nativeID: LINKS_REGION_ID, style: navBarStyles.linksStacked, testID: NAV_TEST_IDS.navBarLinks, children: links }) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
679
|
+
reactNative.ScrollView,
|
|
680
|
+
{
|
|
681
|
+
horizontal: true,
|
|
682
|
+
showsHorizontalScrollIndicator: false,
|
|
683
|
+
nativeID: LINKS_REGION_ID,
|
|
684
|
+
style: navBarStyles.linksScroll,
|
|
685
|
+
contentContainerStyle: navBarStyles.links,
|
|
686
|
+
testID: NAV_TEST_IDS.navBarLinks,
|
|
687
|
+
children: links
|
|
688
|
+
}
|
|
689
|
+
);
|
|
690
|
+
})() : null,
|
|
669
691
|
showLinks && right !== void 0 ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: collapsed ? navBarStyles.rightStacked : navBarStyles.right, children: right }) : null
|
|
670
692
|
] })
|
|
671
693
|
}
|
|
672
694
|
);
|
|
673
695
|
};
|
|
696
|
+
var MOBILE_BREAKPOINT = 768;
|
|
697
|
+
var SCRIM_COLOR = "rgba(0, 0, 0, 0.5)";
|
|
698
|
+
var MENU_GLYPH2 = "\u2630";
|
|
699
|
+
var MENU_MIN_TARGET = 44;
|
|
700
|
+
var MENU_GLYPH_FONT_SIZE = 20;
|
|
701
|
+
var MENU_TOGGLE_PADDING = 12;
|
|
702
|
+
var DRAWER_Z_INDEX = 50;
|
|
703
|
+
var NAV_ACTIVATABLE_SELECTOR = 'a, [role="button"], [role="link"]';
|
|
704
|
+
var DEFAULT_DRAWER_LABELS = {
|
|
705
|
+
openLabel: "Menu",
|
|
706
|
+
openHint: "Open the navigation menu",
|
|
707
|
+
closeLabel: "Close menu",
|
|
708
|
+
closeHint: "Close the navigation menu"
|
|
709
|
+
};
|
|
710
|
+
var styles = reactNative.StyleSheet.create({
|
|
711
|
+
overlay: { ...reactNative.StyleSheet.absoluteFillObject, zIndex: DRAWER_Z_INDEX },
|
|
712
|
+
scrim: { ...reactNative.StyleSheet.absoluteFillObject, backgroundColor: SCRIM_COLOR },
|
|
713
|
+
panel: { position: "absolute", top: 0, bottom: 0, left: 0 },
|
|
714
|
+
menuToggle: {
|
|
715
|
+
minWidth: MENU_MIN_TARGET,
|
|
716
|
+
minHeight: MENU_MIN_TARGET,
|
|
717
|
+
alignItems: "center",
|
|
718
|
+
justifyContent: "center",
|
|
719
|
+
paddingHorizontal: MENU_TOGGLE_PADDING
|
|
720
|
+
},
|
|
721
|
+
menuToggleGlyph: { fontSize: MENU_GLYPH_FONT_SIZE, fontWeight: "700" }
|
|
722
|
+
});
|
|
723
|
+
var MenuToggle = ({ label, hint, onOpen, testID }) => {
|
|
724
|
+
const { theme } = uiFeedback.useUi();
|
|
725
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
726
|
+
FocusableTouchable,
|
|
727
|
+
{
|
|
728
|
+
accessibilityHint: hint,
|
|
729
|
+
accessibilityLabel: label,
|
|
730
|
+
ringColor: theme.palette.primary["500"],
|
|
731
|
+
style: styles.menuToggle,
|
|
732
|
+
testID,
|
|
733
|
+
onPress: onOpen,
|
|
734
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles.menuToggleGlyph, { color: theme.colors.text }], children: MENU_GLYPH2 })
|
|
735
|
+
}
|
|
736
|
+
);
|
|
737
|
+
};
|
|
738
|
+
var MobileDrawer = ({
|
|
739
|
+
sidebar,
|
|
740
|
+
labels,
|
|
741
|
+
onClose,
|
|
742
|
+
drawerTestID,
|
|
743
|
+
scrimTestID
|
|
744
|
+
}) => {
|
|
745
|
+
const panelRef = react.useRef(null);
|
|
746
|
+
react.useEffect(() => {
|
|
747
|
+
const node = panelRef.current;
|
|
748
|
+
if (node === null || typeof node.addEventListener !== "function") return void 0;
|
|
749
|
+
const handleClick = (event) => {
|
|
750
|
+
const target = event.target;
|
|
751
|
+
const onNavItem = target !== null && target.closest(NAV_ACTIVATABLE_SELECTOR) !== null;
|
|
752
|
+
if (onNavItem) onClose();
|
|
753
|
+
};
|
|
754
|
+
node.addEventListener("click", handleClick);
|
|
755
|
+
return () => node.removeEventListener("click", handleClick);
|
|
756
|
+
}, [onClose]);
|
|
757
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles.overlay, children: [
|
|
758
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
759
|
+
reactNative.Pressable,
|
|
760
|
+
{
|
|
761
|
+
accessibilityHint: labels.closeHint,
|
|
762
|
+
accessibilityLabel: labels.closeLabel,
|
|
763
|
+
accessibilityRole: "button",
|
|
764
|
+
style: styles.scrim,
|
|
765
|
+
testID: scrimTestID,
|
|
766
|
+
onPress: onClose
|
|
767
|
+
}
|
|
768
|
+
),
|
|
769
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { ref: panelRef, "aria-modal": true, role: "dialog", style: styles.panel, testID: drawerTestID, children: sidebar })
|
|
770
|
+
] });
|
|
771
|
+
};
|
|
674
772
|
function resolveContentMaxWidth(width, viewport) {
|
|
675
773
|
if (width === "full") return "full";
|
|
676
774
|
const wideMinViewport = width.wideMinViewport ?? Number.POSITIVE_INFINITY;
|
|
@@ -688,11 +786,14 @@ var CARD_BORDER_WIDTH = 1;
|
|
|
688
786
|
var CARD_TITLE_FONT_SIZE = 16;
|
|
689
787
|
var CARD_MESSAGE_FONT_SIZE = 14;
|
|
690
788
|
var CARD_TITLE_MARGIN_BOTTOM = 8;
|
|
691
|
-
var
|
|
789
|
+
var styles2 = reactNative.StyleSheet.create({
|
|
692
790
|
root: { flex: 1 },
|
|
693
791
|
centerFill: { flex: 1, justifyContent: "center", alignItems: "center" },
|
|
694
792
|
scroll: { flex: 1 },
|
|
695
793
|
scrollContent: { flexGrow: 1 },
|
|
794
|
+
// Mobile back-office header: the hamburger sits inline before the header slot.
|
|
795
|
+
headerRow: { flexDirection: "row", alignItems: "center" },
|
|
796
|
+
headerFill: { flex: 1 },
|
|
696
797
|
// Back-office layout: a persistent left rail beside the scrolling content column.
|
|
697
798
|
bodyRow: { flex: 1, flexDirection: "row" },
|
|
698
799
|
columnFull: { flex: 1 },
|
|
@@ -718,11 +819,11 @@ function MessageCard({
|
|
|
718
819
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
719
820
|
reactNative.View,
|
|
720
821
|
{
|
|
721
|
-
style: [
|
|
822
|
+
style: [styles2.card, { backgroundColor: colors.surface, borderColor: accentColor }],
|
|
722
823
|
testID,
|
|
723
824
|
children: [
|
|
724
|
-
/* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [
|
|
725
|
-
/* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [
|
|
825
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles2.cardTitle, { color: accentColor }], children: message.titleText }),
|
|
826
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles2.cardMessage, { color: colors.textSecondary }], children: message.messageText })
|
|
726
827
|
]
|
|
727
828
|
}
|
|
728
829
|
);
|
|
@@ -736,13 +837,14 @@ function useContentBody(state, children, testID) {
|
|
|
736
837
|
if (state?.error)
|
|
737
838
|
return /* @__PURE__ */ jsxRuntime.jsx(MessageCard, { accentColor: errorColor, message: state.error, testID: `${testID}${APP_SHELL_SUFFIX.error}` });
|
|
738
839
|
if (state?.loading === true)
|
|
739
|
-
return /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style:
|
|
840
|
+
return /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: styles2.centerFill, testID: `${testID}${APP_SHELL_SUFFIX.loading}`, children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.ActivityIndicator, { color: primary, size: "large" }) });
|
|
740
841
|
return children;
|
|
741
842
|
}
|
|
742
843
|
var AppShell = ({
|
|
743
844
|
header,
|
|
744
845
|
nav,
|
|
745
846
|
sidebar,
|
|
847
|
+
mobileMenu,
|
|
746
848
|
banner,
|
|
747
849
|
width = "full",
|
|
748
850
|
contentPadding = DEFAULT_CONTENT_PADDING,
|
|
@@ -756,32 +858,65 @@ var AppShell = ({
|
|
|
756
858
|
const primary = theme.palette.primary["500"];
|
|
757
859
|
const maxWidth = useContentMaxWidth(width);
|
|
758
860
|
const body = useContentBody(state, children, testID);
|
|
861
|
+
const { width: viewportWidth } = reactNative.useWindowDimensions();
|
|
862
|
+
const isNarrow = viewportWidth < MOBILE_BREAKPOINT;
|
|
863
|
+
const useDrawer = sidebar !== void 0 && isNarrow;
|
|
864
|
+
const [drawerOpen, setDrawerOpen] = react.useState(false);
|
|
865
|
+
const openDrawer = react.useCallback(() => setDrawerOpen(true), []);
|
|
866
|
+
const closeDrawer = react.useCallback(() => setDrawerOpen(false), []);
|
|
867
|
+
react.useEffect(() => {
|
|
868
|
+
if (!isNarrow && drawerOpen) setDrawerOpen(false);
|
|
869
|
+
}, [isNarrow, drawerOpen]);
|
|
870
|
+
const drawerLabels = { ...DEFAULT_DRAWER_LABELS, ...mobileMenu };
|
|
759
871
|
const isUnauthenticated = gate !== void 0 && !gate.pending && !gate.authenticated;
|
|
760
872
|
react.useEffect(() => {
|
|
761
873
|
if (isUnauthenticated && gate !== void 0) gate.onRedirect();
|
|
762
874
|
}, [isUnauthenticated, gate]);
|
|
763
875
|
if (gate?.pending === true)
|
|
764
|
-
return /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [
|
|
876
|
+
return /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [styles2.root, styles2.centerFill, { backgroundColor: theme.colors.background }], testID: `${testID}${APP_SHELL_SUFFIX.pending}`, children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.ActivityIndicator, { color: primary, size: "large" }) });
|
|
765
877
|
if (isUnauthenticated) return null;
|
|
766
|
-
const columnStyle = maxWidth === "full" ?
|
|
767
|
-
const navInnerStyle = chromeAlignment === "content" && maxWidth !== "full" ? [
|
|
878
|
+
const columnStyle = maxWidth === "full" ? styles2.columnFull : [styles2.columnCapped, { maxWidth }];
|
|
879
|
+
const navInnerStyle = chromeAlignment === "content" && maxWidth !== "full" ? [styles2.chromeCapped, { maxWidth, paddingHorizontal: contentPadding }] : void 0;
|
|
768
880
|
const scroller = /* @__PURE__ */ jsxRuntime.jsx(
|
|
769
881
|
reactNative.ScrollView,
|
|
770
882
|
{
|
|
771
|
-
contentContainerStyle: [
|
|
772
|
-
style:
|
|
883
|
+
contentContainerStyle: [styles2.scrollContent, { padding: contentPadding }],
|
|
884
|
+
style: styles2.scroll,
|
|
773
885
|
testID: `${testID}${APP_SHELL_SUFFIX.content}`,
|
|
774
886
|
children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: columnStyle, children: body })
|
|
775
887
|
}
|
|
776
888
|
);
|
|
777
|
-
|
|
778
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
889
|
+
const headerRegion = useDrawer ? /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles2.headerRow, testID: `${testID}${APP_SHELL_SUFFIX.header}`, children: [
|
|
890
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
891
|
+
MenuToggle,
|
|
892
|
+
{
|
|
893
|
+
hint: drawerLabels.openHint,
|
|
894
|
+
label: drawerLabels.openLabel,
|
|
895
|
+
testID: `${testID}${APP_SHELL_SUFFIX.menuToggle}`,
|
|
896
|
+
onOpen: openDrawer
|
|
897
|
+
}
|
|
898
|
+
),
|
|
899
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: styles2.headerFill, children: header })
|
|
900
|
+
] }) : /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { testID: `${testID}${APP_SHELL_SUFFIX.header}`, children: header });
|
|
901
|
+
const showRail = sidebar !== void 0 && !isNarrow;
|
|
902
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: [styles2.root, { backgroundColor: theme.colors.background }], testID, children: [
|
|
903
|
+
headerRegion,
|
|
779
904
|
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,
|
|
780
905
|
banner !== void 0 ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { testID: `${testID}${APP_SHELL_SUFFIX.banner}`, children: banner }) : null,
|
|
781
|
-
|
|
906
|
+
showRail ? /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles2.bodyRow, children: [
|
|
782
907
|
/* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { testID: `${testID}${APP_SHELL_SUFFIX.sidebar}`, children: sidebar }),
|
|
783
908
|
scroller
|
|
784
|
-
] }) : scroller
|
|
909
|
+
] }) : scroller,
|
|
910
|
+
useDrawer && drawerOpen ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
911
|
+
MobileDrawer,
|
|
912
|
+
{
|
|
913
|
+
drawerTestID: `${testID}${APP_SHELL_SUFFIX.drawer}`,
|
|
914
|
+
labels: drawerLabels,
|
|
915
|
+
scrimTestID: `${testID}${APP_SHELL_SUFFIX.scrim}`,
|
|
916
|
+
sidebar,
|
|
917
|
+
onClose: closeDrawer
|
|
918
|
+
}
|
|
919
|
+
) : null
|
|
785
920
|
] });
|
|
786
921
|
};
|
|
787
922
|
function roleRoutesToNavItems(routes, translate) {
|