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.
- package/CHANGELOG.md +38 -10
- package/COMPONENT-GOTCHAS.md +41 -4
- package/DESIGN-GUIDE.md +9 -1
- package/dist/Components/NavLauncher/LauncherTabs.d.ts +18 -0
- package/dist/Components/NavLauncher/LauncherTabs.js +64 -0
- package/dist/Components/NavLauncher/NavLauncher.d.ts +1 -1
- package/dist/Components/NavLauncher/NavLauncher.js +203 -53
- package/dist/Components/NavLauncher/NavLauncherProps.d.ts +23 -4
- package/dist/Components/NavLauncher/NewTabLink.d.ts +2 -2
- package/dist/Components/NavLauncher/NewTabLink.js +7 -3
- package/dist/Components/NavLauncher/launcherParts.d.ts +4 -1
- package/dist/Components/NavLauncher/launcherParts.js +16 -8
- package/dist/Components/NavLauncher/resultNavigation.d.ts +3 -0
- package/dist/Components/NavLauncher/resultNavigation.js +65 -0
- package/dist/Components/NavLauncher/revealStyles.d.ts +1 -1
- package/dist/Components/NavLauncher/revealStyles.js +2 -2
- package/dist/Components/NavigationBar/NavigationBar.js +2 -1
- package/dist/Components/NavigationBar/NavigationBarProps.d.ts +1 -0
- package/dist/Components/OrgSwitcher/OrgSwitcher.js +18 -6
- package/dist/Components/OrgSwitcher/OrgSwitcherProps.d.ts +4 -0
- package/dist/esm/Components/NavLauncher/LauncherTabs.js +58 -0
- package/dist/esm/Components/NavLauncher/NavLauncher.js +204 -54
- package/dist/esm/Components/NavLauncher/NewTabLink.js +4 -3
- package/dist/esm/Components/NavLauncher/launcherParts.js +15 -8
- package/dist/esm/Components/NavLauncher/resultNavigation.js +61 -0
- package/dist/esm/Components/NavLauncher/revealStyles.js +2 -2
- package/dist/esm/Components/NavigationBar/NavigationBar.js +2 -1
- package/dist/esm/Components/OrgSwitcher/OrgSwitcher.js +16 -7
- package/dist/index.d.ts +1 -0
- package/package.json +1 -1
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import { Box, Flex,
|
|
3
|
-
import {
|
|
2
|
+
import { Box, Flex, IconButton, Text, VisuallyHidden } from "@chakra-ui/react";
|
|
3
|
+
import { Search as SearchIcon, X } from "lucide-react";
|
|
4
4
|
import { useCallback, useEffect, useId, useMemo, useRef, useState, } from "react";
|
|
5
5
|
import { useMatchMedia } from "../../Hooks/useMatchMedia.js";
|
|
6
6
|
import { focusVisibleStyles } from "../../Theme/chakra/focusRing.styles.js";
|
|
7
|
+
import { reducedMotionSx } from "../../Theme/chakra/motion.js";
|
|
7
8
|
import { chartMode } from "../../Theme/tokens/builders/chartColorsFromTheme.js";
|
|
8
9
|
import { placeholderInk } from "../../Theme/tokens/builders/placeholderInk.js";
|
|
9
|
-
import { resolveFilledSurface } from "../../Theme/tokens/builders/filledSurface.js";
|
|
10
10
|
import { listRowBackgrounds } from "../../Theme/tokens/builders/rowStates.js";
|
|
11
11
|
import { transitionFor } from "../../Theme/tokens/primitives/motion.js";
|
|
12
12
|
import { useCustomTheme } from "../../Theme/useCustomTheme.js";
|
|
@@ -18,12 +18,15 @@ import ToolTip from "../ToolTip/ToolTip.js";
|
|
|
18
18
|
import { useIsTruncated } from "../../Hooks/useIsTruncated.js";
|
|
19
19
|
import { shortcutKeys } from "../AppSwitcher/AppSwitcherProps.js";
|
|
20
20
|
import Skeletons from "../Skeletons/Skeletons.js";
|
|
21
|
-
import { activeRowSurface, AppsSection, CountBadge, Highlight, isPlainClick, ITEM_SELECTOR, ModuleRow, NewTag, PinnedTile, PinTextButton, RailRow, ROW_CONTAINER, RowActions, RowIcon, RowTip, SectionHeader, titleInset, WIDE_ROW, } from "./launcherParts.js";
|
|
21
|
+
import { activeRowSurface, AppsSection, CountBadge, Highlight, isPlainClick, ITEM_SELECTOR, ModuleRow, NewTag, PinnedTile, PinTextButton, RailRow, ROW_CONTAINER, RowActions, RowIcon, RowTip, SectionHeader, titleInset, TouchDensity, useTouchDensity, WIDE_ROW, } from "./launcherParts.js";
|
|
22
|
+
import { LauncherTabs, panelId, tabId, } from "./LauncherTabs.js";
|
|
22
23
|
import { searchLauncherSections } from "./match.js";
|
|
23
24
|
import NewTabLink from "./NewTabLink.js";
|
|
24
25
|
import PinToggle from "./PinToggle.js";
|
|
26
|
+
import { moveResult } from "./resultNavigation.js";
|
|
25
27
|
const NO_APPS = [];
|
|
26
28
|
const MD_QUERY = "(min-width: 48em)";
|
|
29
|
+
const LG_QUERY = "(min-width: 62em)";
|
|
27
30
|
const XL_QUERY = "(min-width: 80em)";
|
|
28
31
|
const CELL_SELECTOR = "[data-nav-cell]";
|
|
29
32
|
const RAIL_WIDTH = "clamp(20rem, 30%, 26rem)";
|
|
@@ -31,6 +34,19 @@ const PINNED_COLUMNS = 2;
|
|
|
31
34
|
const ENTER_CHIP_WIDTH = 50;
|
|
32
35
|
const ARROW_KEYS = ["↑", "↓", "←", "→"];
|
|
33
36
|
const ENTER_KEY = shortcutKeys("enter")[0];
|
|
37
|
+
const MOD_KEY = shortcutKeys("mod+enter")[0];
|
|
38
|
+
const RESULT_KEYS = [
|
|
39
|
+
"ArrowUp",
|
|
40
|
+
"ArrowDown",
|
|
41
|
+
"ArrowLeft",
|
|
42
|
+
"ArrowRight",
|
|
43
|
+
"Home",
|
|
44
|
+
"End",
|
|
45
|
+
"PageUp",
|
|
46
|
+
"PageDown",
|
|
47
|
+
];
|
|
48
|
+
const TEXT_TARGET = "input, textarea, select, [contenteditable=true]";
|
|
49
|
+
const ROW_HEIGHT = 46;
|
|
34
50
|
const DEFAULT_LABELS = {
|
|
35
51
|
title: "Menu",
|
|
36
52
|
searchPlaceholder: "Search menus and screens",
|
|
@@ -56,6 +72,13 @@ const DEFAULT_LABELS = {
|
|
|
56
72
|
hintPin: "pin",
|
|
57
73
|
hintClose: "close",
|
|
58
74
|
hintOpenMenu: "open",
|
|
75
|
+
hintClickNewTab: "+ click for new tab",
|
|
76
|
+
newTabTip: "Open in new tab",
|
|
77
|
+
resultsCount: (count) => `${count} ${count === 1 ? "result" : "results"}`,
|
|
78
|
+
tabsLabel: "Menu sections",
|
|
79
|
+
tabMenu: "Menu",
|
|
80
|
+
tabQuickAccess: "Quick access",
|
|
81
|
+
tabApps: "Apps",
|
|
59
82
|
apps: "Your apps",
|
|
60
83
|
found: (count) => `${count} found`,
|
|
61
84
|
pinTip: "Pin to quick access",
|
|
@@ -92,19 +115,19 @@ const GRID_COLUMNS = {
|
|
|
92
115
|
function ItemGrid({ children, columns, }) {
|
|
93
116
|
return (_jsx(Box, { "data-nav-grid": true, "data-nav-columns": columns, display: "grid", gridTemplateColumns: GRID_COLUMNS, gap: "1px 8px", alignItems: "start", children: children }));
|
|
94
117
|
}
|
|
95
|
-
function Pane({ children }) {
|
|
96
|
-
return (_jsx(Flex, { direction: "column", gap: "14px", flex: 1, minW: 0, px: "18px", pt: "12px", pb: "16px", overflowY: { base: "visible", lg: "auto" }, children: children }));
|
|
118
|
+
function Pane({ children, scrolls = false, }) {
|
|
119
|
+
return (_jsx(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 }));
|
|
97
120
|
}
|
|
98
|
-
function Rail({ label, children }) {
|
|
121
|
+
function Rail({ label, children, scrolls = false, }) {
|
|
99
122
|
const { colors } = useCustomTheme();
|
|
100
|
-
return (_jsx(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 }));
|
|
123
|
+
return (_jsx(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 }));
|
|
101
124
|
}
|
|
102
125
|
function SkeletonLayout({ label }) {
|
|
103
126
|
return (_jsxs(Flex, { role: "status", "aria-busy": "true", "aria-label": label, direction: { base: "column", lg: "row" }, flex: 1, minH: 0, children: [_jsx(Pane, { children: [0, 1, 2].map((section) => (_jsxs(Box, { children: [_jsx(Skeletons, { type: "skeleton", height: "0.75rem", width: "6rem" }), _jsx(ItemGrid, { children: [0, 1, 2, 3].map((row) => (_jsxs(Flex, { align: "center", gap: "10px", h: "34px", mt: 2, children: [_jsx(Skeletons, { type: "skeleton", height: "1rem", width: "1rem" }), _jsx(Skeletons, { type: "skeleton", height: "0.875rem", width: "8rem" })] }, row))) })] }, section))) }), _jsx(Rail, { label: label, children: [0, 1].map((group) => (_jsxs(Box, { children: [_jsx(Skeletons, { type: "skeleton", height: "0.75rem", width: "5rem" }), [0, 1, 2].map((row) => (_jsx(Skeletons, { type: "skeleton", height: "1.5rem", width: "100%", mt: 2 }, row)))] }, group))) })] }));
|
|
104
127
|
}
|
|
105
128
|
function SearchField({ inputRef, value, onChange, onKeyDown, labels, hint, listboxId, activeId, isExpanded, hasResults, }) {
|
|
106
129
|
const { colors } = useCustomTheme();
|
|
107
|
-
return (_jsxs(Flex, { flex: 1,
|
|
130
|
+
return (_jsxs(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: [_jsx(Box, { as: "span", color: colors.accentText, display: "inline-flex", flexShrink: 0, children: _jsx(SearchIcon, { size: 18, strokeWidth: 2.2, "aria-hidden": true }) }), _jsx(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: placeholderInk(colors), opacity: 1 } }), value ? (_jsx(Box, { as: "button", type: "button", "aria-label": labels.clearSearch, onClick: () => {
|
|
108
131
|
var _a;
|
|
109
132
|
onChange("");
|
|
110
133
|
(_a = inputRef.current) === null || _a === void 0 ? void 0 : _a.focus();
|
|
@@ -112,6 +135,7 @@ function SearchField({ inputRef, value, onChange, onKeyDown, labels, hint, listb
|
|
|
112
135
|
}
|
|
113
136
|
function SearchRow({ item, query, isActive, link, labels, optionId, isPinned, canPin, onPin, onOpen, onHover, }) {
|
|
114
137
|
const { colors } = useCustomTheme();
|
|
138
|
+
const isTouch = useTouchDensity();
|
|
115
139
|
const isScreen = item.kind === "screen";
|
|
116
140
|
const hasStar = Boolean(item.screenId) && canPin;
|
|
117
141
|
const base = item.badge ? 24 : 0;
|
|
@@ -122,7 +146,7 @@ function SearchRow({ item, query, isActive, link, labels, optionId, isPinned, ca
|
|
|
122
146
|
const titleRef = title.ref;
|
|
123
147
|
const contextRef = context.ref;
|
|
124
148
|
const tipLabel = isScreen || !item.context ? fullLabel : `${fullLabel} · ${item.context}`;
|
|
125
|
-
return (_jsx(ToolTip, { label: tipLabel, placement: "top-start", isDisabled: !title.isTruncated && !context.isTruncated, children: _jsxs(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: transitionFor(["background-color"]), onMouseMove: onHover, sx: isActive
|
|
149
|
+
return (_jsx(ToolTip, { label: tipLabel, placement: "top-start", isDisabled: !title.isTruncated && !context.isTruncated, children: _jsxs(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: transitionFor(["background-color"]), onMouseMove: onHover, sx: isActive
|
|
126
150
|
? { ...ROW_CONTAINER, ...activeRowSurface(colors) }
|
|
127
151
|
: ROW_CONTAINER, children: [_jsxs(Flex, { id: optionId, role: "option", "aria-selected": isActive, "data-active": isActive, flex: 1, minW: 0, align: "flex-start", gap: "10px", cursor: "pointer", onClick: () => {
|
|
128
152
|
if (!item.href)
|
|
@@ -131,20 +155,23 @@ function SearchRow({ item, query, isActive, link, labels, optionId, isPinned, ca
|
|
|
131
155
|
display: "none",
|
|
132
156
|
[WIDE_ROW]: { display: "inline-flex" },
|
|
133
157
|
"@media (hover: none)": { display: "none" },
|
|
134
|
-
}, children: _jsx(KbdShortcut, { shortcut: "enter", size: "xs" }) })) : null, _jsx(CountBadge, { value: item.badge }), hasStar ? (_jsx(PinToggle, { isPinned: isPinned, label: isPinned ? labels.unpin(item.label) : labels.pin(item.label), title: "", onToggle: onPin, tabIndex: -1 })) : null, item.href ? (_jsx(NewTabLink, { href: item.href, label: labels.openInNewTab(isScreen ? `${item.label} — ${item.context}` : item.label), linkAs: link.linkAs, tabIndex: -1 })) : null] }) }), hasStar ? (_jsx(RowTip, { text: isPinned ? labels.unpinTip : labels.pinTip })) : null] }) }));
|
|
158
|
+
}, children: _jsx(KbdShortcut, { shortcut: "enter", size: "xs" }) })) : null, _jsx(CountBadge, { value: item.badge }), hasStar ? (_jsx(PinToggle, { isPinned: isPinned, label: isPinned ? labels.unpin(item.label) : labels.pin(item.label), title: "", onToggle: onPin, tabIndex: -1 })) : null, item.href ? (_jsx(NewTabLink, { href: item.href, label: labels.openInNewTab(isScreen ? `${item.label} — ${item.context}` : item.label), tip: labels.newTabTip, linkAs: link.linkAs, tabIndex: -1 })) : null] }) }), hasStar ? (_jsx(RowTip, { text: isPinned ? labels.unpinTip : labels.pinTip })) : null] }) }));
|
|
135
159
|
}
|
|
136
160
|
/** Left-drawer navigation launcher: browse every module, pins, Frequent, Recent, search and (compact) the suite's apps. */
|
|
137
|
-
export default 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,
|
|
161
|
+
export default 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, }) {
|
|
138
162
|
const theme = useCustomTheme();
|
|
139
163
|
const { colors } = theme;
|
|
140
164
|
const inputRef = useRef(null);
|
|
141
165
|
const bodyRef = useRef(null);
|
|
142
166
|
const baseId = useId();
|
|
143
167
|
const isDesktop = useMatchMedia(MD_QUERY, true);
|
|
168
|
+
const isLg = useMatchMedia(LG_QUERY, true);
|
|
144
169
|
const isWide = useMatchMedia(XL_QUERY, false);
|
|
145
170
|
const columns = isWide ? 3 : isDesktop ? 2 : 1;
|
|
146
171
|
const [innerQuery, setInnerQuery] = useState("");
|
|
147
172
|
const [activeIndex, setActiveIndex] = useState(0);
|
|
173
|
+
const [tab, setTab] = useState("menu");
|
|
174
|
+
const navigating = useRef(false);
|
|
148
175
|
const labels = useMemo(() => ({ ...DEFAULT_LABELS, ...labelOverrides }), [labelOverrides]);
|
|
149
176
|
const isControlled = controlledQuery !== undefined;
|
|
150
177
|
const query = isControlled ? controlledQuery : innerQuery;
|
|
@@ -156,10 +183,16 @@ export default function NavLauncher({ isOpen, onClose, sections: rawSections, pi
|
|
|
156
183
|
const latest = useRef({ query, setQuery });
|
|
157
184
|
latest.current = { query, setQuery };
|
|
158
185
|
useEffect(() => {
|
|
159
|
-
if (
|
|
186
|
+
if (isOpen)
|
|
187
|
+
return;
|
|
188
|
+
setTab("menu");
|
|
189
|
+
if (latest.current.query !== "")
|
|
160
190
|
latest.current.setQuery("");
|
|
161
191
|
}, [isOpen]);
|
|
162
|
-
useEffect(() =>
|
|
192
|
+
useEffect(() => {
|
|
193
|
+
navigating.current = false;
|
|
194
|
+
setActiveIndex(0);
|
|
195
|
+
}, [query]);
|
|
163
196
|
const sections = useMemo(() => visibleSections(rawSections), [rawSections]);
|
|
164
197
|
const byId = useMemo(() => indexScreens(sections), [sections]);
|
|
165
198
|
const pinned = useMemo(() => new Set(pinnedIds), [pinnedIds]);
|
|
@@ -186,6 +219,26 @@ export default function NavLauncher({ isOpen, onClose, sections: rawSections, pi
|
|
|
186
219
|
const groupStarts = useMemo(() => groups.map((_, i) => groups.slice(0, i).reduce((n, g) => n + g.items.length, 0)), [groups]);
|
|
187
220
|
const isSearchView = query.trim().length > 0;
|
|
188
221
|
const active = Math.min(activeIndex, Math.max(results.length - 1, 0));
|
|
222
|
+
const appList = apps !== null && apps !== void 0 ? apps : NO_APPS;
|
|
223
|
+
const showApps = appList.some((app) => app.id !== currentAppId) &&
|
|
224
|
+
(appsVisibility === "always" || !isDesktop);
|
|
225
|
+
const railFrequent = frequentItems
|
|
226
|
+
.filter(({ screen }) => !pinned.has(screen.id))
|
|
227
|
+
.slice(0, frequentLimit);
|
|
228
|
+
const hasQuick = pinnedItems.length > 0 || railFrequent.length > 0 || recentItems.length > 0;
|
|
229
|
+
const tabs = useMemo(() => [
|
|
230
|
+
{ id: "menu", label: labels.tabMenu },
|
|
231
|
+
...(hasQuick
|
|
232
|
+
? [{ id: "quick", label: labels.tabQuickAccess }]
|
|
233
|
+
: []),
|
|
234
|
+
...(showApps ? [{ id: "apps", label: labels.tabApps }] : []),
|
|
235
|
+
], [hasQuick, showApps, labels]);
|
|
236
|
+
const isTabbed = !isLg && !isSearchView && !isLoading && tabs.length > 1;
|
|
237
|
+
const activeTab = tabs.some((item) => item.id === tab) ? tab : "menu";
|
|
238
|
+
const countText = isSearchView && results.length > 0
|
|
239
|
+
? labels.resultsCount(results.length)
|
|
240
|
+
: "";
|
|
241
|
+
const groupSizes = useMemo(() => groups.map((g) => g.items.length), [groups]);
|
|
189
242
|
const link = useMemo(() => ({
|
|
190
243
|
linkAs,
|
|
191
244
|
onPlainClick: (event) => {
|
|
@@ -205,14 +258,31 @@ export default function NavLauncher({ isOpen, onClose, sections: rawSections, pi
|
|
|
205
258
|
onClose();
|
|
206
259
|
onNavigate(item.href, { newTab });
|
|
207
260
|
}, [onClose, onNavigate]);
|
|
261
|
+
const focusField = (shouldSelect = false) => {
|
|
262
|
+
var _a, _b;
|
|
263
|
+
(_a = inputRef.current) === null || _a === void 0 ? void 0 : _a.focus();
|
|
264
|
+
if (shouldSelect)
|
|
265
|
+
(_b = inputRef.current) === null || _b === void 0 ? void 0 : _b.select();
|
|
266
|
+
};
|
|
267
|
+
const scrollHeight = (from) => {
|
|
268
|
+
var _a, _b, _c, _d, _e;
|
|
269
|
+
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;
|
|
270
|
+
};
|
|
271
|
+
const leaveTop = () => {
|
|
272
|
+
var _a;
|
|
273
|
+
const selected = isTabbed
|
|
274
|
+
? document.getElementById(tabId(baseId, activeTab))
|
|
275
|
+
: null;
|
|
276
|
+
(_a = (selected !== null && selected !== void 0 ? selected : inputRef.current)) === null || _a === void 0 ? void 0 : _a.focus();
|
|
277
|
+
};
|
|
208
278
|
const moveFocus = (delta) => {
|
|
209
|
-
var _a, _b
|
|
279
|
+
var _a, _b;
|
|
210
280
|
const items = Array.from((_b = (_a = bodyRef.current) === null || _a === void 0 ? void 0 : _a.querySelectorAll(ITEM_SELECTOR)) !== null && _b !== void 0 ? _b : []);
|
|
211
281
|
if (items.length === 0)
|
|
212
282
|
return;
|
|
213
283
|
const current = items.indexOf(document.activeElement);
|
|
214
284
|
if (typeof delta === "number" && current + delta < 0) {
|
|
215
|
-
(
|
|
285
|
+
leaveTop();
|
|
216
286
|
return;
|
|
217
287
|
}
|
|
218
288
|
const next = delta === "first"
|
|
@@ -222,6 +292,22 @@ export default function NavLauncher({ isOpen, onClose, sections: rawSections, pi
|
|
|
222
292
|
: Math.min(items.length - 1, current + delta);
|
|
223
293
|
items[next].focus();
|
|
224
294
|
};
|
|
295
|
+
const pageFocus = (direction) => {
|
|
296
|
+
var _a, _b;
|
|
297
|
+
const items = Array.from((_b = (_a = bodyRef.current) === null || _a === void 0 ? void 0 : _a.querySelectorAll(ITEM_SELECTOR)) !== null && _b !== void 0 ? _b : []);
|
|
298
|
+
const from = document.activeElement;
|
|
299
|
+
const current = items.indexOf(from);
|
|
300
|
+
if (items.length === 0 || current < 0)
|
|
301
|
+
return;
|
|
302
|
+
const height = scrollHeight(from);
|
|
303
|
+
const goal = from.getBoundingClientRect().top + direction * height * 0.85;
|
|
304
|
+
const byDistance = items
|
|
305
|
+
.filter((_, i) => (direction > 0 ? i > current : i < current))
|
|
306
|
+
.sort((a, b) => Math.abs(a.getBoundingClientRect().top - goal) -
|
|
307
|
+
Math.abs(b.getBoundingClientRect().top - goal));
|
|
308
|
+
const fallback = items[Math.min(items.length - 1, Math.max(0, current + direction * 3))];
|
|
309
|
+
(height > 0 && byDistance.length ? byDistance[0] : fallback).focus();
|
|
310
|
+
};
|
|
225
311
|
const onInputKeyDown = (event) => {
|
|
226
312
|
if (!isSearchView) {
|
|
227
313
|
if (event.key === "ArrowDown") {
|
|
@@ -230,20 +316,32 @@ export default function NavLauncher({ isOpen, onClose, sections: rawSections, pi
|
|
|
230
316
|
}
|
|
231
317
|
return;
|
|
232
318
|
}
|
|
233
|
-
if (results.length === 0)
|
|
319
|
+
if (results.length === 0 || event.nativeEvent.isComposing)
|
|
234
320
|
return;
|
|
235
|
-
if (event.key === "
|
|
236
|
-
event.preventDefault();
|
|
237
|
-
setActiveIndex((active + 1) % results.length);
|
|
238
|
-
}
|
|
239
|
-
else if (event.key === "ArrowUp") {
|
|
240
|
-
event.preventDefault();
|
|
241
|
-
setActiveIndex((active - 1 + results.length) % results.length);
|
|
242
|
-
}
|
|
243
|
-
else if (event.key === "Enter" && !event.nativeEvent.isComposing) {
|
|
321
|
+
if (event.key === "Enter") {
|
|
244
322
|
event.preventDefault();
|
|
245
323
|
open(results[active], event.metaKey || event.ctrlKey);
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
const hasModifier = event.altKey || event.metaKey || event.ctrlKey || event.shiftKey;
|
|
327
|
+
if (!RESULT_KEYS.includes(event.key) || hasModifier)
|
|
328
|
+
return;
|
|
329
|
+
const input = event.currentTarget;
|
|
330
|
+
const atEnd = input.selectionStart === input.value.length &&
|
|
331
|
+
input.selectionEnd === input.value.length;
|
|
332
|
+
const isSideways = event.key === "ArrowLeft" || event.key === "ArrowRight";
|
|
333
|
+
const isEdit = isSideways || event.key === "Home" || event.key === "End";
|
|
334
|
+
if (isEdit && !navigating.current) {
|
|
335
|
+
const entersGrid = event.key === "ArrowRight" && atEnd && columns > 1;
|
|
336
|
+
if (!entersGrid)
|
|
337
|
+
return;
|
|
246
338
|
}
|
|
339
|
+
if (isSideways && columns === 1)
|
|
340
|
+
return;
|
|
341
|
+
event.preventDefault();
|
|
342
|
+
navigating.current = true;
|
|
343
|
+
const pageRows = Math.max(3, Math.floor(scrollHeight(input) / ROW_HEIGHT) - 1);
|
|
344
|
+
setActiveIndex(moveResult(groupSizes, columns, active, event.key, pageRows));
|
|
247
345
|
};
|
|
248
346
|
const moveInGrid = (event, cell) => {
|
|
249
347
|
var _a, _b, _c, _d;
|
|
@@ -295,27 +393,39 @@ export default function NavLauncher({ isOpen, onClose, sections: rawSections, pi
|
|
|
295
393
|
return true;
|
|
296
394
|
};
|
|
297
395
|
const onBodyKeyDown = (event) => {
|
|
298
|
-
var _a
|
|
299
|
-
|
|
396
|
+
var _a;
|
|
397
|
+
const target = event.target;
|
|
398
|
+
if (target.closest(TEXT_TARGET))
|
|
300
399
|
return;
|
|
400
|
+
const inBody = Boolean((_a = bodyRef.current) === null || _a === void 0 ? void 0 : _a.contains(target));
|
|
301
401
|
const isArrow = event.key.startsWith("Arrow");
|
|
302
|
-
const cell =
|
|
303
|
-
if (
|
|
402
|
+
const cell = target.closest(CELL_SELECTOR);
|
|
403
|
+
if (inBody &&
|
|
404
|
+
isArrow &&
|
|
405
|
+
cell &&
|
|
406
|
+
!event.altKey &&
|
|
407
|
+
!event.metaKey &&
|
|
408
|
+
!event.ctrlKey) {
|
|
304
409
|
event.preventDefault();
|
|
305
410
|
moveInGrid(event, cell);
|
|
306
411
|
}
|
|
307
|
-
else if (
|
|
412
|
+
else if (inBody &&
|
|
413
|
+
(event.key === "ArrowDown" || event.key === "ArrowUp")) {
|
|
308
414
|
event.preventDefault();
|
|
309
415
|
moveFocus(event.key === "ArrowDown" ? 1 : -1);
|
|
310
416
|
}
|
|
311
|
-
else if (event.key === "Home" || event.key === "End") {
|
|
417
|
+
else if (inBody && (event.key === "Home" || event.key === "End")) {
|
|
312
418
|
event.preventDefault();
|
|
313
419
|
moveFocus(event.key === "Home" ? "first" : "last");
|
|
314
420
|
}
|
|
421
|
+
else if (inBody && (event.key === "PageDown" || event.key === "PageUp")) {
|
|
422
|
+
event.preventDefault();
|
|
423
|
+
pageFocus(event.key === "PageDown" ? 1 : -1);
|
|
424
|
+
}
|
|
315
425
|
else if (event.key === "Backspace" && query) {
|
|
316
426
|
event.preventDefault();
|
|
317
427
|
setQuery(query.slice(0, -1));
|
|
318
|
-
(
|
|
428
|
+
focusField();
|
|
319
429
|
}
|
|
320
430
|
else if (event.key.length === 1 &&
|
|
321
431
|
!event.metaKey &&
|
|
@@ -326,9 +436,46 @@ export default function NavLauncher({ isOpen, onClose, sections: rawSections, pi
|
|
|
326
436
|
return;
|
|
327
437
|
event.preventDefault();
|
|
328
438
|
setQuery(query + event.key);
|
|
329
|
-
(
|
|
439
|
+
focusField();
|
|
330
440
|
}
|
|
331
441
|
};
|
|
442
|
+
const onContentKeyDown = (event) => {
|
|
443
|
+
if (event.key === "Escape") {
|
|
444
|
+
event.stopPropagation();
|
|
445
|
+
if (event.nativeEvent.isComposing)
|
|
446
|
+
return;
|
|
447
|
+
event.preventDefault();
|
|
448
|
+
if (query) {
|
|
449
|
+
setQuery("");
|
|
450
|
+
focusField();
|
|
451
|
+
}
|
|
452
|
+
else {
|
|
453
|
+
onClose();
|
|
454
|
+
}
|
|
455
|
+
return;
|
|
456
|
+
}
|
|
457
|
+
const hasMod = event.metaKey || event.ctrlKey;
|
|
458
|
+
if (hasMod &&
|
|
459
|
+
!event.altKey &&
|
|
460
|
+
!event.shiftKey &&
|
|
461
|
+
event.key.toLowerCase() === "k") {
|
|
462
|
+
event.preventDefault();
|
|
463
|
+
focusField(true);
|
|
464
|
+
return;
|
|
465
|
+
}
|
|
466
|
+
const target = event.target;
|
|
467
|
+
if (target === inputRef.current)
|
|
468
|
+
return;
|
|
469
|
+
if (event.key === "/" &&
|
|
470
|
+
!hasMod &&
|
|
471
|
+
!event.altKey &&
|
|
472
|
+
!target.closest(TEXT_TARGET)) {
|
|
473
|
+
event.preventDefault();
|
|
474
|
+
focusField(true);
|
|
475
|
+
return;
|
|
476
|
+
}
|
|
477
|
+
onBodyKeyDown(event);
|
|
478
|
+
};
|
|
332
479
|
useEffect(() => {
|
|
333
480
|
var _a, _b, _c;
|
|
334
481
|
(_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" });
|
|
@@ -352,7 +499,6 @@ export default function NavLauncher({ isOpen, onClose, sections: rawSections, pi
|
|
|
352
499
|
document.removeEventListener("pointerdown", forget);
|
|
353
500
|
};
|
|
354
501
|
}, [isOpen]);
|
|
355
|
-
const appList = apps !== null && apps !== void 0 ? apps : NO_APPS;
|
|
356
502
|
useEffect(() => {
|
|
357
503
|
var _a, _b;
|
|
358
504
|
if (lastItemIndex.current < 0 || document.activeElement !== document.body)
|
|
@@ -363,23 +509,20 @@ export default function NavLauncher({ isOpen, onClose, sections: rawSections, pi
|
|
|
363
509
|
items[Math.min(lastItemIndex.current, items.length - 1)].focus();
|
|
364
510
|
}, [appList]);
|
|
365
511
|
const hues = useMemo(() => assignAppHues(appList, chartMode(colors)), [appList, colors]);
|
|
366
|
-
const showApps = appList.some((app) => app.id !== currentAppId) &&
|
|
367
|
-
(appsVisibility === "always" || !isDesktop);
|
|
368
512
|
const handleAppSelect = (app, event) => {
|
|
369
513
|
onAppSelect === null || onAppSelect === void 0 ? void 0 : onAppSelect(app, event);
|
|
370
514
|
if (!event.defaultPrevented)
|
|
371
515
|
onClose();
|
|
372
516
|
};
|
|
373
|
-
const
|
|
517
|
+
const renderApps = (hideHeading) => (_jsx(AppsSection, { apps: appList, hues: hues, currentAppId: currentAppId, heading: labels.apps, hideHeading: hideHeading, onSelect: handleAppSelect }));
|
|
374
518
|
const listboxId = `${baseId}-results`;
|
|
375
519
|
const optionId = (index) => `${baseId}-opt-${index}`;
|
|
376
520
|
const togglePinFor = (id) => onTogglePin ? () => onTogglePin(id) : undefined;
|
|
377
521
|
const hasResults = results.length > 0;
|
|
378
522
|
const trimmed = query.trim();
|
|
379
|
-
const
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
const rail = (_jsxs(Rail, { label: labels.quickAccess, children: [_jsxs(Flex, { as: "section", direction: "column", gap: "6px", children: [_jsx(SectionHeader, { label: labels.pinned, meta: `(${pinnedItems.length})`, isRail: true }), pinnedItems.length === 0 ? (_jsx(Text, { fontSize: "13px", color: colors.textMuted, px: "4px", children: labels.pinnedEmpty })) : (_jsx(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 }) => (_jsx(PinnedTile, { screen: screen, module: module, link: link, labels: labels, onTogglePin: togglePinFor(screen.id) }, screen.id))) }))] }), railFrequent.length ? (_jsxs(Flex, { as: "section", direction: "column", gap: "2px", children: [_jsx(SectionHeader, { label: labels.frequent, isRail: true, hasCount: false }), railFrequent.map(({ screen, module }) => (_jsx(RailRow, { screen: screen, module: module, link: link, isStrong: true, pinId: onTogglePin ? screen.id : undefined, trailing: _jsxs(_Fragment, { children: [onTogglePin ? (_jsx(PinTextButton, { label: labels.pin(screen.label), text: labels.pinShort, onToggle: () => onTogglePin(screen.id) })) : null, _jsx(NewTabLink, { href: screen.href, label: labels.openInNewTab(screen.label), linkAs: link.linkAs })] }) }, screen.id)))] })) : null, recentItems.length ? (_jsxs(Flex, { as: "section", direction: "column", gap: "2px", children: [_jsx(SectionHeader, { label: labels.recent, isRail: true, hasCount: false }), recentItems.map(({ screen, module }) => (_jsx(RailRow, { screen: screen, module: module, link: link, trailing: _jsx(NewTabLink, { href: screen.href, label: labels.openInNewTab(screen.label), linkAs: link.linkAs }) }, screen.id)))] })) : null, appsBlock] }));
|
|
523
|
+
const pinnedColumns = !isLg && isDesktop ? 3 : PINNED_COLUMNS;
|
|
524
|
+
const quickSections = (_jsxs(_Fragment, { children: [_jsxs(Flex, { as: "section", direction: "column", gap: "6px", children: [_jsx(SectionHeader, { label: labels.pinned, meta: `(${pinnedItems.length})`, isRail: true }), pinnedItems.length === 0 ? (_jsx(Text, { fontSize: "13px", color: colors.textMuted, px: "4px", children: labels.pinnedEmpty })) : (_jsx(Box, { "data-nav-grid": true, "data-nav-columns": pinnedColumns, display: "grid", gridTemplateColumns: `repeat(${pinnedColumns}, minmax(0, 1fr))`, gap: "6px", children: pinnedItems.map(({ screen, module }) => (_jsx(PinnedTile, { screen: screen, module: module, link: link, labels: labels, onTogglePin: togglePinFor(screen.id) }, screen.id))) }))] }), railFrequent.length ? (_jsxs(Flex, { as: "section", direction: "column", gap: "2px", children: [_jsx(SectionHeader, { label: labels.frequent, isRail: true, hasCount: false }), railFrequent.map(({ screen, module }) => (_jsx(RailRow, { screen: screen, module: module, link: link, isStrong: true, pinId: onTogglePin ? screen.id : undefined, trailing: _jsxs(_Fragment, { children: [onTogglePin ? (_jsx(PinTextButton, { label: labels.pin(screen.label), text: labels.pinShort, onToggle: () => onTogglePin(screen.id) })) : null, _jsx(NewTabLink, { href: screen.href, label: labels.openInNewTab(screen.label), tip: labels.newTabTip, linkAs: link.linkAs })] }) }, screen.id)))] })) : null, recentItems.length ? (_jsxs(Flex, { as: "section", direction: "column", gap: "2px", children: [_jsx(SectionHeader, { label: labels.recent, isRail: true, hasCount: false }), recentItems.map(({ screen, module }) => (_jsx(RailRow, { screen: screen, module: module, link: link, trailing: _jsx(NewTabLink, { href: screen.href, label: labels.openInNewTab(screen.label), tip: labels.newTabTip, linkAs: link.linkAs }) }, screen.id)))] })) : null] }));
|
|
525
|
+
const rail = (_jsxs(Rail, { label: labels.quickAccess, scrolls: isLg, children: [quickSections, showApps ? renderApps(false) : null] }));
|
|
383
526
|
const browsePane = sections.length === 0 ? (_jsx(EmptyState, { title: labels.empty, titleAs: "h3", size: "sm" })) : (sections.map((section) => (_jsxs(Flex, { as: "section", "aria-label": section.label, direction: "column", gap: "3px", children: [_jsx(SectionHeader, { label: section.label, meta: `(${section.modules.length})` }), _jsx(ItemGrid, { columns: columns, children: section.modules.map((module) => {
|
|
384
527
|
const pinId = module.screens[0].id;
|
|
385
528
|
return (_jsx(ModuleRow, { module: module, keyScreens: module.screens
|
|
@@ -398,16 +541,23 @@ export default function NavLauncher({ isOpen, onClose, sections: rawSections, pi
|
|
|
398
541
|
const index = groupStarts[groupIndex] + itemIndex;
|
|
399
542
|
return (_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));
|
|
400
543
|
}) })] }, group.id))) })) : (noResults);
|
|
401
|
-
const
|
|
402
|
-
const
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
544
|
+
const stacked = (_jsxs(_Fragment, { children: [_jsx(Pane, { scrolls: isLg, children: isSearchView ? searchPane : browsePane }), rail] }));
|
|
545
|
+
const body = isLoading ? _jsx(SkeletonLayout, { label: labels.loading }) : stacked;
|
|
546
|
+
const tabPanels = {
|
|
547
|
+
menu: _jsx(Pane, { children: browsePane }),
|
|
548
|
+
quick: (_jsx(Flex, { direction: "column", gap: "16px", px: "18px", pt: "14px", pb: "16px", children: quickSections })),
|
|
549
|
+
apps: (_jsx(Box, { px: "14px", pt: "10px", pb: "16px", children: renderApps(true) })),
|
|
550
|
+
};
|
|
551
|
+
const compactBody = (_jsx(Flex, { "data-scroll-region": true, role: isTabbed ? "tabpanel" : undefined, id: isTabbed ? panelId(baseId) : undefined, "aria-labelledby": isTabbed ? tabId(baseId, activeTab) : undefined, direction: "column", flex: 1, minH: 0, overflowY: "auto", children: isTabbed ? tabPanels[activeTab] : body }));
|
|
552
|
+
return (_jsx(TouchDensity, { value: !isLg, children: _jsxs(Drawer, { isOpen: isOpen, onClose: onClose, placement: "left", size: "full", initialFocusRef: inputRef, hideCloseButton: true, closeOnEsc: false, contentProps: {
|
|
553
|
+
maxW: { base: "100vw", lg: `min(${maxWidth}, calc(100vw - 56px))` },
|
|
554
|
+
h: { base: "100%", lg: "auto" },
|
|
555
|
+
maxH: "100%",
|
|
556
|
+
bg: colors.surfaceRaised,
|
|
557
|
+
borderBottomRightRadius: { base: 0, lg: "16px" },
|
|
558
|
+
boxShadow: colors.boxShadow.default,
|
|
559
|
+
overflow: "hidden",
|
|
560
|
+
onKeyDown: onContentKeyDown,
|
|
561
|
+
...{ "data-testid": testId },
|
|
562
|
+
}, children: [_jsx(DrawerHeader, { p: 0, flexShrink: 0, children: _jsxs(Flex, { align: "center", gap: { base: "8px", md: "12px" }, minH: { base: "60px", md: "68px" }, pl: { base: "12px", md: "18px" }, pr: { base: "8px", md: "12px" }, children: [_jsx(VisuallyHidden, { as: "h2", children: labels.title }), _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 }), _jsx(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: transitionFor(["width", "opacity"]), sx: { fontVariantNumeric: "tabular-nums", ...reducedMotionSx() }, children: countText }), headerActions ? (_jsx(Flex, { align: "center", gap: "4px", flexShrink: 0, children: headerActions })) : null, _jsx(IconButton, { "aria-label": labels.close, icon: _jsx(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 })] }) }), _jsx(DrawerBody, { p: 0, display: "flex", flexDirection: "column", overflowY: "hidden", minH: 0, children: _jsxs(Flex, { ref: bodyRef, direction: "column", flex: 1, minH: 0, minW: 0, children: [_jsx(VisuallyHidden, { "data-testid": "launcher-live", "aria-live": "polite", "aria-atomic": "true", children: countText }), isTabbed ? (_jsx(LauncherTabs, { tabs: tabs, selected: activeTab, onSelect: setTab, idBase: baseId, label: labels.tabsLabel, onLeaveUp: () => focusField(), onLeaveDown: () => moveFocus("first") })) : null, isLg ? (_jsx(Flex, { direction: "row", flex: 1, minH: 0, minW: 0, children: body })) : (compactBody)] }) }), _jsxs(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: [_jsxs(Flex, { gap: "6px 14px", align: "center", wrap: "wrap", "aria-hidden": true, sx: { "@media (hover: none)": { display: "none" } }, children: [_jsxs(Flex, { as: "span", align: "center", gap: "5px", children: [_jsx(Flex, { as: "span", gap: "2px", children: ARROW_KEYS.map((arrow) => (_jsx(Kbd, { size: "xs", children: arrow }, arrow))) }), labels.hintMove] }), _jsxs(Flex, { as: "span", align: "center", gap: "5px", children: [_jsx(KbdShortcut, { shortcut: "enter", size: "xs" }), labels.hintOpen] }), _jsxs(Flex, { as: "span", align: "center", gap: "5px", children: [modKeyLabel ? (_jsxs(Flex, { as: "span", gap: "4px", children: [_jsx(Kbd, { size: "xs", children: modKeyLabel }), _jsx(Kbd, { size: "xs", children: ENTER_KEY })] })) : (_jsx(KbdShortcut, { shortcut: "mod+enter", size: "xs" })), labels.hintNewTab] }), _jsxs(Flex, { as: "span", align: "center", gap: "5px", children: [_jsx(Kbd, { size: "xs", children: modKeyLabel !== null && modKeyLabel !== void 0 ? modKeyLabel : MOD_KEY }), labels.hintClickNewTab] }), onTogglePin ? (_jsxs(Flex, { as: "span", align: "center", gap: "5px", children: [_jsx(Kbd, { size: "xs", children: "P" }), labels.hintPin] })) : null, _jsxs(Flex, { as: "span", align: "center", gap: "5px", children: [_jsx(KbdShortcut, { shortcut: "esc", size: "xs" }), labels.hintClose] })] }), _jsx(Text, { as: "span", ml: "auto", children: labels.footerNote })] })] }) }));
|
|
413
563
|
}
|
|
@@ -3,9 +3,10 @@ import { Box } from "@chakra-ui/react";
|
|
|
3
3
|
import { ArrowUpRight } from "lucide-react";
|
|
4
4
|
import { transitionFor } from "../../Theme/tokens/primitives/motion.js";
|
|
5
5
|
import { useCustomTheme } from "../../Theme/useCustomTheme.js";
|
|
6
|
+
import ToolTip from "../ToolTip/ToolTip.js";
|
|
6
7
|
import { revealStyles } from "./revealStyles.js";
|
|
7
|
-
/** "Open in a new tab" anchor; visible on row hover, focus-within or its own focus
|
|
8
|
-
export default function NewTabLink({ href, label, linkAs = "a", tabIndex }) {
|
|
8
|
+
/** "Open in a new tab" anchor with a tooltip; visible on row hover, keyboard-active or focus-within, or its own focus. */
|
|
9
|
+
export default function NewTabLink({ href, label, tip, linkAs = "a", tabIndex, }) {
|
|
9
10
|
const { colors } = useCustomTheme();
|
|
10
|
-
return (_jsx(Box, { as: linkAs, href: href, target: "_blank", rel: "noopener noreferrer", "aria-label": label,
|
|
11
|
+
return (_jsx(ToolTip, { label: tip !== null && tip !== void 0 ? tip : label, placement: "bottom", children: _jsx(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: transitionFor(["opacity", "background-color"]), sx: revealStyles(colors), _hover: { bg: colors.backgroundColor.subtle }, children: _jsx(ArrowUpRight, { size: 13, "aria-hidden": true }) }) }));
|
|
11
12
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { Box, Flex, Heading, Text } from "@chakra-ui/react";
|
|
3
3
|
import { Lock, Star } from "lucide-react";
|
|
4
|
-
import { Fragment, } from "react";
|
|
4
|
+
import { createContext, Fragment, useContext, } from "react";
|
|
5
5
|
import { FOCUS_VISIBLE_OVER_STATE, focusVisibleStyles, } from "../../Theme/chakra/focusRing.styles.js";
|
|
6
6
|
import { resolveFilledSurface } from "../../Theme/tokens/builders/filledSurface.js";
|
|
7
7
|
import { AA_NON_TEXT, liftToContrast, } from "../../Theme/tokens/builders/outlineRung.js";
|
|
@@ -15,6 +15,9 @@ import NewTabLink from "./NewTabLink.js";
|
|
|
15
15
|
import PinToggle from "./PinToggle.js";
|
|
16
16
|
import { revealStyles } from "./revealStyles.js";
|
|
17
17
|
export const ITEM_SELECTOR = "[data-launcher-item]";
|
|
18
|
+
const TouchDensityContext = createContext(false);
|
|
19
|
+
export const TouchDensity = TouchDensityContext.Provider;
|
|
20
|
+
export const useTouchDensity = () => useContext(TouchDensityContext);
|
|
18
21
|
export const isPlainClick = (event) => event.button === 0 &&
|
|
19
22
|
!event.metaKey &&
|
|
20
23
|
!event.ctrlKey &&
|
|
@@ -113,6 +116,7 @@ export function Highlight({ text, query }) {
|
|
|
113
116
|
}
|
|
114
117
|
export function RowActions({ children }) {
|
|
115
118
|
const { colors } = useCustomTheme();
|
|
119
|
+
const isTouch = useTouchDensity();
|
|
116
120
|
const revealed = REVEALED.replace(/ &/g, " > &");
|
|
117
121
|
return (_jsx(Flex, { sx: {
|
|
118
122
|
[NARROW_ROW]: {
|
|
@@ -121,7 +125,7 @@ export function RowActions({ children }) {
|
|
|
121
125
|
borderRadius: "6px",
|
|
122
126
|
},
|
|
123
127
|
},
|
|
124
|
-
}, position: "absolute", top: "7px", right: "4px", h: "19px", align: "center", gap: "2px", zIndex: 1, children: children }));
|
|
128
|
+
}, position: "absolute", top: isTouch ? "12px" : "7px", right: "4px", h: "19px", align: "center", gap: "2px", zIndex: 1, children: children }));
|
|
125
129
|
}
|
|
126
130
|
export function RowTip({ text }) {
|
|
127
131
|
const { colors } = useCustomTheme();
|
|
@@ -132,19 +136,21 @@ export function ModuleRow({ module, keyScreens, isPinned, onTogglePin, pinId, la
|
|
|
132
136
|
const hover = useRowHover();
|
|
133
137
|
const focus = useRowFocus();
|
|
134
138
|
const ring = focusVisibleStyles(colors);
|
|
139
|
+
const isTouch = useTouchDensity();
|
|
135
140
|
const base = typeof module.badge === "number" || module.badge ? 24 : 0;
|
|
136
141
|
const open = base + (onTogglePin ? 46 : 24);
|
|
137
|
-
return (_jsxs(Flex, { "data-group": true, "data-nav-cell": true, position: "relative", align: "flex-start", gap: "10px", minH: "34px", pl: "8px", pr: "4px", pt: "7px", pb: "5px", borderRadius: "8px", transition: transitionFor(["background-color"]), _hover: hover._hover, _focusWithin: hover._focusWithin, sx: { ...focus.sx, ...ROW_CONTAINER }, children: [_jsx(RowIcon, { icon: module.icon, size: 16, mt: "2px" }), _jsxs(Flex, { direction: "column", flex: 1, minW: 0, children: [_jsxs(Flex, { align: "center", gap: "6px", h: "19px", minW: 0, sx: titleInset(base, open), children: [_jsx(OverflowToolTip, { label: module.label, placement: "top-start", children: _jsx(Box, { as: link.linkAs, href: module.href, "data-launcher-item": true, ...focus.primary, "data-pin-id": onTogglePin ? pinId : undefined, onClick: link.onPlainClick, minW: 0, fontSize: "13.5px", fontWeight: 500, color: colors.text[900], noOfLines: 1, borderRadius: "sm", sx: stretchedLink("8px"), children: module.label }) }), module.isNew ? _jsx(NewTag, { label: labels.newBadge }) : null] }), keyScreens.length ? (_jsx(OverflowToolTip, { label: keyScreens.map((screen) => screen.label).join(" · "), placement: "bottom-start", children: _jsx(Box, { role: "group", "aria-label": `${module.label} ${labels.screens}`, position: "relative", zIndex: 1, w: "fit-content", maxW: "100%", fontSize: "11.5px", lineHeight: "15px", color: colors.textMuted, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis", children: keyScreens.map((screen, i) => (_jsxs(Fragment, { children: [i > 0 ? (_jsx(Box, { as: "span", "aria-hidden": true, mx: "5px", opacity: 0.7, children: "\u00B7" })) : null, _jsx(Box, { as: link.linkAs, href: screen.href, "data-launcher-item": true, onClick: link.onPlainClick, color: "inherit", borderRadius: "sm", position: "relative", zIndex: 1, _hover: {
|
|
142
|
+
return (_jsxs(Flex, { "data-group": true, "data-nav-cell": true, 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: transitionFor(["background-color"]), _hover: hover._hover, _focusWithin: hover._focusWithin, sx: { ...focus.sx, ...ROW_CONTAINER }, children: [_jsx(RowIcon, { icon: module.icon, size: 16, mt: "2px" }), _jsxs(Flex, { direction: "column", flex: 1, minW: 0, children: [_jsxs(Flex, { align: "center", gap: "6px", h: "19px", minW: 0, sx: titleInset(base, open), children: [_jsx(OverflowToolTip, { label: module.label, placement: "top-start", children: _jsx(Box, { as: link.linkAs, href: module.href, "data-launcher-item": true, ...focus.primary, "data-pin-id": onTogglePin ? pinId : undefined, onClick: link.onPlainClick, minW: 0, fontSize: "13.5px", fontWeight: 500, color: colors.text[900], noOfLines: 1, borderRadius: "sm", sx: stretchedLink("8px"), children: module.label }) }), module.isNew ? _jsx(NewTag, { label: labels.newBadge }) : null] }), keyScreens.length ? (_jsx(OverflowToolTip, { label: keyScreens.map((screen) => screen.label).join(" · "), placement: "bottom-start", children: _jsx(Box, { role: "group", "aria-label": `${module.label} ${labels.screens}`, position: "relative", zIndex: 1, w: "fit-content", maxW: "100%", fontSize: "11.5px", lineHeight: "15px", color: colors.textMuted, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis", children: keyScreens.map((screen, i) => (_jsxs(Fragment, { children: [i > 0 ? (_jsx(Box, { as: "span", "aria-hidden": true, mx: "5px", opacity: 0.7, children: "\u00B7" })) : null, _jsx(Box, { as: link.linkAs, href: screen.href, "data-launcher-item": true, onClick: link.onPlainClick, color: "inherit", borderRadius: "sm", position: "relative", zIndex: 1, _hover: {
|
|
138
143
|
textDecoration: "underline",
|
|
139
144
|
textUnderlineOffset: "2px",
|
|
140
|
-
}, sx: ring, children: screen.label })] }, screen.id))) }) })) : null] }), _jsxs(RowActions, { children: [_jsx(CountBadge, { value: module.badge }), onTogglePin ? (_jsx(PinToggle, { isPinned: isPinned, label: isPinned ? labels.unpin(module.label) : labels.pin(module.label), title: "", onToggle: onTogglePin })) : null, _jsx(NewTabLink, { href: module.href, label: labels.openInNewTab(module.label), linkAs: link.linkAs })] }), onTogglePin ? (_jsx(RowTip, { text: isPinned ? labels.unpinTip : labels.pinTip })) : null] }));
|
|
145
|
+
}, sx: ring, children: screen.label })] }, screen.id))) }) })) : null] }), _jsxs(RowActions, { children: [_jsx(CountBadge, { value: module.badge }), onTogglePin ? (_jsx(PinToggle, { isPinned: isPinned, label: isPinned ? labels.unpin(module.label) : labels.pin(module.label), title: "", onToggle: onTogglePin })) : null, _jsx(NewTabLink, { href: module.href, label: labels.openInNewTab(module.label), tip: labels.newTabTip, linkAs: link.linkAs })] }), onTogglePin ? (_jsx(RowTip, { text: isPinned ? labels.unpinTip : labels.pinTip })) : null] }));
|
|
141
146
|
}
|
|
142
147
|
export function RailRow({ screen, module, link, isStrong = false, trailing, pinId, }) {
|
|
143
148
|
var _a;
|
|
144
149
|
const { colors } = useCustomTheme();
|
|
145
150
|
const hover = useRowHover();
|
|
146
151
|
const focus = useRowFocus();
|
|
147
|
-
|
|
152
|
+
const isTouch = useTouchDensity();
|
|
153
|
+
return (_jsxs(Flex, { "data-group": true, position: "relative", align: "center", gap: "10px", h: isTouch ? "44px" : "32px", pl: "8px", pr: "4px", borderRadius: "8px", transition: transitionFor(["background-color"]), _hover: hover._hover, _focusWithin: hover._focusWithin, sx: focus.sx, children: [_jsx(RowIcon, { icon: (_a = screen.icon) !== null && _a !== void 0 ? _a : module.icon, size: 15 }), _jsx(OverflowToolTip, { label: screen.label, placement: "top-start", children: _jsx(Box, { as: link.linkAs, href: screen.href, "data-launcher-item": true, ...focus.primary, "data-pin-id": pinId, onClick: link.onPlainClick, flex: 1, minW: 0, fontSize: "13px", fontWeight: isStrong ? 500 : 400, color: colors.text[900], noOfLines: 1, borderRadius: "sm", sx: stretchedLink("8px"), children: screen.label }) }), _jsx(Flex, { position: "relative", zIndex: 1, align: "center", gap: "2px", children: trailing })] }));
|
|
148
154
|
}
|
|
149
155
|
export function PinTextButton({ label, text, onToggle }) {
|
|
150
156
|
const { colors } = useCustomTheme();
|
|
@@ -159,13 +165,14 @@ export function PinnedTile({ screen, module, link, labels, onTogglePin, }) {
|
|
|
159
165
|
const { colors } = useCustomTheme();
|
|
160
166
|
const Icon = (_a = screen.icon) !== null && _a !== void 0 ? _a : module.icon;
|
|
161
167
|
const focus = useRowFocus(true);
|
|
162
|
-
|
|
168
|
+
const isTouch = useTouchDensity();
|
|
169
|
+
return (_jsxs(Flex, { "data-group": true, "data-nav-cell": true, position: "relative", align: "center", gap: "8px", h: isTouch ? "44px" : "36px", pl: "9px", pr: "4px", borderRadius: "9px", borderWidth: "1px", borderColor: colors.boxborder[200], bg: colors.surfaceRaised, transition: transitionFor(["border-color", "background-color"]), _hover: { borderColor: colors.gray[300] }, sx: focus.sx, children: [Icon ? (_jsx(Box, { as: "span", display: "inline-flex", flexShrink: 0, color: colors.accentText, children: _jsx(Icon, { size: 15, strokeWidth: 1.8, "aria-hidden": true }) })) : null, _jsx(OverflowToolTip, { label: screen.label, placement: "top-start", children: _jsx(Box, { as: link.linkAs, href: screen.href, "data-launcher-item": true, ...focus.primary, "data-pin-id": onTogglePin ? screen.id : undefined, onClick: link.onPlainClick, flex: 1, minW: 0, fontSize: "13px", fontWeight: 500, color: colors.text[900], noOfLines: 1, borderRadius: "sm", sx: stretchedLink("9px"), children: screen.label }) }), _jsx(CountBadge, { value: screen.badge, size: "tile" }), onTogglePin ? (_jsx(Box, { position: "relative", zIndex: 1, flexShrink: 0, w: "0", overflow: "hidden", transition: transitionFor(["width"]), _groupHover: { w: "22px" }, _groupFocusWithin: { w: "22px" }, children: _jsx(PinToggle, { isPinned: true, size: 12, label: labels.unpin(screen.label), title: labels.unpinTip, onToggle: onTogglePin }) })) : null] }));
|
|
163
170
|
}
|
|
164
|
-
export function AppsSection({ apps, hues, currentAppId, heading, onSelect, }) {
|
|
171
|
+
export function AppsSection({ apps, hues, currentAppId, heading, hideHeading = false, onSelect, }) {
|
|
165
172
|
const { colors } = useCustomTheme();
|
|
166
173
|
const rows = listRowBackgrounds(colors);
|
|
167
174
|
const rowRing = activeRowSurface(colors);
|
|
168
|
-
return (_jsxs(Box, { children: [_jsx(SectionHeader, { label: heading, isRail: true, hasCount: false }), _jsx(Box, { as: "ul", listStyleType: "none", m: 0, p: 0, "aria-label": heading, children: apps.map((app, index) => {
|
|
175
|
+
return (_jsxs(Box, { children: [hideHeading ? null : _jsx(SectionHeader, { label: heading, isRail: true, hasCount: false }), _jsx(Box, { as: "ul", listStyleType: "none", m: 0, p: 0, "aria-label": heading, children: apps.map((app, index) => {
|
|
169
176
|
var _a;
|
|
170
177
|
const isCurrent = app.id === currentAppId;
|
|
171
178
|
const interactive = !isCurrent && !app.locked;
|