@dloizides/ui-nav 1.16.1 → 1.18.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 +54 -0
- package/dist/index.d.mts +114 -40
- package/dist/index.d.ts +114 -40
- package/dist/index.js +267 -200
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +215 -149
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,59 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.18.0
|
|
4
|
+
|
|
5
|
+
Root-cause fix for sidebar leaf co-activation. Behavioral/additive — no breaking API.
|
|
6
|
+
|
|
7
|
+
- **Fix (two sidebar leaves showed `aria-current="page"` at once).** On the live finreg
|
|
8
|
+
`/accounting/journals`, BOTH "Chart of Accounts" (route `/accounting`) and "Journals"
|
|
9
|
+
(route `/accounting/journals`) were marked active, because the active predicate was purely
|
|
10
|
+
prefix-based (`pathname === route || pathname.startsWith(route + '/')`) — a module-home leaf
|
|
11
|
+
routed at `/accounting` is a prefix of every `/accounting/*` sibling, so it co-activated on all
|
|
12
|
+
of them. (Second occurrence of this class; the first, CRM Contacts at `/crm`, was worked around
|
|
13
|
+
by moving the route.) Now resolution is **most-specific (longest) match wins**: among a nav
|
|
14
|
+
model's LEAF items, at most ONE is active for any path — the leaf whose route is the longest
|
|
15
|
+
prefix match. On `/accounting/journals` only Journals is active; on `/accounting` exactly only
|
|
16
|
+
Chart of Accounts is. For nav models with no parent/child route overlap (the common case)
|
|
17
|
+
behaviour is unchanged.
|
|
18
|
+
- **New export** `resolveActiveRoute(items, pathname)`: flattens the tree to its leaf routes and
|
|
19
|
+
returns the single longest matching route (honouring per-leaf `exact`), or `undefined`.
|
|
20
|
+
- `Sidebar` resolves the winning route once over the full leaf set and passes `activeRoute` to
|
|
21
|
+
each row; `NavExpandableItem` marks a leaf active iff `item.route === activeRoute` (falls back
|
|
22
|
+
to the per-item predicate when used standalone — backward compatible). Group HEADER expansion
|
|
23
|
+
(`hasActiveDescendant`) is a separate concern and is unchanged.
|
|
24
|
+
- The same winner-based resolution now backs `NavBar`, `PillNav`, `CollapsedRail` and
|
|
25
|
+
`NavOverflowMenu`, so the latent prefix co-activation cannot resurface in the horizontal navs.
|
|
26
|
+
- **Additive API**: new optional `NavItem.exact?: boolean` — a leaf active ONLY on its exact
|
|
27
|
+
path (never a nested one). A complementary escape hatch; the longest-match default is the real
|
|
28
|
+
fix, so no consumer needs to set it.
|
|
29
|
+
- `isRouteActive(pathname, route, exact?)` gains an optional third arg; the two-arg call is
|
|
30
|
+
unchanged.
|
|
31
|
+
|
|
32
|
+
## 1.17.0
|
|
33
|
+
|
|
34
|
+
Two live-defect fixes to the shared sidebar chrome. Behavioral/additive — no breaking API.
|
|
35
|
+
|
|
36
|
+
- **Fix (two search bars on desktop → mutually exclusive by affordance).** 1.16.1 shipped the
|
|
37
|
+
desktop inline `SidebarSearch` field, but the ⌘K `CommandPaletteTrigger` could be present at
|
|
38
|
+
the same time, so a wide viewport showed BOTH. `Sidebar` now owns both affordances and renders
|
|
39
|
+
exactly ONE, chosen by its own viewport width via `resolveSearchAffordance`:
|
|
40
|
+
- inline affordance (≥768) → the inline field ONLY; the ⌘K trigger is suppressed.
|
|
41
|
+
- palette affordance (<768) → the ⌘K trigger ONLY; the inline field is suppressed.
|
|
42
|
+
- The ⌘K keyboard shortcut (`useCommandPaletteHotkey`) is untouched and stays bound at every width.
|
|
43
|
+
- **Additive API**: new `Sidebar.paletteTrigger?: SidebarPaletteTriggerConfig`
|
|
44
|
+
(`{ label, hint, shortcut, onOpen, testID? }`), forwarded from `NavShellSideRail.paletteTrigger`.
|
|
45
|
+
Consumers that wire neither, or only the inline field, are unchanged (the inline field simply
|
|
46
|
+
no longer renders below 768, where a persistent field would steal vertical space).
|
|
47
|
+
|
|
48
|
+
- **Fix (hover wash never appeared on RN-web).** The nav rows were `TouchableOpacity`, which in
|
|
49
|
+
react-native-web has NO hover support and silently drops `onHoverIn`/`onHoverOut` — so the row's
|
|
50
|
+
`hovered` state never toggled and the primary-tinted overlay stayed at opacity 0. The leaf and
|
|
51
|
+
group-header rows in `NavExpandableItem` are now `Pressable`, whose `useHover` responder actually
|
|
52
|
+
drives `hovered`, lifting the token-tinted overlay to the hover wash (`HOVER_TINT_OPACITY`, ~160ms
|
|
53
|
+
reduced-motion-gated transition) — distinct from and lighter than the active tint. Token-only
|
|
54
|
+
(colours via the theme; no raw hex/rgb/hsl). aria wiring (`aria-current` / `aria-expanded`, which
|
|
55
|
+
`MobileDrawer` depends on) is preserved across the switch.
|
|
56
|
+
|
|
3
57
|
## 1.16.1
|
|
4
58
|
|
|
5
59
|
- **Fix (NavShell forwards inline-search props to the inner `Sidebar`).** 1.16.0 added
|
package/dist/index.d.mts
CHANGED
|
@@ -5,12 +5,71 @@ import { DropdownVariant } from '@dloizides/ui-layout';
|
|
|
5
5
|
import { resolveAccessibleRoutes, RoleRouteTable, RoleRoute } from '@dloizides/auth-web';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* only
|
|
8
|
+
* Public prop types for the `@dloizides/ui-nav` config-driven navigation shell.
|
|
9
|
+
*
|
|
10
|
+
* The rendering chrome (sidebar + topbar) was byte-identical across erevna-web
|
|
11
|
+
* and katalogos-web; only the *item data* (which routes, how they group) differs
|
|
12
|
+
* per app. So this package renders a caller-supplied `NavItem[]` — labels are
|
|
13
|
+
* pre-localized strings and icons are render slots, keeping the package free of
|
|
14
|
+
* any app's i18n helper, icon set, router, or store.
|
|
12
15
|
*/
|
|
13
|
-
|
|
16
|
+
|
|
17
|
+
/** One navigation entry. Labels are already localized by the caller. */
|
|
18
|
+
interface NavItem {
|
|
19
|
+
/** Stable key + default testID. */
|
|
20
|
+
key: string;
|
|
21
|
+
/** Localized display label. */
|
|
22
|
+
label: string;
|
|
23
|
+
/** Route/path this item navigates to (passed back to `onNavigate`). */
|
|
24
|
+
route: string;
|
|
25
|
+
/** Optional testID override (defaults to `key`). */
|
|
26
|
+
testID?: string;
|
|
27
|
+
/**
|
|
28
|
+
* Optional leading icon. Receives the resolved foreground colour and a size,
|
|
29
|
+
* so the app supplies its own icon component without this package importing
|
|
30
|
+
* an icon set.
|
|
31
|
+
*/
|
|
32
|
+
renderIcon?: (color: string, size: number) => React.ReactNode;
|
|
33
|
+
/** Optional nested items — rendered as an expandable section. */
|
|
34
|
+
children?: NavItem[];
|
|
35
|
+
/**
|
|
36
|
+
* Opt this leaf out of prefix matching: when `true` it is active ONLY when the
|
|
37
|
+
* pathname equals its route exactly (never for a nested `route/child` path). A
|
|
38
|
+
* complementary escape hatch — the default most-specific/longest-match resolution
|
|
39
|
+
* (see `resolveActiveRoute`) already prevents a module-home leaf like `/accounting`
|
|
40
|
+
* from co-activating with `/accounting/journals`, so most consumers never need
|
|
41
|
+
* this. Reach for it only when a leaf must NEVER light up on any descendant path
|
|
42
|
+
* even when no longer sibling route exists.
|
|
43
|
+
*/
|
|
44
|
+
exact?: boolean;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Active-route matching for the nav shells.
|
|
49
|
+
*
|
|
50
|
+
* `isRouteActive` is the low-level per-route predicate (prefix-based, ported
|
|
51
|
+
* verbatim from the twin app sidebars): a route is active when the current
|
|
52
|
+
* pathname equals it or is nested under it (`/foo` matches `/foo` and `/foo/bar`,
|
|
53
|
+
* but `/` only matches `/`). With `exact`, only an exact path matches.
|
|
54
|
+
*
|
|
55
|
+
* `resolveActiveRoute` is the CROSS-ITEM resolver that fixes the sidebar
|
|
56
|
+
* co-activation bug: because a module-home leaf (`/accounting`) is a prefix of
|
|
57
|
+
* every sibling (`/accounting/journals`), the naive per-item predicate lights up
|
|
58
|
+
* BOTH on `/accounting/journals`. `resolveActiveRoute` instead returns the ONE
|
|
59
|
+
* most-specific (longest) leaf route that matches — so at most one leaf is ever
|
|
60
|
+
* active, the longest wins on nested paths, and the module-home leaf is active
|
|
61
|
+
* only on its own exact path. For a nav model with no parent/child route overlap
|
|
62
|
+
* (the common case) this is identical to the old behaviour.
|
|
63
|
+
*/
|
|
64
|
+
|
|
65
|
+
declare function isRouteActive(pathname: string, route: string, exact?: boolean): boolean;
|
|
66
|
+
/**
|
|
67
|
+
* The single active leaf route for `pathname`: the LONGEST leaf route that
|
|
68
|
+
* matches (most-specific wins). Returns `undefined` when no leaf matches. Marking
|
|
69
|
+
* a leaf active iff `item.route === resolveActiveRoute(items, pathname)` guarantees
|
|
70
|
+
* at most one active leaf — the whack-a-mole prefix co-activation fix.
|
|
71
|
+
*/
|
|
72
|
+
declare function resolveActiveRoute(items: NavItem[], pathname: string): string | undefined;
|
|
14
73
|
|
|
15
74
|
/**
|
|
16
75
|
* SidebarSearch — the persistent, DESKTOP inline search field that lives at the
|
|
@@ -51,36 +110,6 @@ interface SidebarSearchProps {
|
|
|
51
110
|
/** The inline sidebar search field (desktop) with a clear button. */
|
|
52
111
|
declare const SidebarSearch: ({ value, onChangeText, onClear, labels, testID, }: SidebarSearchProps) => React.ReactElement;
|
|
53
112
|
|
|
54
|
-
/**
|
|
55
|
-
* Public prop types for the `@dloizides/ui-nav` config-driven navigation shell.
|
|
56
|
-
*
|
|
57
|
-
* The rendering chrome (sidebar + topbar) was byte-identical across erevna-web
|
|
58
|
-
* and katalogos-web; only the *item data* (which routes, how they group) differs
|
|
59
|
-
* per app. So this package renders a caller-supplied `NavItem[]` — labels are
|
|
60
|
-
* pre-localized strings and icons are render slots, keeping the package free of
|
|
61
|
-
* any app's i18n helper, icon set, router, or store.
|
|
62
|
-
*/
|
|
63
|
-
|
|
64
|
-
/** One navigation entry. Labels are already localized by the caller. */
|
|
65
|
-
interface NavItem {
|
|
66
|
-
/** Stable key + default testID. */
|
|
67
|
-
key: string;
|
|
68
|
-
/** Localized display label. */
|
|
69
|
-
label: string;
|
|
70
|
-
/** Route/path this item navigates to (passed back to `onNavigate`). */
|
|
71
|
-
route: string;
|
|
72
|
-
/** Optional testID override (defaults to `key`). */
|
|
73
|
-
testID?: string;
|
|
74
|
-
/**
|
|
75
|
-
* Optional leading icon. Receives the resolved foreground colour and a size,
|
|
76
|
-
* so the app supplies its own icon component without this package importing
|
|
77
|
-
* an icon set.
|
|
78
|
-
*/
|
|
79
|
-
renderIcon?: (color: string, size: number) => React.ReactNode;
|
|
80
|
-
/** Optional nested items — rendered as an expandable section. */
|
|
81
|
-
children?: NavItem[];
|
|
82
|
-
}
|
|
83
|
-
|
|
84
113
|
/**
|
|
85
114
|
* Sidebar — the config-driven left navigation shell promoted from the
|
|
86
115
|
* byte-identical erevna-web / katalogos-web `Sidebar`. It renders a caller
|
|
@@ -100,6 +129,24 @@ interface SidebarSearchConfig extends SidebarSearchLabels {
|
|
|
100
129
|
/** Shown in place of the list when the query matches nothing. */
|
|
101
130
|
emptyText: string;
|
|
102
131
|
}
|
|
132
|
+
/**
|
|
133
|
+
* The ⌘K command-palette trigger the sidebar renders as its NARROW-viewport search
|
|
134
|
+
* affordance (a persistent inline field would steal scarce vertical space on mobile
|
|
135
|
+
* — see {@link resolveSearchAffordance}). Pre-localized strings + the open handler;
|
|
136
|
+
* wire `onOpen` to the same handler as the ⌘K hotkey ({@link useCommandPaletteHotkey}).
|
|
137
|
+
*/
|
|
138
|
+
interface SidebarPaletteTriggerConfig {
|
|
139
|
+
/** Placeholder-style label, e.g. "Search". */
|
|
140
|
+
label: string;
|
|
141
|
+
/** a11y hint describing what activating it does. */
|
|
142
|
+
hint: string;
|
|
143
|
+
/** The shortcut badge text, e.g. "⌘K". */
|
|
144
|
+
shortcut: string;
|
|
145
|
+
/** Open the palette (same handler as the ⌘K hotkey). */
|
|
146
|
+
onOpen: () => void;
|
|
147
|
+
/** testID for the trigger. Defaults to `sidebar-command-palette-trigger`. */
|
|
148
|
+
testID?: string;
|
|
149
|
+
}
|
|
103
150
|
interface SidebarProps {
|
|
104
151
|
/** Nav entries — already role-filtered / grouped by the app. */
|
|
105
152
|
items: NavItem[];
|
|
@@ -121,12 +168,21 @@ interface SidebarProps {
|
|
|
121
168
|
renderChevron?: (expanded: boolean, color: string, size: number) => React.ReactNode;
|
|
122
169
|
/**
|
|
123
170
|
* Opt into the persistent inline search field (desktop). Requires `search`
|
|
124
|
-
* labels; when omitted the sidebar renders exactly as before.
|
|
125
|
-
*
|
|
171
|
+
* labels; when omitted the sidebar renders exactly as before. The field shows
|
|
172
|
+
* ONLY at the inline affordance (viewport ≥ 768, {@link resolveSearchAffordance});
|
|
173
|
+
* on a narrow viewport the ⌘K `paletteTrigger` is the search affordance instead.
|
|
126
174
|
*/
|
|
127
175
|
enableInlineSearch?: boolean;
|
|
128
176
|
/** Pre-localized inline-search strings — required for `enableInlineSearch`. */
|
|
129
177
|
search?: SidebarSearchConfig;
|
|
178
|
+
/**
|
|
179
|
+
* Opt into the ⌘K {@link CommandPaletteTrigger} as the NARROW-viewport search
|
|
180
|
+
* affordance. Rendered ONLY at the palette affordance (viewport < 768); on a wide
|
|
181
|
+
* viewport it is suppressed so the inline field and the trigger are never both
|
|
182
|
+
* shown — they are mutually exclusive by affordance. The ⌘K hotkey stays bound at
|
|
183
|
+
* every width independently ({@link useCommandPaletteHotkey}).
|
|
184
|
+
*/
|
|
185
|
+
paletteTrigger?: SidebarPaletteTriggerConfig;
|
|
130
186
|
/** Controlled search query (optional — the sidebar owns the state when omitted). */
|
|
131
187
|
searchQuery?: string;
|
|
132
188
|
/** Called on each query change (with either controlled or internal state). */
|
|
@@ -138,7 +194,7 @@ interface SidebarProps {
|
|
|
138
194
|
/** Extra container style overrides. */
|
|
139
195
|
containerStyle?: ViewStyle | ViewStyle[];
|
|
140
196
|
}
|
|
141
|
-
declare const Sidebar: ({ items, pathname, onNavigate, title, regionLabel, navigateHint, expandHint, collapseHint, renderChevron, enableInlineSearch, search, searchQuery, onSearchChange, header, footer, containerStyle, }: SidebarProps) => React.ReactElement;
|
|
197
|
+
declare const Sidebar: ({ items, pathname, onNavigate, title, regionLabel, navigateHint, expandHint, collapseHint, renderChevron, enableInlineSearch, search, paletteTrigger, searchQuery, onSearchChange, header, footer, containerStyle, }: SidebarProps) => React.ReactElement;
|
|
142
198
|
|
|
143
199
|
/**
|
|
144
200
|
* SidebarChevron — the built-in disclosure caret for an expandable sidebar
|
|
@@ -737,6 +793,12 @@ interface NavShellSideRail {
|
|
|
737
793
|
enableInlineSearch?: boolean;
|
|
738
794
|
/** Pre-localized inline-search strings — required for `enableInlineSearch`. Forwarded to `Sidebar.search`. */
|
|
739
795
|
search?: SidebarSearchConfig;
|
|
796
|
+
/**
|
|
797
|
+
* Opt the inner `Sidebar` into the ⌘K palette trigger as its narrow-viewport
|
|
798
|
+
* search affordance. Mutually exclusive with the inline field by viewport width
|
|
799
|
+
* (never both shown). Forwarded to `Sidebar.paletteTrigger`.
|
|
800
|
+
*/
|
|
801
|
+
paletteTrigger?: SidebarPaletteTriggerConfig;
|
|
740
802
|
/** Controlled search query (optional — the Sidebar owns the state when omitted). Forwarded to `Sidebar.searchQuery`. */
|
|
741
803
|
searchQuery?: string;
|
|
742
804
|
/** Called on each query change (controlled or internal). Forwarded to `Sidebar.onSearchChange`. */
|
|
@@ -1047,6 +1109,15 @@ interface NavExpandableItemProps {
|
|
|
1047
1109
|
item: NavItem;
|
|
1048
1110
|
pathname: string;
|
|
1049
1111
|
onNavigate: (route: string) => void;
|
|
1112
|
+
/**
|
|
1113
|
+
* The single winning active leaf route for the whole nav model, resolved once by
|
|
1114
|
+
* the parent via `resolveActiveRoute` (most-specific/longest match wins). A leaf
|
|
1115
|
+
* is active iff `item.route === activeRoute`, so at most one leaf ever carries
|
|
1116
|
+
* `aria-current="page"` — the fix for `/accounting` co-activating with
|
|
1117
|
+
* `/accounting/journals`. When omitted (standalone use) each leaf falls back to
|
|
1118
|
+
* the per-item prefix predicate, preserving the pre-1.18 behaviour.
|
|
1119
|
+
*/
|
|
1120
|
+
activeRoute?: string;
|
|
1050
1121
|
/** a11y hint for a leaf item, given its label. */
|
|
1051
1122
|
navigateHint: (label: string) => string;
|
|
1052
1123
|
/** a11y hint shown when the section is collapsed (press expands). */
|
|
@@ -1061,7 +1132,7 @@ interface NavExpandableItemProps {
|
|
|
1061
1132
|
renderChevron?: (expanded: boolean, color: string, size: number) => React.ReactNode;
|
|
1062
1133
|
depth?: number;
|
|
1063
1134
|
}
|
|
1064
|
-
declare const NavExpandableItem: ({ item, pathname, onNavigate, navigateHint, expandHint, collapseHint, renderChevron, depth, }: NavExpandableItemProps) => React.ReactElement;
|
|
1135
|
+
declare const NavExpandableItem: ({ item, pathname, onNavigate, navigateHint, expandHint, collapseHint, renderChevron, activeRoute, depth, }: NavExpandableItemProps) => React.ReactElement;
|
|
1065
1136
|
|
|
1066
1137
|
/**
|
|
1067
1138
|
* Pure matching logic for the {@link CommandPalette}. Kept framework-free so the
|
|
@@ -1233,6 +1304,9 @@ declare const navStyles: {
|
|
|
1233
1304
|
sidebarSpacer: {
|
|
1234
1305
|
flex: number;
|
|
1235
1306
|
};
|
|
1307
|
+
sidebarTriggerSlot: {
|
|
1308
|
+
marginBottom: number;
|
|
1309
|
+
};
|
|
1236
1310
|
sidebarEmpty: {
|
|
1237
1311
|
fontSize: number;
|
|
1238
1312
|
paddingVertical: number;
|
|
@@ -1453,4 +1527,4 @@ declare const NAV_ICON_SIZE = 14;
|
|
|
1453
1527
|
/** Chevron icon size for expandable sections. */
|
|
1454
1528
|
declare const CHEVRON_ICON_SIZE = 12;
|
|
1455
1529
|
|
|
1456
|
-
export { ACTIVE_BORDER_RADIUS, ACTIVE_TINT_OPACITY, APP_SHELL_SUFFIX, type AccountPlan, type AccountState, AppShell, type AppShellProps, type AppShellWidth, BASE_INDENT, CHEVRON_ICON_SIZE, CollapsedRail, type CollapsedRailProps, type CollapsedRailRange, type CommandItem, CommandPalette, type CommandPaletteLabels, type CommandPaletteProps, CommandPaletteTrigger, type CommandPaletteTriggerProps, DEFAULT_COLLAPSED_RAIL_MAX, DEFAULT_SEARCH_BREAKPOINT, DarkModeControl, type DarkModeControlProps, type DarkModeOption, type DarkModeVariant, HOVER_TINT_OPACITY, NAV_ICON_SIZE, NAV_LINK_GAP, NAV_ROW_RADIUS, NAV_TEST_IDS, Nav, NavBar, type NavBarProps, NavExpandableItem, type NavExpandableItemProps, type NavItem, type NavOrientation, NavOverflowMenu, type NavOverflowMenuProps, type NavProps, NavShell, type NavShellCollapsedRail, type NavShellLayout, type NavShellProps, type NavShellSideRail, type NavShellTopBar, type NavUser, PillNav, type PillNavProps, RAIL_FULL_BREAKPOINT, type RailMode, type ResolveRailModeArgs, type SearchAffordance, type ShellMessage, Sidebar, SidebarChevron, type SidebarChevronProps, type SidebarProps, SidebarSearch, type SidebarSearchConfig, type SidebarSearchLabels, type SidebarSearchProps, Topbar, type TopbarAction, type TopbarProps, type TopbarUser, accessibleNavItems, collapsedRailStyles, darkModeStyles, expandableStyles, filterCommands, filterNavItems, isFilterActive, isRouteActive, navStyles, pillNavStyles, resolveContentMaxWidth, resolveRailMode, resolveSearchAffordance, roleRoutesToNavItems, searchTokens, useCommandPaletteHotkey, useContentMaxWidth };
|
|
1530
|
+
export { ACTIVE_BORDER_RADIUS, ACTIVE_TINT_OPACITY, APP_SHELL_SUFFIX, type AccountPlan, type AccountState, AppShell, type AppShellProps, type AppShellWidth, BASE_INDENT, CHEVRON_ICON_SIZE, CollapsedRail, type CollapsedRailProps, type CollapsedRailRange, type CommandItem, CommandPalette, type CommandPaletteLabels, type CommandPaletteProps, CommandPaletteTrigger, type CommandPaletteTriggerProps, DEFAULT_COLLAPSED_RAIL_MAX, DEFAULT_SEARCH_BREAKPOINT, DarkModeControl, type DarkModeControlProps, type DarkModeOption, type DarkModeVariant, HOVER_TINT_OPACITY, NAV_ICON_SIZE, NAV_LINK_GAP, NAV_ROW_RADIUS, NAV_TEST_IDS, Nav, NavBar, type NavBarProps, NavExpandableItem, type NavExpandableItemProps, type NavItem, type NavOrientation, NavOverflowMenu, type NavOverflowMenuProps, type NavProps, NavShell, type NavShellCollapsedRail, type NavShellLayout, type NavShellProps, type NavShellSideRail, type NavShellTopBar, type NavUser, PillNav, type PillNavProps, RAIL_FULL_BREAKPOINT, type RailMode, type ResolveRailModeArgs, type SearchAffordance, type ShellMessage, Sidebar, SidebarChevron, type SidebarChevronProps, type SidebarPaletteTriggerConfig, type SidebarProps, SidebarSearch, type SidebarSearchConfig, type SidebarSearchLabels, type SidebarSearchProps, Topbar, type TopbarAction, type TopbarProps, type TopbarUser, accessibleNavItems, collapsedRailStyles, darkModeStyles, expandableStyles, filterCommands, filterNavItems, isFilterActive, isRouteActive, navStyles, pillNavStyles, resolveActiveRoute, resolveContentMaxWidth, resolveRailMode, resolveSearchAffordance, roleRoutesToNavItems, searchTokens, useCommandPaletteHotkey, useContentMaxWidth };
|
package/dist/index.d.ts
CHANGED
|
@@ -5,12 +5,71 @@ import { DropdownVariant } from '@dloizides/ui-layout';
|
|
|
5
5
|
import { resolveAccessibleRoutes, RoleRouteTable, RoleRoute } from '@dloizides/auth-web';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* only
|
|
8
|
+
* Public prop types for the `@dloizides/ui-nav` config-driven navigation shell.
|
|
9
|
+
*
|
|
10
|
+
* The rendering chrome (sidebar + topbar) was byte-identical across erevna-web
|
|
11
|
+
* and katalogos-web; only the *item data* (which routes, how they group) differs
|
|
12
|
+
* per app. So this package renders a caller-supplied `NavItem[]` — labels are
|
|
13
|
+
* pre-localized strings and icons are render slots, keeping the package free of
|
|
14
|
+
* any app's i18n helper, icon set, router, or store.
|
|
12
15
|
*/
|
|
13
|
-
|
|
16
|
+
|
|
17
|
+
/** One navigation entry. Labels are already localized by the caller. */
|
|
18
|
+
interface NavItem {
|
|
19
|
+
/** Stable key + default testID. */
|
|
20
|
+
key: string;
|
|
21
|
+
/** Localized display label. */
|
|
22
|
+
label: string;
|
|
23
|
+
/** Route/path this item navigates to (passed back to `onNavigate`). */
|
|
24
|
+
route: string;
|
|
25
|
+
/** Optional testID override (defaults to `key`). */
|
|
26
|
+
testID?: string;
|
|
27
|
+
/**
|
|
28
|
+
* Optional leading icon. Receives the resolved foreground colour and a size,
|
|
29
|
+
* so the app supplies its own icon component without this package importing
|
|
30
|
+
* an icon set.
|
|
31
|
+
*/
|
|
32
|
+
renderIcon?: (color: string, size: number) => React.ReactNode;
|
|
33
|
+
/** Optional nested items — rendered as an expandable section. */
|
|
34
|
+
children?: NavItem[];
|
|
35
|
+
/**
|
|
36
|
+
* Opt this leaf out of prefix matching: when `true` it is active ONLY when the
|
|
37
|
+
* pathname equals its route exactly (never for a nested `route/child` path). A
|
|
38
|
+
* complementary escape hatch — the default most-specific/longest-match resolution
|
|
39
|
+
* (see `resolveActiveRoute`) already prevents a module-home leaf like `/accounting`
|
|
40
|
+
* from co-activating with `/accounting/journals`, so most consumers never need
|
|
41
|
+
* this. Reach for it only when a leaf must NEVER light up on any descendant path
|
|
42
|
+
* even when no longer sibling route exists.
|
|
43
|
+
*/
|
|
44
|
+
exact?: boolean;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Active-route matching for the nav shells.
|
|
49
|
+
*
|
|
50
|
+
* `isRouteActive` is the low-level per-route predicate (prefix-based, ported
|
|
51
|
+
* verbatim from the twin app sidebars): a route is active when the current
|
|
52
|
+
* pathname equals it or is nested under it (`/foo` matches `/foo` and `/foo/bar`,
|
|
53
|
+
* but `/` only matches `/`). With `exact`, only an exact path matches.
|
|
54
|
+
*
|
|
55
|
+
* `resolveActiveRoute` is the CROSS-ITEM resolver that fixes the sidebar
|
|
56
|
+
* co-activation bug: because a module-home leaf (`/accounting`) is a prefix of
|
|
57
|
+
* every sibling (`/accounting/journals`), the naive per-item predicate lights up
|
|
58
|
+
* BOTH on `/accounting/journals`. `resolveActiveRoute` instead returns the ONE
|
|
59
|
+
* most-specific (longest) leaf route that matches — so at most one leaf is ever
|
|
60
|
+
* active, the longest wins on nested paths, and the module-home leaf is active
|
|
61
|
+
* only on its own exact path. For a nav model with no parent/child route overlap
|
|
62
|
+
* (the common case) this is identical to the old behaviour.
|
|
63
|
+
*/
|
|
64
|
+
|
|
65
|
+
declare function isRouteActive(pathname: string, route: string, exact?: boolean): boolean;
|
|
66
|
+
/**
|
|
67
|
+
* The single active leaf route for `pathname`: the LONGEST leaf route that
|
|
68
|
+
* matches (most-specific wins). Returns `undefined` when no leaf matches. Marking
|
|
69
|
+
* a leaf active iff `item.route === resolveActiveRoute(items, pathname)` guarantees
|
|
70
|
+
* at most one active leaf — the whack-a-mole prefix co-activation fix.
|
|
71
|
+
*/
|
|
72
|
+
declare function resolveActiveRoute(items: NavItem[], pathname: string): string | undefined;
|
|
14
73
|
|
|
15
74
|
/**
|
|
16
75
|
* SidebarSearch — the persistent, DESKTOP inline search field that lives at the
|
|
@@ -51,36 +110,6 @@ interface SidebarSearchProps {
|
|
|
51
110
|
/** The inline sidebar search field (desktop) with a clear button. */
|
|
52
111
|
declare const SidebarSearch: ({ value, onChangeText, onClear, labels, testID, }: SidebarSearchProps) => React.ReactElement;
|
|
53
112
|
|
|
54
|
-
/**
|
|
55
|
-
* Public prop types for the `@dloizides/ui-nav` config-driven navigation shell.
|
|
56
|
-
*
|
|
57
|
-
* The rendering chrome (sidebar + topbar) was byte-identical across erevna-web
|
|
58
|
-
* and katalogos-web; only the *item data* (which routes, how they group) differs
|
|
59
|
-
* per app. So this package renders a caller-supplied `NavItem[]` — labels are
|
|
60
|
-
* pre-localized strings and icons are render slots, keeping the package free of
|
|
61
|
-
* any app's i18n helper, icon set, router, or store.
|
|
62
|
-
*/
|
|
63
|
-
|
|
64
|
-
/** One navigation entry. Labels are already localized by the caller. */
|
|
65
|
-
interface NavItem {
|
|
66
|
-
/** Stable key + default testID. */
|
|
67
|
-
key: string;
|
|
68
|
-
/** Localized display label. */
|
|
69
|
-
label: string;
|
|
70
|
-
/** Route/path this item navigates to (passed back to `onNavigate`). */
|
|
71
|
-
route: string;
|
|
72
|
-
/** Optional testID override (defaults to `key`). */
|
|
73
|
-
testID?: string;
|
|
74
|
-
/**
|
|
75
|
-
* Optional leading icon. Receives the resolved foreground colour and a size,
|
|
76
|
-
* so the app supplies its own icon component without this package importing
|
|
77
|
-
* an icon set.
|
|
78
|
-
*/
|
|
79
|
-
renderIcon?: (color: string, size: number) => React.ReactNode;
|
|
80
|
-
/** Optional nested items — rendered as an expandable section. */
|
|
81
|
-
children?: NavItem[];
|
|
82
|
-
}
|
|
83
|
-
|
|
84
113
|
/**
|
|
85
114
|
* Sidebar — the config-driven left navigation shell promoted from the
|
|
86
115
|
* byte-identical erevna-web / katalogos-web `Sidebar`. It renders a caller
|
|
@@ -100,6 +129,24 @@ interface SidebarSearchConfig extends SidebarSearchLabels {
|
|
|
100
129
|
/** Shown in place of the list when the query matches nothing. */
|
|
101
130
|
emptyText: string;
|
|
102
131
|
}
|
|
132
|
+
/**
|
|
133
|
+
* The ⌘K command-palette trigger the sidebar renders as its NARROW-viewport search
|
|
134
|
+
* affordance (a persistent inline field would steal scarce vertical space on mobile
|
|
135
|
+
* — see {@link resolveSearchAffordance}). Pre-localized strings + the open handler;
|
|
136
|
+
* wire `onOpen` to the same handler as the ⌘K hotkey ({@link useCommandPaletteHotkey}).
|
|
137
|
+
*/
|
|
138
|
+
interface SidebarPaletteTriggerConfig {
|
|
139
|
+
/** Placeholder-style label, e.g. "Search". */
|
|
140
|
+
label: string;
|
|
141
|
+
/** a11y hint describing what activating it does. */
|
|
142
|
+
hint: string;
|
|
143
|
+
/** The shortcut badge text, e.g. "⌘K". */
|
|
144
|
+
shortcut: string;
|
|
145
|
+
/** Open the palette (same handler as the ⌘K hotkey). */
|
|
146
|
+
onOpen: () => void;
|
|
147
|
+
/** testID for the trigger. Defaults to `sidebar-command-palette-trigger`. */
|
|
148
|
+
testID?: string;
|
|
149
|
+
}
|
|
103
150
|
interface SidebarProps {
|
|
104
151
|
/** Nav entries — already role-filtered / grouped by the app. */
|
|
105
152
|
items: NavItem[];
|
|
@@ -121,12 +168,21 @@ interface SidebarProps {
|
|
|
121
168
|
renderChevron?: (expanded: boolean, color: string, size: number) => React.ReactNode;
|
|
122
169
|
/**
|
|
123
170
|
* Opt into the persistent inline search field (desktop). Requires `search`
|
|
124
|
-
* labels; when omitted the sidebar renders exactly as before.
|
|
125
|
-
*
|
|
171
|
+
* labels; when omitted the sidebar renders exactly as before. The field shows
|
|
172
|
+
* ONLY at the inline affordance (viewport ≥ 768, {@link resolveSearchAffordance});
|
|
173
|
+
* on a narrow viewport the ⌘K `paletteTrigger` is the search affordance instead.
|
|
126
174
|
*/
|
|
127
175
|
enableInlineSearch?: boolean;
|
|
128
176
|
/** Pre-localized inline-search strings — required for `enableInlineSearch`. */
|
|
129
177
|
search?: SidebarSearchConfig;
|
|
178
|
+
/**
|
|
179
|
+
* Opt into the ⌘K {@link CommandPaletteTrigger} as the NARROW-viewport search
|
|
180
|
+
* affordance. Rendered ONLY at the palette affordance (viewport < 768); on a wide
|
|
181
|
+
* viewport it is suppressed so the inline field and the trigger are never both
|
|
182
|
+
* shown — they are mutually exclusive by affordance. The ⌘K hotkey stays bound at
|
|
183
|
+
* every width independently ({@link useCommandPaletteHotkey}).
|
|
184
|
+
*/
|
|
185
|
+
paletteTrigger?: SidebarPaletteTriggerConfig;
|
|
130
186
|
/** Controlled search query (optional — the sidebar owns the state when omitted). */
|
|
131
187
|
searchQuery?: string;
|
|
132
188
|
/** Called on each query change (with either controlled or internal state). */
|
|
@@ -138,7 +194,7 @@ interface SidebarProps {
|
|
|
138
194
|
/** Extra container style overrides. */
|
|
139
195
|
containerStyle?: ViewStyle | ViewStyle[];
|
|
140
196
|
}
|
|
141
|
-
declare const Sidebar: ({ items, pathname, onNavigate, title, regionLabel, navigateHint, expandHint, collapseHint, renderChevron, enableInlineSearch, search, searchQuery, onSearchChange, header, footer, containerStyle, }: SidebarProps) => React.ReactElement;
|
|
197
|
+
declare const Sidebar: ({ items, pathname, onNavigate, title, regionLabel, navigateHint, expandHint, collapseHint, renderChevron, enableInlineSearch, search, paletteTrigger, searchQuery, onSearchChange, header, footer, containerStyle, }: SidebarProps) => React.ReactElement;
|
|
142
198
|
|
|
143
199
|
/**
|
|
144
200
|
* SidebarChevron — the built-in disclosure caret for an expandable sidebar
|
|
@@ -737,6 +793,12 @@ interface NavShellSideRail {
|
|
|
737
793
|
enableInlineSearch?: boolean;
|
|
738
794
|
/** Pre-localized inline-search strings — required for `enableInlineSearch`. Forwarded to `Sidebar.search`. */
|
|
739
795
|
search?: SidebarSearchConfig;
|
|
796
|
+
/**
|
|
797
|
+
* Opt the inner `Sidebar` into the ⌘K palette trigger as its narrow-viewport
|
|
798
|
+
* search affordance. Mutually exclusive with the inline field by viewport width
|
|
799
|
+
* (never both shown). Forwarded to `Sidebar.paletteTrigger`.
|
|
800
|
+
*/
|
|
801
|
+
paletteTrigger?: SidebarPaletteTriggerConfig;
|
|
740
802
|
/** Controlled search query (optional — the Sidebar owns the state when omitted). Forwarded to `Sidebar.searchQuery`. */
|
|
741
803
|
searchQuery?: string;
|
|
742
804
|
/** Called on each query change (controlled or internal). Forwarded to `Sidebar.onSearchChange`. */
|
|
@@ -1047,6 +1109,15 @@ interface NavExpandableItemProps {
|
|
|
1047
1109
|
item: NavItem;
|
|
1048
1110
|
pathname: string;
|
|
1049
1111
|
onNavigate: (route: string) => void;
|
|
1112
|
+
/**
|
|
1113
|
+
* The single winning active leaf route for the whole nav model, resolved once by
|
|
1114
|
+
* the parent via `resolveActiveRoute` (most-specific/longest match wins). A leaf
|
|
1115
|
+
* is active iff `item.route === activeRoute`, so at most one leaf ever carries
|
|
1116
|
+
* `aria-current="page"` — the fix for `/accounting` co-activating with
|
|
1117
|
+
* `/accounting/journals`. When omitted (standalone use) each leaf falls back to
|
|
1118
|
+
* the per-item prefix predicate, preserving the pre-1.18 behaviour.
|
|
1119
|
+
*/
|
|
1120
|
+
activeRoute?: string;
|
|
1050
1121
|
/** a11y hint for a leaf item, given its label. */
|
|
1051
1122
|
navigateHint: (label: string) => string;
|
|
1052
1123
|
/** a11y hint shown when the section is collapsed (press expands). */
|
|
@@ -1061,7 +1132,7 @@ interface NavExpandableItemProps {
|
|
|
1061
1132
|
renderChevron?: (expanded: boolean, color: string, size: number) => React.ReactNode;
|
|
1062
1133
|
depth?: number;
|
|
1063
1134
|
}
|
|
1064
|
-
declare const NavExpandableItem: ({ item, pathname, onNavigate, navigateHint, expandHint, collapseHint, renderChevron, depth, }: NavExpandableItemProps) => React.ReactElement;
|
|
1135
|
+
declare const NavExpandableItem: ({ item, pathname, onNavigate, navigateHint, expandHint, collapseHint, renderChevron, activeRoute, depth, }: NavExpandableItemProps) => React.ReactElement;
|
|
1065
1136
|
|
|
1066
1137
|
/**
|
|
1067
1138
|
* Pure matching logic for the {@link CommandPalette}. Kept framework-free so the
|
|
@@ -1233,6 +1304,9 @@ declare const navStyles: {
|
|
|
1233
1304
|
sidebarSpacer: {
|
|
1234
1305
|
flex: number;
|
|
1235
1306
|
};
|
|
1307
|
+
sidebarTriggerSlot: {
|
|
1308
|
+
marginBottom: number;
|
|
1309
|
+
};
|
|
1236
1310
|
sidebarEmpty: {
|
|
1237
1311
|
fontSize: number;
|
|
1238
1312
|
paddingVertical: number;
|
|
@@ -1453,4 +1527,4 @@ declare const NAV_ICON_SIZE = 14;
|
|
|
1453
1527
|
/** Chevron icon size for expandable sections. */
|
|
1454
1528
|
declare const CHEVRON_ICON_SIZE = 12;
|
|
1455
1529
|
|
|
1456
|
-
export { ACTIVE_BORDER_RADIUS, ACTIVE_TINT_OPACITY, APP_SHELL_SUFFIX, type AccountPlan, type AccountState, AppShell, type AppShellProps, type AppShellWidth, BASE_INDENT, CHEVRON_ICON_SIZE, CollapsedRail, type CollapsedRailProps, type CollapsedRailRange, type CommandItem, CommandPalette, type CommandPaletteLabels, type CommandPaletteProps, CommandPaletteTrigger, type CommandPaletteTriggerProps, DEFAULT_COLLAPSED_RAIL_MAX, DEFAULT_SEARCH_BREAKPOINT, DarkModeControl, type DarkModeControlProps, type DarkModeOption, type DarkModeVariant, HOVER_TINT_OPACITY, NAV_ICON_SIZE, NAV_LINK_GAP, NAV_ROW_RADIUS, NAV_TEST_IDS, Nav, NavBar, type NavBarProps, NavExpandableItem, type NavExpandableItemProps, type NavItem, type NavOrientation, NavOverflowMenu, type NavOverflowMenuProps, type NavProps, NavShell, type NavShellCollapsedRail, type NavShellLayout, type NavShellProps, type NavShellSideRail, type NavShellTopBar, type NavUser, PillNav, type PillNavProps, RAIL_FULL_BREAKPOINT, type RailMode, type ResolveRailModeArgs, type SearchAffordance, type ShellMessage, Sidebar, SidebarChevron, type SidebarChevronProps, type SidebarProps, SidebarSearch, type SidebarSearchConfig, type SidebarSearchLabels, type SidebarSearchProps, Topbar, type TopbarAction, type TopbarProps, type TopbarUser, accessibleNavItems, collapsedRailStyles, darkModeStyles, expandableStyles, filterCommands, filterNavItems, isFilterActive, isRouteActive, navStyles, pillNavStyles, resolveContentMaxWidth, resolveRailMode, resolveSearchAffordance, roleRoutesToNavItems, searchTokens, useCommandPaletteHotkey, useContentMaxWidth };
|
|
1530
|
+
export { ACTIVE_BORDER_RADIUS, ACTIVE_TINT_OPACITY, APP_SHELL_SUFFIX, type AccountPlan, type AccountState, AppShell, type AppShellProps, type AppShellWidth, BASE_INDENT, CHEVRON_ICON_SIZE, CollapsedRail, type CollapsedRailProps, type CollapsedRailRange, type CommandItem, CommandPalette, type CommandPaletteLabels, type CommandPaletteProps, CommandPaletteTrigger, type CommandPaletteTriggerProps, DEFAULT_COLLAPSED_RAIL_MAX, DEFAULT_SEARCH_BREAKPOINT, DarkModeControl, type DarkModeControlProps, type DarkModeOption, type DarkModeVariant, HOVER_TINT_OPACITY, NAV_ICON_SIZE, NAV_LINK_GAP, NAV_ROW_RADIUS, NAV_TEST_IDS, Nav, NavBar, type NavBarProps, NavExpandableItem, type NavExpandableItemProps, type NavItem, type NavOrientation, NavOverflowMenu, type NavOverflowMenuProps, type NavProps, NavShell, type NavShellCollapsedRail, type NavShellLayout, type NavShellProps, type NavShellSideRail, type NavShellTopBar, type NavUser, PillNav, type PillNavProps, RAIL_FULL_BREAKPOINT, type RailMode, type ResolveRailModeArgs, type SearchAffordance, type ShellMessage, Sidebar, SidebarChevron, type SidebarChevronProps, type SidebarPaletteTriggerConfig, type SidebarProps, SidebarSearch, type SidebarSearchConfig, type SidebarSearchLabels, type SidebarSearchProps, Topbar, type TopbarAction, type TopbarProps, type TopbarUser, accessibleNavItems, collapsedRailStyles, darkModeStyles, expandableStyles, filterCommands, filterNavItems, isFilterActive, isRouteActive, navStyles, pillNavStyles, resolveActiveRoute, resolveContentMaxWidth, resolveRailMode, resolveSearchAffordance, roleRoutesToNavItems, searchTokens, useCommandPaletteHotkey, useContentMaxWidth };
|