@dloizides/ui-nav 1.10.2 → 1.12.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 +23 -0
- package/dist/index.d.mts +95 -1
- package/dist/index.d.ts +95 -1
- package/dist/index.js +216 -12
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +215 -14
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,28 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.11.0
|
|
4
|
+
|
|
5
|
+
- **Feature (an expandable nav group now auto-opens when the current route is inside it).**
|
|
6
|
+
`NavExpandableItem` used to mount every group collapsed (`useState(false)`), so on a fresh
|
|
7
|
+
page load the group containing the active route was closed and its active leaf was hidden.
|
|
8
|
+
A persistent-rail console that groups its nav by module (e.g. finreg-web's Payments / CRM /
|
|
9
|
+
Accounting rail) needs the active module's group open on load, the others collapsed — with
|
|
10
|
+
no way for the caller to express that, since expansion state was internal and uncontrolled.
|
|
11
|
+
- The group now initializes its expansion from whether the current route is inside it:
|
|
12
|
+
`useState(() => hasActiveDescendant(item, pathname))`, where `hasActiveDescendant` is true
|
|
13
|
+
when the item's own route is active (`isRouteActive`) or any descendant's is (recursive, so
|
|
14
|
+
it holds at any nesting depth). The group of the active route mounts EXPANDED (active leaf
|
|
15
|
+
visible + highlighted); a group with no active descendant mounts COLLAPSED — exactly the
|
|
16
|
+
prior behaviour.
|
|
17
|
+
- **No API change.** `NavItem` is unchanged — the behaviour is derived from the `pathname`
|
|
18
|
+
the component already receives. Consumers with no expandable groups, or whose groups hold no
|
|
19
|
+
active route, are byte-for-byte unaffected; all 7 portals gain the auto-open on their next
|
|
20
|
+
bump for free.
|
|
21
|
+
- Guarded by `src/navExpandableItem.test.tsx` (RED-first): the two "mounts EXPANDED" assertions
|
|
22
|
+
FAIL against the pre-change `useState(false)` (the active group's child is absent from the DOM)
|
|
23
|
+
and pass after; the "mounts COLLAPSED" + "header always renders" assertions lock the
|
|
24
|
+
backward-compatible half.
|
|
25
|
+
|
|
3
26
|
## 1.10.2
|
|
4
27
|
|
|
5
28
|
- **Fix (the mobile nav DRAWER was not clickable — a tap on a nav item did not navigate, it
|
package/dist/index.d.mts
CHANGED
|
@@ -898,6 +898,87 @@ interface NavExpandableItemProps {
|
|
|
898
898
|
}
|
|
899
899
|
declare const NavExpandableItem: ({ item, pathname, onNavigate, navigateHint, expandHint, collapseHint, renderChevron, depth, }: NavExpandableItemProps) => React.ReactElement;
|
|
900
900
|
|
|
901
|
+
/**
|
|
902
|
+
* Pure matching logic for the {@link CommandPalette}. Kept framework-free so the
|
|
903
|
+
* ranking/filtering is unit-testable without rendering: a command matches when
|
|
904
|
+
* every whitespace-separated token of the query appears (case-insensitively) in
|
|
905
|
+
* its haystack (label + hint + keywords). Requiring ALL tokens lets an operator
|
|
906
|
+
* narrow with "pay dash" the way a fuzzy launcher would, without a fuzzy lib.
|
|
907
|
+
*/
|
|
908
|
+
|
|
909
|
+
/** One actionable destination/command shown in the palette. */
|
|
910
|
+
interface CommandItem {
|
|
911
|
+
/** Stable key + default testID. */
|
|
912
|
+
key: string;
|
|
913
|
+
/** Optional testID override (defaults to `key`). */
|
|
914
|
+
testID?: string;
|
|
915
|
+
/** Localized display label (what the operator reads). */
|
|
916
|
+
label: string;
|
|
917
|
+
/** Optional localized secondary line (e.g. the module it lives in). */
|
|
918
|
+
hint?: string;
|
|
919
|
+
/** Optional extra search terms not shown but matched (synonyms, route words). */
|
|
920
|
+
keywords?: readonly string[];
|
|
921
|
+
/** Invoked when the item is chosen (the consumer navigates / runs the action). */
|
|
922
|
+
onSelect: () => void;
|
|
923
|
+
/** Optional leading icon slot, same contract as `NavItem.renderIcon`. */
|
|
924
|
+
renderIcon?: (color: string, size: number) => React.ReactNode;
|
|
925
|
+
}
|
|
926
|
+
/**
|
|
927
|
+
* Filter `items` to those matching `query`. An empty/whitespace query returns
|
|
928
|
+
* the list unchanged (the palette opens showing everything). Order is preserved
|
|
929
|
+
* — the caller supplies items in a meaningful order (nav order).
|
|
930
|
+
*/
|
|
931
|
+
declare function filterCommands(items: readonly CommandItem[], query: string): CommandItem[];
|
|
932
|
+
|
|
933
|
+
/**
|
|
934
|
+
* CommandPalette — the shared ⌘K launcher for `@dloizides/ui-nav`. An overlay
|
|
935
|
+
* (scrim + centred panel) with a search input over a keyboard-navigable list of
|
|
936
|
+
* {@link CommandItem}s — jump to any nav destination (or run any action) without
|
|
937
|
+
* hunting the rail. Web-first (RN-web), but degrades safely: when `open` is
|
|
938
|
+
* false it renders nothing.
|
|
939
|
+
*
|
|
940
|
+
* Package discipline (as the rest of ui-nav): NO FM / router / store / icon-set
|
|
941
|
+
* imports. Labels are pre-localized strings; icons are render slots; every colour
|
|
942
|
+
* comes from the `@dloizides/ui-feedback` UiProvider theme. The consumer owns the
|
|
943
|
+
* `open` state (pair with {@link useCommandPaletteHotkey}) and each item's
|
|
944
|
+
* `onSelect` (navigate / run) — the palette just closes after a choice.
|
|
945
|
+
*/
|
|
946
|
+
|
|
947
|
+
/** Pre-localized labels for the palette chrome. */
|
|
948
|
+
interface CommandPaletteLabels {
|
|
949
|
+
/** Search input placeholder. */
|
|
950
|
+
placeholder: string;
|
|
951
|
+
/** Accessible name of the scrim (tap to close). */
|
|
952
|
+
closeLabel: string;
|
|
953
|
+
/** Accessible hint of the scrim (tap to close). */
|
|
954
|
+
closeHint: string;
|
|
955
|
+
/** Shown when the query matches nothing. */
|
|
956
|
+
emptyText: string;
|
|
957
|
+
/** Accessible label for the palette dialog landmark. */
|
|
958
|
+
regionLabel: string;
|
|
959
|
+
}
|
|
960
|
+
interface CommandPaletteProps {
|
|
961
|
+
/** Whether the palette overlay is shown. */
|
|
962
|
+
open: boolean;
|
|
963
|
+
/** The commands to search over, in a meaningful (nav) order. */
|
|
964
|
+
items: readonly CommandItem[];
|
|
965
|
+
/** Close the palette (scrim tap / Escape / after a selection). */
|
|
966
|
+
onClose: () => void;
|
|
967
|
+
/** Pre-localized chrome labels. */
|
|
968
|
+
labels: CommandPaletteLabels;
|
|
969
|
+
/** testID for the panel (defaults to `command-palette`). */
|
|
970
|
+
testID?: string;
|
|
971
|
+
}
|
|
972
|
+
/** The overlay palette. Renders null when closed. */
|
|
973
|
+
declare const CommandPalette: ({ open, items, onClose, labels, testID }: CommandPaletteProps) => React.ReactElement | null;
|
|
974
|
+
|
|
975
|
+
/**
|
|
976
|
+
* Call `onOpen` when the operator presses ⌘K (macOS) or Ctrl-K (Windows/Linux),
|
|
977
|
+
* anywhere in the document. `enabled=false` unbinds (e.g. when the shell has no
|
|
978
|
+
* palette configured), so the shortcut never fires without a handler behind it.
|
|
979
|
+
*/
|
|
980
|
+
declare function useCommandPaletteHotkey(onOpen: () => void, enabled?: boolean): void;
|
|
981
|
+
|
|
901
982
|
/**
|
|
902
983
|
* Role-gated nav helpers — build a `NavItem[]` from a user's roles, reusing the
|
|
903
984
|
* existing `resolveAccessibleRoutes` from `@dloizides/auth-web` (the same helper
|
|
@@ -1101,6 +1182,8 @@ declare const expandableStyles: {
|
|
|
1101
1182
|
flexDirection: "row";
|
|
1102
1183
|
alignItems: "center";
|
|
1103
1184
|
paddingVertical: number;
|
|
1185
|
+
borderLeftWidth: number;
|
|
1186
|
+
borderLeftColor: string;
|
|
1104
1187
|
};
|
|
1105
1188
|
childItemText: {
|
|
1106
1189
|
fontSize: number;
|
|
@@ -1114,6 +1197,7 @@ declare const expandableStyles: {
|
|
|
1114
1197
|
};
|
|
1115
1198
|
childrenContainer: {
|
|
1116
1199
|
overflow: "hidden";
|
|
1200
|
+
marginBottom: number;
|
|
1117
1201
|
};
|
|
1118
1202
|
header: {
|
|
1119
1203
|
borderRadius: number;
|
|
@@ -1121,11 +1205,21 @@ declare const expandableStyles: {
|
|
|
1121
1205
|
alignItems: "center";
|
|
1122
1206
|
paddingVertical: number;
|
|
1123
1207
|
};
|
|
1208
|
+
sectionHeader: {
|
|
1209
|
+
marginTop: number;
|
|
1210
|
+
};
|
|
1124
1211
|
headerText: {
|
|
1125
1212
|
fontSize: number;
|
|
1126
1213
|
fontWeight: "600";
|
|
1127
1214
|
marginLeft: number;
|
|
1128
1215
|
};
|
|
1216
|
+
sectionHeaderText: {
|
|
1217
|
+
fontSize: number;
|
|
1218
|
+
fontWeight: "700";
|
|
1219
|
+
marginLeft: number;
|
|
1220
|
+
textTransform: "uppercase";
|
|
1221
|
+
letterSpacing: number;
|
|
1222
|
+
};
|
|
1129
1223
|
iconWrapper: {
|
|
1130
1224
|
width: number;
|
|
1131
1225
|
alignItems: "center";
|
|
@@ -1138,4 +1232,4 @@ declare const NAV_ICON_SIZE = 14;
|
|
|
1138
1232
|
/** Chevron icon size for expandable sections. */
|
|
1139
1233
|
declare const CHEVRON_ICON_SIZE = 12;
|
|
1140
1234
|
|
|
1141
|
-
export { ACTIVE_BORDER_RADIUS, APP_SHELL_SUFFIX, type AccountPlan, type AccountState, AppShell, type AppShellProps, type AppShellWidth, BASE_INDENT, CHEVRON_ICON_SIZE, CollapsedRail, type CollapsedRailProps, type CollapsedRailRange, DEFAULT_COLLAPSED_RAIL_MAX, DarkModeControl, type DarkModeControlProps, type DarkModeOption, type DarkModeVariant, NAV_ICON_SIZE, NAV_LINK_GAP, 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 ShellMessage, Sidebar, type SidebarProps, Topbar, type TopbarAction, type TopbarProps, type TopbarUser, accessibleNavItems, collapsedRailStyles, darkModeStyles, expandableStyles, isRouteActive, navStyles, pillNavStyles, resolveContentMaxWidth, resolveRailMode, roleRoutesToNavItems, useContentMaxWidth };
|
|
1235
|
+
export { ACTIVE_BORDER_RADIUS, 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, DEFAULT_COLLAPSED_RAIL_MAX, DarkModeControl, type DarkModeControlProps, type DarkModeOption, type DarkModeVariant, NAV_ICON_SIZE, NAV_LINK_GAP, 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 ShellMessage, Sidebar, type SidebarProps, Topbar, type TopbarAction, type TopbarProps, type TopbarUser, accessibleNavItems, collapsedRailStyles, darkModeStyles, expandableStyles, filterCommands, isRouteActive, navStyles, pillNavStyles, resolveContentMaxWidth, resolveRailMode, roleRoutesToNavItems, useCommandPaletteHotkey, useContentMaxWidth };
|
package/dist/index.d.ts
CHANGED
|
@@ -898,6 +898,87 @@ interface NavExpandableItemProps {
|
|
|
898
898
|
}
|
|
899
899
|
declare const NavExpandableItem: ({ item, pathname, onNavigate, navigateHint, expandHint, collapseHint, renderChevron, depth, }: NavExpandableItemProps) => React.ReactElement;
|
|
900
900
|
|
|
901
|
+
/**
|
|
902
|
+
* Pure matching logic for the {@link CommandPalette}. Kept framework-free so the
|
|
903
|
+
* ranking/filtering is unit-testable without rendering: a command matches when
|
|
904
|
+
* every whitespace-separated token of the query appears (case-insensitively) in
|
|
905
|
+
* its haystack (label + hint + keywords). Requiring ALL tokens lets an operator
|
|
906
|
+
* narrow with "pay dash" the way a fuzzy launcher would, without a fuzzy lib.
|
|
907
|
+
*/
|
|
908
|
+
|
|
909
|
+
/** One actionable destination/command shown in the palette. */
|
|
910
|
+
interface CommandItem {
|
|
911
|
+
/** Stable key + default testID. */
|
|
912
|
+
key: string;
|
|
913
|
+
/** Optional testID override (defaults to `key`). */
|
|
914
|
+
testID?: string;
|
|
915
|
+
/** Localized display label (what the operator reads). */
|
|
916
|
+
label: string;
|
|
917
|
+
/** Optional localized secondary line (e.g. the module it lives in). */
|
|
918
|
+
hint?: string;
|
|
919
|
+
/** Optional extra search terms not shown but matched (synonyms, route words). */
|
|
920
|
+
keywords?: readonly string[];
|
|
921
|
+
/** Invoked when the item is chosen (the consumer navigates / runs the action). */
|
|
922
|
+
onSelect: () => void;
|
|
923
|
+
/** Optional leading icon slot, same contract as `NavItem.renderIcon`. */
|
|
924
|
+
renderIcon?: (color: string, size: number) => React.ReactNode;
|
|
925
|
+
}
|
|
926
|
+
/**
|
|
927
|
+
* Filter `items` to those matching `query`. An empty/whitespace query returns
|
|
928
|
+
* the list unchanged (the palette opens showing everything). Order is preserved
|
|
929
|
+
* — the caller supplies items in a meaningful order (nav order).
|
|
930
|
+
*/
|
|
931
|
+
declare function filterCommands(items: readonly CommandItem[], query: string): CommandItem[];
|
|
932
|
+
|
|
933
|
+
/**
|
|
934
|
+
* CommandPalette — the shared ⌘K launcher for `@dloizides/ui-nav`. An overlay
|
|
935
|
+
* (scrim + centred panel) with a search input over a keyboard-navigable list of
|
|
936
|
+
* {@link CommandItem}s — jump to any nav destination (or run any action) without
|
|
937
|
+
* hunting the rail. Web-first (RN-web), but degrades safely: when `open` is
|
|
938
|
+
* false it renders nothing.
|
|
939
|
+
*
|
|
940
|
+
* Package discipline (as the rest of ui-nav): NO FM / router / store / icon-set
|
|
941
|
+
* imports. Labels are pre-localized strings; icons are render slots; every colour
|
|
942
|
+
* comes from the `@dloizides/ui-feedback` UiProvider theme. The consumer owns the
|
|
943
|
+
* `open` state (pair with {@link useCommandPaletteHotkey}) and each item's
|
|
944
|
+
* `onSelect` (navigate / run) — the palette just closes after a choice.
|
|
945
|
+
*/
|
|
946
|
+
|
|
947
|
+
/** Pre-localized labels for the palette chrome. */
|
|
948
|
+
interface CommandPaletteLabels {
|
|
949
|
+
/** Search input placeholder. */
|
|
950
|
+
placeholder: string;
|
|
951
|
+
/** Accessible name of the scrim (tap to close). */
|
|
952
|
+
closeLabel: string;
|
|
953
|
+
/** Accessible hint of the scrim (tap to close). */
|
|
954
|
+
closeHint: string;
|
|
955
|
+
/** Shown when the query matches nothing. */
|
|
956
|
+
emptyText: string;
|
|
957
|
+
/** Accessible label for the palette dialog landmark. */
|
|
958
|
+
regionLabel: string;
|
|
959
|
+
}
|
|
960
|
+
interface CommandPaletteProps {
|
|
961
|
+
/** Whether the palette overlay is shown. */
|
|
962
|
+
open: boolean;
|
|
963
|
+
/** The commands to search over, in a meaningful (nav) order. */
|
|
964
|
+
items: readonly CommandItem[];
|
|
965
|
+
/** Close the palette (scrim tap / Escape / after a selection). */
|
|
966
|
+
onClose: () => void;
|
|
967
|
+
/** Pre-localized chrome labels. */
|
|
968
|
+
labels: CommandPaletteLabels;
|
|
969
|
+
/** testID for the panel (defaults to `command-palette`). */
|
|
970
|
+
testID?: string;
|
|
971
|
+
}
|
|
972
|
+
/** The overlay palette. Renders null when closed. */
|
|
973
|
+
declare const CommandPalette: ({ open, items, onClose, labels, testID }: CommandPaletteProps) => React.ReactElement | null;
|
|
974
|
+
|
|
975
|
+
/**
|
|
976
|
+
* Call `onOpen` when the operator presses ⌘K (macOS) or Ctrl-K (Windows/Linux),
|
|
977
|
+
* anywhere in the document. `enabled=false` unbinds (e.g. when the shell has no
|
|
978
|
+
* palette configured), so the shortcut never fires without a handler behind it.
|
|
979
|
+
*/
|
|
980
|
+
declare function useCommandPaletteHotkey(onOpen: () => void, enabled?: boolean): void;
|
|
981
|
+
|
|
901
982
|
/**
|
|
902
983
|
* Role-gated nav helpers — build a `NavItem[]` from a user's roles, reusing the
|
|
903
984
|
* existing `resolveAccessibleRoutes` from `@dloizides/auth-web` (the same helper
|
|
@@ -1101,6 +1182,8 @@ declare const expandableStyles: {
|
|
|
1101
1182
|
flexDirection: "row";
|
|
1102
1183
|
alignItems: "center";
|
|
1103
1184
|
paddingVertical: number;
|
|
1185
|
+
borderLeftWidth: number;
|
|
1186
|
+
borderLeftColor: string;
|
|
1104
1187
|
};
|
|
1105
1188
|
childItemText: {
|
|
1106
1189
|
fontSize: number;
|
|
@@ -1114,6 +1197,7 @@ declare const expandableStyles: {
|
|
|
1114
1197
|
};
|
|
1115
1198
|
childrenContainer: {
|
|
1116
1199
|
overflow: "hidden";
|
|
1200
|
+
marginBottom: number;
|
|
1117
1201
|
};
|
|
1118
1202
|
header: {
|
|
1119
1203
|
borderRadius: number;
|
|
@@ -1121,11 +1205,21 @@ declare const expandableStyles: {
|
|
|
1121
1205
|
alignItems: "center";
|
|
1122
1206
|
paddingVertical: number;
|
|
1123
1207
|
};
|
|
1208
|
+
sectionHeader: {
|
|
1209
|
+
marginTop: number;
|
|
1210
|
+
};
|
|
1124
1211
|
headerText: {
|
|
1125
1212
|
fontSize: number;
|
|
1126
1213
|
fontWeight: "600";
|
|
1127
1214
|
marginLeft: number;
|
|
1128
1215
|
};
|
|
1216
|
+
sectionHeaderText: {
|
|
1217
|
+
fontSize: number;
|
|
1218
|
+
fontWeight: "700";
|
|
1219
|
+
marginLeft: number;
|
|
1220
|
+
textTransform: "uppercase";
|
|
1221
|
+
letterSpacing: number;
|
|
1222
|
+
};
|
|
1129
1223
|
iconWrapper: {
|
|
1130
1224
|
width: number;
|
|
1131
1225
|
alignItems: "center";
|
|
@@ -1138,4 +1232,4 @@ declare const NAV_ICON_SIZE = 14;
|
|
|
1138
1232
|
/** Chevron icon size for expandable sections. */
|
|
1139
1233
|
declare const CHEVRON_ICON_SIZE = 12;
|
|
1140
1234
|
|
|
1141
|
-
export { ACTIVE_BORDER_RADIUS, APP_SHELL_SUFFIX, type AccountPlan, type AccountState, AppShell, type AppShellProps, type AppShellWidth, BASE_INDENT, CHEVRON_ICON_SIZE, CollapsedRail, type CollapsedRailProps, type CollapsedRailRange, DEFAULT_COLLAPSED_RAIL_MAX, DarkModeControl, type DarkModeControlProps, type DarkModeOption, type DarkModeVariant, NAV_ICON_SIZE, NAV_LINK_GAP, 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 ShellMessage, Sidebar, type SidebarProps, Topbar, type TopbarAction, type TopbarProps, type TopbarUser, accessibleNavItems, collapsedRailStyles, darkModeStyles, expandableStyles, isRouteActive, navStyles, pillNavStyles, resolveContentMaxWidth, resolveRailMode, roleRoutesToNavItems, useContentMaxWidth };
|
|
1235
|
+
export { ACTIVE_BORDER_RADIUS, 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, DEFAULT_COLLAPSED_RAIL_MAX, DarkModeControl, type DarkModeControlProps, type DarkModeOption, type DarkModeVariant, NAV_ICON_SIZE, NAV_LINK_GAP, 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 ShellMessage, Sidebar, type SidebarProps, Topbar, type TopbarAction, type TopbarProps, type TopbarUser, accessibleNavItems, collapsedRailStyles, darkModeStyles, expandableStyles, filterCommands, isRouteActive, navStyles, pillNavStyles, resolveContentMaxWidth, resolveRailMode, roleRoutesToNavItems, useCommandPaletteHotkey, useContentMaxWidth };
|
package/dist/index.js
CHANGED
|
@@ -19,6 +19,7 @@ function isRouteActive(pathname, route) {
|
|
|
19
19
|
return pathname === route || pathname.startsWith(`${route}/`);
|
|
20
20
|
}
|
|
21
21
|
var ACTIVE_BORDER_RADIUS = 4;
|
|
22
|
+
var ACTIVE_ACCENT_WIDTH = 3;
|
|
22
23
|
var NAV_LINK_GAP = 4;
|
|
23
24
|
var navStyles = reactNative.StyleSheet.create({
|
|
24
25
|
// --- Sidebar ---
|
|
@@ -242,23 +243,39 @@ var collapsedRailStyles = reactNative.StyleSheet.create({
|
|
|
242
243
|
slot: { alignItems: "center" }
|
|
243
244
|
});
|
|
244
245
|
var expandableStyles = reactNative.StyleSheet.create({
|
|
246
|
+
// Every leaf reserves the accent-bar gutter (a TRANSPARENT left border of the
|
|
247
|
+
// same width the active item colours in) so activation never shifts the row.
|
|
245
248
|
childItem: {
|
|
246
|
-
borderRadius:
|
|
249
|
+
borderRadius: 6,
|
|
247
250
|
flexDirection: "row",
|
|
248
251
|
alignItems: "center",
|
|
249
|
-
paddingVertical:
|
|
252
|
+
paddingVertical: 9,
|
|
253
|
+
borderLeftWidth: ACTIVE_ACCENT_WIDTH,
|
|
254
|
+
borderLeftColor: "transparent"
|
|
250
255
|
},
|
|
251
256
|
childItemText: { fontSize: 14 },
|
|
252
257
|
childItemTextWithIcon: { fontSize: 14, marginLeft: 6 },
|
|
253
258
|
chevron: { marginLeft: "auto" },
|
|
254
|
-
childrenContainer: { overflow: "hidden" },
|
|
259
|
+
childrenContainer: { overflow: "hidden", marginBottom: 4 },
|
|
255
260
|
header: {
|
|
256
|
-
borderRadius:
|
|
261
|
+
borderRadius: 6,
|
|
257
262
|
flexDirection: "row",
|
|
258
263
|
alignItems: "center",
|
|
259
264
|
paddingVertical: 8
|
|
260
265
|
},
|
|
266
|
+
// Depth-0 module groups render as docs-style SECTION HEADERS: extra top rhythm
|
|
267
|
+
// to separate one module from the next.
|
|
268
|
+
sectionHeader: { marginTop: 12 },
|
|
261
269
|
headerText: { fontSize: 15, fontWeight: "600", marginLeft: 6 },
|
|
270
|
+
// The section-label look: small, uppercased, tracked, muted — the aml-screening
|
|
271
|
+
// docs treatment ("START HERE" / "REFERENCE"), kept on an interactive toggle.
|
|
272
|
+
sectionHeaderText: {
|
|
273
|
+
fontSize: 12,
|
|
274
|
+
fontWeight: "700",
|
|
275
|
+
marginLeft: 6,
|
|
276
|
+
textTransform: "uppercase",
|
|
277
|
+
letterSpacing: 0.8
|
|
278
|
+
},
|
|
262
279
|
iconWrapper: { width: 20, alignItems: "center" }
|
|
263
280
|
});
|
|
264
281
|
var BASE_INDENT = 12;
|
|
@@ -289,6 +306,13 @@ function hoverTransitionStyle(reducedMotion) {
|
|
|
289
306
|
function ariaCurrentProps(isActive) {
|
|
290
307
|
return isActive ? { "aria-current": "page" } : {};
|
|
291
308
|
}
|
|
309
|
+
function ariaExpandedProps(expanded) {
|
|
310
|
+
return { "aria-expanded": expanded };
|
|
311
|
+
}
|
|
312
|
+
function hasActiveDescendant(item, pathname) {
|
|
313
|
+
if (isRouteActive(pathname, item.route)) return true;
|
|
314
|
+
return (item.children ?? []).some((child) => hasActiveDescendant(child, pathname));
|
|
315
|
+
}
|
|
292
316
|
var NavExpandableItem = ({
|
|
293
317
|
item,
|
|
294
318
|
pathname,
|
|
@@ -299,7 +323,7 @@ var NavExpandableItem = ({
|
|
|
299
323
|
renderChevron,
|
|
300
324
|
depth = 0
|
|
301
325
|
}) => {
|
|
302
|
-
const [expanded, setExpanded] = React2.useState(
|
|
326
|
+
const [expanded, setExpanded] = React2.useState(() => hasActiveDescendant(item, pathname));
|
|
303
327
|
const [focused, setFocused] = React2.useState(false);
|
|
304
328
|
const { theme } = uiFeedback.useUi();
|
|
305
329
|
const colors = theme.colors;
|
|
@@ -308,9 +332,15 @@ var NavExpandableItem = ({
|
|
|
308
332
|
const indent = depth * BASE_INDENT;
|
|
309
333
|
const hasChildren = Array.isArray(item.children) && item.children.length > 0;
|
|
310
334
|
const isActive = isRouteActive(pathname, item.route);
|
|
335
|
+
const isSectionHeader = hasChildren && depth === 0;
|
|
311
336
|
const activeItemStyle = React2.useMemo(
|
|
312
|
-
() => ({
|
|
313
|
-
|
|
337
|
+
() => ({
|
|
338
|
+
backgroundColor: colors.border,
|
|
339
|
+
borderRadius: ACTIVE_BORDER_RADIUS,
|
|
340
|
+
borderLeftWidth: ACTIVE_ACCENT_WIDTH,
|
|
341
|
+
borderLeftColor: primaryColor
|
|
342
|
+
}),
|
|
343
|
+
[colors.border, primaryColor]
|
|
314
344
|
);
|
|
315
345
|
const paddingStyle = React2.useMemo(() => ({ paddingLeft: indent + BASE_INDENT }), [indent]);
|
|
316
346
|
const headerPaddingStyle = React2.useMemo(() => ({ paddingLeft: indent }), [indent]);
|
|
@@ -356,14 +386,29 @@ var NavExpandableItem = ({
|
|
|
356
386
|
accessibilityLabel: item.label,
|
|
357
387
|
accessibilityRole: "button",
|
|
358
388
|
accessibilityState: { expanded },
|
|
359
|
-
style: [
|
|
389
|
+
style: [
|
|
390
|
+
expandableStyles.header,
|
|
391
|
+
isSectionHeader ? expandableStyles.sectionHeader : void 0,
|
|
392
|
+
headerPaddingStyle,
|
|
393
|
+
focusRingStyle(focused, primaryColor)
|
|
394
|
+
],
|
|
360
395
|
testID: item.testID ?? item.key,
|
|
361
396
|
onBlur: () => setFocused(false),
|
|
362
397
|
onFocus: () => setFocused(true),
|
|
363
398
|
onPress: toggle,
|
|
399
|
+
...ariaExpandedProps(expanded),
|
|
364
400
|
children: [
|
|
365
|
-
typeof item.renderIcon === "function" ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: expandableStyles.iconWrapper, children: item.renderIcon(colors.text, NAV_ICON_SIZE) }) : null,
|
|
366
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
401
|
+
typeof item.renderIcon === "function" ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: expandableStyles.iconWrapper, children: item.renderIcon(isSectionHeader ? colors.textSecondary : colors.text, NAV_ICON_SIZE) }) : null,
|
|
402
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
403
|
+
reactNative.Text,
|
|
404
|
+
{
|
|
405
|
+
style: [
|
|
406
|
+
isSectionHeader ? expandableStyles.sectionHeaderText : expandableStyles.headerText,
|
|
407
|
+
{ color: isSectionHeader ? colors.textSecondary : colors.text }
|
|
408
|
+
],
|
|
409
|
+
children: item.label
|
|
410
|
+
}
|
|
411
|
+
),
|
|
367
412
|
typeof renderChevron === "function" ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: expandableStyles.chevron, children: renderChevron(expanded, colors.textSecondary, CHEVRON_ICON_SIZE) }) : null
|
|
368
413
|
]
|
|
369
414
|
}
|
|
@@ -1008,6 +1053,7 @@ var DRAWER_WIDTH = 280;
|
|
|
1008
1053
|
var SCRIM_Z_INDEX = 1;
|
|
1009
1054
|
var PANEL_Z_INDEX = 2;
|
|
1010
1055
|
var NAV_ACTIVATABLE_SELECTOR = 'a, [role="button"], [role="link"]';
|
|
1056
|
+
var NAV_TOGGLE_SELECTOR = "[aria-expanded]";
|
|
1011
1057
|
var DEFAULT_DRAWER_LABELS = {
|
|
1012
1058
|
openLabel: "Menu",
|
|
1013
1059
|
openHint: "Open the navigation menu",
|
|
@@ -1070,8 +1116,10 @@ var MobileDrawer = ({
|
|
|
1070
1116
|
if (node === null || typeof node.addEventListener !== "function") return void 0;
|
|
1071
1117
|
const handleClick = (event) => {
|
|
1072
1118
|
const target = event.target;
|
|
1073
|
-
|
|
1074
|
-
|
|
1119
|
+
if (target === null) return;
|
|
1120
|
+
const activatable = target.closest(NAV_ACTIVATABLE_SELECTOR);
|
|
1121
|
+
const isToggle = activatable !== null && activatable.closest(NAV_TOGGLE_SELECTOR) !== null;
|
|
1122
|
+
if (activatable !== null && !isToggle) onClose();
|
|
1075
1123
|
};
|
|
1076
1124
|
node.addEventListener("click", handleClick);
|
|
1077
1125
|
return () => node.removeEventListener("click", handleClick);
|
|
@@ -1484,6 +1532,159 @@ var DarkModeControl = ({
|
|
|
1484
1532
|
}
|
|
1485
1533
|
);
|
|
1486
1534
|
};
|
|
1535
|
+
|
|
1536
|
+
// src/commandPaletteFilter.ts
|
|
1537
|
+
function haystack(item) {
|
|
1538
|
+
return [item.label, item.hint ?? "", ...item.keywords ?? []].join(" ").toLowerCase();
|
|
1539
|
+
}
|
|
1540
|
+
function filterCommands(items, query) {
|
|
1541
|
+
const tokens = query.trim().toLowerCase().split(/\s+/).filter(Boolean);
|
|
1542
|
+
if (tokens.length === 0) return [...items];
|
|
1543
|
+
return items.filter((item) => {
|
|
1544
|
+
const hay = haystack(item);
|
|
1545
|
+
return tokens.every((token) => hay.includes(token));
|
|
1546
|
+
});
|
|
1547
|
+
}
|
|
1548
|
+
var SCRIM_COLOR2 = "rgba(0, 0, 0, 0.5)";
|
|
1549
|
+
var OVERLAY_Z_INDEX = 60;
|
|
1550
|
+
var PANEL_MAX_WIDTH = 560;
|
|
1551
|
+
var PANEL_TOP_OFFSET = 88;
|
|
1552
|
+
var LIST_MAX_HEIGHT = 360;
|
|
1553
|
+
var KEY_DOWN = "ArrowDown";
|
|
1554
|
+
var KEY_UP = "ArrowUp";
|
|
1555
|
+
var KEY_ENTER = "Enter";
|
|
1556
|
+
var KEY_ESCAPE = "Escape";
|
|
1557
|
+
var styles4 = reactNative.StyleSheet.create({
|
|
1558
|
+
overlay: { ...reactNative.StyleSheet.absoluteFillObject, zIndex: OVERLAY_Z_INDEX, alignItems: "center" },
|
|
1559
|
+
scrim: { ...reactNative.StyleSheet.absoluteFillObject, backgroundColor: SCRIM_COLOR2 },
|
|
1560
|
+
panel: {
|
|
1561
|
+
marginTop: PANEL_TOP_OFFSET,
|
|
1562
|
+
width: "92%",
|
|
1563
|
+
maxWidth: PANEL_MAX_WIDTH,
|
|
1564
|
+
borderRadius: 12,
|
|
1565
|
+
borderWidth: 1,
|
|
1566
|
+
overflow: "hidden"
|
|
1567
|
+
},
|
|
1568
|
+
input: { fontSize: 16, paddingHorizontal: 16, paddingVertical: 14, borderBottomWidth: 1 },
|
|
1569
|
+
list: { maxHeight: LIST_MAX_HEIGHT },
|
|
1570
|
+
row: { flexDirection: "row", alignItems: "center", paddingHorizontal: 16, paddingVertical: 12, columnGap: 10 },
|
|
1571
|
+
rowText: { flex: 1 },
|
|
1572
|
+
rowLabel: { fontSize: 15, fontWeight: "600" },
|
|
1573
|
+
rowHint: { fontSize: 12, marginTop: 2 },
|
|
1574
|
+
empty: { paddingHorizontal: 16, paddingVertical: 20, fontSize: 14 }
|
|
1575
|
+
});
|
|
1576
|
+
var PaletteRow = ({ item, selected, onChoose }) => {
|
|
1577
|
+
const { theme } = uiFeedback.useUi();
|
|
1578
|
+
const colors = theme.colors;
|
|
1579
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1580
|
+
reactNative.Pressable,
|
|
1581
|
+
{
|
|
1582
|
+
accessibilityHint: item.hint,
|
|
1583
|
+
accessibilityLabel: item.label,
|
|
1584
|
+
accessibilityRole: "button",
|
|
1585
|
+
accessibilityState: { selected },
|
|
1586
|
+
style: [styles4.row, selected ? { backgroundColor: colors.border } : void 0],
|
|
1587
|
+
testID: item.testID ?? item.key,
|
|
1588
|
+
onPress: () => onChoose(item),
|
|
1589
|
+
children: [
|
|
1590
|
+
typeof item.renderIcon === "function" ? item.renderIcon(colors.textSecondary, NAV_ICON_SIZE) : null,
|
|
1591
|
+
/* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles4.rowText, children: [
|
|
1592
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles4.rowLabel, { color: colors.text }], children: item.label }),
|
|
1593
|
+
item.hint ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles4.rowHint, { color: colors.textSecondary }], children: item.hint }) : null
|
|
1594
|
+
] })
|
|
1595
|
+
]
|
|
1596
|
+
}
|
|
1597
|
+
);
|
|
1598
|
+
};
|
|
1599
|
+
var CommandPalette = ({ open, items, onClose, labels, testID }) => {
|
|
1600
|
+
const { theme } = uiFeedback.useUi();
|
|
1601
|
+
const colors = theme.colors;
|
|
1602
|
+
const [query, setQuery] = React2.useState("");
|
|
1603
|
+
const [selected, setSelected] = React2.useState(0);
|
|
1604
|
+
const inputRef = React2.useRef(null);
|
|
1605
|
+
const results = React2.useMemo(() => filterCommands(items, query), [items, query]);
|
|
1606
|
+
React2.useEffect(() => {
|
|
1607
|
+
if (!open) return;
|
|
1608
|
+
setQuery("");
|
|
1609
|
+
setSelected(0);
|
|
1610
|
+
inputRef.current?.focus();
|
|
1611
|
+
}, [open]);
|
|
1612
|
+
React2.useEffect(() => {
|
|
1613
|
+
setSelected((prev) => Math.min(prev, Math.max(0, results.length - 1)));
|
|
1614
|
+
}, [results.length]);
|
|
1615
|
+
const choose = React2.useCallback(
|
|
1616
|
+
(item) => {
|
|
1617
|
+
item.onSelect();
|
|
1618
|
+
onClose();
|
|
1619
|
+
},
|
|
1620
|
+
[onClose]
|
|
1621
|
+
);
|
|
1622
|
+
const onKeyPress = React2.useCallback(
|
|
1623
|
+
(event) => {
|
|
1624
|
+
const key = event.nativeEvent.key;
|
|
1625
|
+
if (key === KEY_DOWN) setSelected((i) => Math.min(i + 1, results.length - 1));
|
|
1626
|
+
else if (key === KEY_UP) setSelected((i) => Math.max(i - 1, 0));
|
|
1627
|
+
else if (key === KEY_ESCAPE) onClose();
|
|
1628
|
+
else if (key === KEY_ENTER && results[selected]) choose(results[selected]);
|
|
1629
|
+
},
|
|
1630
|
+
[results, selected, choose, onClose]
|
|
1631
|
+
);
|
|
1632
|
+
if (!open) return null;
|
|
1633
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles4.overlay, children: [
|
|
1634
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1635
|
+
reactNative.Pressable,
|
|
1636
|
+
{
|
|
1637
|
+
accessibilityHint: labels.closeHint,
|
|
1638
|
+
accessibilityLabel: labels.closeLabel,
|
|
1639
|
+
accessibilityRole: "button",
|
|
1640
|
+
style: styles4.scrim,
|
|
1641
|
+
testID: `${testID ?? "command-palette"}-scrim`,
|
|
1642
|
+
onPress: onClose
|
|
1643
|
+
}
|
|
1644
|
+
),
|
|
1645
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
1646
|
+
reactNative.View,
|
|
1647
|
+
{
|
|
1648
|
+
"aria-modal": true,
|
|
1649
|
+
"aria-label": labels.regionLabel,
|
|
1650
|
+
role: "dialog",
|
|
1651
|
+
style: [styles4.panel, { backgroundColor: colors.surface, borderColor: colors.border }],
|
|
1652
|
+
testID: testID ?? "command-palette",
|
|
1653
|
+
children: [
|
|
1654
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1655
|
+
reactNative.TextInput,
|
|
1656
|
+
{
|
|
1657
|
+
ref: inputRef,
|
|
1658
|
+
accessibilityLabel: labels.placeholder,
|
|
1659
|
+
placeholder: labels.placeholder,
|
|
1660
|
+
placeholderTextColor: colors.textSecondary,
|
|
1661
|
+
style: [styles4.input, { color: colors.text, borderBottomColor: colors.border }],
|
|
1662
|
+
testID: `${testID ?? "command-palette"}-input`,
|
|
1663
|
+
value: query,
|
|
1664
|
+
onChangeText: setQuery,
|
|
1665
|
+
onKeyPress
|
|
1666
|
+
}
|
|
1667
|
+
),
|
|
1668
|
+
results.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles4.empty, { color: colors.textSecondary }], children: labels.emptyText }) : /* @__PURE__ */ jsxRuntime.jsx(reactNative.ScrollView, { keyboardShouldPersistTaps: "handled", style: styles4.list, children: results.map((item, index) => /* @__PURE__ */ jsxRuntime.jsx(PaletteRow, { item, selected: index === selected, onChoose: choose }, item.key)) })
|
|
1669
|
+
]
|
|
1670
|
+
}
|
|
1671
|
+
)
|
|
1672
|
+
] });
|
|
1673
|
+
};
|
|
1674
|
+
var LAUNCH_KEY = "k";
|
|
1675
|
+
function useCommandPaletteHotkey(onOpen, enabled = true) {
|
|
1676
|
+
React2.useEffect(() => {
|
|
1677
|
+
if (!enabled || typeof document === "undefined") return void 0;
|
|
1678
|
+
const handler = (event) => {
|
|
1679
|
+
const isLauncher = event.key.toLowerCase() === LAUNCH_KEY && (event.metaKey || event.ctrlKey);
|
|
1680
|
+
if (!isLauncher) return;
|
|
1681
|
+
event.preventDefault();
|
|
1682
|
+
onOpen();
|
|
1683
|
+
};
|
|
1684
|
+
document.addEventListener("keydown", handler);
|
|
1685
|
+
return () => document.removeEventListener("keydown", handler);
|
|
1686
|
+
}, [onOpen, enabled]);
|
|
1687
|
+
}
|
|
1487
1688
|
function roleRoutesToNavItems(routes, translate) {
|
|
1488
1689
|
return routes.map((entry) => ({
|
|
1489
1690
|
key: entry.role,
|
|
@@ -1501,6 +1702,7 @@ exports.AppShell = AppShell;
|
|
|
1501
1702
|
exports.BASE_INDENT = BASE_INDENT;
|
|
1502
1703
|
exports.CHEVRON_ICON_SIZE = CHEVRON_ICON_SIZE;
|
|
1503
1704
|
exports.CollapsedRail = CollapsedRail;
|
|
1705
|
+
exports.CommandPalette = CommandPalette;
|
|
1504
1706
|
exports.DEFAULT_COLLAPSED_RAIL_MAX = DEFAULT_COLLAPSED_RAIL_MAX;
|
|
1505
1707
|
exports.DarkModeControl = DarkModeControl;
|
|
1506
1708
|
exports.NAV_ICON_SIZE = NAV_ICON_SIZE;
|
|
@@ -1519,12 +1721,14 @@ exports.accessibleNavItems = accessibleNavItems;
|
|
|
1519
1721
|
exports.collapsedRailStyles = collapsedRailStyles;
|
|
1520
1722
|
exports.darkModeStyles = darkModeStyles;
|
|
1521
1723
|
exports.expandableStyles = expandableStyles;
|
|
1724
|
+
exports.filterCommands = filterCommands;
|
|
1522
1725
|
exports.isRouteActive = isRouteActive;
|
|
1523
1726
|
exports.navStyles = navStyles;
|
|
1524
1727
|
exports.pillNavStyles = pillNavStyles;
|
|
1525
1728
|
exports.resolveContentMaxWidth = resolveContentMaxWidth;
|
|
1526
1729
|
exports.resolveRailMode = resolveRailMode;
|
|
1527
1730
|
exports.roleRoutesToNavItems = roleRoutesToNavItems;
|
|
1731
|
+
exports.useCommandPaletteHotkey = useCommandPaletteHotkey;
|
|
1528
1732
|
exports.useContentMaxWidth = useContentMaxWidth;
|
|
1529
1733
|
//# sourceMappingURL=index.js.map
|
|
1530
1734
|
//# sourceMappingURL=index.js.map
|