@dloizides/ui-nav 1.1.1 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/README.md +31 -0
- package/dist/index.d.mts +48 -1
- package/dist/index.d.ts +48 -1
- package/dist/index.js +167 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +167 -2
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.2.0
|
|
4
|
+
|
|
5
|
+
Add `NavBar` — a config-driven **horizontal top navigation bar**, the top-bar
|
|
6
|
+
counterpart to `Sidebar`. It renders the same caller-supplied `NavItem[]` as inline
|
|
7
|
+
links across the top (brand left slot + inline links + free-form right slot), with
|
|
8
|
+
an active-link highlight (`aria-current="page"`) and a responsive collapse to a
|
|
9
|
+
hamburger toggle below a configurable breakpoint (`collapseBelow`, default `760`) —
|
|
10
|
+
mirroring the v1 AML console `.global-nav`. Theme-driven (UiProvider colours),
|
|
11
|
+
`role="navigation"` landmark, keyboard-operable links. Purely additive: `Sidebar`,
|
|
12
|
+
`Topbar`, `AppShell`, and the role-gating helpers are unchanged. New `NAV_TEST_IDS`
|
|
13
|
+
entries `navBarToggle` / `navBarLinks`.
|
|
14
|
+
|
|
3
15
|
## 1.0.0
|
|
4
16
|
|
|
5
17
|
Initial release. `Sidebar` (leaf + expandable items, active-route highlight, header/footer
|
package/README.md
CHANGED
|
@@ -46,6 +46,37 @@ const items: NavItem[] = buildItems(); // your role-filtered / grouped items
|
|
|
46
46
|
/>
|
|
47
47
|
```
|
|
48
48
|
|
|
49
|
+
### Horizontal top bar (`NavBar`)
|
|
50
|
+
|
|
51
|
+
Prefer the nav links **on top** (a horizontal menu bar) instead of the left rail?
|
|
52
|
+
`NavBar` renders the SAME `NavItem[]` model horizontally — brand slot on the left,
|
|
53
|
+
inline links, a free-form right slot (auth / user / logout) — and collapses the
|
|
54
|
+
links behind a hamburger below a configurable breakpoint (default `760`, matching
|
|
55
|
+
the v1 AML console `.global-nav`). It is purely additive; `Sidebar` is unchanged.
|
|
56
|
+
|
|
57
|
+
```tsx
|
|
58
|
+
import { NavBar, type NavItem } from '@dloizides/ui-nav';
|
|
59
|
+
|
|
60
|
+
<NavBar
|
|
61
|
+
items={items} // same role-filtered NavItem[] as Sidebar
|
|
62
|
+
pathname={usePathname()}
|
|
63
|
+
onNavigate={(route) => router.push(route)}
|
|
64
|
+
regionLabel={FM('accessibility.navigationRegion')}
|
|
65
|
+
navigateHint={(label) => FM('menu.navigateToHint', label)}
|
|
66
|
+
brand={<BrandLogo />} // left slot
|
|
67
|
+
right={<AuthLink />} // right slot (auth / user / logout)
|
|
68
|
+
menuLabel={FM('menu.toggle')} // a11y label for the hamburger
|
|
69
|
+
menuHint={FM('menu.toggleHint')}
|
|
70
|
+
collapseBelow={760} // hamburger under this viewport width
|
|
71
|
+
/>
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
An app that renders a `Sidebar` today switches to a top bar by swapping the
|
|
75
|
+
`Sidebar` element for `NavBar` (same `items` / `pathname` / `onNavigate`) — e.g.
|
|
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. Active links expose
|
|
78
|
+
`aria-current="page"`; the container is a `role="navigation"` landmark.
|
|
79
|
+
|
|
49
80
|
### Role gating
|
|
50
81
|
|
|
51
82
|
`accessibleNavItems(user, roleRouteTable, translate)` builds the `NavItem[]` a user's roles
|
package/dist/index.d.mts
CHANGED
|
@@ -145,6 +145,49 @@ interface TopbarProps {
|
|
|
145
145
|
}
|
|
146
146
|
declare const Topbar: ({ left, language, notificationSlot, user, account, logout, containerStyle, }: TopbarProps) => React.ReactElement;
|
|
147
147
|
|
|
148
|
+
/**
|
|
149
|
+
* NavBar — the config-driven HORIZONTAL top navigation bar. It is the top-bar
|
|
150
|
+
* counterpart to `Sidebar`: it renders the SAME caller-supplied `NavItem[]`
|
|
151
|
+
* (already role-filtered / localized by the app) as inline links across the top
|
|
152
|
+
* instead of a left rail, with an optional brand slot on the left and a free-form
|
|
153
|
+
* right slot (auth link / user / logout). Below a configurable breakpoint it
|
|
154
|
+
* collapses the links behind a hamburger toggle — mirroring the v1 AML console's
|
|
155
|
+
* responsive `.global-nav`.
|
|
156
|
+
*
|
|
157
|
+
* Contract discipline (same as the rest of `@dloizides/ui-nav`): NO FM, router,
|
|
158
|
+
* icon set, or store imports. Labels are pre-localized strings, icons are render
|
|
159
|
+
* slots, and every colour is read from the `@dloizides/ui-feedback` UiProvider
|
|
160
|
+
* theme. This is purely additive — `Sidebar`/`Topbar` are unchanged.
|
|
161
|
+
*/
|
|
162
|
+
|
|
163
|
+
interface NavBarProps {
|
|
164
|
+
/** Nav entries — already role-filtered / grouped by the app. */
|
|
165
|
+
items: NavItem[];
|
|
166
|
+
/** Current active route/path. */
|
|
167
|
+
pathname: string;
|
|
168
|
+
/** Navigation callback — receives a `NavItem.route`. */
|
|
169
|
+
onNavigate: (route: string) => void;
|
|
170
|
+
/** Localized accessibility label for the navigation landmark. */
|
|
171
|
+
regionLabel: string;
|
|
172
|
+
/** a11y hint for a link, given its label. Defaults to the label. */
|
|
173
|
+
navigateHint?: (label: string) => string;
|
|
174
|
+
/** Left slot — typically the brand / tenant logo. */
|
|
175
|
+
brand?: React.ReactNode;
|
|
176
|
+
/** Right slot — free-form (auth link, user block, logout). */
|
|
177
|
+
right?: React.ReactNode;
|
|
178
|
+
/** Localized accessibility label for the responsive menu toggle. */
|
|
179
|
+
menuLabel?: string;
|
|
180
|
+
/** Localized accessibility hint for the responsive menu toggle. */
|
|
181
|
+
menuHint?: string;
|
|
182
|
+
/** Optional custom glyph renderer for the toggle; defaults to ☰. */
|
|
183
|
+
renderMenuIcon?: (color: string, open: boolean) => React.ReactNode;
|
|
184
|
+
/** Collapse the links behind a hamburger below this viewport width (default 760). */
|
|
185
|
+
collapseBelow?: number;
|
|
186
|
+
/** Extra container style overrides. */
|
|
187
|
+
containerStyle?: ViewStyle | ViewStyle[];
|
|
188
|
+
}
|
|
189
|
+
declare const NavBar: ({ items, pathname, onNavigate, regionLabel, navigateHint, brand, right, menuLabel, menuHint, renderMenuIcon, collapseBelow, containerStyle, }: NavBarProps) => React.ReactElement;
|
|
190
|
+
|
|
148
191
|
/** Content-width policy: a capped column, optionally wider past a viewport breakpoint, or full-bleed. */
|
|
149
192
|
type AppShellWidth = {
|
|
150
193
|
max: number;
|
|
@@ -229,6 +272,10 @@ declare const NAV_TEST_IDS: {
|
|
|
229
272
|
readonly accountPlan: "topbar-account-plan";
|
|
230
273
|
/** default testID for the rich header's upgrade action (overridable per-action). */
|
|
231
274
|
readonly accountUpgrade: "topbar-account-upgrade";
|
|
275
|
+
/** responsive hamburger toggle in the horizontal NavBar (shown below the breakpoint). */
|
|
276
|
+
readonly navBarToggle: "navbar-toggle";
|
|
277
|
+
/** links container in the horizontal NavBar. */
|
|
278
|
+
readonly navBarLinks: "navbar-links";
|
|
232
279
|
};
|
|
233
280
|
/** Suffixes appended to an `AppShell`'s required `testID` to name its regions. */
|
|
234
281
|
declare const APP_SHELL_SUFFIX: {
|
|
@@ -408,4 +455,4 @@ declare const NAV_ICON_SIZE = 14;
|
|
|
408
455
|
/** Chevron icon size for expandable sections. */
|
|
409
456
|
declare const CHEVRON_ICON_SIZE = 12;
|
|
410
457
|
|
|
411
|
-
export { ACTIVE_BORDER_RADIUS, APP_SHELL_SUFFIX, type AccountPlan, type AccountState, AppShell, type AppShellProps, type AppShellWidth, BASE_INDENT, CHEVRON_ICON_SIZE, NAV_ICON_SIZE, NAV_TEST_IDS, NavExpandableItem, type NavExpandableItemProps, type NavItem, type NavUser, type ShellMessage, Sidebar, type SidebarProps, Topbar, type TopbarAction, type TopbarProps, type TopbarUser, accessibleNavItems, expandableStyles, isRouteActive, navStyles, resolveContentMaxWidth, roleRoutesToNavItems, useContentMaxWidth };
|
|
458
|
+
export { ACTIVE_BORDER_RADIUS, APP_SHELL_SUFFIX, type AccountPlan, type AccountState, AppShell, type AppShellProps, type AppShellWidth, BASE_INDENT, CHEVRON_ICON_SIZE, NAV_ICON_SIZE, NAV_TEST_IDS, NavBar, type NavBarProps, NavExpandableItem, type NavExpandableItemProps, type NavItem, type NavUser, type ShellMessage, Sidebar, type SidebarProps, Topbar, type TopbarAction, type TopbarProps, type TopbarUser, accessibleNavItems, expandableStyles, isRouteActive, navStyles, resolveContentMaxWidth, roleRoutesToNavItems, useContentMaxWidth };
|
package/dist/index.d.ts
CHANGED
|
@@ -145,6 +145,49 @@ interface TopbarProps {
|
|
|
145
145
|
}
|
|
146
146
|
declare const Topbar: ({ left, language, notificationSlot, user, account, logout, containerStyle, }: TopbarProps) => React.ReactElement;
|
|
147
147
|
|
|
148
|
+
/**
|
|
149
|
+
* NavBar — the config-driven HORIZONTAL top navigation bar. It is the top-bar
|
|
150
|
+
* counterpart to `Sidebar`: it renders the SAME caller-supplied `NavItem[]`
|
|
151
|
+
* (already role-filtered / localized by the app) as inline links across the top
|
|
152
|
+
* instead of a left rail, with an optional brand slot on the left and a free-form
|
|
153
|
+
* right slot (auth link / user / logout). Below a configurable breakpoint it
|
|
154
|
+
* collapses the links behind a hamburger toggle — mirroring the v1 AML console's
|
|
155
|
+
* responsive `.global-nav`.
|
|
156
|
+
*
|
|
157
|
+
* Contract discipline (same as the rest of `@dloizides/ui-nav`): NO FM, router,
|
|
158
|
+
* icon set, or store imports. Labels are pre-localized strings, icons are render
|
|
159
|
+
* slots, and every colour is read from the `@dloizides/ui-feedback` UiProvider
|
|
160
|
+
* theme. This is purely additive — `Sidebar`/`Topbar` are unchanged.
|
|
161
|
+
*/
|
|
162
|
+
|
|
163
|
+
interface NavBarProps {
|
|
164
|
+
/** Nav entries — already role-filtered / grouped by the app. */
|
|
165
|
+
items: NavItem[];
|
|
166
|
+
/** Current active route/path. */
|
|
167
|
+
pathname: string;
|
|
168
|
+
/** Navigation callback — receives a `NavItem.route`. */
|
|
169
|
+
onNavigate: (route: string) => void;
|
|
170
|
+
/** Localized accessibility label for the navigation landmark. */
|
|
171
|
+
regionLabel: string;
|
|
172
|
+
/** a11y hint for a link, given its label. Defaults to the label. */
|
|
173
|
+
navigateHint?: (label: string) => string;
|
|
174
|
+
/** Left slot — typically the brand / tenant logo. */
|
|
175
|
+
brand?: React.ReactNode;
|
|
176
|
+
/** Right slot — free-form (auth link, user block, logout). */
|
|
177
|
+
right?: React.ReactNode;
|
|
178
|
+
/** Localized accessibility label for the responsive menu toggle. */
|
|
179
|
+
menuLabel?: string;
|
|
180
|
+
/** Localized accessibility hint for the responsive menu toggle. */
|
|
181
|
+
menuHint?: string;
|
|
182
|
+
/** Optional custom glyph renderer for the toggle; defaults to ☰. */
|
|
183
|
+
renderMenuIcon?: (color: string, open: boolean) => React.ReactNode;
|
|
184
|
+
/** Collapse the links behind a hamburger below this viewport width (default 760). */
|
|
185
|
+
collapseBelow?: number;
|
|
186
|
+
/** Extra container style overrides. */
|
|
187
|
+
containerStyle?: ViewStyle | ViewStyle[];
|
|
188
|
+
}
|
|
189
|
+
declare const NavBar: ({ items, pathname, onNavigate, regionLabel, navigateHint, brand, right, menuLabel, menuHint, renderMenuIcon, collapseBelow, containerStyle, }: NavBarProps) => React.ReactElement;
|
|
190
|
+
|
|
148
191
|
/** Content-width policy: a capped column, optionally wider past a viewport breakpoint, or full-bleed. */
|
|
149
192
|
type AppShellWidth = {
|
|
150
193
|
max: number;
|
|
@@ -229,6 +272,10 @@ declare const NAV_TEST_IDS: {
|
|
|
229
272
|
readonly accountPlan: "topbar-account-plan";
|
|
230
273
|
/** default testID for the rich header's upgrade action (overridable per-action). */
|
|
231
274
|
readonly accountUpgrade: "topbar-account-upgrade";
|
|
275
|
+
/** responsive hamburger toggle in the horizontal NavBar (shown below the breakpoint). */
|
|
276
|
+
readonly navBarToggle: "navbar-toggle";
|
|
277
|
+
/** links container in the horizontal NavBar. */
|
|
278
|
+
readonly navBarLinks: "navbar-links";
|
|
232
279
|
};
|
|
233
280
|
/** Suffixes appended to an `AppShell`'s required `testID` to name its regions. */
|
|
234
281
|
declare const APP_SHELL_SUFFIX: {
|
|
@@ -408,4 +455,4 @@ declare const NAV_ICON_SIZE = 14;
|
|
|
408
455
|
/** Chevron icon size for expandable sections. */
|
|
409
456
|
declare const CHEVRON_ICON_SIZE = 12;
|
|
410
457
|
|
|
411
|
-
export { ACTIVE_BORDER_RADIUS, APP_SHELL_SUFFIX, type AccountPlan, type AccountState, AppShell, type AppShellProps, type AppShellWidth, BASE_INDENT, CHEVRON_ICON_SIZE, NAV_ICON_SIZE, NAV_TEST_IDS, NavExpandableItem, type NavExpandableItemProps, type NavItem, type NavUser, type ShellMessage, Sidebar, type SidebarProps, Topbar, type TopbarAction, type TopbarProps, type TopbarUser, accessibleNavItems, expandableStyles, isRouteActive, navStyles, resolveContentMaxWidth, roleRoutesToNavItems, useContentMaxWidth };
|
|
458
|
+
export { ACTIVE_BORDER_RADIUS, APP_SHELL_SUFFIX, type AccountPlan, type AccountState, AppShell, type AppShellProps, type AppShellWidth, BASE_INDENT, CHEVRON_ICON_SIZE, NAV_ICON_SIZE, NAV_TEST_IDS, NavBar, type NavBarProps, NavExpandableItem, type NavExpandableItemProps, type NavItem, type NavUser, type ShellMessage, Sidebar, type SidebarProps, Topbar, type TopbarAction, type TopbarProps, type TopbarUser, accessibleNavItems, expandableStyles, isRouteActive, navStyles, resolveContentMaxWidth, roleRoutesToNavItems, useContentMaxWidth };
|
package/dist/index.js
CHANGED
|
@@ -61,6 +61,71 @@ var navStyles = reactNative.StyleSheet.create({
|
|
|
61
61
|
planBadge: { marginTop: 2, paddingHorizontal: 8, paddingVertical: 2, borderRadius: 10, alignSelf: "flex-end" },
|
|
62
62
|
planBadgeText: { fontSize: 11, fontWeight: "700" }
|
|
63
63
|
});
|
|
64
|
+
var navBarStyles = reactNative.StyleSheet.create({
|
|
65
|
+
// --- Horizontal top NavBar ---
|
|
66
|
+
container: {
|
|
67
|
+
borderBottomWidth: 1,
|
|
68
|
+
paddingHorizontal: 12
|
|
69
|
+
},
|
|
70
|
+
inner: {
|
|
71
|
+
minHeight: 56,
|
|
72
|
+
flexDirection: "row",
|
|
73
|
+
alignItems: "center",
|
|
74
|
+
flexWrap: "wrap",
|
|
75
|
+
columnGap: 8,
|
|
76
|
+
rowGap: 4,
|
|
77
|
+
paddingVertical: 8
|
|
78
|
+
},
|
|
79
|
+
brand: { marginRight: 16 },
|
|
80
|
+
toggle: {
|
|
81
|
+
marginLeft: "auto",
|
|
82
|
+
paddingHorizontal: 10,
|
|
83
|
+
paddingVertical: 6,
|
|
84
|
+
borderRadius: 4
|
|
85
|
+
},
|
|
86
|
+
toggleGlyph: { fontSize: 20, fontWeight: "700" },
|
|
87
|
+
links: {
|
|
88
|
+
flexDirection: "row",
|
|
89
|
+
alignItems: "center",
|
|
90
|
+
flexWrap: "wrap",
|
|
91
|
+
columnGap: 4,
|
|
92
|
+
rowGap: 4
|
|
93
|
+
},
|
|
94
|
+
// Collapsed (below breakpoint): links drop onto their own full-width line,
|
|
95
|
+
// stacked vertically — mirrors the v1 `.gn-links` responsive behaviour.
|
|
96
|
+
linksStacked: {
|
|
97
|
+
width: "100%",
|
|
98
|
+
flexBasis: "100%",
|
|
99
|
+
flexDirection: "column",
|
|
100
|
+
alignItems: "stretch",
|
|
101
|
+
rowGap: 2,
|
|
102
|
+
marginTop: 8
|
|
103
|
+
},
|
|
104
|
+
link: {
|
|
105
|
+
paddingVertical: 8,
|
|
106
|
+
paddingHorizontal: 14,
|
|
107
|
+
borderRadius: 8,
|
|
108
|
+
flexDirection: "row",
|
|
109
|
+
alignItems: "center"
|
|
110
|
+
},
|
|
111
|
+
linkStacked: { paddingVertical: 10 },
|
|
112
|
+
linkIcon: { marginRight: 6 },
|
|
113
|
+
linkText: { fontSize: 15, fontWeight: "600" },
|
|
114
|
+
right: {
|
|
115
|
+
marginLeft: "auto",
|
|
116
|
+
flexDirection: "row",
|
|
117
|
+
alignItems: "center",
|
|
118
|
+
columnGap: 8
|
|
119
|
+
},
|
|
120
|
+
rightStacked: {
|
|
121
|
+
width: "100%",
|
|
122
|
+
flexBasis: "100%",
|
|
123
|
+
marginTop: 4,
|
|
124
|
+
flexDirection: "row",
|
|
125
|
+
alignItems: "center",
|
|
126
|
+
columnGap: 8
|
|
127
|
+
}
|
|
128
|
+
});
|
|
64
129
|
var expandableStyles = reactNative.StyleSheet.create({
|
|
65
130
|
childItem: {
|
|
66
131
|
borderRadius: 4,
|
|
@@ -226,7 +291,11 @@ var NAV_TEST_IDS = {
|
|
|
226
291
|
/** small plan badge (free / pro / enterprise). */
|
|
227
292
|
accountPlan: "topbar-account-plan",
|
|
228
293
|
/** default testID for the rich header's upgrade action (overridable per-action). */
|
|
229
|
-
accountUpgrade: "topbar-account-upgrade"
|
|
294
|
+
accountUpgrade: "topbar-account-upgrade",
|
|
295
|
+
/** responsive hamburger toggle in the horizontal NavBar (shown below the breakpoint). */
|
|
296
|
+
navBarToggle: "navbar-toggle",
|
|
297
|
+
/** links container in the horizontal NavBar. */
|
|
298
|
+
navBarLinks: "navbar-links"
|
|
230
299
|
};
|
|
231
300
|
var APP_SHELL_SUFFIX = {
|
|
232
301
|
content: "-content",
|
|
@@ -361,6 +430,102 @@ var Topbar = ({
|
|
|
361
430
|
}
|
|
362
431
|
);
|
|
363
432
|
};
|
|
433
|
+
var TEXT_ON_PRIMARY2 = "#ffffff";
|
|
434
|
+
var DEFAULT_COLLAPSE_BELOW = 760;
|
|
435
|
+
var MENU_GLYPH = "\u2630";
|
|
436
|
+
function activeAriaProps(isActive) {
|
|
437
|
+
return isActive ? { "aria-current": "page" } : {};
|
|
438
|
+
}
|
|
439
|
+
var NavBar = ({
|
|
440
|
+
items,
|
|
441
|
+
pathname,
|
|
442
|
+
onNavigate,
|
|
443
|
+
regionLabel,
|
|
444
|
+
navigateHint = (label) => label,
|
|
445
|
+
brand,
|
|
446
|
+
right,
|
|
447
|
+
menuLabel = "Menu",
|
|
448
|
+
menuHint = "",
|
|
449
|
+
renderMenuIcon,
|
|
450
|
+
collapseBelow = DEFAULT_COLLAPSE_BELOW,
|
|
451
|
+
containerStyle
|
|
452
|
+
}) => {
|
|
453
|
+
const { theme } = uiFeedback.useUi();
|
|
454
|
+
const colors = theme.colors;
|
|
455
|
+
const primaryColor = theme.palette.primary["500"];
|
|
456
|
+
const { width } = reactNative.useWindowDimensions();
|
|
457
|
+
const [open, setOpen] = react.useState(false);
|
|
458
|
+
const collapsed = width < collapseBelow;
|
|
459
|
+
const showLinks = !collapsed || open;
|
|
460
|
+
const toggleMenu = react.useCallback(() => setOpen((v) => !v), []);
|
|
461
|
+
const handlePress = react.useCallback(
|
|
462
|
+
(route) => {
|
|
463
|
+
setOpen(false);
|
|
464
|
+
onNavigate(route);
|
|
465
|
+
},
|
|
466
|
+
[onNavigate]
|
|
467
|
+
);
|
|
468
|
+
const activeLinkStyle = react.useMemo(
|
|
469
|
+
() => ({ backgroundColor: primaryColor, borderRadius: ACTIVE_BORDER_RADIUS }),
|
|
470
|
+
[primaryColor]
|
|
471
|
+
);
|
|
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
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
496
|
+
reactNative.View,
|
|
497
|
+
{
|
|
498
|
+
accessibilityRole: "none",
|
|
499
|
+
"aria-label": regionLabel,
|
|
500
|
+
role: "navigation",
|
|
501
|
+
style: [
|
|
502
|
+
navBarStyles.container,
|
|
503
|
+
{ backgroundColor: colors.surface, borderBottomColor: colors.border },
|
|
504
|
+
containerStyle
|
|
505
|
+
],
|
|
506
|
+
children: /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: navBarStyles.inner, children: [
|
|
507
|
+
brand !== void 0 ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: navBarStyles.brand, children: brand }) : null,
|
|
508
|
+
collapsed ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
509
|
+
reactNative.TouchableOpacity,
|
|
510
|
+
{
|
|
511
|
+
accessible: true,
|
|
512
|
+
accessibilityHint: menuHint,
|
|
513
|
+
accessibilityLabel: menuLabel,
|
|
514
|
+
accessibilityRole: "button",
|
|
515
|
+
accessibilityState: { expanded: open },
|
|
516
|
+
"aria-expanded": open,
|
|
517
|
+
style: navBarStyles.toggle,
|
|
518
|
+
testID: NAV_TEST_IDS.navBarToggle,
|
|
519
|
+
onPress: toggleMenu,
|
|
520
|
+
children: typeof renderMenuIcon === "function" ? renderMenuIcon(colors.text, open) : /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [navBarStyles.toggleGlyph, { color: colors.text }], children: MENU_GLYPH })
|
|
521
|
+
}
|
|
522
|
+
) : null,
|
|
523
|
+
showLinks ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: collapsed ? navBarStyles.linksStacked : navBarStyles.links, testID: NAV_TEST_IDS.navBarLinks, children: items.map(renderLink) }) : null,
|
|
524
|
+
showLinks && right !== void 0 ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: collapsed ? navBarStyles.rightStacked : navBarStyles.right, children: right }) : null
|
|
525
|
+
] })
|
|
526
|
+
}
|
|
527
|
+
);
|
|
528
|
+
};
|
|
364
529
|
function resolveContentMaxWidth(width, viewport) {
|
|
365
530
|
if (width === "full") return "full";
|
|
366
531
|
const wideMinViewport = width.wideMinViewport ?? Number.POSITIVE_INFINITY;
|
|
@@ -485,6 +650,7 @@ exports.BASE_INDENT = BASE_INDENT;
|
|
|
485
650
|
exports.CHEVRON_ICON_SIZE = CHEVRON_ICON_SIZE;
|
|
486
651
|
exports.NAV_ICON_SIZE = NAV_ICON_SIZE;
|
|
487
652
|
exports.NAV_TEST_IDS = NAV_TEST_IDS;
|
|
653
|
+
exports.NavBar = NavBar;
|
|
488
654
|
exports.NavExpandableItem = NavExpandableItem;
|
|
489
655
|
exports.Sidebar = Sidebar;
|
|
490
656
|
exports.Topbar = Topbar;
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/isRouteActive.ts","../src/styles.ts","../src/NavExpandableItem.tsx","../src/Sidebar.tsx","../src/constants.ts","../src/Topbar.tsx","../src/useContentMaxWidth.ts","../src/AppShell.tsx","../src/roleNav.ts"],"names":["StyleSheet","useState","useUi","useCallback","useMemo","jsxs","TouchableOpacity","jsx","View","Text","useWindowDimensions","ActivityIndicator","useEffect","ScrollView","resolveAccessibleRoutes"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Active-route matcher shared by the sidebar entries. Ported verbatim from the\n * twin app sidebars: an item is active when the current pathname equals its\n * route or is nested under it (`/foo` matches `/foo` and `/foo/bar`, but `/`\n * only matches `/`).\n */\nexport function isRouteActive(pathname: string, route: string): boolean {\n if (route === '/') return pathname === '/';\n return pathname === route || pathname.startsWith(`${route}/`);\n}\n","/**\n * Layout styles for the nav shell — ported verbatim (dimensions unchanged) from\n * erevna-web / katalogos-web `theme/utils/layoutSidebar.ts` + `layoutTopbar.ts`,\n * so a migrated app's sidebar/topbar keep the exact same metrics. Colours are\n * NOT baked in here; they are applied at render time from the UiProvider theme.\n */\nimport { StyleSheet } from 'react-native';\n\nexport const ACTIVE_BORDER_RADIUS = 4;\n\nexport const navStyles = StyleSheet.create({\n // --- Sidebar ---\n sidebarContainer: {\n width: 220,\n paddingTop: 24,\n paddingHorizontal: 12,\n borderRightWidth: 1,\n height: '100%',\n },\n sidebarTitle: {\n fontWeight: '700',\n marginBottom: 12,\n },\n sidebarItem: {\n paddingVertical: 10,\n paddingHorizontal: 6,\n borderRadius: 4,\n },\n sidebarItemText: {\n fontSize: 16,\n },\n sidebarSpacer: {\n flex: 1,\n },\n\n // --- Topbar ---\n topbarContainer: {\n height: 64,\n paddingHorizontal: 12,\n borderBottomWidth: 1,\n flexDirection: 'row',\n alignItems: 'center',\n justifyContent: 'space-between',\n },\n topbarLeft: { flex: 1 },\n topbarRight: { flexDirection: 'row', alignItems: 'center' },\n topbarRowItem: { marginHorizontal: 8, alignItems: 'center' },\n topbarLabel: { fontSize: 14 },\n userBlock: { marginHorizontal: 12, alignItems: 'flex-end' },\n userName: { fontWeight: '600' },\n userEmail: { fontSize: 12 },\n accountBtn: { marginLeft: 8, paddingHorizontal: 10, paddingVertical: 6, borderRadius: 4 },\n accountText: { fontWeight: '600' },\n // --- Rich account-state header (Move 1c) ---\n accountTenant: { fontSize: 12 },\n planBadge: { marginTop: 2, paddingHorizontal: 8, paddingVertical: 2, borderRadius: 10, alignSelf: 'flex-end' },\n planBadgeText: { fontSize: 11, fontWeight: '700' },\n});\n\nexport const expandableStyles = StyleSheet.create({\n childItem: {\n borderRadius: 4,\n flexDirection: 'row',\n alignItems: 'center',\n paddingVertical: 6,\n },\n childItemText: { fontSize: 14 },\n childItemTextWithIcon: { fontSize: 14, marginLeft: 6 },\n chevron: { marginLeft: 'auto' },\n childrenContainer: { overflow: 'hidden' },\n header: {\n borderRadius: 4,\n flexDirection: 'row',\n alignItems: 'center',\n paddingVertical: 8,\n },\n headerText: { fontSize: 15, fontWeight: '600', marginLeft: 6 },\n iconWrapper: { width: 20, alignItems: 'center' },\n});\n\n/** Base indent applied per nesting depth in the expandable item. */\nexport const BASE_INDENT = 12;\n/** Default icon size for nav item icons. */\nexport const NAV_ICON_SIZE = 14;\n/** Chevron icon size for expandable sections. */\nexport const CHEVRON_ICON_SIZE = 12;\n","/**\n * NavExpandableItem — a single sidebar entry that is either a leaf (navigates)\n * or an expandable section (toggles nested children). Ported from the twin\n * erevna/katalogos `Sidebar/NavExpandableItem`, made config-driven: labels are\n * pre-localized strings, icons are render slots, colours come from `useUi`.\n */\nimport React, { useCallback, useMemo, useState } from 'react';\n\nimport { Text, TouchableOpacity, View } from 'react-native';\n\nimport { useUi } from '@dloizides/ui-feedback';\n\nimport {\n BASE_INDENT,\n CHEVRON_ICON_SIZE,\n NAV_ICON_SIZE,\n ACTIVE_BORDER_RADIUS,\n expandableStyles as styles,\n} from './styles';\nimport { isRouteActive } from './isRouteActive';\nimport type { NavItem } from './types';\n\nexport interface NavExpandableItemProps {\n item: NavItem;\n pathname: string;\n onNavigate: (route: string) => void;\n /** a11y hint for a leaf item, given its label. */\n navigateHint: (label: string) => string;\n /** a11y hint shown when the section is collapsed (press expands). */\n expandHint: string;\n /** a11y hint shown when the section is expanded (press collapses). */\n collapseHint: string;\n /** Optional chevron icon renderer for expandable sections. */\n renderChevron?: (expanded: boolean, color: string, size: number) => React.ReactNode;\n depth?: number;\n}\n\nexport const NavExpandableItem = ({\n item,\n pathname,\n onNavigate,\n navigateHint,\n expandHint,\n collapseHint,\n renderChevron,\n depth = 0,\n}: NavExpandableItemProps): React.ReactElement => {\n const [expanded, setExpanded] = useState(false);\n const { theme } = useUi();\n const colors = theme.colors;\n const primaryColor = theme.palette.primary['500'];\n\n const toggle = useCallback(() => setExpanded((v) => !v), []);\n\n const indent = depth * BASE_INDENT;\n const hasChildren = Array.isArray(item.children) && item.children.length > 0;\n const isActive = isRouteActive(pathname, item.route);\n\n const activeItemStyle = useMemo(\n () => ({ backgroundColor: colors.border, borderRadius: ACTIVE_BORDER_RADIUS }),\n [colors.border],\n );\n const paddingStyle = useMemo(() => ({ paddingLeft: indent + BASE_INDENT }), [indent]);\n const headerPaddingStyle = useMemo(() => ({ paddingLeft: indent }), [indent]);\n\n if (!hasChildren)\n return (\n <TouchableOpacity\n accessibilityHint={navigateHint(item.label)}\n accessibilityLabel={item.label}\n accessibilityRole=\"button\"\n style={[styles.childItem, paddingStyle, isActive ? activeItemStyle : undefined]}\n testID={item.testID ?? item.key}\n onPress={() => onNavigate(item.route)}\n >\n {typeof item.renderIcon === 'function' ? (\n <View style={styles.iconWrapper}>{item.renderIcon(colors.textSecondary, NAV_ICON_SIZE)}</View>\n ) : null}\n <Text\n style={[\n typeof item.renderIcon === 'function' ? styles.childItemTextWithIcon : styles.childItemText,\n { color: isActive ? primaryColor : colors.text },\n ]}\n >\n {item.label}\n </Text>\n </TouchableOpacity>\n );\n\n return (\n <View>\n <TouchableOpacity\n accessibilityHint={expanded ? collapseHint : expandHint}\n accessibilityLabel={item.label}\n accessibilityRole=\"button\"\n accessibilityState={{ expanded }}\n style={[styles.header, headerPaddingStyle]}\n testID={item.testID ?? item.key}\n onPress={toggle}\n >\n {typeof item.renderIcon === 'function' ? (\n <View style={styles.iconWrapper}>{item.renderIcon(colors.text, NAV_ICON_SIZE)}</View>\n ) : null}\n <Text style={[styles.headerText, { color: colors.text }]}>{item.label}</Text>\n {typeof renderChevron === 'function' ? (\n <View style={styles.chevron}>{renderChevron(expanded, colors.textSecondary, CHEVRON_ICON_SIZE)}</View>\n ) : null}\n </TouchableOpacity>\n\n {expanded ? (\n <View style={styles.childrenContainer}>\n {item.children?.map((child) => (\n <NavExpandableItem\n key={child.key}\n collapseHint={collapseHint}\n depth={depth + 1}\n expandHint={expandHint}\n item={child}\n navigateHint={navigateHint}\n pathname={pathname}\n renderChevron={renderChevron}\n onNavigate={onNavigate}\n />\n ))}\n </View>\n ) : null}\n </View>\n );\n};\n\nexport default NavExpandableItem;\n","/**\n * Sidebar — the config-driven left navigation shell promoted from the\n * byte-identical erevna-web / katalogos-web `Sidebar`. It renders a caller\n * supplied `NavItem[]` (leaf + expandable), highlights the active route, and\n * exposes header/footer slots for app-specific chrome (title, dark-mode toggle,\n * logout, notification bell). Every colour is read from the UiProvider theme.\n */\nimport React from 'react';\n\nimport { Text, View, type ViewStyle } from 'react-native';\n\nimport { useUi } from '@dloizides/ui-feedback';\n\nimport { isRouteActive } from './isRouteActive';\nimport { NavExpandableItem } from './NavExpandableItem';\nimport { navStyles as styles } from './styles';\nimport type { NavItem } from './types';\n\nexport interface SidebarProps {\n /** Nav entries — already role-filtered / grouped by the app. */\n items: NavItem[];\n /** Current active route/path. */\n pathname: string;\n /** Navigation callback — receives a `NavItem.route`. */\n onNavigate: (route: string) => void;\n /** Localized menu title (heading). */\n title: string;\n /** Localized accessibility label for the navigation landmark. */\n regionLabel: string;\n /** a11y hint for a leaf item, given its label. Defaults to the label. */\n navigateHint?: (label: string) => string;\n /** a11y hint shown when an expandable section is collapsed. */\n expandHint?: string;\n /** a11y hint shown when an expandable section is expanded. */\n collapseHint?: string;\n /** Optional chevron renderer for expandable sections. */\n renderChevron?: (expanded: boolean, color: string, size: number) => React.ReactNode;\n /** Optional header slot rendered above the items (e.g. a Home shortcut). */\n header?: React.ReactNode;\n /** Optional footer slot rendered after a flex spacer (dark-mode toggle, logout). */\n footer?: React.ReactNode;\n /** Extra container style overrides. */\n containerStyle?: ViewStyle | ViewStyle[];\n}\n\nexport const Sidebar = ({\n items,\n pathname,\n onNavigate,\n title,\n regionLabel,\n navigateHint = (label) => label,\n expandHint = '',\n collapseHint = '',\n renderChevron,\n header,\n footer,\n containerStyle,\n}: SidebarProps): React.ReactElement => {\n const { theme } = useUi();\n const colors = theme.colors;\n\n return (\n <View\n accessibilityRole=\"none\"\n aria-label={regionLabel}\n role=\"navigation\"\n style={[\n styles.sidebarContainer,\n { backgroundColor: colors.surface, borderRightColor: colors.border },\n containerStyle,\n ]}\n >\n <Text accessibilityRole=\"header\" style={[styles.sidebarTitle, { color: colors.text }]}>\n {title}\n </Text>\n\n {header}\n\n {items.map((item) => (\n <NavExpandableItem\n key={item.key}\n collapseHint={collapseHint}\n expandHint={expandHint}\n item={item}\n navigateHint={navigateHint}\n pathname={pathname}\n renderChevron={renderChevron}\n onNavigate={onNavigate}\n />\n ))}\n\n <View style={styles.sidebarSpacer} />\n\n {footer}\n </View>\n );\n};\n\nexport { isRouteActive };\nexport default Sidebar;\n","/**\n * Default testIDs for `@dloizides/ui-nav`. Kept as a small central map (mirrors\n * `@dloizides/ui-layout`'s `LAYOUT_TEST_IDS`) so the app-agnostic chrome exposes\n * stable Playwright selectors. The existing Topbar logout/account buttons keep\n * their *caller-supplied* testIDs (via `TopbarAction.testID`) — these constants\n * only name the NEW account-state bits and the AppShell region suffixes.\n */\nexport const NAV_TEST_IDS = {\n /** displayName line in the rich account header (tappable when `onAccount` set). */\n accountName: 'topbar-account-name',\n /** muted tenant-name line under the displayName. */\n accountTenant: 'topbar-account-tenant',\n /** small plan badge (free / pro / enterprise). */\n accountPlan: 'topbar-account-plan',\n /** default testID for the rich header's upgrade action (overridable per-action). */\n accountUpgrade: 'topbar-account-upgrade',\n} as const;\n\n/** Suffixes appended to an `AppShell`'s required `testID` to name its regions. */\nexport const APP_SHELL_SUFFIX = {\n content: '-content',\n header: '-header',\n nav: '-nav',\n banner: '-banner',\n pending: '-pending',\n loading: '-loading',\n error: '-error',\n forbidden: '-forbidden',\n} as const;\n","/**\n * Topbar — the config-driven top navigation bar promoted from the byte-identical\n * erevna-web / katalogos-web `Topbar`. Structured slots replace the app-specific\n * wiring: a `left` logo slot, an optional language toggle, a notification slot,\n * an optional user block, an optional account button, and a required logout\n * action. Every colour comes from the UiProvider theme.\n *\n * Move 1c enriches this WITHOUT breaking the promoted contract: `account` now\n * accepts EITHER the legacy `TopbarAction` (today's erevna/katalogos calls) OR a\n * richer `AccountState` (displayName + muted tenant line + plan badge + upgrade\n * action). The `logout` action stays required, so every existing call keeps\n * compiling and the logout button keeps its caller-supplied testID.\n */\nimport React from 'react';\n\nimport { Text, TouchableOpacity, View, type ViewStyle } from 'react-native';\n\nimport { useUi, type UiTheme } from '@dloizides/ui-feedback';\n\nimport { NAV_TEST_IDS } from './constants';\nimport { navStyles as styles } from './styles';\n\n/** Text color for elements on primary/coloured badge backgrounds. */\nconst TEXT_ON_PRIMARY = '#ffffff';\n\nexport interface TopbarAction {\n label: string;\n hint: string;\n onPress: () => void;\n testID?: string;\n}\n\nexport interface TopbarUser {\n name: string;\n email: string;\n}\n\n/** Plan tier shown as a small badge in the rich account header. */\nexport interface AccountPlan {\n label: string;\n tone?: 'free' | 'pro' | 'enterprise';\n}\n\n/**\n * Richer account model for the header (Move 1c). Supplied via `account` as an\n * alternative to the legacy `TopbarAction` — the Topbar renders the display name\n * (tappable when `onAccount` is set), a muted tenant line, a plan badge, and an\n * `upgrade` action beside logout. No FM/router/store — everything is a\n * pre-localized string or a callback.\n */\nexport interface AccountState {\n displayName: string;\n tenantName?: string;\n plan?: AccountPlan;\n onLogout: () => void;\n onAccount?: () => void;\n upgrade?: TopbarAction;\n}\n\n/** Type guard: distinguishes the rich `AccountState` from a legacy `TopbarAction`. */\nfunction isAccountState(account: TopbarAction | AccountState): account is AccountState {\n return 'displayName' in account;\n}\n\n/** Resolve a plan badge background from its tone, routed through the theme. */\nfunction planToneColor(theme: UiTheme, tone: AccountPlan['tone']): string {\n const palette = theme.palette;\n if (tone === 'pro') return palette.primary['500'];\n if (tone === 'enterprise') return palette.accent?.['500'] ?? palette.primary['500'];\n return theme.colors.textSecondary;\n}\n\nexport interface TopbarProps {\n /** Left slot — typically the tenant logo. */\n left?: React.ReactNode;\n /** Optional language toggle. */\n language?: { label: string; hint: string; onPress: () => void };\n /** Optional notification slot (e.g. a notification bell). */\n notificationSlot?: React.ReactNode;\n /** Optional user identity block. */\n user?: TopbarUser;\n /**\n * Optional account slot. Legacy callers pass a `TopbarAction` (an account\n * button). Move-1c callers pass an `AccountState` for the richer header.\n */\n account?: TopbarAction | AccountState;\n /** Required logout action (drives the stable logout button testID). */\n logout: TopbarAction;\n /** Extra container style overrides. */\n containerStyle?: ViewStyle | ViewStyle[];\n}\n\nexport const Topbar = ({\n left,\n language,\n notificationSlot,\n user,\n account,\n logout,\n containerStyle,\n}: TopbarProps): React.ReactElement => {\n const { theme } = useUi();\n const colors = theme.colors;\n const primaryColor = theme.palette.primary['500'];\n\n const richAccount = account !== undefined && isAccountState(account) ? account : undefined;\n const legacyAccount = account !== undefined && !isAccountState(account) ? account : undefined;\n const upgrade = richAccount?.upgrade;\n\n return (\n <View\n style={[\n styles.topbarContainer,\n { borderBottomColor: colors.border, backgroundColor: colors.background },\n containerStyle,\n ]}\n >\n <View style={styles.topbarLeft}>{left}</View>\n <View style={styles.topbarRight}>\n {language ? (\n <TouchableOpacity\n accessibilityHint={language.hint}\n accessibilityLabel={language.label}\n accessibilityRole=\"button\"\n style={styles.topbarRowItem}\n onPress={language.onPress}\n >\n <Text style={[styles.topbarLabel, { color: colors.text }]}>{language.label}</Text>\n </TouchableOpacity>\n ) : null}\n\n {notificationSlot}\n\n {user ? (\n <View style={styles.userBlock}>\n <Text style={[styles.userName, { color: colors.text }]}>{user.name}</Text>\n <Text style={[styles.userEmail, { color: colors.textSecondary }]}>{user.email}</Text>\n </View>\n ) : null}\n\n {richAccount ? (\n <View style={styles.userBlock}>\n {richAccount.onAccount ? (\n <TouchableOpacity\n accessibilityHint={richAccount.displayName}\n accessibilityLabel={richAccount.displayName}\n accessibilityRole=\"button\"\n testID={NAV_TEST_IDS.accountName}\n onPress={richAccount.onAccount}\n >\n <Text style={[styles.userName, { color: colors.text }]}>{richAccount.displayName}</Text>\n </TouchableOpacity>\n ) : (\n <Text style={[styles.userName, { color: colors.text }]} testID={NAV_TEST_IDS.accountName}>\n {richAccount.displayName}\n </Text>\n )}\n\n {richAccount.tenantName !== undefined ? (\n <Text\n style={[styles.accountTenant, { color: colors.textSecondary }]}\n testID={NAV_TEST_IDS.accountTenant}\n >\n {richAccount.tenantName}\n </Text>\n ) : null}\n\n {richAccount.plan ? (\n <View\n style={[styles.planBadge, { backgroundColor: planToneColor(theme, richAccount.plan.tone) }]}\n testID={NAV_TEST_IDS.accountPlan}\n >\n <Text style={[styles.planBadgeText, { color: TEXT_ON_PRIMARY }]}>{richAccount.plan.label}</Text>\n </View>\n ) : null}\n </View>\n ) : null}\n\n {legacyAccount ? (\n <TouchableOpacity\n accessibilityHint={legacyAccount.hint}\n accessibilityLabel={legacyAccount.label}\n accessibilityRole=\"button\"\n style={[styles.accountBtn, { backgroundColor: primaryColor }]}\n testID={legacyAccount.testID}\n onPress={legacyAccount.onPress}\n >\n <Text style={[styles.accountText, { color: TEXT_ON_PRIMARY }]}>{legacyAccount.label}</Text>\n </TouchableOpacity>\n ) : null}\n\n {upgrade ? (\n <TouchableOpacity\n accessibilityHint={upgrade.hint}\n accessibilityLabel={upgrade.label}\n accessibilityRole=\"button\"\n style={[styles.accountBtn, { backgroundColor: primaryColor }]}\n testID={upgrade.testID ?? NAV_TEST_IDS.accountUpgrade}\n onPress={upgrade.onPress}\n >\n <Text style={[styles.accountText, { color: TEXT_ON_PRIMARY }]}>{upgrade.label}</Text>\n </TouchableOpacity>\n ) : null}\n\n <TouchableOpacity\n accessible\n accessibilityHint={logout.hint}\n accessibilityLabel={logout.label}\n accessibilityRole=\"button\"\n style={[styles.accountBtn, { backgroundColor: primaryColor }]}\n testID={logout.testID}\n onPress={richAccount ? richAccount.onLogout : logout.onPress}\n >\n <Text style={[styles.accountText, { color: TEXT_ON_PRIMARY }]}>{logout.label}</Text>\n </TouchableOpacity>\n </View>\n </View>\n );\n};\n\nexport default Topbar;\n","/**\n * Width-discipline helper for `AppShell` (Move 1c). Split out from the component\n * so the (viewport → max-width) rule is a small, directly unit-testable pure\n * function, with a thin hook wrapper that feeds it the live viewport width.\n */\nimport { useWindowDimensions } from 'react-native';\n\n/** Content-width policy: a capped column, optionally wider past a viewport breakpoint, or full-bleed. */\nexport type AppShellWidth =\n | { max: number; wideMax?: number; wideMinViewport?: number }\n | 'full';\n\n/**\n * Resolve the content column's max width for a given viewport. `'full'` passes\n * through (no cap); an object caps at `max`, widening to `wideMax` once the\n * viewport reaches `wideMinViewport`.\n */\nexport function resolveContentMaxWidth(width: AppShellWidth, viewport: number): number | 'full' {\n if (width === 'full') return 'full';\n const wideMinViewport = width.wideMinViewport ?? Number.POSITIVE_INFINITY;\n const isWide = viewport >= wideMinViewport;\n return isWide ? (width.wideMax ?? width.max) : width.max;\n}\n\n/** Hook wrapper: resolves the content max width against the live window width. */\nexport function useContentMaxWidth(width: AppShellWidth): number | 'full' {\n const { width: viewport } = useWindowDimensions();\n return resolveContentMaxWidth(width, viewport);\n}\n","/**\n * AppShell — the shared dashboard-chrome composition (Move 1c). It hosts, top to\n * bottom: an app-supplied header (full-bleed), an optional per-app nav region, an\n * optional banner, and a width-disciplined scrollable content column. It also\n * folds in the two things every product page re-implements: an auth gate\n * (pending spinner / redirect-when-unauthenticated) and page state cards\n * (loading / error / forbidden).\n *\n * Contract discipline (same as the rest of `@dloizides/ui-nav`): NO FM, router,\n * or store imports. The app passes wired elements, pre-localized strings, and\n * callbacks; every colour is routed through the `@dloizides/ui-feedback` theme.\n */\nimport React, { useEffect } from 'react';\n\nimport { ActivityIndicator, ScrollView, StyleSheet, Text, View, type ViewStyle } from 'react-native';\n\nimport { useUi } from '@dloizides/ui-feedback';\n\nimport { APP_SHELL_SUFFIX } from './constants';\nimport { useContentMaxWidth, type AppShellWidth } from './useContentMaxWidth';\n\nconst DEFAULT_CONTENT_PADDING = 24;\nconst CARD_PADDING = 20;\nconst CARD_BORDER_RADIUS = 12;\nconst CARD_BORDER_WIDTH = 1;\nconst CARD_TITLE_FONT_SIZE = 16;\nconst CARD_MESSAGE_FONT_SIZE = 14;\nconst CARD_TITLE_MARGIN_BOTTOM = 8;\n\nconst styles = StyleSheet.create({\n root: { flex: 1 },\n centerFill: { flex: 1, justifyContent: 'center', alignItems: 'center' },\n scroll: { flex: 1 },\n scrollContent: { flexGrow: 1 },\n columnFull: { flex: 1 },\n columnCapped: { flex: 1, width: '100%', alignSelf: 'center' },\n // Non-flex variant used to cap the nav strip's inner content to the content\n // column so the nav aligns with the page content instead of running full-bleed.\n chromeCapped: { width: '100%', alignSelf: 'center' },\n card: {\n padding: CARD_PADDING,\n borderRadius: CARD_BORDER_RADIUS,\n borderWidth: CARD_BORDER_WIDTH,\n },\n cardTitle: { fontSize: CARD_TITLE_FONT_SIZE, fontWeight: '700', marginBottom: CARD_TITLE_MARGIN_BOTTOM },\n cardMessage: { fontSize: CARD_MESSAGE_FONT_SIZE },\n});\n\n/** A titled message card (error / forbidden). Colours come from the theme. */\nexport interface ShellMessage {\n titleText: string;\n messageText: string;\n}\n\nexport interface AppShellProps {\n /** App-supplied wired header (Topbar / AppHeader), rendered full-bleed. */\n header: React.ReactNode;\n /** Per-app nav region (kefi coral pills / erevna sidebar+drawer). */\n nav?: React.ReactNode;\n /** Banner slot (e.g. verification-pending). */\n banner?: React.ReactNode;\n /** Content-width policy. Defaults to full-bleed. */\n width?: AppShellWidth;\n /** Content padding inside the scroll column. Defaults to 24. */\n contentPadding?: number;\n /**\n * Whether the nav strip aligns with the width-capped content column\n * (`'content'`) or runs full-bleed (`'full'`, the default). Only has an effect\n * when `width` caps the content. The header always renders full-bleed.\n */\n chromeAlignment?: 'full' | 'content';\n /** Auth gate — spinner while pending, `onRedirect` when unauthenticated. */\n gate?: { pending: boolean; authenticated: boolean; onRedirect: () => void };\n /** Page state — loading spinner card, or a titled error / forbidden card. */\n state?: {\n loading?: boolean;\n error?: ShellMessage | null;\n forbidden?: ShellMessage | null;\n };\n /** Root testID; the content region is exposed as `${testID}-content`. */\n testID: string;\n children: React.ReactNode;\n}\n\nfunction MessageCard({\n message,\n accentColor,\n testID,\n}: {\n message: ShellMessage;\n accentColor: string;\n testID: string;\n}): React.ReactElement {\n const { theme } = useUi();\n const colors = theme.colors;\n return (\n <View\n style={[styles.card, { backgroundColor: colors.surface, borderColor: accentColor }]}\n testID={testID}\n >\n <Text style={[styles.cardTitle, { color: accentColor }]}>{message.titleText}</Text>\n <Text style={[styles.cardMessage, { color: colors.textSecondary }]}>{message.messageText}</Text>\n </View>\n );\n}\n\n/** Pick what renders inside the content column, honouring state precedence. */\nfunction useContentBody(\n state: AppShellProps['state'],\n children: React.ReactNode,\n testID: string,\n): React.ReactNode {\n const { theme } = useUi();\n const primary = theme.palette.primary['500'];\n const errorColor = theme.semantic.error['500'];\n\n if (state?.forbidden)\n return <MessageCard accentColor={errorColor} message={state.forbidden} testID={`${testID}${APP_SHELL_SUFFIX.forbidden}`} />;\n if (state?.error)\n return <MessageCard accentColor={errorColor} message={state.error} testID={`${testID}${APP_SHELL_SUFFIX.error}`} />;\n if (state?.loading === true)\n return (\n <View style={styles.centerFill} testID={`${testID}${APP_SHELL_SUFFIX.loading}`}>\n <ActivityIndicator color={primary} size=\"large\" />\n </View>\n );\n return children;\n}\n\nexport const AppShell = ({\n header,\n nav,\n banner,\n width = 'full',\n contentPadding = DEFAULT_CONTENT_PADDING,\n chromeAlignment = 'full',\n gate,\n state,\n testID,\n children,\n}: AppShellProps): React.ReactElement | null => {\n const { theme } = useUi();\n const primary = theme.palette.primary['500'];\n const maxWidth = useContentMaxWidth(width);\n const body = useContentBody(state, children, testID);\n\n const isUnauthenticated = gate !== undefined && !gate.pending && !gate.authenticated;\n useEffect(() => {\n if (isUnauthenticated && gate !== undefined) gate.onRedirect();\n }, [isUnauthenticated, gate]);\n\n if (gate?.pending === true)\n return (\n <View style={[styles.root, styles.centerFill, { backgroundColor: theme.colors.background }]} testID={`${testID}${APP_SHELL_SUFFIX.pending}`}>\n <ActivityIndicator color={primary} size=\"large\" />\n </View>\n );\n\n if (isUnauthenticated) return null;\n\n const columnStyle: ViewStyle | ViewStyle[] =\n maxWidth === 'full' ? styles.columnFull : [styles.columnCapped, { maxWidth }];\n\n // Align the nav strip's inner content with the capped content column when asked\n // (and only when the content is actually capped); otherwise it runs full-bleed.\n const navInnerStyle: ViewStyle | ViewStyle[] | undefined =\n chromeAlignment === 'content' && maxWidth !== 'full'\n ? [styles.chromeCapped, { maxWidth, paddingHorizontal: contentPadding }]\n : undefined;\n\n return (\n <View style={[styles.root, { backgroundColor: theme.colors.background }]} testID={testID}>\n <View testID={`${testID}${APP_SHELL_SUFFIX.header}`}>{header}</View>\n {nav !== undefined ? (\n <View testID={`${testID}${APP_SHELL_SUFFIX.nav}`}>\n {navInnerStyle !== undefined ? <View style={navInnerStyle}>{nav}</View> : nav}\n </View>\n ) : null}\n {banner !== undefined ? <View testID={`${testID}${APP_SHELL_SUFFIX.banner}`}>{banner}</View> : null}\n <ScrollView\n contentContainerStyle={[styles.scrollContent, { padding: contentPadding }]}\n style={styles.scroll}\n testID={`${testID}${APP_SHELL_SUFFIX.content}`}\n >\n <View style={columnStyle}>{body}</View>\n </ScrollView>\n </View>\n );\n};\n\nexport default AppShell;\n","/**\n * Role-gated nav helpers — build a `NavItem[]` from a user's roles, reusing the\n * existing `resolveAccessibleRoutes` from `@dloizides/auth-web` (the same helper\n * the multi-role dashboard nav uses) rather than reimplementing role filtering.\n *\n * The app supplies its ordered `RoleRouteTable` (role → route + optional\n * `labelKey`) and a `translate` fn (its `FM`); this returns the ordered set of\n * `NavItem`s the user's roles unlock — most privileged first, empty when none.\n */\nimport { resolveAccessibleRoutes } from '@dloizides/auth-web';\nimport type { RoleRoute, RoleRouteTable } from '@dloizides/auth-web';\n\nimport type { NavItem } from './types';\n\n/** The user shape `resolveAccessibleRoutes` accepts (derived — no extra dep). */\nexport type NavUser = Parameters<typeof resolveAccessibleRoutes>[0];\n\n/** Map already-resolved role routes to nav items, localizing each `labelKey`. */\nexport function roleRoutesToNavItems(\n routes: RoleRoute[],\n translate: (key: string) => string,\n): NavItem[] {\n return routes.map((entry) => ({\n key: entry.role,\n route: entry.route,\n label: entry.labelKey !== undefined ? translate(entry.labelKey) : entry.route,\n }));\n}\n\n/**\n * Resolve the nav items a user can reach, in table (priority) order. Wraps\n * `resolveAccessibleRoutes` so role gating lives in one place.\n */\nexport function accessibleNavItems(\n user: NavUser,\n table: RoleRouteTable,\n translate: (key: string) => string,\n): NavItem[] {\n return roleRoutesToNavItems(resolveAccessibleRoutes(user, table), translate);\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/isRouteActive.ts","../src/styles.ts","../src/NavExpandableItem.tsx","../src/Sidebar.tsx","../src/constants.ts","../src/Topbar.tsx","../src/NavBar.tsx","../src/useContentMaxWidth.ts","../src/AppShell.tsx","../src/roleNav.ts"],"names":["StyleSheet","useState","useUi","useCallback","useMemo","jsxs","TouchableOpacity","jsx","View","Text","TEXT_ON_PRIMARY","useWindowDimensions","ActivityIndicator","useEffect","ScrollView","resolveAccessibleRoutes"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Active-route matcher shared by the sidebar entries. Ported verbatim from the\n * twin app sidebars: an item is active when the current pathname equals its\n * route or is nested under it (`/foo` matches `/foo` and `/foo/bar`, but `/`\n * only matches `/`).\n */\nexport function isRouteActive(pathname: string, route: string): boolean {\n if (route === '/') return pathname === '/';\n return pathname === route || pathname.startsWith(`${route}/`);\n}\n","/**\n * Layout styles for the nav shell — ported verbatim (dimensions unchanged) from\n * erevna-web / katalogos-web `theme/utils/layoutSidebar.ts` + `layoutTopbar.ts`,\n * so a migrated app's sidebar/topbar keep the exact same metrics. Colours are\n * NOT baked in here; they are applied at render time from the UiProvider theme.\n */\nimport { StyleSheet } from 'react-native';\n\nexport const ACTIVE_BORDER_RADIUS = 4;\n\nexport const navStyles = StyleSheet.create({\n // --- Sidebar ---\n sidebarContainer: {\n width: 220,\n paddingTop: 24,\n paddingHorizontal: 12,\n borderRightWidth: 1,\n height: '100%',\n },\n sidebarTitle: {\n fontWeight: '700',\n marginBottom: 12,\n },\n sidebarItem: {\n paddingVertical: 10,\n paddingHorizontal: 6,\n borderRadius: 4,\n },\n sidebarItemText: {\n fontSize: 16,\n },\n sidebarSpacer: {\n flex: 1,\n },\n\n // --- Topbar ---\n topbarContainer: {\n height: 64,\n paddingHorizontal: 12,\n borderBottomWidth: 1,\n flexDirection: 'row',\n alignItems: 'center',\n justifyContent: 'space-between',\n },\n topbarLeft: { flex: 1 },\n topbarRight: { flexDirection: 'row', alignItems: 'center' },\n topbarRowItem: { marginHorizontal: 8, alignItems: 'center' },\n topbarLabel: { fontSize: 14 },\n userBlock: { marginHorizontal: 12, alignItems: 'flex-end' },\n userName: { fontWeight: '600' },\n userEmail: { fontSize: 12 },\n accountBtn: { marginLeft: 8, paddingHorizontal: 10, paddingVertical: 6, borderRadius: 4 },\n accountText: { fontWeight: '600' },\n // --- Rich account-state header (Move 1c) ---\n accountTenant: { fontSize: 12 },\n planBadge: { marginTop: 2, paddingHorizontal: 8, paddingVertical: 2, borderRadius: 10, alignSelf: 'flex-end' },\n planBadgeText: { fontSize: 11, fontWeight: '700' },\n});\n\nexport const navBarStyles = StyleSheet.create({\n // --- Horizontal top NavBar ---\n container: {\n borderBottomWidth: 1,\n paddingHorizontal: 12,\n },\n inner: {\n minHeight: 56,\n flexDirection: 'row',\n alignItems: 'center',\n flexWrap: 'wrap',\n columnGap: 8,\n rowGap: 4,\n paddingVertical: 8,\n },\n brand: { marginRight: 16 },\n toggle: {\n marginLeft: 'auto',\n paddingHorizontal: 10,\n paddingVertical: 6,\n borderRadius: 4,\n },\n toggleGlyph: { fontSize: 20, fontWeight: '700' },\n links: {\n flexDirection: 'row',\n alignItems: 'center',\n flexWrap: 'wrap',\n columnGap: 4,\n rowGap: 4,\n },\n // Collapsed (below breakpoint): links drop onto their own full-width line,\n // stacked vertically — mirrors the v1 `.gn-links` responsive behaviour.\n linksStacked: {\n width: '100%',\n flexBasis: '100%',\n flexDirection: 'column',\n alignItems: 'stretch',\n rowGap: 2,\n marginTop: 8,\n },\n link: {\n paddingVertical: 8,\n paddingHorizontal: 14,\n borderRadius: 8,\n flexDirection: 'row',\n alignItems: 'center',\n },\n linkStacked: { paddingVertical: 10 },\n linkIcon: { marginRight: 6 },\n linkText: { fontSize: 15, fontWeight: '600' },\n right: {\n marginLeft: 'auto',\n flexDirection: 'row',\n alignItems: 'center',\n columnGap: 8,\n },\n rightStacked: {\n width: '100%',\n flexBasis: '100%',\n marginTop: 4,\n flexDirection: 'row',\n alignItems: 'center',\n columnGap: 8,\n },\n});\n\nexport const expandableStyles = StyleSheet.create({\n childItem: {\n borderRadius: 4,\n flexDirection: 'row',\n alignItems: 'center',\n paddingVertical: 6,\n },\n childItemText: { fontSize: 14 },\n childItemTextWithIcon: { fontSize: 14, marginLeft: 6 },\n chevron: { marginLeft: 'auto' },\n childrenContainer: { overflow: 'hidden' },\n header: {\n borderRadius: 4,\n flexDirection: 'row',\n alignItems: 'center',\n paddingVertical: 8,\n },\n headerText: { fontSize: 15, fontWeight: '600', marginLeft: 6 },\n iconWrapper: { width: 20, alignItems: 'center' },\n});\n\n/** Base indent applied per nesting depth in the expandable item. */\nexport const BASE_INDENT = 12;\n/** Default icon size for nav item icons. */\nexport const NAV_ICON_SIZE = 14;\n/** Chevron icon size for expandable sections. */\nexport const CHEVRON_ICON_SIZE = 12;\n","/**\n * NavExpandableItem — a single sidebar entry that is either a leaf (navigates)\n * or an expandable section (toggles nested children). Ported from the twin\n * erevna/katalogos `Sidebar/NavExpandableItem`, made config-driven: labels are\n * pre-localized strings, icons are render slots, colours come from `useUi`.\n */\nimport React, { useCallback, useMemo, useState } from 'react';\n\nimport { Text, TouchableOpacity, View } from 'react-native';\n\nimport { useUi } from '@dloizides/ui-feedback';\n\nimport {\n BASE_INDENT,\n CHEVRON_ICON_SIZE,\n NAV_ICON_SIZE,\n ACTIVE_BORDER_RADIUS,\n expandableStyles as styles,\n} from './styles';\nimport { isRouteActive } from './isRouteActive';\nimport type { NavItem } from './types';\n\nexport interface NavExpandableItemProps {\n item: NavItem;\n pathname: string;\n onNavigate: (route: string) => void;\n /** a11y hint for a leaf item, given its label. */\n navigateHint: (label: string) => string;\n /** a11y hint shown when the section is collapsed (press expands). */\n expandHint: string;\n /** a11y hint shown when the section is expanded (press collapses). */\n collapseHint: string;\n /** Optional chevron icon renderer for expandable sections. */\n renderChevron?: (expanded: boolean, color: string, size: number) => React.ReactNode;\n depth?: number;\n}\n\nexport const NavExpandableItem = ({\n item,\n pathname,\n onNavigate,\n navigateHint,\n expandHint,\n collapseHint,\n renderChevron,\n depth = 0,\n}: NavExpandableItemProps): React.ReactElement => {\n const [expanded, setExpanded] = useState(false);\n const { theme } = useUi();\n const colors = theme.colors;\n const primaryColor = theme.palette.primary['500'];\n\n const toggle = useCallback(() => setExpanded((v) => !v), []);\n\n const indent = depth * BASE_INDENT;\n const hasChildren = Array.isArray(item.children) && item.children.length > 0;\n const isActive = isRouteActive(pathname, item.route);\n\n const activeItemStyle = useMemo(\n () => ({ backgroundColor: colors.border, borderRadius: ACTIVE_BORDER_RADIUS }),\n [colors.border],\n );\n const paddingStyle = useMemo(() => ({ paddingLeft: indent + BASE_INDENT }), [indent]);\n const headerPaddingStyle = useMemo(() => ({ paddingLeft: indent }), [indent]);\n\n if (!hasChildren)\n return (\n <TouchableOpacity\n accessibilityHint={navigateHint(item.label)}\n accessibilityLabel={item.label}\n accessibilityRole=\"button\"\n style={[styles.childItem, paddingStyle, isActive ? activeItemStyle : undefined]}\n testID={item.testID ?? item.key}\n onPress={() => onNavigate(item.route)}\n >\n {typeof item.renderIcon === 'function' ? (\n <View style={styles.iconWrapper}>{item.renderIcon(colors.textSecondary, NAV_ICON_SIZE)}</View>\n ) : null}\n <Text\n style={[\n typeof item.renderIcon === 'function' ? styles.childItemTextWithIcon : styles.childItemText,\n { color: isActive ? primaryColor : colors.text },\n ]}\n >\n {item.label}\n </Text>\n </TouchableOpacity>\n );\n\n return (\n <View>\n <TouchableOpacity\n accessibilityHint={expanded ? collapseHint : expandHint}\n accessibilityLabel={item.label}\n accessibilityRole=\"button\"\n accessibilityState={{ expanded }}\n style={[styles.header, headerPaddingStyle]}\n testID={item.testID ?? item.key}\n onPress={toggle}\n >\n {typeof item.renderIcon === 'function' ? (\n <View style={styles.iconWrapper}>{item.renderIcon(colors.text, NAV_ICON_SIZE)}</View>\n ) : null}\n <Text style={[styles.headerText, { color: colors.text }]}>{item.label}</Text>\n {typeof renderChevron === 'function' ? (\n <View style={styles.chevron}>{renderChevron(expanded, colors.textSecondary, CHEVRON_ICON_SIZE)}</View>\n ) : null}\n </TouchableOpacity>\n\n {expanded ? (\n <View style={styles.childrenContainer}>\n {item.children?.map((child) => (\n <NavExpandableItem\n key={child.key}\n collapseHint={collapseHint}\n depth={depth + 1}\n expandHint={expandHint}\n item={child}\n navigateHint={navigateHint}\n pathname={pathname}\n renderChevron={renderChevron}\n onNavigate={onNavigate}\n />\n ))}\n </View>\n ) : null}\n </View>\n );\n};\n\nexport default NavExpandableItem;\n","/**\n * Sidebar — the config-driven left navigation shell promoted from the\n * byte-identical erevna-web / katalogos-web `Sidebar`. It renders a caller\n * supplied `NavItem[]` (leaf + expandable), highlights the active route, and\n * exposes header/footer slots for app-specific chrome (title, dark-mode toggle,\n * logout, notification bell). Every colour is read from the UiProvider theme.\n */\nimport React from 'react';\n\nimport { Text, View, type ViewStyle } from 'react-native';\n\nimport { useUi } from '@dloizides/ui-feedback';\n\nimport { isRouteActive } from './isRouteActive';\nimport { NavExpandableItem } from './NavExpandableItem';\nimport { navStyles as styles } from './styles';\nimport type { NavItem } from './types';\n\nexport interface SidebarProps {\n /** Nav entries — already role-filtered / grouped by the app. */\n items: NavItem[];\n /** Current active route/path. */\n pathname: string;\n /** Navigation callback — receives a `NavItem.route`. */\n onNavigate: (route: string) => void;\n /** Localized menu title (heading). */\n title: string;\n /** Localized accessibility label for the navigation landmark. */\n regionLabel: string;\n /** a11y hint for a leaf item, given its label. Defaults to the label. */\n navigateHint?: (label: string) => string;\n /** a11y hint shown when an expandable section is collapsed. */\n expandHint?: string;\n /** a11y hint shown when an expandable section is expanded. */\n collapseHint?: string;\n /** Optional chevron renderer for expandable sections. */\n renderChevron?: (expanded: boolean, color: string, size: number) => React.ReactNode;\n /** Optional header slot rendered above the items (e.g. a Home shortcut). */\n header?: React.ReactNode;\n /** Optional footer slot rendered after a flex spacer (dark-mode toggle, logout). */\n footer?: React.ReactNode;\n /** Extra container style overrides. */\n containerStyle?: ViewStyle | ViewStyle[];\n}\n\nexport const Sidebar = ({\n items,\n pathname,\n onNavigate,\n title,\n regionLabel,\n navigateHint = (label) => label,\n expandHint = '',\n collapseHint = '',\n renderChevron,\n header,\n footer,\n containerStyle,\n}: SidebarProps): React.ReactElement => {\n const { theme } = useUi();\n const colors = theme.colors;\n\n return (\n <View\n accessibilityRole=\"none\"\n aria-label={regionLabel}\n role=\"navigation\"\n style={[\n styles.sidebarContainer,\n { backgroundColor: colors.surface, borderRightColor: colors.border },\n containerStyle,\n ]}\n >\n <Text accessibilityRole=\"header\" style={[styles.sidebarTitle, { color: colors.text }]}>\n {title}\n </Text>\n\n {header}\n\n {items.map((item) => (\n <NavExpandableItem\n key={item.key}\n collapseHint={collapseHint}\n expandHint={expandHint}\n item={item}\n navigateHint={navigateHint}\n pathname={pathname}\n renderChevron={renderChevron}\n onNavigate={onNavigate}\n />\n ))}\n\n <View style={styles.sidebarSpacer} />\n\n {footer}\n </View>\n );\n};\n\nexport { isRouteActive };\nexport default Sidebar;\n","/**\n * Default testIDs for `@dloizides/ui-nav`. Kept as a small central map (mirrors\n * `@dloizides/ui-layout`'s `LAYOUT_TEST_IDS`) so the app-agnostic chrome exposes\n * stable Playwright selectors. The existing Topbar logout/account buttons keep\n * their *caller-supplied* testIDs (via `TopbarAction.testID`) — these constants\n * only name the NEW account-state bits and the AppShell region suffixes.\n */\nexport const NAV_TEST_IDS = {\n /** displayName line in the rich account header (tappable when `onAccount` set). */\n accountName: 'topbar-account-name',\n /** muted tenant-name line under the displayName. */\n accountTenant: 'topbar-account-tenant',\n /** small plan badge (free / pro / enterprise). */\n accountPlan: 'topbar-account-plan',\n /** default testID for the rich header's upgrade action (overridable per-action). */\n accountUpgrade: 'topbar-account-upgrade',\n /** responsive hamburger toggle in the horizontal NavBar (shown below the breakpoint). */\n navBarToggle: 'navbar-toggle',\n /** links container in the horizontal NavBar. */\n navBarLinks: 'navbar-links',\n} as const;\n\n/** Suffixes appended to an `AppShell`'s required `testID` to name its regions. */\nexport const APP_SHELL_SUFFIX = {\n content: '-content',\n header: '-header',\n nav: '-nav',\n banner: '-banner',\n pending: '-pending',\n loading: '-loading',\n error: '-error',\n forbidden: '-forbidden',\n} as const;\n","/**\n * Topbar — the config-driven top navigation bar promoted from the byte-identical\n * erevna-web / katalogos-web `Topbar`. Structured slots replace the app-specific\n * wiring: a `left` logo slot, an optional language toggle, a notification slot,\n * an optional user block, an optional account button, and a required logout\n * action. Every colour comes from the UiProvider theme.\n *\n * Move 1c enriches this WITHOUT breaking the promoted contract: `account` now\n * accepts EITHER the legacy `TopbarAction` (today's erevna/katalogos calls) OR a\n * richer `AccountState` (displayName + muted tenant line + plan badge + upgrade\n * action). The `logout` action stays required, so every existing call keeps\n * compiling and the logout button keeps its caller-supplied testID.\n */\nimport React from 'react';\n\nimport { Text, TouchableOpacity, View, type ViewStyle } from 'react-native';\n\nimport { useUi, type UiTheme } from '@dloizides/ui-feedback';\n\nimport { NAV_TEST_IDS } from './constants';\nimport { navStyles as styles } from './styles';\n\n/** Text color for elements on primary/coloured badge backgrounds. */\nconst TEXT_ON_PRIMARY = '#ffffff';\n\nexport interface TopbarAction {\n label: string;\n hint: string;\n onPress: () => void;\n testID?: string;\n}\n\nexport interface TopbarUser {\n name: string;\n email: string;\n}\n\n/** Plan tier shown as a small badge in the rich account header. */\nexport interface AccountPlan {\n label: string;\n tone?: 'free' | 'pro' | 'enterprise';\n}\n\n/**\n * Richer account model for the header (Move 1c). Supplied via `account` as an\n * alternative to the legacy `TopbarAction` — the Topbar renders the display name\n * (tappable when `onAccount` is set), a muted tenant line, a plan badge, and an\n * `upgrade` action beside logout. No FM/router/store — everything is a\n * pre-localized string or a callback.\n */\nexport interface AccountState {\n displayName: string;\n tenantName?: string;\n plan?: AccountPlan;\n onLogout: () => void;\n onAccount?: () => void;\n upgrade?: TopbarAction;\n}\n\n/** Type guard: distinguishes the rich `AccountState` from a legacy `TopbarAction`. */\nfunction isAccountState(account: TopbarAction | AccountState): account is AccountState {\n return 'displayName' in account;\n}\n\n/** Resolve a plan badge background from its tone, routed through the theme. */\nfunction planToneColor(theme: UiTheme, tone: AccountPlan['tone']): string {\n const palette = theme.palette;\n if (tone === 'pro') return palette.primary['500'];\n if (tone === 'enterprise') return palette.accent?.['500'] ?? palette.primary['500'];\n return theme.colors.textSecondary;\n}\n\nexport interface TopbarProps {\n /** Left slot — typically the tenant logo. */\n left?: React.ReactNode;\n /** Optional language toggle. */\n language?: { label: string; hint: string; onPress: () => void };\n /** Optional notification slot (e.g. a notification bell). */\n notificationSlot?: React.ReactNode;\n /** Optional user identity block. */\n user?: TopbarUser;\n /**\n * Optional account slot. Legacy callers pass a `TopbarAction` (an account\n * button). Move-1c callers pass an `AccountState` for the richer header.\n */\n account?: TopbarAction | AccountState;\n /** Required logout action (drives the stable logout button testID). */\n logout: TopbarAction;\n /** Extra container style overrides. */\n containerStyle?: ViewStyle | ViewStyle[];\n}\n\nexport const Topbar = ({\n left,\n language,\n notificationSlot,\n user,\n account,\n logout,\n containerStyle,\n}: TopbarProps): React.ReactElement => {\n const { theme } = useUi();\n const colors = theme.colors;\n const primaryColor = theme.palette.primary['500'];\n\n const richAccount = account !== undefined && isAccountState(account) ? account : undefined;\n const legacyAccount = account !== undefined && !isAccountState(account) ? account : undefined;\n const upgrade = richAccount?.upgrade;\n\n return (\n <View\n style={[\n styles.topbarContainer,\n { borderBottomColor: colors.border, backgroundColor: colors.background },\n containerStyle,\n ]}\n >\n <View style={styles.topbarLeft}>{left}</View>\n <View style={styles.topbarRight}>\n {language ? (\n <TouchableOpacity\n accessibilityHint={language.hint}\n accessibilityLabel={language.label}\n accessibilityRole=\"button\"\n style={styles.topbarRowItem}\n onPress={language.onPress}\n >\n <Text style={[styles.topbarLabel, { color: colors.text }]}>{language.label}</Text>\n </TouchableOpacity>\n ) : null}\n\n {notificationSlot}\n\n {user ? (\n <View style={styles.userBlock}>\n <Text style={[styles.userName, { color: colors.text }]}>{user.name}</Text>\n <Text style={[styles.userEmail, { color: colors.textSecondary }]}>{user.email}</Text>\n </View>\n ) : null}\n\n {richAccount ? (\n <View style={styles.userBlock}>\n {richAccount.onAccount ? (\n <TouchableOpacity\n accessibilityHint={richAccount.displayName}\n accessibilityLabel={richAccount.displayName}\n accessibilityRole=\"button\"\n testID={NAV_TEST_IDS.accountName}\n onPress={richAccount.onAccount}\n >\n <Text style={[styles.userName, { color: colors.text }]}>{richAccount.displayName}</Text>\n </TouchableOpacity>\n ) : (\n <Text style={[styles.userName, { color: colors.text }]} testID={NAV_TEST_IDS.accountName}>\n {richAccount.displayName}\n </Text>\n )}\n\n {richAccount.tenantName !== undefined ? (\n <Text\n style={[styles.accountTenant, { color: colors.textSecondary }]}\n testID={NAV_TEST_IDS.accountTenant}\n >\n {richAccount.tenantName}\n </Text>\n ) : null}\n\n {richAccount.plan ? (\n <View\n style={[styles.planBadge, { backgroundColor: planToneColor(theme, richAccount.plan.tone) }]}\n testID={NAV_TEST_IDS.accountPlan}\n >\n <Text style={[styles.planBadgeText, { color: TEXT_ON_PRIMARY }]}>{richAccount.plan.label}</Text>\n </View>\n ) : null}\n </View>\n ) : null}\n\n {legacyAccount ? (\n <TouchableOpacity\n accessibilityHint={legacyAccount.hint}\n accessibilityLabel={legacyAccount.label}\n accessibilityRole=\"button\"\n style={[styles.accountBtn, { backgroundColor: primaryColor }]}\n testID={legacyAccount.testID}\n onPress={legacyAccount.onPress}\n >\n <Text style={[styles.accountText, { color: TEXT_ON_PRIMARY }]}>{legacyAccount.label}</Text>\n </TouchableOpacity>\n ) : null}\n\n {upgrade ? (\n <TouchableOpacity\n accessibilityHint={upgrade.hint}\n accessibilityLabel={upgrade.label}\n accessibilityRole=\"button\"\n style={[styles.accountBtn, { backgroundColor: primaryColor }]}\n testID={upgrade.testID ?? NAV_TEST_IDS.accountUpgrade}\n onPress={upgrade.onPress}\n >\n <Text style={[styles.accountText, { color: TEXT_ON_PRIMARY }]}>{upgrade.label}</Text>\n </TouchableOpacity>\n ) : null}\n\n <TouchableOpacity\n accessible\n accessibilityHint={logout.hint}\n accessibilityLabel={logout.label}\n accessibilityRole=\"button\"\n style={[styles.accountBtn, { backgroundColor: primaryColor }]}\n testID={logout.testID}\n onPress={richAccount ? richAccount.onLogout : logout.onPress}\n >\n <Text style={[styles.accountText, { color: TEXT_ON_PRIMARY }]}>{logout.label}</Text>\n </TouchableOpacity>\n </View>\n </View>\n );\n};\n\nexport default Topbar;\n","/**\n * NavBar — the config-driven HORIZONTAL top navigation bar. It is the top-bar\n * counterpart to `Sidebar`: it renders the SAME caller-supplied `NavItem[]`\n * (already role-filtered / localized by the app) as inline links across the top\n * instead of a left rail, with an optional brand slot on the left and a free-form\n * right slot (auth link / user / logout). Below a configurable breakpoint it\n * collapses the links behind a hamburger toggle — mirroring the v1 AML console's\n * responsive `.global-nav`.\n *\n * Contract discipline (same as the rest of `@dloizides/ui-nav`): NO FM, router,\n * icon set, or store imports. Labels are pre-localized strings, icons are render\n * slots, and every colour is read from the `@dloizides/ui-feedback` UiProvider\n * theme. This is purely additive — `Sidebar`/`Topbar` are unchanged.\n */\nimport React, { useCallback, useMemo, useState } from 'react';\n\nimport {\n Text,\n TouchableOpacity,\n useWindowDimensions,\n View,\n type TouchableOpacityProps,\n type ViewStyle,\n} from 'react-native';\n\nimport { useUi } from '@dloizides/ui-feedback';\n\nimport { NAV_TEST_IDS } from './constants';\nimport { isRouteActive } from './isRouteActive';\nimport { ACTIVE_BORDER_RADIUS, navBarStyles as styles, NAV_ICON_SIZE } from './styles';\nimport type { NavItem } from './types';\n\n/** Text colour for a link sitting on the primary (active) background. */\nconst TEXT_ON_PRIMARY = '#ffffff';\n/** Collapse to a hamburger at/under this viewport width (matches v1's 760px). */\nconst DEFAULT_COLLAPSE_BELOW = 760;\n/** Default hamburger glyph (☰) when no `renderMenuIcon` is supplied. */\nconst MENU_GLYPH = '☰';\n\nexport interface NavBarProps {\n /** Nav entries — already role-filtered / grouped by the app. */\n items: NavItem[];\n /** Current active route/path. */\n pathname: string;\n /** Navigation callback — receives a `NavItem.route`. */\n onNavigate: (route: string) => void;\n /** Localized accessibility label for the navigation landmark. */\n regionLabel: string;\n /** a11y hint for a link, given its label. Defaults to the label. */\n navigateHint?: (label: string) => string;\n /** Left slot — typically the brand / tenant logo. */\n brand?: React.ReactNode;\n /** Right slot — free-form (auth link, user block, logout). */\n right?: React.ReactNode;\n /** Localized accessibility label for the responsive menu toggle. */\n menuLabel?: string;\n /** Localized accessibility hint for the responsive menu toggle. */\n menuHint?: string;\n /** Optional custom glyph renderer for the toggle; defaults to ☰. */\n renderMenuIcon?: (color: string, open: boolean) => React.ReactNode;\n /** Collapse the links behind a hamburger below this viewport width (default 760). */\n collapseBelow?: number;\n /** Extra container style overrides. */\n containerStyle?: ViewStyle | ViewStyle[];\n}\n\n/**\n * `aria-current=\"page\"` marks the active link for assistive tech. react-native's\n * prop types omit `aria-current`, but react-native-web forwards `aria-*` to the\n * DOM — so we attach it (no `any`) only when a link is active.\n */\nfunction activeAriaProps(isActive: boolean): TouchableOpacityProps & { 'aria-current'?: 'page' } {\n return isActive ? { 'aria-current': 'page' } : {};\n}\n\nexport const NavBar = ({\n items,\n pathname,\n onNavigate,\n regionLabel,\n navigateHint = (label) => label,\n brand,\n right,\n menuLabel = 'Menu',\n menuHint = '',\n renderMenuIcon,\n collapseBelow = DEFAULT_COLLAPSE_BELOW,\n containerStyle,\n}: NavBarProps): React.ReactElement => {\n const { theme } = useUi();\n const colors = theme.colors;\n const primaryColor = theme.palette.primary['500'];\n const { width } = useWindowDimensions();\n const [open, setOpen] = useState(false);\n\n const collapsed = width < collapseBelow;\n const showLinks = !collapsed || open;\n\n const toggleMenu = useCallback(() => setOpen((v) => !v), []);\n const handlePress = useCallback(\n (route: string) => {\n setOpen(false);\n onNavigate(route);\n },\n [onNavigate],\n );\n\n const activeLinkStyle = useMemo(\n () => ({ backgroundColor: primaryColor, borderRadius: ACTIVE_BORDER_RADIUS }),\n [primaryColor],\n );\n\n const renderLink = (item: NavItem): React.ReactElement => {\n const isActive = isRouteActive(pathname, item.route);\n const textColor = isActive ? TEXT_ON_PRIMARY : colors.textSecondary;\n return (\n <TouchableOpacity\n key={item.key}\n accessible\n accessibilityHint={navigateHint(item.label)}\n accessibilityLabel={item.label}\n accessibilityRole=\"link\"\n accessibilityState={{ selected: isActive }}\n style={[styles.link, collapsed ? styles.linkStacked : undefined, isActive ? activeLinkStyle : undefined]}\n testID={item.testID ?? item.key}\n onPress={() => handlePress(item.route)}\n {...activeAriaProps(isActive)}\n >\n {typeof item.renderIcon === 'function' ? (\n <View style={styles.linkIcon}>{item.renderIcon(textColor, NAV_ICON_SIZE)}</View>\n ) : null}\n <Text style={[styles.linkText, { color: textColor }]}>{item.label}</Text>\n </TouchableOpacity>\n );\n };\n\n return (\n <View\n accessibilityRole=\"none\"\n aria-label={regionLabel}\n role=\"navigation\"\n style={[\n styles.container,\n { backgroundColor: colors.surface, borderBottomColor: colors.border },\n containerStyle,\n ]}\n >\n <View style={styles.inner}>\n {brand !== undefined ? <View style={styles.brand}>{brand}</View> : null}\n\n {collapsed ? (\n <TouchableOpacity\n accessible\n accessibilityHint={menuHint}\n accessibilityLabel={menuLabel}\n accessibilityRole=\"button\"\n accessibilityState={{ expanded: open }}\n aria-expanded={open}\n style={styles.toggle}\n testID={NAV_TEST_IDS.navBarToggle}\n onPress={toggleMenu}\n >\n {typeof renderMenuIcon === 'function' ? (\n renderMenuIcon(colors.text, open)\n ) : (\n <Text style={[styles.toggleGlyph, { color: colors.text }]}>{MENU_GLYPH}</Text>\n )}\n </TouchableOpacity>\n ) : null}\n\n {showLinks ? (\n <View style={collapsed ? styles.linksStacked : styles.links} testID={NAV_TEST_IDS.navBarLinks}>\n {items.map(renderLink)}\n </View>\n ) : null}\n\n {showLinks && right !== undefined ? (\n <View style={collapsed ? styles.rightStacked : styles.right}>{right}</View>\n ) : null}\n </View>\n </View>\n );\n};\n\nexport { isRouteActive };\nexport default NavBar;\n","/**\n * Width-discipline helper for `AppShell` (Move 1c). Split out from the component\n * so the (viewport → max-width) rule is a small, directly unit-testable pure\n * function, with a thin hook wrapper that feeds it the live viewport width.\n */\nimport { useWindowDimensions } from 'react-native';\n\n/** Content-width policy: a capped column, optionally wider past a viewport breakpoint, or full-bleed. */\nexport type AppShellWidth =\n | { max: number; wideMax?: number; wideMinViewport?: number }\n | 'full';\n\n/**\n * Resolve the content column's max width for a given viewport. `'full'` passes\n * through (no cap); an object caps at `max`, widening to `wideMax` once the\n * viewport reaches `wideMinViewport`.\n */\nexport function resolveContentMaxWidth(width: AppShellWidth, viewport: number): number | 'full' {\n if (width === 'full') return 'full';\n const wideMinViewport = width.wideMinViewport ?? Number.POSITIVE_INFINITY;\n const isWide = viewport >= wideMinViewport;\n return isWide ? (width.wideMax ?? width.max) : width.max;\n}\n\n/** Hook wrapper: resolves the content max width against the live window width. */\nexport function useContentMaxWidth(width: AppShellWidth): number | 'full' {\n const { width: viewport } = useWindowDimensions();\n return resolveContentMaxWidth(width, viewport);\n}\n","/**\n * AppShell — the shared dashboard-chrome composition (Move 1c). It hosts, top to\n * bottom: an app-supplied header (full-bleed), an optional per-app nav region, an\n * optional banner, and a width-disciplined scrollable content column. It also\n * folds in the two things every product page re-implements: an auth gate\n * (pending spinner / redirect-when-unauthenticated) and page state cards\n * (loading / error / forbidden).\n *\n * Contract discipline (same as the rest of `@dloizides/ui-nav`): NO FM, router,\n * or store imports. The app passes wired elements, pre-localized strings, and\n * callbacks; every colour is routed through the `@dloizides/ui-feedback` theme.\n */\nimport React, { useEffect } from 'react';\n\nimport { ActivityIndicator, ScrollView, StyleSheet, Text, View, type ViewStyle } from 'react-native';\n\nimport { useUi } from '@dloizides/ui-feedback';\n\nimport { APP_SHELL_SUFFIX } from './constants';\nimport { useContentMaxWidth, type AppShellWidth } from './useContentMaxWidth';\n\nconst DEFAULT_CONTENT_PADDING = 24;\nconst CARD_PADDING = 20;\nconst CARD_BORDER_RADIUS = 12;\nconst CARD_BORDER_WIDTH = 1;\nconst CARD_TITLE_FONT_SIZE = 16;\nconst CARD_MESSAGE_FONT_SIZE = 14;\nconst CARD_TITLE_MARGIN_BOTTOM = 8;\n\nconst styles = StyleSheet.create({\n root: { flex: 1 },\n centerFill: { flex: 1, justifyContent: 'center', alignItems: 'center' },\n scroll: { flex: 1 },\n scrollContent: { flexGrow: 1 },\n columnFull: { flex: 1 },\n columnCapped: { flex: 1, width: '100%', alignSelf: 'center' },\n // Non-flex variant used to cap the nav strip's inner content to the content\n // column so the nav aligns with the page content instead of running full-bleed.\n chromeCapped: { width: '100%', alignSelf: 'center' },\n card: {\n padding: CARD_PADDING,\n borderRadius: CARD_BORDER_RADIUS,\n borderWidth: CARD_BORDER_WIDTH,\n },\n cardTitle: { fontSize: CARD_TITLE_FONT_SIZE, fontWeight: '700', marginBottom: CARD_TITLE_MARGIN_BOTTOM },\n cardMessage: { fontSize: CARD_MESSAGE_FONT_SIZE },\n});\n\n/** A titled message card (error / forbidden). Colours come from the theme. */\nexport interface ShellMessage {\n titleText: string;\n messageText: string;\n}\n\nexport interface AppShellProps {\n /** App-supplied wired header (Topbar / AppHeader), rendered full-bleed. */\n header: React.ReactNode;\n /** Per-app nav region (kefi coral pills / erevna sidebar+drawer). */\n nav?: React.ReactNode;\n /** Banner slot (e.g. verification-pending). */\n banner?: React.ReactNode;\n /** Content-width policy. Defaults to full-bleed. */\n width?: AppShellWidth;\n /** Content padding inside the scroll column. Defaults to 24. */\n contentPadding?: number;\n /**\n * Whether the nav strip aligns with the width-capped content column\n * (`'content'`) or runs full-bleed (`'full'`, the default). Only has an effect\n * when `width` caps the content. The header always renders full-bleed.\n */\n chromeAlignment?: 'full' | 'content';\n /** Auth gate — spinner while pending, `onRedirect` when unauthenticated. */\n gate?: { pending: boolean; authenticated: boolean; onRedirect: () => void };\n /** Page state — loading spinner card, or a titled error / forbidden card. */\n state?: {\n loading?: boolean;\n error?: ShellMessage | null;\n forbidden?: ShellMessage | null;\n };\n /** Root testID; the content region is exposed as `${testID}-content`. */\n testID: string;\n children: React.ReactNode;\n}\n\nfunction MessageCard({\n message,\n accentColor,\n testID,\n}: {\n message: ShellMessage;\n accentColor: string;\n testID: string;\n}): React.ReactElement {\n const { theme } = useUi();\n const colors = theme.colors;\n return (\n <View\n style={[styles.card, { backgroundColor: colors.surface, borderColor: accentColor }]}\n testID={testID}\n >\n <Text style={[styles.cardTitle, { color: accentColor }]}>{message.titleText}</Text>\n <Text style={[styles.cardMessage, { color: colors.textSecondary }]}>{message.messageText}</Text>\n </View>\n );\n}\n\n/** Pick what renders inside the content column, honouring state precedence. */\nfunction useContentBody(\n state: AppShellProps['state'],\n children: React.ReactNode,\n testID: string,\n): React.ReactNode {\n const { theme } = useUi();\n const primary = theme.palette.primary['500'];\n const errorColor = theme.semantic.error['500'];\n\n if (state?.forbidden)\n return <MessageCard accentColor={errorColor} message={state.forbidden} testID={`${testID}${APP_SHELL_SUFFIX.forbidden}`} />;\n if (state?.error)\n return <MessageCard accentColor={errorColor} message={state.error} testID={`${testID}${APP_SHELL_SUFFIX.error}`} />;\n if (state?.loading === true)\n return (\n <View style={styles.centerFill} testID={`${testID}${APP_SHELL_SUFFIX.loading}`}>\n <ActivityIndicator color={primary} size=\"large\" />\n </View>\n );\n return children;\n}\n\nexport const AppShell = ({\n header,\n nav,\n banner,\n width = 'full',\n contentPadding = DEFAULT_CONTENT_PADDING,\n chromeAlignment = 'full',\n gate,\n state,\n testID,\n children,\n}: AppShellProps): React.ReactElement | null => {\n const { theme } = useUi();\n const primary = theme.palette.primary['500'];\n const maxWidth = useContentMaxWidth(width);\n const body = useContentBody(state, children, testID);\n\n const isUnauthenticated = gate !== undefined && !gate.pending && !gate.authenticated;\n useEffect(() => {\n if (isUnauthenticated && gate !== undefined) gate.onRedirect();\n }, [isUnauthenticated, gate]);\n\n if (gate?.pending === true)\n return (\n <View style={[styles.root, styles.centerFill, { backgroundColor: theme.colors.background }]} testID={`${testID}${APP_SHELL_SUFFIX.pending}`}>\n <ActivityIndicator color={primary} size=\"large\" />\n </View>\n );\n\n if (isUnauthenticated) return null;\n\n const columnStyle: ViewStyle | ViewStyle[] =\n maxWidth === 'full' ? styles.columnFull : [styles.columnCapped, { maxWidth }];\n\n // Align the nav strip's inner content with the capped content column when asked\n // (and only when the content is actually capped); otherwise it runs full-bleed.\n const navInnerStyle: ViewStyle | ViewStyle[] | undefined =\n chromeAlignment === 'content' && maxWidth !== 'full'\n ? [styles.chromeCapped, { maxWidth, paddingHorizontal: contentPadding }]\n : undefined;\n\n return (\n <View style={[styles.root, { backgroundColor: theme.colors.background }]} testID={testID}>\n <View testID={`${testID}${APP_SHELL_SUFFIX.header}`}>{header}</View>\n {nav !== undefined ? (\n <View testID={`${testID}${APP_SHELL_SUFFIX.nav}`}>\n {navInnerStyle !== undefined ? <View style={navInnerStyle}>{nav}</View> : nav}\n </View>\n ) : null}\n {banner !== undefined ? <View testID={`${testID}${APP_SHELL_SUFFIX.banner}`}>{banner}</View> : null}\n <ScrollView\n contentContainerStyle={[styles.scrollContent, { padding: contentPadding }]}\n style={styles.scroll}\n testID={`${testID}${APP_SHELL_SUFFIX.content}`}\n >\n <View style={columnStyle}>{body}</View>\n </ScrollView>\n </View>\n );\n};\n\nexport default AppShell;\n","/**\n * Role-gated nav helpers — build a `NavItem[]` from a user's roles, reusing the\n * existing `resolveAccessibleRoutes` from `@dloizides/auth-web` (the same helper\n * the multi-role dashboard nav uses) rather than reimplementing role filtering.\n *\n * The app supplies its ordered `RoleRouteTable` (role → route + optional\n * `labelKey`) and a `translate` fn (its `FM`); this returns the ordered set of\n * `NavItem`s the user's roles unlock — most privileged first, empty when none.\n */\nimport { resolveAccessibleRoutes } from '@dloizides/auth-web';\nimport type { RoleRoute, RoleRouteTable } from '@dloizides/auth-web';\n\nimport type { NavItem } from './types';\n\n/** The user shape `resolveAccessibleRoutes` accepts (derived — no extra dep). */\nexport type NavUser = Parameters<typeof resolveAccessibleRoutes>[0];\n\n/** Map already-resolved role routes to nav items, localizing each `labelKey`. */\nexport function roleRoutesToNavItems(\n routes: RoleRoute[],\n translate: (key: string) => string,\n): NavItem[] {\n return routes.map((entry) => ({\n key: entry.role,\n route: entry.route,\n label: entry.labelKey !== undefined ? translate(entry.labelKey) : entry.route,\n }));\n}\n\n/**\n * Resolve the nav items a user can reach, in table (priority) order. Wraps\n * `resolveAccessibleRoutes` so role gating lives in one place.\n */\nexport function accessibleNavItems(\n user: NavUser,\n table: RoleRouteTable,\n translate: (key: string) => string,\n): NavItem[] {\n return roleRoutesToNavItems(resolveAccessibleRoutes(user, table), translate);\n}\n"]}
|
package/dist/index.mjs
CHANGED
|
@@ -59,6 +59,71 @@ var navStyles = StyleSheet.create({
|
|
|
59
59
|
planBadge: { marginTop: 2, paddingHorizontal: 8, paddingVertical: 2, borderRadius: 10, alignSelf: "flex-end" },
|
|
60
60
|
planBadgeText: { fontSize: 11, fontWeight: "700" }
|
|
61
61
|
});
|
|
62
|
+
var navBarStyles = StyleSheet.create({
|
|
63
|
+
// --- Horizontal top NavBar ---
|
|
64
|
+
container: {
|
|
65
|
+
borderBottomWidth: 1,
|
|
66
|
+
paddingHorizontal: 12
|
|
67
|
+
},
|
|
68
|
+
inner: {
|
|
69
|
+
minHeight: 56,
|
|
70
|
+
flexDirection: "row",
|
|
71
|
+
alignItems: "center",
|
|
72
|
+
flexWrap: "wrap",
|
|
73
|
+
columnGap: 8,
|
|
74
|
+
rowGap: 4,
|
|
75
|
+
paddingVertical: 8
|
|
76
|
+
},
|
|
77
|
+
brand: { marginRight: 16 },
|
|
78
|
+
toggle: {
|
|
79
|
+
marginLeft: "auto",
|
|
80
|
+
paddingHorizontal: 10,
|
|
81
|
+
paddingVertical: 6,
|
|
82
|
+
borderRadius: 4
|
|
83
|
+
},
|
|
84
|
+
toggleGlyph: { fontSize: 20, fontWeight: "700" },
|
|
85
|
+
links: {
|
|
86
|
+
flexDirection: "row",
|
|
87
|
+
alignItems: "center",
|
|
88
|
+
flexWrap: "wrap",
|
|
89
|
+
columnGap: 4,
|
|
90
|
+
rowGap: 4
|
|
91
|
+
},
|
|
92
|
+
// Collapsed (below breakpoint): links drop onto their own full-width line,
|
|
93
|
+
// stacked vertically — mirrors the v1 `.gn-links` responsive behaviour.
|
|
94
|
+
linksStacked: {
|
|
95
|
+
width: "100%",
|
|
96
|
+
flexBasis: "100%",
|
|
97
|
+
flexDirection: "column",
|
|
98
|
+
alignItems: "stretch",
|
|
99
|
+
rowGap: 2,
|
|
100
|
+
marginTop: 8
|
|
101
|
+
},
|
|
102
|
+
link: {
|
|
103
|
+
paddingVertical: 8,
|
|
104
|
+
paddingHorizontal: 14,
|
|
105
|
+
borderRadius: 8,
|
|
106
|
+
flexDirection: "row",
|
|
107
|
+
alignItems: "center"
|
|
108
|
+
},
|
|
109
|
+
linkStacked: { paddingVertical: 10 },
|
|
110
|
+
linkIcon: { marginRight: 6 },
|
|
111
|
+
linkText: { fontSize: 15, fontWeight: "600" },
|
|
112
|
+
right: {
|
|
113
|
+
marginLeft: "auto",
|
|
114
|
+
flexDirection: "row",
|
|
115
|
+
alignItems: "center",
|
|
116
|
+
columnGap: 8
|
|
117
|
+
},
|
|
118
|
+
rightStacked: {
|
|
119
|
+
width: "100%",
|
|
120
|
+
flexBasis: "100%",
|
|
121
|
+
marginTop: 4,
|
|
122
|
+
flexDirection: "row",
|
|
123
|
+
alignItems: "center",
|
|
124
|
+
columnGap: 8
|
|
125
|
+
}
|
|
126
|
+
});
|
|
62
127
|
var expandableStyles = StyleSheet.create({
|
|
63
128
|
childItem: {
|
|
64
129
|
borderRadius: 4,
|
|
@@ -224,7 +289,11 @@ var NAV_TEST_IDS = {
|
|
|
224
289
|
/** small plan badge (free / pro / enterprise). */
|
|
225
290
|
accountPlan: "topbar-account-plan",
|
|
226
291
|
/** default testID for the rich header's upgrade action (overridable per-action). */
|
|
227
|
-
accountUpgrade: "topbar-account-upgrade"
|
|
292
|
+
accountUpgrade: "topbar-account-upgrade",
|
|
293
|
+
/** responsive hamburger toggle in the horizontal NavBar (shown below the breakpoint). */
|
|
294
|
+
navBarToggle: "navbar-toggle",
|
|
295
|
+
/** links container in the horizontal NavBar. */
|
|
296
|
+
navBarLinks: "navbar-links"
|
|
228
297
|
};
|
|
229
298
|
var APP_SHELL_SUFFIX = {
|
|
230
299
|
content: "-content",
|
|
@@ -359,6 +428,102 @@ var Topbar = ({
|
|
|
359
428
|
}
|
|
360
429
|
);
|
|
361
430
|
};
|
|
431
|
+
var TEXT_ON_PRIMARY2 = "#ffffff";
|
|
432
|
+
var DEFAULT_COLLAPSE_BELOW = 760;
|
|
433
|
+
var MENU_GLYPH = "\u2630";
|
|
434
|
+
function activeAriaProps(isActive) {
|
|
435
|
+
return isActive ? { "aria-current": "page" } : {};
|
|
436
|
+
}
|
|
437
|
+
var NavBar = ({
|
|
438
|
+
items,
|
|
439
|
+
pathname,
|
|
440
|
+
onNavigate,
|
|
441
|
+
regionLabel,
|
|
442
|
+
navigateHint = (label) => label,
|
|
443
|
+
brand,
|
|
444
|
+
right,
|
|
445
|
+
menuLabel = "Menu",
|
|
446
|
+
menuHint = "",
|
|
447
|
+
renderMenuIcon,
|
|
448
|
+
collapseBelow = DEFAULT_COLLAPSE_BELOW,
|
|
449
|
+
containerStyle
|
|
450
|
+
}) => {
|
|
451
|
+
const { theme } = useUi();
|
|
452
|
+
const colors = theme.colors;
|
|
453
|
+
const primaryColor = theme.palette.primary["500"];
|
|
454
|
+
const { width } = useWindowDimensions();
|
|
455
|
+
const [open, setOpen] = useState(false);
|
|
456
|
+
const collapsed = width < collapseBelow;
|
|
457
|
+
const showLinks = !collapsed || open;
|
|
458
|
+
const toggleMenu = useCallback(() => setOpen((v) => !v), []);
|
|
459
|
+
const handlePress = useCallback(
|
|
460
|
+
(route) => {
|
|
461
|
+
setOpen(false);
|
|
462
|
+
onNavigate(route);
|
|
463
|
+
},
|
|
464
|
+
[onNavigate]
|
|
465
|
+
);
|
|
466
|
+
const activeLinkStyle = useMemo(
|
|
467
|
+
() => ({ backgroundColor: primaryColor, borderRadius: ACTIVE_BORDER_RADIUS }),
|
|
468
|
+
[primaryColor]
|
|
469
|
+
);
|
|
470
|
+
const renderLink = (item) => {
|
|
471
|
+
const isActive = isRouteActive(pathname, item.route);
|
|
472
|
+
const textColor = isActive ? TEXT_ON_PRIMARY2 : colors.textSecondary;
|
|
473
|
+
return /* @__PURE__ */ jsxs(
|
|
474
|
+
TouchableOpacity,
|
|
475
|
+
{
|
|
476
|
+
accessible: true,
|
|
477
|
+
accessibilityHint: navigateHint(item.label),
|
|
478
|
+
accessibilityLabel: item.label,
|
|
479
|
+
accessibilityRole: "link",
|
|
480
|
+
accessibilityState: { selected: isActive },
|
|
481
|
+
style: [navBarStyles.link, collapsed ? navBarStyles.linkStacked : void 0, isActive ? activeLinkStyle : void 0],
|
|
482
|
+
testID: item.testID ?? item.key,
|
|
483
|
+
onPress: () => handlePress(item.route),
|
|
484
|
+
...activeAriaProps(isActive),
|
|
485
|
+
children: [
|
|
486
|
+
typeof item.renderIcon === "function" ? /* @__PURE__ */ jsx(View, { style: navBarStyles.linkIcon, children: item.renderIcon(textColor, NAV_ICON_SIZE) }) : null,
|
|
487
|
+
/* @__PURE__ */ jsx(Text, { style: [navBarStyles.linkText, { color: textColor }], children: item.label })
|
|
488
|
+
]
|
|
489
|
+
},
|
|
490
|
+
item.key
|
|
491
|
+
);
|
|
492
|
+
};
|
|
493
|
+
return /* @__PURE__ */ jsx(
|
|
494
|
+
View,
|
|
495
|
+
{
|
|
496
|
+
accessibilityRole: "none",
|
|
497
|
+
"aria-label": regionLabel,
|
|
498
|
+
role: "navigation",
|
|
499
|
+
style: [
|
|
500
|
+
navBarStyles.container,
|
|
501
|
+
{ backgroundColor: colors.surface, borderBottomColor: colors.border },
|
|
502
|
+
containerStyle
|
|
503
|
+
],
|
|
504
|
+
children: /* @__PURE__ */ jsxs(View, { style: navBarStyles.inner, children: [
|
|
505
|
+
brand !== void 0 ? /* @__PURE__ */ jsx(View, { style: navBarStyles.brand, children: brand }) : null,
|
|
506
|
+
collapsed ? /* @__PURE__ */ jsx(
|
|
507
|
+
TouchableOpacity,
|
|
508
|
+
{
|
|
509
|
+
accessible: true,
|
|
510
|
+
accessibilityHint: menuHint,
|
|
511
|
+
accessibilityLabel: menuLabel,
|
|
512
|
+
accessibilityRole: "button",
|
|
513
|
+
accessibilityState: { expanded: open },
|
|
514
|
+
"aria-expanded": open,
|
|
515
|
+
style: navBarStyles.toggle,
|
|
516
|
+
testID: NAV_TEST_IDS.navBarToggle,
|
|
517
|
+
onPress: toggleMenu,
|
|
518
|
+
children: typeof renderMenuIcon === "function" ? renderMenuIcon(colors.text, open) : /* @__PURE__ */ jsx(Text, { style: [navBarStyles.toggleGlyph, { color: colors.text }], children: MENU_GLYPH })
|
|
519
|
+
}
|
|
520
|
+
) : null,
|
|
521
|
+
showLinks ? /* @__PURE__ */ jsx(View, { style: collapsed ? navBarStyles.linksStacked : navBarStyles.links, testID: NAV_TEST_IDS.navBarLinks, children: items.map(renderLink) }) : null,
|
|
522
|
+
showLinks && right !== void 0 ? /* @__PURE__ */ jsx(View, { style: collapsed ? navBarStyles.rightStacked : navBarStyles.right, children: right }) : null
|
|
523
|
+
] })
|
|
524
|
+
}
|
|
525
|
+
);
|
|
526
|
+
};
|
|
362
527
|
function resolveContentMaxWidth(width, viewport) {
|
|
363
528
|
if (width === "full") return "full";
|
|
364
529
|
const wideMinViewport = width.wideMinViewport ?? Number.POSITIVE_INFINITY;
|
|
@@ -476,6 +641,6 @@ function accessibleNavItems(user, table, translate) {
|
|
|
476
641
|
return roleRoutesToNavItems(resolveAccessibleRoutes(user, table), translate);
|
|
477
642
|
}
|
|
478
643
|
|
|
479
|
-
export { ACTIVE_BORDER_RADIUS, APP_SHELL_SUFFIX, AppShell, BASE_INDENT, CHEVRON_ICON_SIZE, NAV_ICON_SIZE, NAV_TEST_IDS, NavExpandableItem, Sidebar, Topbar, accessibleNavItems, expandableStyles, isRouteActive, navStyles, resolveContentMaxWidth, roleRoutesToNavItems, useContentMaxWidth };
|
|
644
|
+
export { ACTIVE_BORDER_RADIUS, APP_SHELL_SUFFIX, AppShell, BASE_INDENT, CHEVRON_ICON_SIZE, NAV_ICON_SIZE, NAV_TEST_IDS, NavBar, NavExpandableItem, Sidebar, Topbar, accessibleNavItems, expandableStyles, isRouteActive, navStyles, resolveContentMaxWidth, roleRoutesToNavItems, useContentMaxWidth };
|
|
480
645
|
//# sourceMappingURL=index.mjs.map
|
|
481
646
|
//# sourceMappingURL=index.mjs.map
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/isRouteActive.ts","../src/styles.ts","../src/NavExpandableItem.tsx","../src/Sidebar.tsx","../src/constants.ts","../src/Topbar.tsx","../src/useContentMaxWidth.ts","../src/AppShell.tsx","../src/roleNav.ts"],"names":["useUi","jsxs","View","jsx","Text","TouchableOpacity","StyleSheet"],"mappings":"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","file":"index.mjs","sourcesContent":["/**\n * Active-route matcher shared by the sidebar entries. Ported verbatim from the\n * twin app sidebars: an item is active when the current pathname equals its\n * route or is nested under it (`/foo` matches `/foo` and `/foo/bar`, but `/`\n * only matches `/`).\n */\nexport function isRouteActive(pathname: string, route: string): boolean {\n if (route === '/') return pathname === '/';\n return pathname === route || pathname.startsWith(`${route}/`);\n}\n","/**\n * Layout styles for the nav shell — ported verbatim (dimensions unchanged) from\n * erevna-web / katalogos-web `theme/utils/layoutSidebar.ts` + `layoutTopbar.ts`,\n * so a migrated app's sidebar/topbar keep the exact same metrics. Colours are\n * NOT baked in here; they are applied at render time from the UiProvider theme.\n */\nimport { StyleSheet } from 'react-native';\n\nexport const ACTIVE_BORDER_RADIUS = 4;\n\nexport const navStyles = StyleSheet.create({\n // --- Sidebar ---\n sidebarContainer: {\n width: 220,\n paddingTop: 24,\n paddingHorizontal: 12,\n borderRightWidth: 1,\n height: '100%',\n },\n sidebarTitle: {\n fontWeight: '700',\n marginBottom: 12,\n },\n sidebarItem: {\n paddingVertical: 10,\n paddingHorizontal: 6,\n borderRadius: 4,\n },\n sidebarItemText: {\n fontSize: 16,\n },\n sidebarSpacer: {\n flex: 1,\n },\n\n // --- Topbar ---\n topbarContainer: {\n height: 64,\n paddingHorizontal: 12,\n borderBottomWidth: 1,\n flexDirection: 'row',\n alignItems: 'center',\n justifyContent: 'space-between',\n },\n topbarLeft: { flex: 1 },\n topbarRight: { flexDirection: 'row', alignItems: 'center' },\n topbarRowItem: { marginHorizontal: 8, alignItems: 'center' },\n topbarLabel: { fontSize: 14 },\n userBlock: { marginHorizontal: 12, alignItems: 'flex-end' },\n userName: { fontWeight: '600' },\n userEmail: { fontSize: 12 },\n accountBtn: { marginLeft: 8, paddingHorizontal: 10, paddingVertical: 6, borderRadius: 4 },\n accountText: { fontWeight: '600' },\n // --- Rich account-state header (Move 1c) ---\n accountTenant: { fontSize: 12 },\n planBadge: { marginTop: 2, paddingHorizontal: 8, paddingVertical: 2, borderRadius: 10, alignSelf: 'flex-end' },\n planBadgeText: { fontSize: 11, fontWeight: '700' },\n});\n\nexport const expandableStyles = StyleSheet.create({\n childItem: {\n borderRadius: 4,\n flexDirection: 'row',\n alignItems: 'center',\n paddingVertical: 6,\n },\n childItemText: { fontSize: 14 },\n childItemTextWithIcon: { fontSize: 14, marginLeft: 6 },\n chevron: { marginLeft: 'auto' },\n childrenContainer: { overflow: 'hidden' },\n header: {\n borderRadius: 4,\n flexDirection: 'row',\n alignItems: 'center',\n paddingVertical: 8,\n },\n headerText: { fontSize: 15, fontWeight: '600', marginLeft: 6 },\n iconWrapper: { width: 20, alignItems: 'center' },\n});\n\n/** Base indent applied per nesting depth in the expandable item. */\nexport const BASE_INDENT = 12;\n/** Default icon size for nav item icons. */\nexport const NAV_ICON_SIZE = 14;\n/** Chevron icon size for expandable sections. */\nexport const CHEVRON_ICON_SIZE = 12;\n","/**\n * NavExpandableItem — a single sidebar entry that is either a leaf (navigates)\n * or an expandable section (toggles nested children). Ported from the twin\n * erevna/katalogos `Sidebar/NavExpandableItem`, made config-driven: labels are\n * pre-localized strings, icons are render slots, colours come from `useUi`.\n */\nimport React, { useCallback, useMemo, useState } from 'react';\n\nimport { Text, TouchableOpacity, View } from 'react-native';\n\nimport { useUi } from '@dloizides/ui-feedback';\n\nimport {\n BASE_INDENT,\n CHEVRON_ICON_SIZE,\n NAV_ICON_SIZE,\n ACTIVE_BORDER_RADIUS,\n expandableStyles as styles,\n} from './styles';\nimport { isRouteActive } from './isRouteActive';\nimport type { NavItem } from './types';\n\nexport interface NavExpandableItemProps {\n item: NavItem;\n pathname: string;\n onNavigate: (route: string) => void;\n /** a11y hint for a leaf item, given its label. */\n navigateHint: (label: string) => string;\n /** a11y hint shown when the section is collapsed (press expands). */\n expandHint: string;\n /** a11y hint shown when the section is expanded (press collapses). */\n collapseHint: string;\n /** Optional chevron icon renderer for expandable sections. */\n renderChevron?: (expanded: boolean, color: string, size: number) => React.ReactNode;\n depth?: number;\n}\n\nexport const NavExpandableItem = ({\n item,\n pathname,\n onNavigate,\n navigateHint,\n expandHint,\n collapseHint,\n renderChevron,\n depth = 0,\n}: NavExpandableItemProps): React.ReactElement => {\n const [expanded, setExpanded] = useState(false);\n const { theme } = useUi();\n const colors = theme.colors;\n const primaryColor = theme.palette.primary['500'];\n\n const toggle = useCallback(() => setExpanded((v) => !v), []);\n\n const indent = depth * BASE_INDENT;\n const hasChildren = Array.isArray(item.children) && item.children.length > 0;\n const isActive = isRouteActive(pathname, item.route);\n\n const activeItemStyle = useMemo(\n () => ({ backgroundColor: colors.border, borderRadius: ACTIVE_BORDER_RADIUS }),\n [colors.border],\n );\n const paddingStyle = useMemo(() => ({ paddingLeft: indent + BASE_INDENT }), [indent]);\n const headerPaddingStyle = useMemo(() => ({ paddingLeft: indent }), [indent]);\n\n if (!hasChildren)\n return (\n <TouchableOpacity\n accessibilityHint={navigateHint(item.label)}\n accessibilityLabel={item.label}\n accessibilityRole=\"button\"\n style={[styles.childItem, paddingStyle, isActive ? activeItemStyle : undefined]}\n testID={item.testID ?? item.key}\n onPress={() => onNavigate(item.route)}\n >\n {typeof item.renderIcon === 'function' ? (\n <View style={styles.iconWrapper}>{item.renderIcon(colors.textSecondary, NAV_ICON_SIZE)}</View>\n ) : null}\n <Text\n style={[\n typeof item.renderIcon === 'function' ? styles.childItemTextWithIcon : styles.childItemText,\n { color: isActive ? primaryColor : colors.text },\n ]}\n >\n {item.label}\n </Text>\n </TouchableOpacity>\n );\n\n return (\n <View>\n <TouchableOpacity\n accessibilityHint={expanded ? collapseHint : expandHint}\n accessibilityLabel={item.label}\n accessibilityRole=\"button\"\n accessibilityState={{ expanded }}\n style={[styles.header, headerPaddingStyle]}\n testID={item.testID ?? item.key}\n onPress={toggle}\n >\n {typeof item.renderIcon === 'function' ? (\n <View style={styles.iconWrapper}>{item.renderIcon(colors.text, NAV_ICON_SIZE)}</View>\n ) : null}\n <Text style={[styles.headerText, { color: colors.text }]}>{item.label}</Text>\n {typeof renderChevron === 'function' ? (\n <View style={styles.chevron}>{renderChevron(expanded, colors.textSecondary, CHEVRON_ICON_SIZE)}</View>\n ) : null}\n </TouchableOpacity>\n\n {expanded ? (\n <View style={styles.childrenContainer}>\n {item.children?.map((child) => (\n <NavExpandableItem\n key={child.key}\n collapseHint={collapseHint}\n depth={depth + 1}\n expandHint={expandHint}\n item={child}\n navigateHint={navigateHint}\n pathname={pathname}\n renderChevron={renderChevron}\n onNavigate={onNavigate}\n />\n ))}\n </View>\n ) : null}\n </View>\n );\n};\n\nexport default NavExpandableItem;\n","/**\n * Sidebar — the config-driven left navigation shell promoted from the\n * byte-identical erevna-web / katalogos-web `Sidebar`. It renders a caller\n * supplied `NavItem[]` (leaf + expandable), highlights the active route, and\n * exposes header/footer slots for app-specific chrome (title, dark-mode toggle,\n * logout, notification bell). Every colour is read from the UiProvider theme.\n */\nimport React from 'react';\n\nimport { Text, View, type ViewStyle } from 'react-native';\n\nimport { useUi } from '@dloizides/ui-feedback';\n\nimport { isRouteActive } from './isRouteActive';\nimport { NavExpandableItem } from './NavExpandableItem';\nimport { navStyles as styles } from './styles';\nimport type { NavItem } from './types';\n\nexport interface SidebarProps {\n /** Nav entries — already role-filtered / grouped by the app. */\n items: NavItem[];\n /** Current active route/path. */\n pathname: string;\n /** Navigation callback — receives a `NavItem.route`. */\n onNavigate: (route: string) => void;\n /** Localized menu title (heading). */\n title: string;\n /** Localized accessibility label for the navigation landmark. */\n regionLabel: string;\n /** a11y hint for a leaf item, given its label. Defaults to the label. */\n navigateHint?: (label: string) => string;\n /** a11y hint shown when an expandable section is collapsed. */\n expandHint?: string;\n /** a11y hint shown when an expandable section is expanded. */\n collapseHint?: string;\n /** Optional chevron renderer for expandable sections. */\n renderChevron?: (expanded: boolean, color: string, size: number) => React.ReactNode;\n /** Optional header slot rendered above the items (e.g. a Home shortcut). */\n header?: React.ReactNode;\n /** Optional footer slot rendered after a flex spacer (dark-mode toggle, logout). */\n footer?: React.ReactNode;\n /** Extra container style overrides. */\n containerStyle?: ViewStyle | ViewStyle[];\n}\n\nexport const Sidebar = ({\n items,\n pathname,\n onNavigate,\n title,\n regionLabel,\n navigateHint = (label) => label,\n expandHint = '',\n collapseHint = '',\n renderChevron,\n header,\n footer,\n containerStyle,\n}: SidebarProps): React.ReactElement => {\n const { theme } = useUi();\n const colors = theme.colors;\n\n return (\n <View\n accessibilityRole=\"none\"\n aria-label={regionLabel}\n role=\"navigation\"\n style={[\n styles.sidebarContainer,\n { backgroundColor: colors.surface, borderRightColor: colors.border },\n containerStyle,\n ]}\n >\n <Text accessibilityRole=\"header\" style={[styles.sidebarTitle, { color: colors.text }]}>\n {title}\n </Text>\n\n {header}\n\n {items.map((item) => (\n <NavExpandableItem\n key={item.key}\n collapseHint={collapseHint}\n expandHint={expandHint}\n item={item}\n navigateHint={navigateHint}\n pathname={pathname}\n renderChevron={renderChevron}\n onNavigate={onNavigate}\n />\n ))}\n\n <View style={styles.sidebarSpacer} />\n\n {footer}\n </View>\n );\n};\n\nexport { isRouteActive };\nexport default Sidebar;\n","/**\n * Default testIDs for `@dloizides/ui-nav`. Kept as a small central map (mirrors\n * `@dloizides/ui-layout`'s `LAYOUT_TEST_IDS`) so the app-agnostic chrome exposes\n * stable Playwright selectors. The existing Topbar logout/account buttons keep\n * their *caller-supplied* testIDs (via `TopbarAction.testID`) — these constants\n * only name the NEW account-state bits and the AppShell region suffixes.\n */\nexport const NAV_TEST_IDS = {\n /** displayName line in the rich account header (tappable when `onAccount` set). */\n accountName: 'topbar-account-name',\n /** muted tenant-name line under the displayName. */\n accountTenant: 'topbar-account-tenant',\n /** small plan badge (free / pro / enterprise). */\n accountPlan: 'topbar-account-plan',\n /** default testID for the rich header's upgrade action (overridable per-action). */\n accountUpgrade: 'topbar-account-upgrade',\n} as const;\n\n/** Suffixes appended to an `AppShell`'s required `testID` to name its regions. */\nexport const APP_SHELL_SUFFIX = {\n content: '-content',\n header: '-header',\n nav: '-nav',\n banner: '-banner',\n pending: '-pending',\n loading: '-loading',\n error: '-error',\n forbidden: '-forbidden',\n} as const;\n","/**\n * Topbar — the config-driven top navigation bar promoted from the byte-identical\n * erevna-web / katalogos-web `Topbar`. Structured slots replace the app-specific\n * wiring: a `left` logo slot, an optional language toggle, a notification slot,\n * an optional user block, an optional account button, and a required logout\n * action. Every colour comes from the UiProvider theme.\n *\n * Move 1c enriches this WITHOUT breaking the promoted contract: `account` now\n * accepts EITHER the legacy `TopbarAction` (today's erevna/katalogos calls) OR a\n * richer `AccountState` (displayName + muted tenant line + plan badge + upgrade\n * action). The `logout` action stays required, so every existing call keeps\n * compiling and the logout button keeps its caller-supplied testID.\n */\nimport React from 'react';\n\nimport { Text, TouchableOpacity, View, type ViewStyle } from 'react-native';\n\nimport { useUi, type UiTheme } from '@dloizides/ui-feedback';\n\nimport { NAV_TEST_IDS } from './constants';\nimport { navStyles as styles } from './styles';\n\n/** Text color for elements on primary/coloured badge backgrounds. */\nconst TEXT_ON_PRIMARY = '#ffffff';\n\nexport interface TopbarAction {\n label: string;\n hint: string;\n onPress: () => void;\n testID?: string;\n}\n\nexport interface TopbarUser {\n name: string;\n email: string;\n}\n\n/** Plan tier shown as a small badge in the rich account header. */\nexport interface AccountPlan {\n label: string;\n tone?: 'free' | 'pro' | 'enterprise';\n}\n\n/**\n * Richer account model for the header (Move 1c). Supplied via `account` as an\n * alternative to the legacy `TopbarAction` — the Topbar renders the display name\n * (tappable when `onAccount` is set), a muted tenant line, a plan badge, and an\n * `upgrade` action beside logout. No FM/router/store — everything is a\n * pre-localized string or a callback.\n */\nexport interface AccountState {\n displayName: string;\n tenantName?: string;\n plan?: AccountPlan;\n onLogout: () => void;\n onAccount?: () => void;\n upgrade?: TopbarAction;\n}\n\n/** Type guard: distinguishes the rich `AccountState` from a legacy `TopbarAction`. */\nfunction isAccountState(account: TopbarAction | AccountState): account is AccountState {\n return 'displayName' in account;\n}\n\n/** Resolve a plan badge background from its tone, routed through the theme. */\nfunction planToneColor(theme: UiTheme, tone: AccountPlan['tone']): string {\n const palette = theme.palette;\n if (tone === 'pro') return palette.primary['500'];\n if (tone === 'enterprise') return palette.accent?.['500'] ?? palette.primary['500'];\n return theme.colors.textSecondary;\n}\n\nexport interface TopbarProps {\n /** Left slot — typically the tenant logo. */\n left?: React.ReactNode;\n /** Optional language toggle. */\n language?: { label: string; hint: string; onPress: () => void };\n /** Optional notification slot (e.g. a notification bell). */\n notificationSlot?: React.ReactNode;\n /** Optional user identity block. */\n user?: TopbarUser;\n /**\n * Optional account slot. Legacy callers pass a `TopbarAction` (an account\n * button). Move-1c callers pass an `AccountState` for the richer header.\n */\n account?: TopbarAction | AccountState;\n /** Required logout action (drives the stable logout button testID). */\n logout: TopbarAction;\n /** Extra container style overrides. */\n containerStyle?: ViewStyle | ViewStyle[];\n}\n\nexport const Topbar = ({\n left,\n language,\n notificationSlot,\n user,\n account,\n logout,\n containerStyle,\n}: TopbarProps): React.ReactElement => {\n const { theme } = useUi();\n const colors = theme.colors;\n const primaryColor = theme.palette.primary['500'];\n\n const richAccount = account !== undefined && isAccountState(account) ? account : undefined;\n const legacyAccount = account !== undefined && !isAccountState(account) ? account : undefined;\n const upgrade = richAccount?.upgrade;\n\n return (\n <View\n style={[\n styles.topbarContainer,\n { borderBottomColor: colors.border, backgroundColor: colors.background },\n containerStyle,\n ]}\n >\n <View style={styles.topbarLeft}>{left}</View>\n <View style={styles.topbarRight}>\n {language ? (\n <TouchableOpacity\n accessibilityHint={language.hint}\n accessibilityLabel={language.label}\n accessibilityRole=\"button\"\n style={styles.topbarRowItem}\n onPress={language.onPress}\n >\n <Text style={[styles.topbarLabel, { color: colors.text }]}>{language.label}</Text>\n </TouchableOpacity>\n ) : null}\n\n {notificationSlot}\n\n {user ? (\n <View style={styles.userBlock}>\n <Text style={[styles.userName, { color: colors.text }]}>{user.name}</Text>\n <Text style={[styles.userEmail, { color: colors.textSecondary }]}>{user.email}</Text>\n </View>\n ) : null}\n\n {richAccount ? (\n <View style={styles.userBlock}>\n {richAccount.onAccount ? (\n <TouchableOpacity\n accessibilityHint={richAccount.displayName}\n accessibilityLabel={richAccount.displayName}\n accessibilityRole=\"button\"\n testID={NAV_TEST_IDS.accountName}\n onPress={richAccount.onAccount}\n >\n <Text style={[styles.userName, { color: colors.text }]}>{richAccount.displayName}</Text>\n </TouchableOpacity>\n ) : (\n <Text style={[styles.userName, { color: colors.text }]} testID={NAV_TEST_IDS.accountName}>\n {richAccount.displayName}\n </Text>\n )}\n\n {richAccount.tenantName !== undefined ? (\n <Text\n style={[styles.accountTenant, { color: colors.textSecondary }]}\n testID={NAV_TEST_IDS.accountTenant}\n >\n {richAccount.tenantName}\n </Text>\n ) : null}\n\n {richAccount.plan ? (\n <View\n style={[styles.planBadge, { backgroundColor: planToneColor(theme, richAccount.plan.tone) }]}\n testID={NAV_TEST_IDS.accountPlan}\n >\n <Text style={[styles.planBadgeText, { color: TEXT_ON_PRIMARY }]}>{richAccount.plan.label}</Text>\n </View>\n ) : null}\n </View>\n ) : null}\n\n {legacyAccount ? (\n <TouchableOpacity\n accessibilityHint={legacyAccount.hint}\n accessibilityLabel={legacyAccount.label}\n accessibilityRole=\"button\"\n style={[styles.accountBtn, { backgroundColor: primaryColor }]}\n testID={legacyAccount.testID}\n onPress={legacyAccount.onPress}\n >\n <Text style={[styles.accountText, { color: TEXT_ON_PRIMARY }]}>{legacyAccount.label}</Text>\n </TouchableOpacity>\n ) : null}\n\n {upgrade ? (\n <TouchableOpacity\n accessibilityHint={upgrade.hint}\n accessibilityLabel={upgrade.label}\n accessibilityRole=\"button\"\n style={[styles.accountBtn, { backgroundColor: primaryColor }]}\n testID={upgrade.testID ?? NAV_TEST_IDS.accountUpgrade}\n onPress={upgrade.onPress}\n >\n <Text style={[styles.accountText, { color: TEXT_ON_PRIMARY }]}>{upgrade.label}</Text>\n </TouchableOpacity>\n ) : null}\n\n <TouchableOpacity\n accessible\n accessibilityHint={logout.hint}\n accessibilityLabel={logout.label}\n accessibilityRole=\"button\"\n style={[styles.accountBtn, { backgroundColor: primaryColor }]}\n testID={logout.testID}\n onPress={richAccount ? richAccount.onLogout : logout.onPress}\n >\n <Text style={[styles.accountText, { color: TEXT_ON_PRIMARY }]}>{logout.label}</Text>\n </TouchableOpacity>\n </View>\n </View>\n );\n};\n\nexport default Topbar;\n","/**\n * Width-discipline helper for `AppShell` (Move 1c). Split out from the component\n * so the (viewport → max-width) rule is a small, directly unit-testable pure\n * function, with a thin hook wrapper that feeds it the live viewport width.\n */\nimport { useWindowDimensions } from 'react-native';\n\n/** Content-width policy: a capped column, optionally wider past a viewport breakpoint, or full-bleed. */\nexport type AppShellWidth =\n | { max: number; wideMax?: number; wideMinViewport?: number }\n | 'full';\n\n/**\n * Resolve the content column's max width for a given viewport. `'full'` passes\n * through (no cap); an object caps at `max`, widening to `wideMax` once the\n * viewport reaches `wideMinViewport`.\n */\nexport function resolveContentMaxWidth(width: AppShellWidth, viewport: number): number | 'full' {\n if (width === 'full') return 'full';\n const wideMinViewport = width.wideMinViewport ?? Number.POSITIVE_INFINITY;\n const isWide = viewport >= wideMinViewport;\n return isWide ? (width.wideMax ?? width.max) : width.max;\n}\n\n/** Hook wrapper: resolves the content max width against the live window width. */\nexport function useContentMaxWidth(width: AppShellWidth): number | 'full' {\n const { width: viewport } = useWindowDimensions();\n return resolveContentMaxWidth(width, viewport);\n}\n","/**\n * AppShell — the shared dashboard-chrome composition (Move 1c). It hosts, top to\n * bottom: an app-supplied header (full-bleed), an optional per-app nav region, an\n * optional banner, and a width-disciplined scrollable content column. It also\n * folds in the two things every product page re-implements: an auth gate\n * (pending spinner / redirect-when-unauthenticated) and page state cards\n * (loading / error / forbidden).\n *\n * Contract discipline (same as the rest of `@dloizides/ui-nav`): NO FM, router,\n * or store imports. The app passes wired elements, pre-localized strings, and\n * callbacks; every colour is routed through the `@dloizides/ui-feedback` theme.\n */\nimport React, { useEffect } from 'react';\n\nimport { ActivityIndicator, ScrollView, StyleSheet, Text, View, type ViewStyle } from 'react-native';\n\nimport { useUi } from '@dloizides/ui-feedback';\n\nimport { APP_SHELL_SUFFIX } from './constants';\nimport { useContentMaxWidth, type AppShellWidth } from './useContentMaxWidth';\n\nconst DEFAULT_CONTENT_PADDING = 24;\nconst CARD_PADDING = 20;\nconst CARD_BORDER_RADIUS = 12;\nconst CARD_BORDER_WIDTH = 1;\nconst CARD_TITLE_FONT_SIZE = 16;\nconst CARD_MESSAGE_FONT_SIZE = 14;\nconst CARD_TITLE_MARGIN_BOTTOM = 8;\n\nconst styles = StyleSheet.create({\n root: { flex: 1 },\n centerFill: { flex: 1, justifyContent: 'center', alignItems: 'center' },\n scroll: { flex: 1 },\n scrollContent: { flexGrow: 1 },\n columnFull: { flex: 1 },\n columnCapped: { flex: 1, width: '100%', alignSelf: 'center' },\n // Non-flex variant used to cap the nav strip's inner content to the content\n // column so the nav aligns with the page content instead of running full-bleed.\n chromeCapped: { width: '100%', alignSelf: 'center' },\n card: {\n padding: CARD_PADDING,\n borderRadius: CARD_BORDER_RADIUS,\n borderWidth: CARD_BORDER_WIDTH,\n },\n cardTitle: { fontSize: CARD_TITLE_FONT_SIZE, fontWeight: '700', marginBottom: CARD_TITLE_MARGIN_BOTTOM },\n cardMessage: { fontSize: CARD_MESSAGE_FONT_SIZE },\n});\n\n/** A titled message card (error / forbidden). Colours come from the theme. */\nexport interface ShellMessage {\n titleText: string;\n messageText: string;\n}\n\nexport interface AppShellProps {\n /** App-supplied wired header (Topbar / AppHeader), rendered full-bleed. */\n header: React.ReactNode;\n /** Per-app nav region (kefi coral pills / erevna sidebar+drawer). */\n nav?: React.ReactNode;\n /** Banner slot (e.g. verification-pending). */\n banner?: React.ReactNode;\n /** Content-width policy. Defaults to full-bleed. */\n width?: AppShellWidth;\n /** Content padding inside the scroll column. Defaults to 24. */\n contentPadding?: number;\n /**\n * Whether the nav strip aligns with the width-capped content column\n * (`'content'`) or runs full-bleed (`'full'`, the default). Only has an effect\n * when `width` caps the content. The header always renders full-bleed.\n */\n chromeAlignment?: 'full' | 'content';\n /** Auth gate — spinner while pending, `onRedirect` when unauthenticated. */\n gate?: { pending: boolean; authenticated: boolean; onRedirect: () => void };\n /** Page state — loading spinner card, or a titled error / forbidden card. */\n state?: {\n loading?: boolean;\n error?: ShellMessage | null;\n forbidden?: ShellMessage | null;\n };\n /** Root testID; the content region is exposed as `${testID}-content`. */\n testID: string;\n children: React.ReactNode;\n}\n\nfunction MessageCard({\n message,\n accentColor,\n testID,\n}: {\n message: ShellMessage;\n accentColor: string;\n testID: string;\n}): React.ReactElement {\n const { theme } = useUi();\n const colors = theme.colors;\n return (\n <View\n style={[styles.card, { backgroundColor: colors.surface, borderColor: accentColor }]}\n testID={testID}\n >\n <Text style={[styles.cardTitle, { color: accentColor }]}>{message.titleText}</Text>\n <Text style={[styles.cardMessage, { color: colors.textSecondary }]}>{message.messageText}</Text>\n </View>\n );\n}\n\n/** Pick what renders inside the content column, honouring state precedence. */\nfunction useContentBody(\n state: AppShellProps['state'],\n children: React.ReactNode,\n testID: string,\n): React.ReactNode {\n const { theme } = useUi();\n const primary = theme.palette.primary['500'];\n const errorColor = theme.semantic.error['500'];\n\n if (state?.forbidden)\n return <MessageCard accentColor={errorColor} message={state.forbidden} testID={`${testID}${APP_SHELL_SUFFIX.forbidden}`} />;\n if (state?.error)\n return <MessageCard accentColor={errorColor} message={state.error} testID={`${testID}${APP_SHELL_SUFFIX.error}`} />;\n if (state?.loading === true)\n return (\n <View style={styles.centerFill} testID={`${testID}${APP_SHELL_SUFFIX.loading}`}>\n <ActivityIndicator color={primary} size=\"large\" />\n </View>\n );\n return children;\n}\n\nexport const AppShell = ({\n header,\n nav,\n banner,\n width = 'full',\n contentPadding = DEFAULT_CONTENT_PADDING,\n chromeAlignment = 'full',\n gate,\n state,\n testID,\n children,\n}: AppShellProps): React.ReactElement | null => {\n const { theme } = useUi();\n const primary = theme.palette.primary['500'];\n const maxWidth = useContentMaxWidth(width);\n const body = useContentBody(state, children, testID);\n\n const isUnauthenticated = gate !== undefined && !gate.pending && !gate.authenticated;\n useEffect(() => {\n if (isUnauthenticated && gate !== undefined) gate.onRedirect();\n }, [isUnauthenticated, gate]);\n\n if (gate?.pending === true)\n return (\n <View style={[styles.root, styles.centerFill, { backgroundColor: theme.colors.background }]} testID={`${testID}${APP_SHELL_SUFFIX.pending}`}>\n <ActivityIndicator color={primary} size=\"large\" />\n </View>\n );\n\n if (isUnauthenticated) return null;\n\n const columnStyle: ViewStyle | ViewStyle[] =\n maxWidth === 'full' ? styles.columnFull : [styles.columnCapped, { maxWidth }];\n\n // Align the nav strip's inner content with the capped content column when asked\n // (and only when the content is actually capped); otherwise it runs full-bleed.\n const navInnerStyle: ViewStyle | ViewStyle[] | undefined =\n chromeAlignment === 'content' && maxWidth !== 'full'\n ? [styles.chromeCapped, { maxWidth, paddingHorizontal: contentPadding }]\n : undefined;\n\n return (\n <View style={[styles.root, { backgroundColor: theme.colors.background }]} testID={testID}>\n <View testID={`${testID}${APP_SHELL_SUFFIX.header}`}>{header}</View>\n {nav !== undefined ? (\n <View testID={`${testID}${APP_SHELL_SUFFIX.nav}`}>\n {navInnerStyle !== undefined ? <View style={navInnerStyle}>{nav}</View> : nav}\n </View>\n ) : null}\n {banner !== undefined ? <View testID={`${testID}${APP_SHELL_SUFFIX.banner}`}>{banner}</View> : null}\n <ScrollView\n contentContainerStyle={[styles.scrollContent, { padding: contentPadding }]}\n style={styles.scroll}\n testID={`${testID}${APP_SHELL_SUFFIX.content}`}\n >\n <View style={columnStyle}>{body}</View>\n </ScrollView>\n </View>\n );\n};\n\nexport default AppShell;\n","/**\n * Role-gated nav helpers — build a `NavItem[]` from a user's roles, reusing the\n * existing `resolveAccessibleRoutes` from `@dloizides/auth-web` (the same helper\n * the multi-role dashboard nav uses) rather than reimplementing role filtering.\n *\n * The app supplies its ordered `RoleRouteTable` (role → route + optional\n * `labelKey`) and a `translate` fn (its `FM`); this returns the ordered set of\n * `NavItem`s the user's roles unlock — most privileged first, empty when none.\n */\nimport { resolveAccessibleRoutes } from '@dloizides/auth-web';\nimport type { RoleRoute, RoleRouteTable } from '@dloizides/auth-web';\n\nimport type { NavItem } from './types';\n\n/** The user shape `resolveAccessibleRoutes` accepts (derived — no extra dep). */\nexport type NavUser = Parameters<typeof resolveAccessibleRoutes>[0];\n\n/** Map already-resolved role routes to nav items, localizing each `labelKey`. */\nexport function roleRoutesToNavItems(\n routes: RoleRoute[],\n translate: (key: string) => string,\n): NavItem[] {\n return routes.map((entry) => ({\n key: entry.role,\n route: entry.route,\n label: entry.labelKey !== undefined ? translate(entry.labelKey) : entry.route,\n }));\n}\n\n/**\n * Resolve the nav items a user can reach, in table (priority) order. Wraps\n * `resolveAccessibleRoutes` so role gating lives in one place.\n */\nexport function accessibleNavItems(\n user: NavUser,\n table: RoleRouteTable,\n translate: (key: string) => string,\n): NavItem[] {\n return roleRoutesToNavItems(resolveAccessibleRoutes(user, table), translate);\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/isRouteActive.ts","../src/styles.ts","../src/NavExpandableItem.tsx","../src/Sidebar.tsx","../src/constants.ts","../src/Topbar.tsx","../src/NavBar.tsx","../src/useContentMaxWidth.ts","../src/AppShell.tsx","../src/roleNav.ts"],"names":["useUi","jsxs","View","jsx","Text","TouchableOpacity","TEXT_ON_PRIMARY","useState","useCallback","useMemo","useWindowDimensions","StyleSheet"],"mappings":"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","file":"index.mjs","sourcesContent":["/**\n * Active-route matcher shared by the sidebar entries. Ported verbatim from the\n * twin app sidebars: an item is active when the current pathname equals its\n * route or is nested under it (`/foo` matches `/foo` and `/foo/bar`, but `/`\n * only matches `/`).\n */\nexport function isRouteActive(pathname: string, route: string): boolean {\n if (route === '/') return pathname === '/';\n return pathname === route || pathname.startsWith(`${route}/`);\n}\n","/**\n * Layout styles for the nav shell — ported verbatim (dimensions unchanged) from\n * erevna-web / katalogos-web `theme/utils/layoutSidebar.ts` + `layoutTopbar.ts`,\n * so a migrated app's sidebar/topbar keep the exact same metrics. Colours are\n * NOT baked in here; they are applied at render time from the UiProvider theme.\n */\nimport { StyleSheet } from 'react-native';\n\nexport const ACTIVE_BORDER_RADIUS = 4;\n\nexport const navStyles = StyleSheet.create({\n // --- Sidebar ---\n sidebarContainer: {\n width: 220,\n paddingTop: 24,\n paddingHorizontal: 12,\n borderRightWidth: 1,\n height: '100%',\n },\n sidebarTitle: {\n fontWeight: '700',\n marginBottom: 12,\n },\n sidebarItem: {\n paddingVertical: 10,\n paddingHorizontal: 6,\n borderRadius: 4,\n },\n sidebarItemText: {\n fontSize: 16,\n },\n sidebarSpacer: {\n flex: 1,\n },\n\n // --- Topbar ---\n topbarContainer: {\n height: 64,\n paddingHorizontal: 12,\n borderBottomWidth: 1,\n flexDirection: 'row',\n alignItems: 'center',\n justifyContent: 'space-between',\n },\n topbarLeft: { flex: 1 },\n topbarRight: { flexDirection: 'row', alignItems: 'center' },\n topbarRowItem: { marginHorizontal: 8, alignItems: 'center' },\n topbarLabel: { fontSize: 14 },\n userBlock: { marginHorizontal: 12, alignItems: 'flex-end' },\n userName: { fontWeight: '600' },\n userEmail: { fontSize: 12 },\n accountBtn: { marginLeft: 8, paddingHorizontal: 10, paddingVertical: 6, borderRadius: 4 },\n accountText: { fontWeight: '600' },\n // --- Rich account-state header (Move 1c) ---\n accountTenant: { fontSize: 12 },\n planBadge: { marginTop: 2, paddingHorizontal: 8, paddingVertical: 2, borderRadius: 10, alignSelf: 'flex-end' },\n planBadgeText: { fontSize: 11, fontWeight: '700' },\n});\n\nexport const navBarStyles = StyleSheet.create({\n // --- Horizontal top NavBar ---\n container: {\n borderBottomWidth: 1,\n paddingHorizontal: 12,\n },\n inner: {\n minHeight: 56,\n flexDirection: 'row',\n alignItems: 'center',\n flexWrap: 'wrap',\n columnGap: 8,\n rowGap: 4,\n paddingVertical: 8,\n },\n brand: { marginRight: 16 },\n toggle: {\n marginLeft: 'auto',\n paddingHorizontal: 10,\n paddingVertical: 6,\n borderRadius: 4,\n },\n toggleGlyph: { fontSize: 20, fontWeight: '700' },\n links: {\n flexDirection: 'row',\n alignItems: 'center',\n flexWrap: 'wrap',\n columnGap: 4,\n rowGap: 4,\n },\n // Collapsed (below breakpoint): links drop onto their own full-width line,\n // stacked vertically — mirrors the v1 `.gn-links` responsive behaviour.\n linksStacked: {\n width: '100%',\n flexBasis: '100%',\n flexDirection: 'column',\n alignItems: 'stretch',\n rowGap: 2,\n marginTop: 8,\n },\n link: {\n paddingVertical: 8,\n paddingHorizontal: 14,\n borderRadius: 8,\n flexDirection: 'row',\n alignItems: 'center',\n },\n linkStacked: { paddingVertical: 10 },\n linkIcon: { marginRight: 6 },\n linkText: { fontSize: 15, fontWeight: '600' },\n right: {\n marginLeft: 'auto',\n flexDirection: 'row',\n alignItems: 'center',\n columnGap: 8,\n },\n rightStacked: {\n width: '100%',\n flexBasis: '100%',\n marginTop: 4,\n flexDirection: 'row',\n alignItems: 'center',\n columnGap: 8,\n },\n});\n\nexport const expandableStyles = StyleSheet.create({\n childItem: {\n borderRadius: 4,\n flexDirection: 'row',\n alignItems: 'center',\n paddingVertical: 6,\n },\n childItemText: { fontSize: 14 },\n childItemTextWithIcon: { fontSize: 14, marginLeft: 6 },\n chevron: { marginLeft: 'auto' },\n childrenContainer: { overflow: 'hidden' },\n header: {\n borderRadius: 4,\n flexDirection: 'row',\n alignItems: 'center',\n paddingVertical: 8,\n },\n headerText: { fontSize: 15, fontWeight: '600', marginLeft: 6 },\n iconWrapper: { width: 20, alignItems: 'center' },\n});\n\n/** Base indent applied per nesting depth in the expandable item. */\nexport const BASE_INDENT = 12;\n/** Default icon size for nav item icons. */\nexport const NAV_ICON_SIZE = 14;\n/** Chevron icon size for expandable sections. */\nexport const CHEVRON_ICON_SIZE = 12;\n","/**\n * NavExpandableItem — a single sidebar entry that is either a leaf (navigates)\n * or an expandable section (toggles nested children). Ported from the twin\n * erevna/katalogos `Sidebar/NavExpandableItem`, made config-driven: labels are\n * pre-localized strings, icons are render slots, colours come from `useUi`.\n */\nimport React, { useCallback, useMemo, useState } from 'react';\n\nimport { Text, TouchableOpacity, View } from 'react-native';\n\nimport { useUi } from '@dloizides/ui-feedback';\n\nimport {\n BASE_INDENT,\n CHEVRON_ICON_SIZE,\n NAV_ICON_SIZE,\n ACTIVE_BORDER_RADIUS,\n expandableStyles as styles,\n} from './styles';\nimport { isRouteActive } from './isRouteActive';\nimport type { NavItem } from './types';\n\nexport interface NavExpandableItemProps {\n item: NavItem;\n pathname: string;\n onNavigate: (route: string) => void;\n /** a11y hint for a leaf item, given its label. */\n navigateHint: (label: string) => string;\n /** a11y hint shown when the section is collapsed (press expands). */\n expandHint: string;\n /** a11y hint shown when the section is expanded (press collapses). */\n collapseHint: string;\n /** Optional chevron icon renderer for expandable sections. */\n renderChevron?: (expanded: boolean, color: string, size: number) => React.ReactNode;\n depth?: number;\n}\n\nexport const NavExpandableItem = ({\n item,\n pathname,\n onNavigate,\n navigateHint,\n expandHint,\n collapseHint,\n renderChevron,\n depth = 0,\n}: NavExpandableItemProps): React.ReactElement => {\n const [expanded, setExpanded] = useState(false);\n const { theme } = useUi();\n const colors = theme.colors;\n const primaryColor = theme.palette.primary['500'];\n\n const toggle = useCallback(() => setExpanded((v) => !v), []);\n\n const indent = depth * BASE_INDENT;\n const hasChildren = Array.isArray(item.children) && item.children.length > 0;\n const isActive = isRouteActive(pathname, item.route);\n\n const activeItemStyle = useMemo(\n () => ({ backgroundColor: colors.border, borderRadius: ACTIVE_BORDER_RADIUS }),\n [colors.border],\n );\n const paddingStyle = useMemo(() => ({ paddingLeft: indent + BASE_INDENT }), [indent]);\n const headerPaddingStyle = useMemo(() => ({ paddingLeft: indent }), [indent]);\n\n if (!hasChildren)\n return (\n <TouchableOpacity\n accessibilityHint={navigateHint(item.label)}\n accessibilityLabel={item.label}\n accessibilityRole=\"button\"\n style={[styles.childItem, paddingStyle, isActive ? activeItemStyle : undefined]}\n testID={item.testID ?? item.key}\n onPress={() => onNavigate(item.route)}\n >\n {typeof item.renderIcon === 'function' ? (\n <View style={styles.iconWrapper}>{item.renderIcon(colors.textSecondary, NAV_ICON_SIZE)}</View>\n ) : null}\n <Text\n style={[\n typeof item.renderIcon === 'function' ? styles.childItemTextWithIcon : styles.childItemText,\n { color: isActive ? primaryColor : colors.text },\n ]}\n >\n {item.label}\n </Text>\n </TouchableOpacity>\n );\n\n return (\n <View>\n <TouchableOpacity\n accessibilityHint={expanded ? collapseHint : expandHint}\n accessibilityLabel={item.label}\n accessibilityRole=\"button\"\n accessibilityState={{ expanded }}\n style={[styles.header, headerPaddingStyle]}\n testID={item.testID ?? item.key}\n onPress={toggle}\n >\n {typeof item.renderIcon === 'function' ? (\n <View style={styles.iconWrapper}>{item.renderIcon(colors.text, NAV_ICON_SIZE)}</View>\n ) : null}\n <Text style={[styles.headerText, { color: colors.text }]}>{item.label}</Text>\n {typeof renderChevron === 'function' ? (\n <View style={styles.chevron}>{renderChevron(expanded, colors.textSecondary, CHEVRON_ICON_SIZE)}</View>\n ) : null}\n </TouchableOpacity>\n\n {expanded ? (\n <View style={styles.childrenContainer}>\n {item.children?.map((child) => (\n <NavExpandableItem\n key={child.key}\n collapseHint={collapseHint}\n depth={depth + 1}\n expandHint={expandHint}\n item={child}\n navigateHint={navigateHint}\n pathname={pathname}\n renderChevron={renderChevron}\n onNavigate={onNavigate}\n />\n ))}\n </View>\n ) : null}\n </View>\n );\n};\n\nexport default NavExpandableItem;\n","/**\n * Sidebar — the config-driven left navigation shell promoted from the\n * byte-identical erevna-web / katalogos-web `Sidebar`. It renders a caller\n * supplied `NavItem[]` (leaf + expandable), highlights the active route, and\n * exposes header/footer slots for app-specific chrome (title, dark-mode toggle,\n * logout, notification bell). Every colour is read from the UiProvider theme.\n */\nimport React from 'react';\n\nimport { Text, View, type ViewStyle } from 'react-native';\n\nimport { useUi } from '@dloizides/ui-feedback';\n\nimport { isRouteActive } from './isRouteActive';\nimport { NavExpandableItem } from './NavExpandableItem';\nimport { navStyles as styles } from './styles';\nimport type { NavItem } from './types';\n\nexport interface SidebarProps {\n /** Nav entries — already role-filtered / grouped by the app. */\n items: NavItem[];\n /** Current active route/path. */\n pathname: string;\n /** Navigation callback — receives a `NavItem.route`. */\n onNavigate: (route: string) => void;\n /** Localized menu title (heading). */\n title: string;\n /** Localized accessibility label for the navigation landmark. */\n regionLabel: string;\n /** a11y hint for a leaf item, given its label. Defaults to the label. */\n navigateHint?: (label: string) => string;\n /** a11y hint shown when an expandable section is collapsed. */\n expandHint?: string;\n /** a11y hint shown when an expandable section is expanded. */\n collapseHint?: string;\n /** Optional chevron renderer for expandable sections. */\n renderChevron?: (expanded: boolean, color: string, size: number) => React.ReactNode;\n /** Optional header slot rendered above the items (e.g. a Home shortcut). */\n header?: React.ReactNode;\n /** Optional footer slot rendered after a flex spacer (dark-mode toggle, logout). */\n footer?: React.ReactNode;\n /** Extra container style overrides. */\n containerStyle?: ViewStyle | ViewStyle[];\n}\n\nexport const Sidebar = ({\n items,\n pathname,\n onNavigate,\n title,\n regionLabel,\n navigateHint = (label) => label,\n expandHint = '',\n collapseHint = '',\n renderChevron,\n header,\n footer,\n containerStyle,\n}: SidebarProps): React.ReactElement => {\n const { theme } = useUi();\n const colors = theme.colors;\n\n return (\n <View\n accessibilityRole=\"none\"\n aria-label={regionLabel}\n role=\"navigation\"\n style={[\n styles.sidebarContainer,\n { backgroundColor: colors.surface, borderRightColor: colors.border },\n containerStyle,\n ]}\n >\n <Text accessibilityRole=\"header\" style={[styles.sidebarTitle, { color: colors.text }]}>\n {title}\n </Text>\n\n {header}\n\n {items.map((item) => (\n <NavExpandableItem\n key={item.key}\n collapseHint={collapseHint}\n expandHint={expandHint}\n item={item}\n navigateHint={navigateHint}\n pathname={pathname}\n renderChevron={renderChevron}\n onNavigate={onNavigate}\n />\n ))}\n\n <View style={styles.sidebarSpacer} />\n\n {footer}\n </View>\n );\n};\n\nexport { isRouteActive };\nexport default Sidebar;\n","/**\n * Default testIDs for `@dloizides/ui-nav`. Kept as a small central map (mirrors\n * `@dloizides/ui-layout`'s `LAYOUT_TEST_IDS`) so the app-agnostic chrome exposes\n * stable Playwright selectors. The existing Topbar logout/account buttons keep\n * their *caller-supplied* testIDs (via `TopbarAction.testID`) — these constants\n * only name the NEW account-state bits and the AppShell region suffixes.\n */\nexport const NAV_TEST_IDS = {\n /** displayName line in the rich account header (tappable when `onAccount` set). */\n accountName: 'topbar-account-name',\n /** muted tenant-name line under the displayName. */\n accountTenant: 'topbar-account-tenant',\n /** small plan badge (free / pro / enterprise). */\n accountPlan: 'topbar-account-plan',\n /** default testID for the rich header's upgrade action (overridable per-action). */\n accountUpgrade: 'topbar-account-upgrade',\n /** responsive hamburger toggle in the horizontal NavBar (shown below the breakpoint). */\n navBarToggle: 'navbar-toggle',\n /** links container in the horizontal NavBar. */\n navBarLinks: 'navbar-links',\n} as const;\n\n/** Suffixes appended to an `AppShell`'s required `testID` to name its regions. */\nexport const APP_SHELL_SUFFIX = {\n content: '-content',\n header: '-header',\n nav: '-nav',\n banner: '-banner',\n pending: '-pending',\n loading: '-loading',\n error: '-error',\n forbidden: '-forbidden',\n} as const;\n","/**\n * Topbar — the config-driven top navigation bar promoted from the byte-identical\n * erevna-web / katalogos-web `Topbar`. Structured slots replace the app-specific\n * wiring: a `left` logo slot, an optional language toggle, a notification slot,\n * an optional user block, an optional account button, and a required logout\n * action. Every colour comes from the UiProvider theme.\n *\n * Move 1c enriches this WITHOUT breaking the promoted contract: `account` now\n * accepts EITHER the legacy `TopbarAction` (today's erevna/katalogos calls) OR a\n * richer `AccountState` (displayName + muted tenant line + plan badge + upgrade\n * action). The `logout` action stays required, so every existing call keeps\n * compiling and the logout button keeps its caller-supplied testID.\n */\nimport React from 'react';\n\nimport { Text, TouchableOpacity, View, type ViewStyle } from 'react-native';\n\nimport { useUi, type UiTheme } from '@dloizides/ui-feedback';\n\nimport { NAV_TEST_IDS } from './constants';\nimport { navStyles as styles } from './styles';\n\n/** Text color for elements on primary/coloured badge backgrounds. */\nconst TEXT_ON_PRIMARY = '#ffffff';\n\nexport interface TopbarAction {\n label: string;\n hint: string;\n onPress: () => void;\n testID?: string;\n}\n\nexport interface TopbarUser {\n name: string;\n email: string;\n}\n\n/** Plan tier shown as a small badge in the rich account header. */\nexport interface AccountPlan {\n label: string;\n tone?: 'free' | 'pro' | 'enterprise';\n}\n\n/**\n * Richer account model for the header (Move 1c). Supplied via `account` as an\n * alternative to the legacy `TopbarAction` — the Topbar renders the display name\n * (tappable when `onAccount` is set), a muted tenant line, a plan badge, and an\n * `upgrade` action beside logout. No FM/router/store — everything is a\n * pre-localized string or a callback.\n */\nexport interface AccountState {\n displayName: string;\n tenantName?: string;\n plan?: AccountPlan;\n onLogout: () => void;\n onAccount?: () => void;\n upgrade?: TopbarAction;\n}\n\n/** Type guard: distinguishes the rich `AccountState` from a legacy `TopbarAction`. */\nfunction isAccountState(account: TopbarAction | AccountState): account is AccountState {\n return 'displayName' in account;\n}\n\n/** Resolve a plan badge background from its tone, routed through the theme. */\nfunction planToneColor(theme: UiTheme, tone: AccountPlan['tone']): string {\n const palette = theme.palette;\n if (tone === 'pro') return palette.primary['500'];\n if (tone === 'enterprise') return palette.accent?.['500'] ?? palette.primary['500'];\n return theme.colors.textSecondary;\n}\n\nexport interface TopbarProps {\n /** Left slot — typically the tenant logo. */\n left?: React.ReactNode;\n /** Optional language toggle. */\n language?: { label: string; hint: string; onPress: () => void };\n /** Optional notification slot (e.g. a notification bell). */\n notificationSlot?: React.ReactNode;\n /** Optional user identity block. */\n user?: TopbarUser;\n /**\n * Optional account slot. Legacy callers pass a `TopbarAction` (an account\n * button). Move-1c callers pass an `AccountState` for the richer header.\n */\n account?: TopbarAction | AccountState;\n /** Required logout action (drives the stable logout button testID). */\n logout: TopbarAction;\n /** Extra container style overrides. */\n containerStyle?: ViewStyle | ViewStyle[];\n}\n\nexport const Topbar = ({\n left,\n language,\n notificationSlot,\n user,\n account,\n logout,\n containerStyle,\n}: TopbarProps): React.ReactElement => {\n const { theme } = useUi();\n const colors = theme.colors;\n const primaryColor = theme.palette.primary['500'];\n\n const richAccount = account !== undefined && isAccountState(account) ? account : undefined;\n const legacyAccount = account !== undefined && !isAccountState(account) ? account : undefined;\n const upgrade = richAccount?.upgrade;\n\n return (\n <View\n style={[\n styles.topbarContainer,\n { borderBottomColor: colors.border, backgroundColor: colors.background },\n containerStyle,\n ]}\n >\n <View style={styles.topbarLeft}>{left}</View>\n <View style={styles.topbarRight}>\n {language ? (\n <TouchableOpacity\n accessibilityHint={language.hint}\n accessibilityLabel={language.label}\n accessibilityRole=\"button\"\n style={styles.topbarRowItem}\n onPress={language.onPress}\n >\n <Text style={[styles.topbarLabel, { color: colors.text }]}>{language.label}</Text>\n </TouchableOpacity>\n ) : null}\n\n {notificationSlot}\n\n {user ? (\n <View style={styles.userBlock}>\n <Text style={[styles.userName, { color: colors.text }]}>{user.name}</Text>\n <Text style={[styles.userEmail, { color: colors.textSecondary }]}>{user.email}</Text>\n </View>\n ) : null}\n\n {richAccount ? (\n <View style={styles.userBlock}>\n {richAccount.onAccount ? (\n <TouchableOpacity\n accessibilityHint={richAccount.displayName}\n accessibilityLabel={richAccount.displayName}\n accessibilityRole=\"button\"\n testID={NAV_TEST_IDS.accountName}\n onPress={richAccount.onAccount}\n >\n <Text style={[styles.userName, { color: colors.text }]}>{richAccount.displayName}</Text>\n </TouchableOpacity>\n ) : (\n <Text style={[styles.userName, { color: colors.text }]} testID={NAV_TEST_IDS.accountName}>\n {richAccount.displayName}\n </Text>\n )}\n\n {richAccount.tenantName !== undefined ? (\n <Text\n style={[styles.accountTenant, { color: colors.textSecondary }]}\n testID={NAV_TEST_IDS.accountTenant}\n >\n {richAccount.tenantName}\n </Text>\n ) : null}\n\n {richAccount.plan ? (\n <View\n style={[styles.planBadge, { backgroundColor: planToneColor(theme, richAccount.plan.tone) }]}\n testID={NAV_TEST_IDS.accountPlan}\n >\n <Text style={[styles.planBadgeText, { color: TEXT_ON_PRIMARY }]}>{richAccount.plan.label}</Text>\n </View>\n ) : null}\n </View>\n ) : null}\n\n {legacyAccount ? (\n <TouchableOpacity\n accessibilityHint={legacyAccount.hint}\n accessibilityLabel={legacyAccount.label}\n accessibilityRole=\"button\"\n style={[styles.accountBtn, { backgroundColor: primaryColor }]}\n testID={legacyAccount.testID}\n onPress={legacyAccount.onPress}\n >\n <Text style={[styles.accountText, { color: TEXT_ON_PRIMARY }]}>{legacyAccount.label}</Text>\n </TouchableOpacity>\n ) : null}\n\n {upgrade ? (\n <TouchableOpacity\n accessibilityHint={upgrade.hint}\n accessibilityLabel={upgrade.label}\n accessibilityRole=\"button\"\n style={[styles.accountBtn, { backgroundColor: primaryColor }]}\n testID={upgrade.testID ?? NAV_TEST_IDS.accountUpgrade}\n onPress={upgrade.onPress}\n >\n <Text style={[styles.accountText, { color: TEXT_ON_PRIMARY }]}>{upgrade.label}</Text>\n </TouchableOpacity>\n ) : null}\n\n <TouchableOpacity\n accessible\n accessibilityHint={logout.hint}\n accessibilityLabel={logout.label}\n accessibilityRole=\"button\"\n style={[styles.accountBtn, { backgroundColor: primaryColor }]}\n testID={logout.testID}\n onPress={richAccount ? richAccount.onLogout : logout.onPress}\n >\n <Text style={[styles.accountText, { color: TEXT_ON_PRIMARY }]}>{logout.label}</Text>\n </TouchableOpacity>\n </View>\n </View>\n );\n};\n\nexport default Topbar;\n","/**\n * NavBar — the config-driven HORIZONTAL top navigation bar. It is the top-bar\n * counterpart to `Sidebar`: it renders the SAME caller-supplied `NavItem[]`\n * (already role-filtered / localized by the app) as inline links across the top\n * instead of a left rail, with an optional brand slot on the left and a free-form\n * right slot (auth link / user / logout). Below a configurable breakpoint it\n * collapses the links behind a hamburger toggle — mirroring the v1 AML console's\n * responsive `.global-nav`.\n *\n * Contract discipline (same as the rest of `@dloizides/ui-nav`): NO FM, router,\n * icon set, or store imports. Labels are pre-localized strings, icons are render\n * slots, and every colour is read from the `@dloizides/ui-feedback` UiProvider\n * theme. This is purely additive — `Sidebar`/`Topbar` are unchanged.\n */\nimport React, { useCallback, useMemo, useState } from 'react';\n\nimport {\n Text,\n TouchableOpacity,\n useWindowDimensions,\n View,\n type TouchableOpacityProps,\n type ViewStyle,\n} from 'react-native';\n\nimport { useUi } from '@dloizides/ui-feedback';\n\nimport { NAV_TEST_IDS } from './constants';\nimport { isRouteActive } from './isRouteActive';\nimport { ACTIVE_BORDER_RADIUS, navBarStyles as styles, NAV_ICON_SIZE } from './styles';\nimport type { NavItem } from './types';\n\n/** Text colour for a link sitting on the primary (active) background. */\nconst TEXT_ON_PRIMARY = '#ffffff';\n/** Collapse to a hamburger at/under this viewport width (matches v1's 760px). */\nconst DEFAULT_COLLAPSE_BELOW = 760;\n/** Default hamburger glyph (☰) when no `renderMenuIcon` is supplied. */\nconst MENU_GLYPH = '☰';\n\nexport interface NavBarProps {\n /** Nav entries — already role-filtered / grouped by the app. */\n items: NavItem[];\n /** Current active route/path. */\n pathname: string;\n /** Navigation callback — receives a `NavItem.route`. */\n onNavigate: (route: string) => void;\n /** Localized accessibility label for the navigation landmark. */\n regionLabel: string;\n /** a11y hint for a link, given its label. Defaults to the label. */\n navigateHint?: (label: string) => string;\n /** Left slot — typically the brand / tenant logo. */\n brand?: React.ReactNode;\n /** Right slot — free-form (auth link, user block, logout). */\n right?: React.ReactNode;\n /** Localized accessibility label for the responsive menu toggle. */\n menuLabel?: string;\n /** Localized accessibility hint for the responsive menu toggle. */\n menuHint?: string;\n /** Optional custom glyph renderer for the toggle; defaults to ☰. */\n renderMenuIcon?: (color: string, open: boolean) => React.ReactNode;\n /** Collapse the links behind a hamburger below this viewport width (default 760). */\n collapseBelow?: number;\n /** Extra container style overrides. */\n containerStyle?: ViewStyle | ViewStyle[];\n}\n\n/**\n * `aria-current=\"page\"` marks the active link for assistive tech. react-native's\n * prop types omit `aria-current`, but react-native-web forwards `aria-*` to the\n * DOM — so we attach it (no `any`) only when a link is active.\n */\nfunction activeAriaProps(isActive: boolean): TouchableOpacityProps & { 'aria-current'?: 'page' } {\n return isActive ? { 'aria-current': 'page' } : {};\n}\n\nexport const NavBar = ({\n items,\n pathname,\n onNavigate,\n regionLabel,\n navigateHint = (label) => label,\n brand,\n right,\n menuLabel = 'Menu',\n menuHint = '',\n renderMenuIcon,\n collapseBelow = DEFAULT_COLLAPSE_BELOW,\n containerStyle,\n}: NavBarProps): React.ReactElement => {\n const { theme } = useUi();\n const colors = theme.colors;\n const primaryColor = theme.palette.primary['500'];\n const { width } = useWindowDimensions();\n const [open, setOpen] = useState(false);\n\n const collapsed = width < collapseBelow;\n const showLinks = !collapsed || open;\n\n const toggleMenu = useCallback(() => setOpen((v) => !v), []);\n const handlePress = useCallback(\n (route: string) => {\n setOpen(false);\n onNavigate(route);\n },\n [onNavigate],\n );\n\n const activeLinkStyle = useMemo(\n () => ({ backgroundColor: primaryColor, borderRadius: ACTIVE_BORDER_RADIUS }),\n [primaryColor],\n );\n\n const renderLink = (item: NavItem): React.ReactElement => {\n const isActive = isRouteActive(pathname, item.route);\n const textColor = isActive ? TEXT_ON_PRIMARY : colors.textSecondary;\n return (\n <TouchableOpacity\n key={item.key}\n accessible\n accessibilityHint={navigateHint(item.label)}\n accessibilityLabel={item.label}\n accessibilityRole=\"link\"\n accessibilityState={{ selected: isActive }}\n style={[styles.link, collapsed ? styles.linkStacked : undefined, isActive ? activeLinkStyle : undefined]}\n testID={item.testID ?? item.key}\n onPress={() => handlePress(item.route)}\n {...activeAriaProps(isActive)}\n >\n {typeof item.renderIcon === 'function' ? (\n <View style={styles.linkIcon}>{item.renderIcon(textColor, NAV_ICON_SIZE)}</View>\n ) : null}\n <Text style={[styles.linkText, { color: textColor }]}>{item.label}</Text>\n </TouchableOpacity>\n );\n };\n\n return (\n <View\n accessibilityRole=\"none\"\n aria-label={regionLabel}\n role=\"navigation\"\n style={[\n styles.container,\n { backgroundColor: colors.surface, borderBottomColor: colors.border },\n containerStyle,\n ]}\n >\n <View style={styles.inner}>\n {brand !== undefined ? <View style={styles.brand}>{brand}</View> : null}\n\n {collapsed ? (\n <TouchableOpacity\n accessible\n accessibilityHint={menuHint}\n accessibilityLabel={menuLabel}\n accessibilityRole=\"button\"\n accessibilityState={{ expanded: open }}\n aria-expanded={open}\n style={styles.toggle}\n testID={NAV_TEST_IDS.navBarToggle}\n onPress={toggleMenu}\n >\n {typeof renderMenuIcon === 'function' ? (\n renderMenuIcon(colors.text, open)\n ) : (\n <Text style={[styles.toggleGlyph, { color: colors.text }]}>{MENU_GLYPH}</Text>\n )}\n </TouchableOpacity>\n ) : null}\n\n {showLinks ? (\n <View style={collapsed ? styles.linksStacked : styles.links} testID={NAV_TEST_IDS.navBarLinks}>\n {items.map(renderLink)}\n </View>\n ) : null}\n\n {showLinks && right !== undefined ? (\n <View style={collapsed ? styles.rightStacked : styles.right}>{right}</View>\n ) : null}\n </View>\n </View>\n );\n};\n\nexport { isRouteActive };\nexport default NavBar;\n","/**\n * Width-discipline helper for `AppShell` (Move 1c). Split out from the component\n * so the (viewport → max-width) rule is a small, directly unit-testable pure\n * function, with a thin hook wrapper that feeds it the live viewport width.\n */\nimport { useWindowDimensions } from 'react-native';\n\n/** Content-width policy: a capped column, optionally wider past a viewport breakpoint, or full-bleed. */\nexport type AppShellWidth =\n | { max: number; wideMax?: number; wideMinViewport?: number }\n | 'full';\n\n/**\n * Resolve the content column's max width for a given viewport. `'full'` passes\n * through (no cap); an object caps at `max`, widening to `wideMax` once the\n * viewport reaches `wideMinViewport`.\n */\nexport function resolveContentMaxWidth(width: AppShellWidth, viewport: number): number | 'full' {\n if (width === 'full') return 'full';\n const wideMinViewport = width.wideMinViewport ?? Number.POSITIVE_INFINITY;\n const isWide = viewport >= wideMinViewport;\n return isWide ? (width.wideMax ?? width.max) : width.max;\n}\n\n/** Hook wrapper: resolves the content max width against the live window width. */\nexport function useContentMaxWidth(width: AppShellWidth): number | 'full' {\n const { width: viewport } = useWindowDimensions();\n return resolveContentMaxWidth(width, viewport);\n}\n","/**\n * AppShell — the shared dashboard-chrome composition (Move 1c). It hosts, top to\n * bottom: an app-supplied header (full-bleed), an optional per-app nav region, an\n * optional banner, and a width-disciplined scrollable content column. It also\n * folds in the two things every product page re-implements: an auth gate\n * (pending spinner / redirect-when-unauthenticated) and page state cards\n * (loading / error / forbidden).\n *\n * Contract discipline (same as the rest of `@dloizides/ui-nav`): NO FM, router,\n * or store imports. The app passes wired elements, pre-localized strings, and\n * callbacks; every colour is routed through the `@dloizides/ui-feedback` theme.\n */\nimport React, { useEffect } from 'react';\n\nimport { ActivityIndicator, ScrollView, StyleSheet, Text, View, type ViewStyle } from 'react-native';\n\nimport { useUi } from '@dloizides/ui-feedback';\n\nimport { APP_SHELL_SUFFIX } from './constants';\nimport { useContentMaxWidth, type AppShellWidth } from './useContentMaxWidth';\n\nconst DEFAULT_CONTENT_PADDING = 24;\nconst CARD_PADDING = 20;\nconst CARD_BORDER_RADIUS = 12;\nconst CARD_BORDER_WIDTH = 1;\nconst CARD_TITLE_FONT_SIZE = 16;\nconst CARD_MESSAGE_FONT_SIZE = 14;\nconst CARD_TITLE_MARGIN_BOTTOM = 8;\n\nconst styles = StyleSheet.create({\n root: { flex: 1 },\n centerFill: { flex: 1, justifyContent: 'center', alignItems: 'center' },\n scroll: { flex: 1 },\n scrollContent: { flexGrow: 1 },\n columnFull: { flex: 1 },\n columnCapped: { flex: 1, width: '100%', alignSelf: 'center' },\n // Non-flex variant used to cap the nav strip's inner content to the content\n // column so the nav aligns with the page content instead of running full-bleed.\n chromeCapped: { width: '100%', alignSelf: 'center' },\n card: {\n padding: CARD_PADDING,\n borderRadius: CARD_BORDER_RADIUS,\n borderWidth: CARD_BORDER_WIDTH,\n },\n cardTitle: { fontSize: CARD_TITLE_FONT_SIZE, fontWeight: '700', marginBottom: CARD_TITLE_MARGIN_BOTTOM },\n cardMessage: { fontSize: CARD_MESSAGE_FONT_SIZE },\n});\n\n/** A titled message card (error / forbidden). Colours come from the theme. */\nexport interface ShellMessage {\n titleText: string;\n messageText: string;\n}\n\nexport interface AppShellProps {\n /** App-supplied wired header (Topbar / AppHeader), rendered full-bleed. */\n header: React.ReactNode;\n /** Per-app nav region (kefi coral pills / erevna sidebar+drawer). */\n nav?: React.ReactNode;\n /** Banner slot (e.g. verification-pending). */\n banner?: React.ReactNode;\n /** Content-width policy. Defaults to full-bleed. */\n width?: AppShellWidth;\n /** Content padding inside the scroll column. Defaults to 24. */\n contentPadding?: number;\n /**\n * Whether the nav strip aligns with the width-capped content column\n * (`'content'`) or runs full-bleed (`'full'`, the default). Only has an effect\n * when `width` caps the content. The header always renders full-bleed.\n */\n chromeAlignment?: 'full' | 'content';\n /** Auth gate — spinner while pending, `onRedirect` when unauthenticated. */\n gate?: { pending: boolean; authenticated: boolean; onRedirect: () => void };\n /** Page state — loading spinner card, or a titled error / forbidden card. */\n state?: {\n loading?: boolean;\n error?: ShellMessage | null;\n forbidden?: ShellMessage | null;\n };\n /** Root testID; the content region is exposed as `${testID}-content`. */\n testID: string;\n children: React.ReactNode;\n}\n\nfunction MessageCard({\n message,\n accentColor,\n testID,\n}: {\n message: ShellMessage;\n accentColor: string;\n testID: string;\n}): React.ReactElement {\n const { theme } = useUi();\n const colors = theme.colors;\n return (\n <View\n style={[styles.card, { backgroundColor: colors.surface, borderColor: accentColor }]}\n testID={testID}\n >\n <Text style={[styles.cardTitle, { color: accentColor }]}>{message.titleText}</Text>\n <Text style={[styles.cardMessage, { color: colors.textSecondary }]}>{message.messageText}</Text>\n </View>\n );\n}\n\n/** Pick what renders inside the content column, honouring state precedence. */\nfunction useContentBody(\n state: AppShellProps['state'],\n children: React.ReactNode,\n testID: string,\n): React.ReactNode {\n const { theme } = useUi();\n const primary = theme.palette.primary['500'];\n const errorColor = theme.semantic.error['500'];\n\n if (state?.forbidden)\n return <MessageCard accentColor={errorColor} message={state.forbidden} testID={`${testID}${APP_SHELL_SUFFIX.forbidden}`} />;\n if (state?.error)\n return <MessageCard accentColor={errorColor} message={state.error} testID={`${testID}${APP_SHELL_SUFFIX.error}`} />;\n if (state?.loading === true)\n return (\n <View style={styles.centerFill} testID={`${testID}${APP_SHELL_SUFFIX.loading}`}>\n <ActivityIndicator color={primary} size=\"large\" />\n </View>\n );\n return children;\n}\n\nexport const AppShell = ({\n header,\n nav,\n banner,\n width = 'full',\n contentPadding = DEFAULT_CONTENT_PADDING,\n chromeAlignment = 'full',\n gate,\n state,\n testID,\n children,\n}: AppShellProps): React.ReactElement | null => {\n const { theme } = useUi();\n const primary = theme.palette.primary['500'];\n const maxWidth = useContentMaxWidth(width);\n const body = useContentBody(state, children, testID);\n\n const isUnauthenticated = gate !== undefined && !gate.pending && !gate.authenticated;\n useEffect(() => {\n if (isUnauthenticated && gate !== undefined) gate.onRedirect();\n }, [isUnauthenticated, gate]);\n\n if (gate?.pending === true)\n return (\n <View style={[styles.root, styles.centerFill, { backgroundColor: theme.colors.background }]} testID={`${testID}${APP_SHELL_SUFFIX.pending}`}>\n <ActivityIndicator color={primary} size=\"large\" />\n </View>\n );\n\n if (isUnauthenticated) return null;\n\n const columnStyle: ViewStyle | ViewStyle[] =\n maxWidth === 'full' ? styles.columnFull : [styles.columnCapped, { maxWidth }];\n\n // Align the nav strip's inner content with the capped content column when asked\n // (and only when the content is actually capped); otherwise it runs full-bleed.\n const navInnerStyle: ViewStyle | ViewStyle[] | undefined =\n chromeAlignment === 'content' && maxWidth !== 'full'\n ? [styles.chromeCapped, { maxWidth, paddingHorizontal: contentPadding }]\n : undefined;\n\n return (\n <View style={[styles.root, { backgroundColor: theme.colors.background }]} testID={testID}>\n <View testID={`${testID}${APP_SHELL_SUFFIX.header}`}>{header}</View>\n {nav !== undefined ? (\n <View testID={`${testID}${APP_SHELL_SUFFIX.nav}`}>\n {navInnerStyle !== undefined ? <View style={navInnerStyle}>{nav}</View> : nav}\n </View>\n ) : null}\n {banner !== undefined ? <View testID={`${testID}${APP_SHELL_SUFFIX.banner}`}>{banner}</View> : null}\n <ScrollView\n contentContainerStyle={[styles.scrollContent, { padding: contentPadding }]}\n style={styles.scroll}\n testID={`${testID}${APP_SHELL_SUFFIX.content}`}\n >\n <View style={columnStyle}>{body}</View>\n </ScrollView>\n </View>\n );\n};\n\nexport default AppShell;\n","/**\n * Role-gated nav helpers — build a `NavItem[]` from a user's roles, reusing the\n * existing `resolveAccessibleRoutes` from `@dloizides/auth-web` (the same helper\n * the multi-role dashboard nav uses) rather than reimplementing role filtering.\n *\n * The app supplies its ordered `RoleRouteTable` (role → route + optional\n * `labelKey`) and a `translate` fn (its `FM`); this returns the ordered set of\n * `NavItem`s the user's roles unlock — most privileged first, empty when none.\n */\nimport { resolveAccessibleRoutes } from '@dloizides/auth-web';\nimport type { RoleRoute, RoleRouteTable } from '@dloizides/auth-web';\n\nimport type { NavItem } from './types';\n\n/** The user shape `resolveAccessibleRoutes` accepts (derived — no extra dep). */\nexport type NavUser = Parameters<typeof resolveAccessibleRoutes>[0];\n\n/** Map already-resolved role routes to nav items, localizing each `labelKey`. */\nexport function roleRoutesToNavItems(\n routes: RoleRoute[],\n translate: (key: string) => string,\n): NavItem[] {\n return routes.map((entry) => ({\n key: entry.role,\n route: entry.route,\n label: entry.labelKey !== undefined ? translate(entry.labelKey) : entry.route,\n }));\n}\n\n/**\n * Resolve the nav items a user can reach, in table (priority) order. Wraps\n * `resolveAccessibleRoutes` so role gating lives in one place.\n */\nexport function accessibleNavItems(\n user: NavUser,\n table: RoleRouteTable,\n translate: (key: string) => string,\n): NavItem[] {\n return roleRoutesToNavItems(resolveAccessibleRoutes(user, table), translate);\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dloizides/ui-nav",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "Config-driven, brand-agnostic React Native (RN-web) navigation shell — Sidebar + Topbar promoted from the byte-identical erevna-web/katalogos-web nav twins. Renders a caller-supplied NavItem[] (pre-localized labels, icon render slots), routes colours through the @dloizides/ui-feedback UiProvider, and gates items by role via resolveAccessibleRoutes from @dloizides/auth-web. Part of the dloizides.com shared UI kit.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"react",
|