pixelize-design-library 2.4.2-beta.60 → 2.4.2-beta.61

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.
Files changed (30) hide show
  1. package/CHANGELOG.md +38 -10
  2. package/COMPONENT-GOTCHAS.md +41 -4
  3. package/DESIGN-GUIDE.md +9 -1
  4. package/dist/Components/NavLauncher/LauncherTabs.d.ts +18 -0
  5. package/dist/Components/NavLauncher/LauncherTabs.js +64 -0
  6. package/dist/Components/NavLauncher/NavLauncher.d.ts +1 -1
  7. package/dist/Components/NavLauncher/NavLauncher.js +203 -53
  8. package/dist/Components/NavLauncher/NavLauncherProps.d.ts +23 -4
  9. package/dist/Components/NavLauncher/NewTabLink.d.ts +2 -2
  10. package/dist/Components/NavLauncher/NewTabLink.js +7 -3
  11. package/dist/Components/NavLauncher/launcherParts.d.ts +4 -1
  12. package/dist/Components/NavLauncher/launcherParts.js +16 -8
  13. package/dist/Components/NavLauncher/resultNavigation.d.ts +3 -0
  14. package/dist/Components/NavLauncher/resultNavigation.js +65 -0
  15. package/dist/Components/NavLauncher/revealStyles.d.ts +1 -1
  16. package/dist/Components/NavLauncher/revealStyles.js +2 -2
  17. package/dist/Components/NavigationBar/NavigationBar.js +2 -1
  18. package/dist/Components/NavigationBar/NavigationBarProps.d.ts +1 -0
  19. package/dist/Components/OrgSwitcher/OrgSwitcher.js +18 -6
  20. package/dist/Components/OrgSwitcher/OrgSwitcherProps.d.ts +4 -0
  21. package/dist/esm/Components/NavLauncher/LauncherTabs.js +58 -0
  22. package/dist/esm/Components/NavLauncher/NavLauncher.js +204 -54
  23. package/dist/esm/Components/NavLauncher/NewTabLink.js +4 -3
  24. package/dist/esm/Components/NavLauncher/launcherParts.js +15 -8
  25. package/dist/esm/Components/NavLauncher/resultNavigation.js +61 -0
  26. package/dist/esm/Components/NavLauncher/revealStyles.js +2 -2
  27. package/dist/esm/Components/NavigationBar/NavigationBar.js +2 -1
  28. package/dist/esm/Components/OrgSwitcher/OrgSwitcher.js +16 -7
  29. package/dist/index.d.ts +1 -0
  30. package/package.json +1 -1
@@ -43,11 +43,11 @@ const lucide_react_1 = require("lucide-react");
43
43
  const react_2 = require("react");
44
44
  const useMatchMedia_1 = require("../../Hooks/useMatchMedia");
45
45
  const focusRing_styles_1 = require("../../Theme/chakra/focusRing.styles");
46
+ const motion_1 = require("../../Theme/chakra/motion");
46
47
  const chartColorsFromTheme_1 = require("../../Theme/tokens/builders/chartColorsFromTheme");
47
48
  const placeholderInk_1 = require("../../Theme/tokens/builders/placeholderInk");
48
- const filledSurface_1 = require("../../Theme/tokens/builders/filledSurface");
49
49
  const rowStates_1 = require("../../Theme/tokens/builders/rowStates");
50
- const motion_1 = require("../../Theme/tokens/primitives/motion");
50
+ const motion_2 = require("../../Theme/tokens/primitives/motion");
51
51
  const useCustomTheme_1 = require("../../Theme/useCustomTheme");
52
52
  const AppMark_1 = require("../AppSwitcher/AppMark");
53
53
  const Drawer_1 = __importStar(require("../Drawer/Drawer"));
@@ -58,11 +58,14 @@ const useIsTruncated_1 = require("../../Hooks/useIsTruncated");
58
58
  const AppSwitcherProps_1 = require("../AppSwitcher/AppSwitcherProps");
59
59
  const Skeletons_1 = __importDefault(require("../Skeletons/Skeletons"));
60
60
  const launcherParts_1 = require("./launcherParts");
61
+ const LauncherTabs_1 = require("./LauncherTabs");
61
62
  const match_1 = require("./match");
62
63
  const NewTabLink_1 = __importDefault(require("./NewTabLink"));
63
64
  const PinToggle_1 = __importDefault(require("./PinToggle"));
65
+ const resultNavigation_1 = require("./resultNavigation");
64
66
  const NO_APPS = [];
65
67
  const MD_QUERY = "(min-width: 48em)";
68
+ const LG_QUERY = "(min-width: 62em)";
66
69
  const XL_QUERY = "(min-width: 80em)";
67
70
  const CELL_SELECTOR = "[data-nav-cell]";
68
71
  const RAIL_WIDTH = "clamp(20rem, 30%, 26rem)";
@@ -70,6 +73,19 @@ const PINNED_COLUMNS = 2;
70
73
  const ENTER_CHIP_WIDTH = 50;
71
74
  const ARROW_KEYS = ["↑", "↓", "←", "→"];
72
75
  const ENTER_KEY = (0, AppSwitcherProps_1.shortcutKeys)("enter")[0];
76
+ const MOD_KEY = (0, AppSwitcherProps_1.shortcutKeys)("mod+enter")[0];
77
+ const RESULT_KEYS = [
78
+ "ArrowUp",
79
+ "ArrowDown",
80
+ "ArrowLeft",
81
+ "ArrowRight",
82
+ "Home",
83
+ "End",
84
+ "PageUp",
85
+ "PageDown",
86
+ ];
87
+ const TEXT_TARGET = "input, textarea, select, [contenteditable=true]";
88
+ const ROW_HEIGHT = 46;
73
89
  const DEFAULT_LABELS = {
74
90
  title: "Menu",
75
91
  searchPlaceholder: "Search menus and screens",
@@ -95,6 +111,13 @@ const DEFAULT_LABELS = {
95
111
  hintPin: "pin",
96
112
  hintClose: "close",
97
113
  hintOpenMenu: "open",
114
+ hintClickNewTab: "+ click for new tab",
115
+ newTabTip: "Open in new tab",
116
+ resultsCount: (count) => `${count} ${count === 1 ? "result" : "results"}`,
117
+ tabsLabel: "Menu sections",
118
+ tabMenu: "Menu",
119
+ tabQuickAccess: "Quick access",
120
+ tabApps: "Apps",
98
121
  apps: "Your apps",
99
122
  found: (count) => `${count} found`,
100
123
  pinTip: "Pin to quick access",
@@ -131,19 +154,19 @@ const GRID_COLUMNS = {
131
154
  function ItemGrid({ children, columns, }) {
132
155
  return ((0, jsx_runtime_1.jsx)(react_1.Box, { "data-nav-grid": true, "data-nav-columns": columns, display: "grid", gridTemplateColumns: GRID_COLUMNS, gap: "1px 8px", alignItems: "start", children: children }));
133
156
  }
134
- function Pane({ children }) {
135
- return ((0, jsx_runtime_1.jsx)(react_1.Flex, { direction: "column", gap: "14px", flex: 1, minW: 0, px: "18px", pt: "12px", pb: "16px", overflowY: { base: "visible", lg: "auto" }, children: children }));
157
+ function Pane({ children, scrolls = false, }) {
158
+ return ((0, jsx_runtime_1.jsx)(react_1.Flex, { "data-scroll-region": scrolls || undefined, direction: "column", gap: "14px", flex: 1, minW: 0, px: "18px", pt: "12px", pb: "16px", overflowY: { base: "visible", lg: "auto" }, children: children }));
136
159
  }
137
- function Rail({ label, children }) {
160
+ function Rail({ label, children, scrolls = false, }) {
138
161
  const { colors } = (0, useCustomTheme_1.useCustomTheme)();
139
- return ((0, jsx_runtime_1.jsx)(react_1.Flex, { as: "aside", "aria-label": label, direction: "column", gap: "16px", w: { base: "100%", lg: RAIL_WIDTH }, flexShrink: 0, px: "14px", pt: "12px", pb: "16px", bg: colors.backgroundColor.subtle, borderTopWidth: { base: "1px", lg: 0 }, borderLeftWidth: { base: 0, lg: "1px" }, borderColor: colors.boxborder[200], overflowY: { base: "visible", lg: "auto" }, children: children }));
162
+ return ((0, jsx_runtime_1.jsx)(react_1.Flex, { "data-scroll-region": scrolls || undefined, as: "aside", "aria-label": label, direction: "column", gap: "16px", w: { base: "100%", lg: RAIL_WIDTH }, flexShrink: 0, px: "14px", pt: "12px", pb: "16px", bg: colors.backgroundColor.subtle, borderTopWidth: { base: "1px", lg: 0 }, borderLeftWidth: { base: 0, lg: "1px" }, borderColor: colors.boxborder[200], overflowY: { base: "visible", lg: "auto" }, children: children }));
140
163
  }
141
164
  function SkeletonLayout({ label }) {
142
165
  return ((0, jsx_runtime_1.jsxs)(react_1.Flex, { role: "status", "aria-busy": "true", "aria-label": label, direction: { base: "column", lg: "row" }, flex: 1, minH: 0, children: [(0, jsx_runtime_1.jsx)(Pane, { children: [0, 1, 2].map((section) => ((0, jsx_runtime_1.jsxs)(react_1.Box, { children: [(0, jsx_runtime_1.jsx)(Skeletons_1.default, { type: "skeleton", height: "0.75rem", width: "6rem" }), (0, jsx_runtime_1.jsx)(ItemGrid, { children: [0, 1, 2, 3].map((row) => ((0, jsx_runtime_1.jsxs)(react_1.Flex, { align: "center", gap: "10px", h: "34px", mt: 2, children: [(0, jsx_runtime_1.jsx)(Skeletons_1.default, { type: "skeleton", height: "1rem", width: "1rem" }), (0, jsx_runtime_1.jsx)(Skeletons_1.default, { type: "skeleton", height: "0.875rem", width: "8rem" })] }, row))) })] }, section))) }), (0, jsx_runtime_1.jsx)(Rail, { label: label, children: [0, 1].map((group) => ((0, jsx_runtime_1.jsxs)(react_1.Box, { children: [(0, jsx_runtime_1.jsx)(Skeletons_1.default, { type: "skeleton", height: "0.75rem", width: "5rem" }), [0, 1, 2].map((row) => ((0, jsx_runtime_1.jsx)(Skeletons_1.default, { type: "skeleton", height: "1.5rem", width: "100%", mt: 2 }, row)))] }, group))) })] }));
143
166
  }
144
167
  function SearchField({ inputRef, value, onChange, onKeyDown, labels, hint, listboxId, activeId, isExpanded, hasResults, }) {
145
168
  const { colors } = (0, useCustomTheme_1.useCustomTheme)();
146
- return ((0, jsx_runtime_1.jsxs)(react_1.Flex, { flex: 1, maxW: "520px", h: "38px", align: "center", gap: "10px", pl: "12px", pr: "8px", borderRadius: "10px", borderWidth: "1px", borderColor: colors.primary[500], bg: colors.surfaceRaised, boxShadow: `0 0 0 3px ${colors.primary.opacity[16]}`, children: [(0, jsx_runtime_1.jsx)(react_1.Box, { as: "span", color: colors.accentText, display: "inline-flex", flexShrink: 0, children: (0, jsx_runtime_1.jsx)(lucide_react_1.Search, { size: 16, strokeWidth: 2.2, "aria-hidden": true }) }), (0, jsx_runtime_1.jsx)(react_1.Box, { as: "input", ref: inputRef, value: value, onChange: (event) => onChange(event.target.value), onKeyDown: onKeyDown, placeholder: labels.searchPlaceholder, "aria-label": labels.searchPlaceholder, role: "combobox", "aria-expanded": isExpanded, "aria-controls": isExpanded && hasResults ? listboxId : undefined, "aria-autocomplete": "list", "aria-activedescendant": activeId, autoComplete: "off", flex: 1, minW: 0, h: "34px", border: 0, outline: "none", bg: "transparent", fontSize: "14.5px", color: colors.text[900], _placeholder: { color: (0, placeholderInk_1.placeholderInk)(colors), opacity: 1 } }), value ? ((0, jsx_runtime_1.jsx)(react_1.Box, { as: "button", type: "button", "aria-label": labels.clearSearch, onClick: () => {
169
+ return ((0, jsx_runtime_1.jsxs)(react_1.Flex, { flex: 1, minW: 0, h: { base: "40px", md: "44px" }, align: "center", gap: "10px", pl: "14px", pr: "10px", borderRadius: { base: "10px", md: "12px" }, borderWidth: "1px", borderColor: colors.primary[500], bg: colors.surfaceRaised, boxShadow: `0 0 0 3px ${colors.primary.opacity[16]}`, children: [(0, jsx_runtime_1.jsx)(react_1.Box, { as: "span", color: colors.accentText, display: "inline-flex", flexShrink: 0, children: (0, jsx_runtime_1.jsx)(lucide_react_1.Search, { size: 18, strokeWidth: 2.2, "aria-hidden": true }) }), (0, jsx_runtime_1.jsx)(react_1.Box, { as: "input", ref: inputRef, value: value, onChange: (event) => onChange(event.target.value), onKeyDown: onKeyDown, placeholder: labels.searchPlaceholder, "aria-label": labels.searchPlaceholder, role: "combobox", "aria-expanded": isExpanded, "aria-controls": isExpanded && hasResults ? listboxId : undefined, "aria-autocomplete": "list", "aria-activedescendant": activeId, autoComplete: "off", flex: 1, minW: 0, h: "100%", border: 0, outline: "none", bg: "transparent", fontSize: "16px", color: colors.text[900], _placeholder: { color: (0, placeholderInk_1.placeholderInk)(colors), opacity: 1 } }), value ? ((0, jsx_runtime_1.jsx)(react_1.Box, { as: "button", type: "button", "aria-label": labels.clearSearch, onClick: () => {
147
170
  var _a;
148
171
  onChange("");
149
172
  (_a = inputRef.current) === null || _a === void 0 ? void 0 : _a.focus();
@@ -151,6 +174,7 @@ function SearchField({ inputRef, value, onChange, onKeyDown, labels, hint, listb
151
174
  }
152
175
  function SearchRow({ item, query, isActive, link, labels, optionId, isPinned, canPin, onPin, onOpen, onHover, }) {
153
176
  const { colors } = (0, useCustomTheme_1.useCustomTheme)();
177
+ const isTouch = (0, launcherParts_1.useTouchDensity)();
154
178
  const isScreen = item.kind === "screen";
155
179
  const hasStar = Boolean(item.screenId) && canPin;
156
180
  const base = item.badge ? 24 : 0;
@@ -161,7 +185,7 @@ function SearchRow({ item, query, isActive, link, labels, optionId, isPinned, ca
161
185
  const titleRef = title.ref;
162
186
  const contextRef = context.ref;
163
187
  const tipLabel = isScreen || !item.context ? fullLabel : `${fullLabel} · ${item.context}`;
164
- return ((0, jsx_runtime_1.jsx)(ToolTip_1.default, { label: tipLabel, placement: "top-start", isDisabled: !title.isTruncated && !context.isTruncated, children: (0, jsx_runtime_1.jsxs)(react_1.Flex, { role: "presentation", "data-group": true, "data-reveal": isActive, position: "relative", align: "flex-start", gap: "10px", minH: "34px", pl: "8px", pr: "4px", pt: "7px", pb: "5px", borderRadius: "8px", transition: (0, motion_1.transitionFor)(["background-color"]), onMouseMove: onHover, sx: isActive
188
+ return ((0, jsx_runtime_1.jsx)(ToolTip_1.default, { label: tipLabel, placement: "top-start", isDisabled: !title.isTruncated && !context.isTruncated, children: (0, jsx_runtime_1.jsxs)(react_1.Flex, { role: "presentation", "data-group": true, "data-reveal": isActive, position: "relative", align: "flex-start", gap: "10px", minH: isTouch ? "44px" : "34px", pl: "8px", pr: "4px", pt: isTouch ? "12px" : "7px", pb: isTouch ? "8px" : "5px", borderRadius: "8px", transition: (0, motion_2.transitionFor)(["background-color"]), onMouseMove: onHover, sx: isActive
165
189
  ? { ...launcherParts_1.ROW_CONTAINER, ...(0, launcherParts_1.activeRowSurface)(colors) }
166
190
  : launcherParts_1.ROW_CONTAINER, children: [(0, jsx_runtime_1.jsxs)(react_1.Flex, { id: optionId, role: "option", "aria-selected": isActive, "data-active": isActive, flex: 1, minW: 0, align: "flex-start", gap: "10px", cursor: "pointer", onClick: () => {
167
191
  if (!item.href)
@@ -170,20 +194,23 @@ function SearchRow({ item, query, isActive, link, labels, optionId, isPinned, ca
170
194
  display: "none",
171
195
  [launcherParts_1.WIDE_ROW]: { display: "inline-flex" },
172
196
  "@media (hover: none)": { display: "none" },
173
- }, children: (0, jsx_runtime_1.jsx)(Kbd_1.KbdShortcut, { shortcut: "enter", size: "xs" }) })) : null, (0, jsx_runtime_1.jsx)(launcherParts_1.CountBadge, { value: item.badge }), hasStar ? ((0, jsx_runtime_1.jsx)(PinToggle_1.default, { isPinned: isPinned, label: isPinned ? labels.unpin(item.label) : labels.pin(item.label), title: "", onToggle: onPin, tabIndex: -1 })) : null, item.href ? ((0, jsx_runtime_1.jsx)(NewTabLink_1.default, { href: item.href, label: labels.openInNewTab(isScreen ? `${item.label} — ${item.context}` : item.label), linkAs: link.linkAs, tabIndex: -1 })) : null] }) }), hasStar ? ((0, jsx_runtime_1.jsx)(launcherParts_1.RowTip, { text: isPinned ? labels.unpinTip : labels.pinTip })) : null] }) }));
197
+ }, children: (0, jsx_runtime_1.jsx)(Kbd_1.KbdShortcut, { shortcut: "enter", size: "xs" }) })) : null, (0, jsx_runtime_1.jsx)(launcherParts_1.CountBadge, { value: item.badge }), hasStar ? ((0, jsx_runtime_1.jsx)(PinToggle_1.default, { isPinned: isPinned, label: isPinned ? labels.unpin(item.label) : labels.pin(item.label), title: "", onToggle: onPin, tabIndex: -1 })) : null, item.href ? ((0, jsx_runtime_1.jsx)(NewTabLink_1.default, { href: item.href, label: labels.openInNewTab(isScreen ? `${item.label} — ${item.context}` : item.label), tip: labels.newTabTip, linkAs: link.linkAs, tabIndex: -1 })) : null] }) }), hasStar ? ((0, jsx_runtime_1.jsx)(launcherParts_1.RowTip, { text: isPinned ? labels.unpinTip : labels.pinTip })) : null] }) }));
174
198
  }
175
199
  /** Left-drawer navigation launcher: browse every module, pins, Frequent, Recent, search and (compact) the suite's apps. */
176
- function NavLauncher({ isOpen, onClose, sections: rawSections, pinnedIds, frequentIds, recentIds, onTogglePin, onNavigate, linkAs = "a", actions, extraResults, query: controlledQuery, onQueryChange, isSearching = false, isLoading = false, keyScreenLimit = 3, frequentLimit = 4, suggestions, searchHint, openShortcutHint, modKeyLabel, maxWidth = "1080px", labels: labelOverrides, apps, currentAppId, onAppSelect, appsVisibility = "apps-on-compact", "data-testid": testId, }) {
200
+ function NavLauncher({ isOpen, onClose, sections: rawSections, pinnedIds, frequentIds, recentIds, onTogglePin, onNavigate, linkAs = "a", actions, extraResults, query: controlledQuery, onQueryChange, isSearching = false, isLoading = false, keyScreenLimit = 3, frequentLimit = 4, suggestions, searchHint, modKeyLabel, headerActions, maxWidth = "1080px", labels: labelOverrides, apps, currentAppId, onAppSelect, appsVisibility = "apps-on-compact", "data-testid": testId, }) {
177
201
  const theme = (0, useCustomTheme_1.useCustomTheme)();
178
202
  const { colors } = theme;
179
203
  const inputRef = (0, react_2.useRef)(null);
180
204
  const bodyRef = (0, react_2.useRef)(null);
181
205
  const baseId = (0, react_2.useId)();
182
206
  const isDesktop = (0, useMatchMedia_1.useMatchMedia)(MD_QUERY, true);
207
+ const isLg = (0, useMatchMedia_1.useMatchMedia)(LG_QUERY, true);
183
208
  const isWide = (0, useMatchMedia_1.useMatchMedia)(XL_QUERY, false);
184
209
  const columns = isWide ? 3 : isDesktop ? 2 : 1;
185
210
  const [innerQuery, setInnerQuery] = (0, react_2.useState)("");
186
211
  const [activeIndex, setActiveIndex] = (0, react_2.useState)(0);
212
+ const [tab, setTab] = (0, react_2.useState)("menu");
213
+ const navigating = (0, react_2.useRef)(false);
187
214
  const labels = (0, react_2.useMemo)(() => ({ ...DEFAULT_LABELS, ...labelOverrides }), [labelOverrides]);
188
215
  const isControlled = controlledQuery !== undefined;
189
216
  const query = isControlled ? controlledQuery : innerQuery;
@@ -195,10 +222,16 @@ function NavLauncher({ isOpen, onClose, sections: rawSections, pinnedIds, freque
195
222
  const latest = (0, react_2.useRef)({ query, setQuery });
196
223
  latest.current = { query, setQuery };
197
224
  (0, react_2.useEffect)(() => {
198
- if (!isOpen && latest.current.query !== "")
225
+ if (isOpen)
226
+ return;
227
+ setTab("menu");
228
+ if (latest.current.query !== "")
199
229
  latest.current.setQuery("");
200
230
  }, [isOpen]);
201
- (0, react_2.useEffect)(() => setActiveIndex(0), [query]);
231
+ (0, react_2.useEffect)(() => {
232
+ navigating.current = false;
233
+ setActiveIndex(0);
234
+ }, [query]);
202
235
  const sections = (0, react_2.useMemo)(() => visibleSections(rawSections), [rawSections]);
203
236
  const byId = (0, react_2.useMemo)(() => indexScreens(sections), [sections]);
204
237
  const pinned = (0, react_2.useMemo)(() => new Set(pinnedIds), [pinnedIds]);
@@ -225,6 +258,26 @@ function NavLauncher({ isOpen, onClose, sections: rawSections, pinnedIds, freque
225
258
  const groupStarts = (0, react_2.useMemo)(() => groups.map((_, i) => groups.slice(0, i).reduce((n, g) => n + g.items.length, 0)), [groups]);
226
259
  const isSearchView = query.trim().length > 0;
227
260
  const active = Math.min(activeIndex, Math.max(results.length - 1, 0));
261
+ const appList = apps !== null && apps !== void 0 ? apps : NO_APPS;
262
+ const showApps = appList.some((app) => app.id !== currentAppId) &&
263
+ (appsVisibility === "always" || !isDesktop);
264
+ const railFrequent = frequentItems
265
+ .filter(({ screen }) => !pinned.has(screen.id))
266
+ .slice(0, frequentLimit);
267
+ const hasQuick = pinnedItems.length > 0 || railFrequent.length > 0 || recentItems.length > 0;
268
+ const tabs = (0, react_2.useMemo)(() => [
269
+ { id: "menu", label: labels.tabMenu },
270
+ ...(hasQuick
271
+ ? [{ id: "quick", label: labels.tabQuickAccess }]
272
+ : []),
273
+ ...(showApps ? [{ id: "apps", label: labels.tabApps }] : []),
274
+ ], [hasQuick, showApps, labels]);
275
+ const isTabbed = !isLg && !isSearchView && !isLoading && tabs.length > 1;
276
+ const activeTab = tabs.some((item) => item.id === tab) ? tab : "menu";
277
+ const countText = isSearchView && results.length > 0
278
+ ? labels.resultsCount(results.length)
279
+ : "";
280
+ const groupSizes = (0, react_2.useMemo)(() => groups.map((g) => g.items.length), [groups]);
228
281
  const link = (0, react_2.useMemo)(() => ({
229
282
  linkAs,
230
283
  onPlainClick: (event) => {
@@ -244,14 +297,31 @@ function NavLauncher({ isOpen, onClose, sections: rawSections, pinnedIds, freque
244
297
  onClose();
245
298
  onNavigate(item.href, { newTab });
246
299
  }, [onClose, onNavigate]);
300
+ const focusField = (shouldSelect = false) => {
301
+ var _a, _b;
302
+ (_a = inputRef.current) === null || _a === void 0 ? void 0 : _a.focus();
303
+ if (shouldSelect)
304
+ (_b = inputRef.current) === null || _b === void 0 ? void 0 : _b.select();
305
+ };
306
+ const scrollHeight = (from) => {
307
+ var _a, _b, _c, _d, _e;
308
+ return (_e = (_b = (_a = from === null || from === void 0 ? void 0 : from.closest("[data-scroll-region]")) === null || _a === void 0 ? void 0 : _a.clientHeight) !== null && _b !== void 0 ? _b : (_d = (_c = bodyRef.current) === null || _c === void 0 ? void 0 : _c.querySelector("[data-scroll-region]")) === null || _d === void 0 ? void 0 : _d.clientHeight) !== null && _e !== void 0 ? _e : 0;
309
+ };
310
+ const leaveTop = () => {
311
+ var _a;
312
+ const selected = isTabbed
313
+ ? document.getElementById((0, LauncherTabs_1.tabId)(baseId, activeTab))
314
+ : null;
315
+ (_a = (selected !== null && selected !== void 0 ? selected : inputRef.current)) === null || _a === void 0 ? void 0 : _a.focus();
316
+ };
247
317
  const moveFocus = (delta) => {
248
- var _a, _b, _c;
318
+ var _a, _b;
249
319
  const items = Array.from((_b = (_a = bodyRef.current) === null || _a === void 0 ? void 0 : _a.querySelectorAll(launcherParts_1.ITEM_SELECTOR)) !== null && _b !== void 0 ? _b : []);
250
320
  if (items.length === 0)
251
321
  return;
252
322
  const current = items.indexOf(document.activeElement);
253
323
  if (typeof delta === "number" && current + delta < 0) {
254
- (_c = inputRef.current) === null || _c === void 0 ? void 0 : _c.focus();
324
+ leaveTop();
255
325
  return;
256
326
  }
257
327
  const next = delta === "first"
@@ -261,6 +331,22 @@ function NavLauncher({ isOpen, onClose, sections: rawSections, pinnedIds, freque
261
331
  : Math.min(items.length - 1, current + delta);
262
332
  items[next].focus();
263
333
  };
334
+ const pageFocus = (direction) => {
335
+ var _a, _b;
336
+ const items = Array.from((_b = (_a = bodyRef.current) === null || _a === void 0 ? void 0 : _a.querySelectorAll(launcherParts_1.ITEM_SELECTOR)) !== null && _b !== void 0 ? _b : []);
337
+ const from = document.activeElement;
338
+ const current = items.indexOf(from);
339
+ if (items.length === 0 || current < 0)
340
+ return;
341
+ const height = scrollHeight(from);
342
+ const goal = from.getBoundingClientRect().top + direction * height * 0.85;
343
+ const byDistance = items
344
+ .filter((_, i) => (direction > 0 ? i > current : i < current))
345
+ .sort((a, b) => Math.abs(a.getBoundingClientRect().top - goal) -
346
+ Math.abs(b.getBoundingClientRect().top - goal));
347
+ const fallback = items[Math.min(items.length - 1, Math.max(0, current + direction * 3))];
348
+ (height > 0 && byDistance.length ? byDistance[0] : fallback).focus();
349
+ };
264
350
  const onInputKeyDown = (event) => {
265
351
  if (!isSearchView) {
266
352
  if (event.key === "ArrowDown") {
@@ -269,20 +355,32 @@ function NavLauncher({ isOpen, onClose, sections: rawSections, pinnedIds, freque
269
355
  }
270
356
  return;
271
357
  }
272
- if (results.length === 0)
358
+ if (results.length === 0 || event.nativeEvent.isComposing)
273
359
  return;
274
- if (event.key === "ArrowDown") {
275
- event.preventDefault();
276
- setActiveIndex((active + 1) % results.length);
277
- }
278
- else if (event.key === "ArrowUp") {
279
- event.preventDefault();
280
- setActiveIndex((active - 1 + results.length) % results.length);
281
- }
282
- else if (event.key === "Enter" && !event.nativeEvent.isComposing) {
360
+ if (event.key === "Enter") {
283
361
  event.preventDefault();
284
362
  open(results[active], event.metaKey || event.ctrlKey);
363
+ return;
364
+ }
365
+ const hasModifier = event.altKey || event.metaKey || event.ctrlKey || event.shiftKey;
366
+ if (!RESULT_KEYS.includes(event.key) || hasModifier)
367
+ return;
368
+ const input = event.currentTarget;
369
+ const atEnd = input.selectionStart === input.value.length &&
370
+ input.selectionEnd === input.value.length;
371
+ const isSideways = event.key === "ArrowLeft" || event.key === "ArrowRight";
372
+ const isEdit = isSideways || event.key === "Home" || event.key === "End";
373
+ if (isEdit && !navigating.current) {
374
+ const entersGrid = event.key === "ArrowRight" && atEnd && columns > 1;
375
+ if (!entersGrid)
376
+ return;
285
377
  }
378
+ if (isSideways && columns === 1)
379
+ return;
380
+ event.preventDefault();
381
+ navigating.current = true;
382
+ const pageRows = Math.max(3, Math.floor(scrollHeight(input) / ROW_HEIGHT) - 1);
383
+ setActiveIndex((0, resultNavigation_1.moveResult)(groupSizes, columns, active, event.key, pageRows));
286
384
  };
287
385
  const moveInGrid = (event, cell) => {
288
386
  var _a, _b, _c, _d;
@@ -334,27 +432,39 @@ function NavLauncher({ isOpen, onClose, sections: rawSections, pinnedIds, freque
334
432
  return true;
335
433
  };
336
434
  const onBodyKeyDown = (event) => {
337
- var _a, _b;
338
- if (event.target === inputRef.current)
435
+ var _a;
436
+ const target = event.target;
437
+ if (target.closest(TEXT_TARGET))
339
438
  return;
439
+ const inBody = Boolean((_a = bodyRef.current) === null || _a === void 0 ? void 0 : _a.contains(target));
340
440
  const isArrow = event.key.startsWith("Arrow");
341
- const cell = event.target.closest(CELL_SELECTOR);
342
- if (isArrow && cell && !event.altKey && !event.metaKey && !event.ctrlKey) {
441
+ const cell = target.closest(CELL_SELECTOR);
442
+ if (inBody &&
443
+ isArrow &&
444
+ cell &&
445
+ !event.altKey &&
446
+ !event.metaKey &&
447
+ !event.ctrlKey) {
343
448
  event.preventDefault();
344
449
  moveInGrid(event, cell);
345
450
  }
346
- else if (event.key === "ArrowDown" || event.key === "ArrowUp") {
451
+ else if (inBody &&
452
+ (event.key === "ArrowDown" || event.key === "ArrowUp")) {
347
453
  event.preventDefault();
348
454
  moveFocus(event.key === "ArrowDown" ? 1 : -1);
349
455
  }
350
- else if (event.key === "Home" || event.key === "End") {
456
+ else if (inBody && (event.key === "Home" || event.key === "End")) {
351
457
  event.preventDefault();
352
458
  moveFocus(event.key === "Home" ? "first" : "last");
353
459
  }
460
+ else if (inBody && (event.key === "PageDown" || event.key === "PageUp")) {
461
+ event.preventDefault();
462
+ pageFocus(event.key === "PageDown" ? 1 : -1);
463
+ }
354
464
  else if (event.key === "Backspace" && query) {
355
465
  event.preventDefault();
356
466
  setQuery(query.slice(0, -1));
357
- (_a = inputRef.current) === null || _a === void 0 ? void 0 : _a.focus();
467
+ focusField();
358
468
  }
359
469
  else if (event.key.length === 1 &&
360
470
  !event.metaKey &&
@@ -365,9 +475,46 @@ function NavLauncher({ isOpen, onClose, sections: rawSections, pinnedIds, freque
365
475
  return;
366
476
  event.preventDefault();
367
477
  setQuery(query + event.key);
368
- (_b = inputRef.current) === null || _b === void 0 ? void 0 : _b.focus();
478
+ focusField();
369
479
  }
370
480
  };
481
+ const onContentKeyDown = (event) => {
482
+ if (event.key === "Escape") {
483
+ event.stopPropagation();
484
+ if (event.nativeEvent.isComposing)
485
+ return;
486
+ event.preventDefault();
487
+ if (query) {
488
+ setQuery("");
489
+ focusField();
490
+ }
491
+ else {
492
+ onClose();
493
+ }
494
+ return;
495
+ }
496
+ const hasMod = event.metaKey || event.ctrlKey;
497
+ if (hasMod &&
498
+ !event.altKey &&
499
+ !event.shiftKey &&
500
+ event.key.toLowerCase() === "k") {
501
+ event.preventDefault();
502
+ focusField(true);
503
+ return;
504
+ }
505
+ const target = event.target;
506
+ if (target === inputRef.current)
507
+ return;
508
+ if (event.key === "/" &&
509
+ !hasMod &&
510
+ !event.altKey &&
511
+ !target.closest(TEXT_TARGET)) {
512
+ event.preventDefault();
513
+ focusField(true);
514
+ return;
515
+ }
516
+ onBodyKeyDown(event);
517
+ };
371
518
  (0, react_2.useEffect)(() => {
372
519
  var _a, _b, _c;
373
520
  (_c = (_b = (_a = bodyRef.current) === null || _a === void 0 ? void 0 : _a.querySelector('[data-active="true"]')) === null || _b === void 0 ? void 0 : _b.scrollIntoView) === null || _c === void 0 ? void 0 : _c.call(_b, { block: "nearest" });
@@ -391,7 +538,6 @@ function NavLauncher({ isOpen, onClose, sections: rawSections, pinnedIds, freque
391
538
  document.removeEventListener("pointerdown", forget);
392
539
  };
393
540
  }, [isOpen]);
394
- const appList = apps !== null && apps !== void 0 ? apps : NO_APPS;
395
541
  (0, react_2.useEffect)(() => {
396
542
  var _a, _b;
397
543
  if (lastItemIndex.current < 0 || document.activeElement !== document.body)
@@ -402,23 +548,20 @@ function NavLauncher({ isOpen, onClose, sections: rawSections, pinnedIds, freque
402
548
  items[Math.min(lastItemIndex.current, items.length - 1)].focus();
403
549
  }, [appList]);
404
550
  const hues = (0, react_2.useMemo)(() => (0, AppMark_1.assignAppHues)(appList, (0, chartColorsFromTheme_1.chartMode)(colors)), [appList, colors]);
405
- const showApps = appList.some((app) => app.id !== currentAppId) &&
406
- (appsVisibility === "always" || !isDesktop);
407
551
  const handleAppSelect = (app, event) => {
408
552
  onAppSelect === null || onAppSelect === void 0 ? void 0 : onAppSelect(app, event);
409
553
  if (!event.defaultPrevented)
410
554
  onClose();
411
555
  };
412
- const appsBlock = showApps ? ((0, jsx_runtime_1.jsx)(launcherParts_1.AppsSection, { apps: appList, hues: hues, currentAppId: currentAppId, heading: labels.apps, onSelect: handleAppSelect })) : null;
556
+ const renderApps = (hideHeading) => ((0, jsx_runtime_1.jsx)(launcherParts_1.AppsSection, { apps: appList, hues: hues, currentAppId: currentAppId, heading: labels.apps, hideHeading: hideHeading, onSelect: handleAppSelect }));
413
557
  const listboxId = `${baseId}-results`;
414
558
  const optionId = (index) => `${baseId}-opt-${index}`;
415
559
  const togglePinFor = (id) => onTogglePin ? () => onTogglePin(id) : undefined;
416
560
  const hasResults = results.length > 0;
417
561
  const trimmed = query.trim();
418
- const railFrequent = frequentItems
419
- .filter(({ screen }) => !pinned.has(screen.id))
420
- .slice(0, frequentLimit);
421
- const rail = ((0, jsx_runtime_1.jsxs)(Rail, { label: labels.quickAccess, children: [(0, jsx_runtime_1.jsxs)(react_1.Flex, { as: "section", direction: "column", gap: "6px", children: [(0, jsx_runtime_1.jsx)(launcherParts_1.SectionHeader, { label: labels.pinned, meta: `(${pinnedItems.length})`, isRail: true }), pinnedItems.length === 0 ? ((0, jsx_runtime_1.jsx)(react_1.Text, { fontSize: "13px", color: colors.textMuted, px: "4px", children: labels.pinnedEmpty })) : ((0, jsx_runtime_1.jsx)(react_1.Box, { "data-nav-grid": true, "data-nav-columns": PINNED_COLUMNS, display: "grid", gridTemplateColumns: `repeat(${PINNED_COLUMNS}, minmax(0, 1fr))`, gap: "6px", children: pinnedItems.map(({ screen, module }) => ((0, jsx_runtime_1.jsx)(launcherParts_1.PinnedTile, { screen: screen, module: module, link: link, labels: labels, onTogglePin: togglePinFor(screen.id) }, screen.id))) }))] }), railFrequent.length ? ((0, jsx_runtime_1.jsxs)(react_1.Flex, { as: "section", direction: "column", gap: "2px", children: [(0, jsx_runtime_1.jsx)(launcherParts_1.SectionHeader, { label: labels.frequent, isRail: true, hasCount: false }), railFrequent.map(({ screen, module }) => ((0, jsx_runtime_1.jsx)(launcherParts_1.RailRow, { screen: screen, module: module, link: link, isStrong: true, pinId: onTogglePin ? screen.id : undefined, trailing: (0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [onTogglePin ? ((0, jsx_runtime_1.jsx)(launcherParts_1.PinTextButton, { label: labels.pin(screen.label), text: labels.pinShort, onToggle: () => onTogglePin(screen.id) })) : null, (0, jsx_runtime_1.jsx)(NewTabLink_1.default, { href: screen.href, label: labels.openInNewTab(screen.label), linkAs: link.linkAs })] }) }, screen.id)))] })) : null, recentItems.length ? ((0, jsx_runtime_1.jsxs)(react_1.Flex, { as: "section", direction: "column", gap: "2px", children: [(0, jsx_runtime_1.jsx)(launcherParts_1.SectionHeader, { label: labels.recent, isRail: true, hasCount: false }), recentItems.map(({ screen, module }) => ((0, jsx_runtime_1.jsx)(launcherParts_1.RailRow, { screen: screen, module: module, link: link, trailing: (0, jsx_runtime_1.jsx)(NewTabLink_1.default, { href: screen.href, label: labels.openInNewTab(screen.label), linkAs: link.linkAs }) }, screen.id)))] })) : null, appsBlock] }));
562
+ const pinnedColumns = !isLg && isDesktop ? 3 : PINNED_COLUMNS;
563
+ const quickSections = ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsxs)(react_1.Flex, { as: "section", direction: "column", gap: "6px", children: [(0, jsx_runtime_1.jsx)(launcherParts_1.SectionHeader, { label: labels.pinned, meta: `(${pinnedItems.length})`, isRail: true }), pinnedItems.length === 0 ? ((0, jsx_runtime_1.jsx)(react_1.Text, { fontSize: "13px", color: colors.textMuted, px: "4px", children: labels.pinnedEmpty })) : ((0, jsx_runtime_1.jsx)(react_1.Box, { "data-nav-grid": true, "data-nav-columns": pinnedColumns, display: "grid", gridTemplateColumns: `repeat(${pinnedColumns}, minmax(0, 1fr))`, gap: "6px", children: pinnedItems.map(({ screen, module }) => ((0, jsx_runtime_1.jsx)(launcherParts_1.PinnedTile, { screen: screen, module: module, link: link, labels: labels, onTogglePin: togglePinFor(screen.id) }, screen.id))) }))] }), railFrequent.length ? ((0, jsx_runtime_1.jsxs)(react_1.Flex, { as: "section", direction: "column", gap: "2px", children: [(0, jsx_runtime_1.jsx)(launcherParts_1.SectionHeader, { label: labels.frequent, isRail: true, hasCount: false }), railFrequent.map(({ screen, module }) => ((0, jsx_runtime_1.jsx)(launcherParts_1.RailRow, { screen: screen, module: module, link: link, isStrong: true, pinId: onTogglePin ? screen.id : undefined, trailing: (0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [onTogglePin ? ((0, jsx_runtime_1.jsx)(launcherParts_1.PinTextButton, { label: labels.pin(screen.label), text: labels.pinShort, onToggle: () => onTogglePin(screen.id) })) : null, (0, jsx_runtime_1.jsx)(NewTabLink_1.default, { href: screen.href, label: labels.openInNewTab(screen.label), tip: labels.newTabTip, linkAs: link.linkAs })] }) }, screen.id)))] })) : null, recentItems.length ? ((0, jsx_runtime_1.jsxs)(react_1.Flex, { as: "section", direction: "column", gap: "2px", children: [(0, jsx_runtime_1.jsx)(launcherParts_1.SectionHeader, { label: labels.recent, isRail: true, hasCount: false }), recentItems.map(({ screen, module }) => ((0, jsx_runtime_1.jsx)(launcherParts_1.RailRow, { screen: screen, module: module, link: link, trailing: (0, jsx_runtime_1.jsx)(NewTabLink_1.default, { href: screen.href, label: labels.openInNewTab(screen.label), tip: labels.newTabTip, linkAs: link.linkAs }) }, screen.id)))] })) : null] }));
564
+ const rail = ((0, jsx_runtime_1.jsxs)(Rail, { label: labels.quickAccess, scrolls: isLg, children: [quickSections, showApps ? renderApps(false) : null] }));
422
565
  const browsePane = sections.length === 0 ? ((0, jsx_runtime_1.jsx)(EmptyState_1.default, { title: labels.empty, titleAs: "h3", size: "sm" })) : (sections.map((section) => ((0, jsx_runtime_1.jsxs)(react_1.Flex, { as: "section", "aria-label": section.label, direction: "column", gap: "3px", children: [(0, jsx_runtime_1.jsx)(launcherParts_1.SectionHeader, { label: section.label, meta: `(${section.modules.length})` }), (0, jsx_runtime_1.jsx)(ItemGrid, { columns: columns, children: section.modules.map((module) => {
423
566
  const pinId = module.screens[0].id;
424
567
  return ((0, jsx_runtime_1.jsx)(launcherParts_1.ModuleRow, { module: module, keyScreens: module.screens
@@ -432,21 +575,28 @@ function NavLauncher({ isOpen, onClose, sections: rawSections, pinnedIds, freque
432
575
  };
433
576
  const noResults = ((0, jsx_runtime_1.jsxs)(react_1.Flex, { direction: "column", align: "center", gap: "12px", py: "48px", px: "20px", textAlign: "center", role: "status", children: [(0, jsx_runtime_1.jsx)(react_1.Flex, { boxSize: "40px", borderRadius: "12px", bg: colors.backgroundColor.subtle, color: colors.textMuted, align: "center", justify: "center", children: (0, jsx_runtime_1.jsx)(lucide_react_1.Search, { size: 18, strokeWidth: 2, "aria-hidden": true }) }), (0, jsx_runtime_1.jsxs)(react_1.Flex, { direction: "column", gap: "3px", children: [(0, jsx_runtime_1.jsx)(react_1.Text, { fontSize: "14px", fontWeight: 600, color: colors.text[900], children: isSearching ? labels.searching : labels.noResults(trimmed) }), isSearching ? null : ((0, jsx_runtime_1.jsx)(react_1.Text, { fontSize: "12.5px", color: colors.textMuted, children: (suggestions === null || suggestions === void 0 ? void 0 : suggestions.length)
434
577
  ? labels.suggestionsHint
435
- : labels.noResultsHint }))] }), !isSearching && (suggestions === null || suggestions === void 0 ? void 0 : suggestions.length) ? ((0, jsx_runtime_1.jsx)(react_1.Flex, { wrap: "wrap", justify: "center", gap: "6px", children: suggestions.map((suggestion) => ((0, jsx_runtime_1.jsx)(react_1.Box, { as: "button", type: "button", onClick: () => pickSuggestion(suggestion), h: "28px", px: "11px", borderRadius: "full", borderWidth: "1px", borderColor: colors.boxborder[200], bg: colors.surfaceRaised, fontSize: "12.5px", fontWeight: 500, color: colors.text[900], transition: (0, motion_1.transitionFor)(["background-color"]), _hover: { bg: (0, rowStates_1.listRowBackgrounds)(colors).active }, sx: (0, focusRing_styles_1.focusVisibleStyles)(colors), children: suggestion }, suggestion))) })) : null] }));
578
+ : labels.noResultsHint }))] }), !isSearching && (suggestions === null || suggestions === void 0 ? void 0 : suggestions.length) ? ((0, jsx_runtime_1.jsx)(react_1.Flex, { wrap: "wrap", justify: "center", gap: "6px", children: suggestions.map((suggestion) => ((0, jsx_runtime_1.jsx)(react_1.Box, { as: "button", type: "button", onClick: () => pickSuggestion(suggestion), h: "28px", px: "11px", borderRadius: "full", borderWidth: "1px", borderColor: colors.boxborder[200], bg: colors.surfaceRaised, fontSize: "12.5px", fontWeight: 500, color: colors.text[900], transition: (0, motion_2.transitionFor)(["background-color"]), _hover: { bg: (0, rowStates_1.listRowBackgrounds)(colors).active }, sx: (0, focusRing_styles_1.focusVisibleStyles)(colors), children: suggestion }, suggestion))) })) : null] }));
436
579
  const searchPane = hasResults ? ((0, jsx_runtime_1.jsx)(react_1.Flex, { role: "listbox", id: listboxId, "aria-label": labels.title, "aria-busy": isSearching || undefined, direction: "column", gap: "14px", children: groups.map((group, groupIndex) => ((0, jsx_runtime_1.jsxs)(react_1.Flex, { role: "group", "aria-label": group.meta ? `${group.label}, ${group.meta}` : group.label, direction: "column", gap: "3px", children: [(0, jsx_runtime_1.jsx)(launcherParts_1.SectionHeader, { label: group.label, meta: group.meta ? `(${group.meta})` : undefined, isDecorative: true }), (0, jsx_runtime_1.jsx)(ItemGrid, { columns: columns, children: group.items.map((item, itemIndex) => {
437
580
  const index = groupStarts[groupIndex] + itemIndex;
438
581
  return ((0, jsx_runtime_1.jsx)(SearchRow, { item: item, query: query, isActive: index === active, link: link, labels: labels, optionId: optionId(index), isPinned: item.screenId ? pinned.has(item.screenId) : false, canPin: Boolean(onTogglePin) && item.kind === "module", onPin: () => item.screenId && (onTogglePin === null || onTogglePin === void 0 ? void 0 : onTogglePin(item.screenId)), onOpen: () => open(item, false), onHover: () => setActiveIndex(index) }, item.id));
439
582
  }) })] }, group.id))) })) : (noResults);
440
- const body = isLoading ? ((0, jsx_runtime_1.jsx)(SkeletonLayout, { label: labels.loading })) : ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(Pane, { children: isSearchView ? searchPane : browsePane }), rail] }));
441
- const pill = (0, filledSurface_1.resolveFilledSurface)(colors.primary, colors);
442
- return ((0, jsx_runtime_1.jsxs)(Drawer_1.default, { isOpen: isOpen, onClose: onClose, placement: "left", size: "full", initialFocusRef: inputRef, hideCloseButton: true, contentProps: {
443
- maxW: { base: "100vw", lg: `min(${maxWidth}, calc(100vw - 56px))` },
444
- h: { base: "100%", lg: "auto" },
445
- maxH: "100%",
446
- bg: colors.surfaceRaised,
447
- borderBottomRightRadius: { base: 0, lg: "16px" },
448
- boxShadow: colors.boxShadow.default,
449
- overflow: "hidden",
450
- ...{ "data-testid": testId },
451
- }, children: [(0, jsx_runtime_1.jsx)(Drawer_1.DrawerHeader, { p: 0, flexShrink: 0, children: (0, jsx_runtime_1.jsxs)(react_1.Flex, { align: "center", gap: "12px", h: "60px", pl: "18px", pr: "12px", children: [(0, jsx_runtime_1.jsxs)(react_1.Heading, { as: "h2", display: "inline-flex", alignItems: "center", gap: "7px", h: "32px", pl: "9px", pr: "11px", borderRadius: "9px", bg: pill.fill, color: pill.color, fontSize: "13px", fontWeight: 600, flexShrink: 0, children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Compass, { size: 16, strokeWidth: 2, "aria-hidden": true }), labels.title] }), (0, jsx_runtime_1.jsx)(SearchField, { inputRef: inputRef, value: query, onChange: setQuery, onKeyDown: onInputKeyDown, labels: labels, hint: searchHint, listboxId: listboxId, activeId: isSearchView && hasResults ? optionId(active) : undefined, isExpanded: isSearchView, hasResults: hasResults }), (0, jsx_runtime_1.jsx)(react_1.IconButton, { "aria-label": labels.close, icon: (0, jsx_runtime_1.jsx)(lucide_react_1.X, { size: 18, "aria-hidden": true }), variant: "ghost", boxSize: "34px", minW: "34px", borderRadius: "9px", color: colors.textMuted, ml: "auto", onClick: onClose })] }) }), (0, jsx_runtime_1.jsx)(Drawer_1.DrawerBody, { p: 0, display: "flex", flexDirection: { base: "column", lg: "row" }, overflowY: { base: "auto", lg: "hidden" }, minH: 0, children: (0, jsx_runtime_1.jsx)(react_1.Flex, { ref: bodyRef, onKeyDown: onBodyKeyDown, direction: { base: "column", lg: "row" }, flex: 1, minH: 0, minW: 0, children: body }) }), (0, jsx_runtime_1.jsxs)(Drawer_1.DrawerFooter, { p: 0, minH: "32px", flexShrink: 0, px: "18px", py: "6px", gap: "6px 14px", flexWrap: "wrap", bg: colors.backgroundColor.subtle, fontSize: "11.5px", color: colors.textMuted, whiteSpace: "nowrap", justifyContent: "flex-start", children: [(0, jsx_runtime_1.jsxs)(react_1.Flex, { gap: "6px 14px", align: "center", wrap: "wrap", "aria-hidden": true, sx: { "@media (hover: none)": { display: "none" } }, children: [openShortcutHint ? ((0, jsx_runtime_1.jsxs)(react_1.Flex, { as: "span", align: "center", gap: "5px", children: [(0, jsx_runtime_1.jsx)(Kbd_1.default, { size: "xs", children: openShortcutHint }), labels.hintOpenMenu] })) : null, (0, jsx_runtime_1.jsxs)(react_1.Flex, { as: "span", align: "center", gap: "5px", children: [(0, jsx_runtime_1.jsx)(react_1.Flex, { as: "span", gap: "2px", children: ARROW_KEYS.map((arrow) => ((0, jsx_runtime_1.jsx)(Kbd_1.default, { size: "xs", children: arrow }, arrow))) }), labels.hintMove] }), (0, jsx_runtime_1.jsxs)(react_1.Flex, { as: "span", align: "center", gap: "5px", children: [(0, jsx_runtime_1.jsx)(Kbd_1.KbdShortcut, { shortcut: "enter", size: "xs" }), labels.hintOpen] }), (0, jsx_runtime_1.jsxs)(react_1.Flex, { as: "span", align: "center", gap: "5px", children: [modKeyLabel ? ((0, jsx_runtime_1.jsxs)(react_1.Flex, { as: "span", gap: "4px", children: [(0, jsx_runtime_1.jsx)(Kbd_1.default, { size: "xs", children: modKeyLabel }), (0, jsx_runtime_1.jsx)(Kbd_1.default, { size: "xs", children: ENTER_KEY })] })) : ((0, jsx_runtime_1.jsx)(Kbd_1.KbdShortcut, { shortcut: "mod+enter", size: "xs" })), labels.hintNewTab] }), onTogglePin ? ((0, jsx_runtime_1.jsxs)(react_1.Flex, { as: "span", align: "center", gap: "5px", children: [(0, jsx_runtime_1.jsx)(Kbd_1.default, { size: "xs", children: "P" }), labels.hintPin] })) : null, (0, jsx_runtime_1.jsxs)(react_1.Flex, { as: "span", align: "center", gap: "5px", children: [(0, jsx_runtime_1.jsx)(Kbd_1.KbdShortcut, { shortcut: "esc", size: "xs" }), labels.hintClose] })] }), (0, jsx_runtime_1.jsx)(react_1.Text, { as: "span", ml: "auto", children: labels.footerNote })] })] }));
583
+ const stacked = ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(Pane, { scrolls: isLg, children: isSearchView ? searchPane : browsePane }), rail] }));
584
+ const body = isLoading ? (0, jsx_runtime_1.jsx)(SkeletonLayout, { label: labels.loading }) : stacked;
585
+ const tabPanels = {
586
+ menu: (0, jsx_runtime_1.jsx)(Pane, { children: browsePane }),
587
+ quick: ((0, jsx_runtime_1.jsx)(react_1.Flex, { direction: "column", gap: "16px", px: "18px", pt: "14px", pb: "16px", children: quickSections })),
588
+ apps: ((0, jsx_runtime_1.jsx)(react_1.Box, { px: "14px", pt: "10px", pb: "16px", children: renderApps(true) })),
589
+ };
590
+ const compactBody = ((0, jsx_runtime_1.jsx)(react_1.Flex, { "data-scroll-region": true, role: isTabbed ? "tabpanel" : undefined, id: isTabbed ? (0, LauncherTabs_1.panelId)(baseId) : undefined, "aria-labelledby": isTabbed ? (0, LauncherTabs_1.tabId)(baseId, activeTab) : undefined, direction: "column", flex: 1, minH: 0, overflowY: "auto", children: isTabbed ? tabPanels[activeTab] : body }));
591
+ return ((0, jsx_runtime_1.jsx)(launcherParts_1.TouchDensity, { value: !isLg, children: (0, jsx_runtime_1.jsxs)(Drawer_1.default, { isOpen: isOpen, onClose: onClose, placement: "left", size: "full", initialFocusRef: inputRef, hideCloseButton: true, closeOnEsc: false, contentProps: {
592
+ maxW: { base: "100vw", lg: `min(${maxWidth}, calc(100vw - 56px))` },
593
+ h: { base: "100%", lg: "auto" },
594
+ maxH: "100%",
595
+ bg: colors.surfaceRaised,
596
+ borderBottomRightRadius: { base: 0, lg: "16px" },
597
+ boxShadow: colors.boxShadow.default,
598
+ overflow: "hidden",
599
+ onKeyDown: onContentKeyDown,
600
+ ...{ "data-testid": testId },
601
+ }, children: [(0, jsx_runtime_1.jsx)(Drawer_1.DrawerHeader, { p: 0, flexShrink: 0, children: (0, jsx_runtime_1.jsxs)(react_1.Flex, { align: "center", gap: { base: "8px", md: "12px" }, minH: { base: "60px", md: "68px" }, pl: { base: "12px", md: "18px" }, pr: { base: "8px", md: "12px" }, children: [(0, jsx_runtime_1.jsx)(react_1.VisuallyHidden, { as: "h2", children: labels.title }), (0, jsx_runtime_1.jsx)(SearchField, { inputRef: inputRef, value: query, onChange: setQuery, onKeyDown: onInputKeyDown, labels: labels, hint: searchHint, listboxId: listboxId, activeId: isSearchView && hasResults ? optionId(active) : undefined, isExpanded: isSearchView, hasResults: hasResults }), (0, jsx_runtime_1.jsx)(react_1.Box, { "aria-hidden": true, "data-testid": "launcher-count", display: { base: "none", sm: "block" }, flexShrink: 0, overflow: "hidden", w: countText ? "88px" : "0", opacity: countText ? 1 : 0, textAlign: "right", fontSize: "13px", whiteSpace: "nowrap", color: colors.textMuted, transition: (0, motion_2.transitionFor)(["width", "opacity"]), sx: { fontVariantNumeric: "tabular-nums", ...(0, motion_1.reducedMotionSx)() }, children: countText }), headerActions ? ((0, jsx_runtime_1.jsx)(react_1.Flex, { align: "center", gap: "4px", flexShrink: 0, children: headerActions })) : null, (0, jsx_runtime_1.jsx)(react_1.IconButton, { "aria-label": labels.close, icon: (0, jsx_runtime_1.jsx)(lucide_react_1.X, { size: 18, "aria-hidden": true }), variant: "ghost", boxSize: { base: "40px", md: "44px" }, minW: { base: "40px", md: "44px" }, borderRadius: "10px", flexShrink: 0, color: colors.textMuted, onClick: onClose })] }) }), (0, jsx_runtime_1.jsx)(Drawer_1.DrawerBody, { p: 0, display: "flex", flexDirection: "column", overflowY: "hidden", minH: 0, children: (0, jsx_runtime_1.jsxs)(react_1.Flex, { ref: bodyRef, direction: "column", flex: 1, minH: 0, minW: 0, children: [(0, jsx_runtime_1.jsx)(react_1.VisuallyHidden, { "data-testid": "launcher-live", "aria-live": "polite", "aria-atomic": "true", children: countText }), isTabbed ? ((0, jsx_runtime_1.jsx)(LauncherTabs_1.LauncherTabs, { tabs: tabs, selected: activeTab, onSelect: setTab, idBase: baseId, label: labels.tabsLabel, onLeaveUp: () => focusField(), onLeaveDown: () => moveFocus("first") })) : null, isLg ? ((0, jsx_runtime_1.jsx)(react_1.Flex, { direction: "row", flex: 1, minH: 0, minW: 0, children: body })) : (compactBody)] }) }), (0, jsx_runtime_1.jsxs)(Drawer_1.DrawerFooter, { p: 0, minH: "32px", flexShrink: 0, px: "18px", py: "6px", gap: "6px 14px", flexWrap: "wrap", bg: colors.backgroundColor.subtle, fontSize: "11.5px", color: colors.textMuted, whiteSpace: "nowrap", justifyContent: "flex-start", children: [(0, jsx_runtime_1.jsxs)(react_1.Flex, { gap: "6px 14px", align: "center", wrap: "wrap", "aria-hidden": true, sx: { "@media (hover: none)": { display: "none" } }, children: [(0, jsx_runtime_1.jsxs)(react_1.Flex, { as: "span", align: "center", gap: "5px", children: [(0, jsx_runtime_1.jsx)(react_1.Flex, { as: "span", gap: "2px", children: ARROW_KEYS.map((arrow) => ((0, jsx_runtime_1.jsx)(Kbd_1.default, { size: "xs", children: arrow }, arrow))) }), labels.hintMove] }), (0, jsx_runtime_1.jsxs)(react_1.Flex, { as: "span", align: "center", gap: "5px", children: [(0, jsx_runtime_1.jsx)(Kbd_1.KbdShortcut, { shortcut: "enter", size: "xs" }), labels.hintOpen] }), (0, jsx_runtime_1.jsxs)(react_1.Flex, { as: "span", align: "center", gap: "5px", children: [modKeyLabel ? ((0, jsx_runtime_1.jsxs)(react_1.Flex, { as: "span", gap: "4px", children: [(0, jsx_runtime_1.jsx)(Kbd_1.default, { size: "xs", children: modKeyLabel }), (0, jsx_runtime_1.jsx)(Kbd_1.default, { size: "xs", children: ENTER_KEY })] })) : ((0, jsx_runtime_1.jsx)(Kbd_1.KbdShortcut, { shortcut: "mod+enter", size: "xs" })), labels.hintNewTab] }), (0, jsx_runtime_1.jsxs)(react_1.Flex, { as: "span", align: "center", gap: "5px", children: [(0, jsx_runtime_1.jsx)(Kbd_1.default, { size: "xs", children: modKeyLabel !== null && modKeyLabel !== void 0 ? modKeyLabel : MOD_KEY }), labels.hintClickNewTab] }), onTogglePin ? ((0, jsx_runtime_1.jsxs)(react_1.Flex, { as: "span", align: "center", gap: "5px", children: [(0, jsx_runtime_1.jsx)(Kbd_1.default, { size: "xs", children: "P" }), labels.hintPin] })) : null, (0, jsx_runtime_1.jsxs)(react_1.Flex, { as: "span", align: "center", gap: "5px", children: [(0, jsx_runtime_1.jsx)(Kbd_1.KbdShortcut, { shortcut: "esc", size: "xs" }), labels.hintClose] })] }), (0, jsx_runtime_1.jsx)(react_1.Text, { as: "span", ml: "auto", children: labels.footerNote })] })] }) }));
452
602
  }
@@ -1,4 +1,4 @@
1
- import type { ElementType, KeyboardEvent, MouseEvent } from "react";
1
+ import type { ElementType, KeyboardEvent, MouseEvent, ReactNode } from "react";
2
2
  import type { AppOption } from "../AppSwitcher/AppSwitcherProps";
3
3
  /** Rendered as `<Icon size={16} aria-hidden />`, so any lucide-compatible component works. */
4
4
  export type LauncherIcon = ElementType;
@@ -54,6 +54,7 @@ export type LauncherResultGroup = {
54
54
  };
55
55
  /** Every visible string and accessible name; pass any subset, the rest fall back to English. */
56
56
  export type LauncherLabels = {
57
+ /** Accessible name of the dialog and the results list; no longer drawn as a header pill. */
57
58
  title: string;
58
59
  searchPlaceholder: string;
59
60
  clearSearch: string;
@@ -84,8 +85,22 @@ export type LauncherLabels = {
84
85
  hintPin: string;
85
86
  /** Footer hint after the ESC key cap. */
86
87
  hintClose: string;
87
- /** Footer hint after `openShortcutHint`; only rendered when that prop is set. */
88
+ /** @deprecated No longer rendered: the launcher is already open, so the open chord is not shown. */
88
89
  hintOpenMenu: string;
90
+ /** Footer hint after the mod key cap for a modified click, e.g. "+ click for new tab". */
91
+ hintClickNewTab: string;
92
+ /** Tooltip of the arrow link beside a row. */
93
+ newTabTip: string;
94
+ /** Muted count beside the search field and its polite announcement, e.g. "12 results". */
95
+ resultsCount: (count: number) => string;
96
+ /** Name of the Menu / Quick access / Apps tab list shown below `lg`. */
97
+ tabsLabel: string;
98
+ /** Tab showing every module (below `lg`). */
99
+ tabMenu: string;
100
+ /** Tab showing Pinned, Frequently visited and Recent (below `lg`). */
101
+ tabQuickAccess: string;
102
+ /** Tab showing the suite's apps (below `lg`). */
103
+ tabApps: string;
89
104
  /** Heading and list name of the apps section. */
90
105
  apps: string;
91
106
  /** Per-section count shown while searching, e.g. "3 found". */
@@ -146,10 +161,12 @@ export interface NavLauncherProps {
146
161
  suggestions?: readonly string[];
147
162
  /** Key cap shown at the end of the search field, e.g. "/" when the app opens the launcher with it. */
148
163
  searchHint?: string;
149
- /** Chord that opens the launcher in the host app, e.g. "⌘K or /"; shown as the first footer hint. Omit when the app does not own one. */
164
+ /** @deprecated No longer rendered: the launcher is already open. Cmd/Ctrl+K and "/" now focus the search field while it is open. */
150
165
  openShortcutHint?: string;
151
166
  /** Replaces the modifier cap of the mod+Enter hint; defaults to "⌘" on Apple platforms, "Ctrl" elsewhere. */
152
167
  modKeyLabel?: string;
168
+ /** Rendered between the search field and the close button, e.g. a help or customise icon button; use icon-only buttons so the dialog name stays "Menu". */
169
+ headerActions?: ReactNode;
153
170
  /** Panel width at `lg`+; always full width below `lg`. Defaults to "1080px". */
154
171
  maxWidth?: string;
155
172
  labels?: Partial<LauncherLabels>;
@@ -179,8 +196,10 @@ export interface PinToggleProps {
179
196
  /** Props for the small "open in new tab" anchor beside a launcher row. */
180
197
  export interface NewTabLinkProps {
181
198
  href: string;
182
- /** Accessible name and tooltip, e.g. "Open Leave in a new tab". */
199
+ /** Accessible name, e.g. "Open Leave in a new tab". */
183
200
  label: string;
201
+ /** Visible tooltip text; defaults to `label`. */
202
+ tip?: string;
184
203
  /** Anchor-like component receiving `href`, `target`, `rel`; defaults to `"a"`. */
185
204
  linkAs?: ElementType;
186
205
  tabIndex?: number;
@@ -1,3 +1,3 @@
1
1
  import type { NewTabLinkProps } from "./NavLauncherProps";
2
- /** "Open in a new tab" anchor; visible on row hover, focus-within or its own focus only. */
3
- export default function NewTabLink({ href, label, linkAs, tabIndex }: NewTabLinkProps): import("react/jsx-runtime").JSX.Element;
2
+ /** "Open in a new tab" anchor with a tooltip; visible on row hover, keyboard-active or focus-within, or its own focus. */
3
+ export default function NewTabLink({ href, label, tip, linkAs, tabIndex, }: NewTabLinkProps): import("react/jsx-runtime").JSX.Element;
@@ -1,4 +1,7 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
3
6
  exports.default = NewTabLink;
4
7
  const jsx_runtime_1 = require("react/jsx-runtime");
@@ -6,9 +9,10 @@ const react_1 = require("@chakra-ui/react");
6
9
  const lucide_react_1 = require("lucide-react");
7
10
  const motion_1 = require("../../Theme/tokens/primitives/motion");
8
11
  const useCustomTheme_1 = require("../../Theme/useCustomTheme");
12
+ const ToolTip_1 = __importDefault(require("../ToolTip/ToolTip"));
9
13
  const revealStyles_1 = require("./revealStyles");
10
- /** "Open in a new tab" anchor; visible on row hover, focus-within or its own focus only. */
11
- function NewTabLink({ href, label, linkAs = "a", tabIndex }) {
14
+ /** "Open in a new tab" anchor with a tooltip; visible on row hover, keyboard-active or focus-within, or its own focus. */
15
+ function NewTabLink({ href, label, tip, linkAs = "a", tabIndex, }) {
12
16
  const { colors } = (0, useCustomTheme_1.useCustomTheme)();
13
- return ((0, jsx_runtime_1.jsx)(react_1.Box, { as: linkAs, href: href, target: "_blank", rel: "noopener noreferrer", "aria-label": label, title: label, tabIndex: tabIndex, display: "inline-flex", alignItems: "center", justifyContent: "center", w: "22px", h: "22px", flexShrink: 0, borderRadius: "md", color: colors.textMuted, opacity: 0, transition: (0, motion_1.transitionFor)(["opacity", "background-color"]), sx: (0, revealStyles_1.revealStyles)(colors), _hover: { bg: colors.backgroundColor.subtle }, children: (0, jsx_runtime_1.jsx)(lucide_react_1.ArrowUpRight, { size: 13, "aria-hidden": true }) }));
17
+ return ((0, jsx_runtime_1.jsx)(ToolTip_1.default, { label: tip !== null && tip !== void 0 ? tip : label, placement: "bottom", children: (0, jsx_runtime_1.jsx)(react_1.Box, { as: linkAs, href: href, target: "_blank", rel: "noopener noreferrer", "aria-label": label, tabIndex: tabIndex, display: "inline-flex", alignItems: "center", justifyContent: "center", w: "22px", h: "22px", flexShrink: 0, borderRadius: "md", color: colors.textMuted, opacity: 0, transition: (0, motion_1.transitionFor)(["opacity", "background-color"]), sx: (0, revealStyles_1.revealStyles)(colors), _hover: { bg: colors.backgroundColor.subtle }, children: (0, jsx_runtime_1.jsx)(lucide_react_1.ArrowUpRight, { size: 13, "aria-hidden": true }) }) }));
14
18
  }
@@ -3,6 +3,8 @@ import type { PaletteProps } from "../../Theme/tokens/types";
3
3
  import type { AppOption } from "../AppSwitcher/AppSwitcherProps";
4
4
  import type { LauncherLabels, LauncherModule, LauncherScreen } from "./NavLauncherProps";
5
5
  export declare const ITEM_SELECTOR = "[data-launcher-item]";
6
+ export declare const TouchDensity: import("react").Provider<boolean>;
7
+ export declare const useTouchDensity: () => boolean;
6
8
  export declare const isPlainClick: (event: MouseEvent) => boolean;
7
9
  export type LinkCtx = {
8
10
  linkAs: ElementType;
@@ -146,7 +148,8 @@ type AppsSectionProps = {
146
148
  hues: readonly string[];
147
149
  currentAppId?: string;
148
150
  heading: string;
151
+ hideHeading?: boolean;
149
152
  onSelect: (app: AppOption, event: MouseEvent | KeyboardEvent) => void;
150
153
  };
151
- export declare function AppsSection({ apps, hues, currentAppId, heading, onSelect, }: AppsSectionProps): import("react/jsx-runtime").JSX.Element;
154
+ export declare function AppsSection({ apps, hues, currentAppId, heading, hideHeading, onSelect, }: AppsSectionProps): import("react/jsx-runtime").JSX.Element;
152
155
  export {};