entangle-ui 0.8.2 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +77 -0
- package/dist/esm/assets/src/components/controls/Combobox/Combobox.css.ts.vanilla-B7B5ttkq.css +210 -0
- package/dist/esm/assets/src/components/controls/FileUploader/FileUploader.css.ts.vanilla-T4nRiI7s.css +194 -0
- package/dist/esm/assets/src/components/controls/MultiSelect/MultiSelect.css.ts.vanilla-CdYayqaF.css +311 -0
- package/dist/esm/assets/src/components/controls/TagInput/TagInput.css.ts.vanilla-hnkMOPp1.css +141 -0
- package/dist/esm/assets/src/components/data/DataTable/DataTable.css.ts.vanilla-CmRgtjIW.css +231 -0
- package/dist/esm/assets/src/components/feedback/Alert/{Alert.css.ts.vanilla-CRAI-xHx.css → Alert.css.ts.vanilla-CfCDsIEg.css} +2 -0
- package/dist/esm/assets/src/components/feedback/CommandPalette/CommandPalette.css.ts.vanilla-DGdrLKYZ.css +160 -0
- package/dist/esm/assets/src/components/feedback/Drawer/Drawer.css.ts.vanilla-CLPTOUrA.css +247 -0
- package/dist/esm/assets/src/components/feedback/Skeleton/SkeletonLayout.css.ts.vanilla-Db7bpqiI.css +75 -0
- package/dist/esm/assets/src/components/feedback/Stat/Stat.css.ts.vanilla-GBk3JAMB.css +69 -0
- package/dist/esm/assets/src/components/layout/Card/Card.css.ts.vanilla-Ducn1gUX.css +124 -0
- package/dist/esm/assets/src/components/navigation/Pagination/Pagination.css.ts.vanilla-CmlFyyjh.css +103 -0
- package/dist/esm/assets/src/components/primitives/HoverCard/HoverCard.css.ts.vanilla-BYT0qbLp.css +41 -0
- package/dist/esm/components/Icons/CloudUploadIcon.js +24 -0
- package/dist/esm/components/Icons/CloudUploadIcon.js.map +1 -0
- package/dist/esm/components/Icons/ExternalLinkIcon.js +26 -0
- package/dist/esm/components/Icons/ExternalLinkIcon.js.map +1 -0
- package/dist/esm/components/Icons/FirstIcon.js +23 -0
- package/dist/esm/components/Icons/FirstIcon.js.map +1 -0
- package/dist/esm/components/Icons/LastIcon.js +23 -0
- package/dist/esm/components/Icons/LastIcon.js.map +1 -0
- package/dist/esm/components/Icons/UnlinkIcon.js +26 -0
- package/dist/esm/components/Icons/UnlinkIcon.js.map +1 -0
- package/dist/esm/components/controls/Combobox/Combobox.css.js +20 -0
- package/dist/esm/components/controls/Combobox/Combobox.css.js.map +1 -0
- package/dist/esm/components/controls/Combobox/Combobox.js +354 -0
- package/dist/esm/components/controls/Combobox/Combobox.js.map +1 -0
- package/dist/esm/components/controls/FileUploader/FileUploader.css.js +20 -0
- package/dist/esm/components/controls/FileUploader/FileUploader.css.js.map +1 -0
- package/dist/esm/components/controls/FileUploader/FileUploader.js +264 -0
- package/dist/esm/components/controls/FileUploader/FileUploader.js.map +1 -0
- package/dist/esm/components/controls/MultiSelect/MultiSelect.css.js +23 -0
- package/dist/esm/components/controls/MultiSelect/MultiSelect.css.js.map +1 -0
- package/dist/esm/components/controls/MultiSelect/MultiSelect.js +269 -0
- package/dist/esm/components/controls/MultiSelect/MultiSelect.js.map +1 -0
- package/dist/esm/components/controls/Select/Select.js +5 -4
- package/dist/esm/components/controls/Select/Select.js.map +1 -1
- package/dist/esm/components/controls/TagInput/TagInput.css.js +12 -0
- package/dist/esm/components/controls/TagInput/TagInput.css.js.map +1 -0
- package/dist/esm/components/controls/TagInput/TagInput.js +189 -0
- package/dist/esm/components/controls/TagInput/TagInput.js.map +1 -0
- package/dist/esm/components/controls/TreeView/TreeNode.js +87 -1
- package/dist/esm/components/controls/TreeView/TreeNode.js.map +1 -1
- package/dist/esm/components/controls/VectorInput/VectorInput.js +87 -4
- package/dist/esm/components/controls/VectorInput/VectorInput.js.map +1 -1
- package/dist/esm/components/data/DataTable/DataTable.css.js +25 -0
- package/dist/esm/components/data/DataTable/DataTable.css.js.map +1 -0
- package/dist/esm/components/data/DataTable/DataTable.js +502 -0
- package/dist/esm/components/data/DataTable/DataTable.js.map +1 -0
- package/dist/esm/components/editor/ChatPanel/ChatCodeBlock.js +87 -5
- package/dist/esm/components/editor/ChatPanel/ChatCodeBlock.js.map +1 -1
- package/dist/esm/components/editor/ChatPanel/ChatInput.js +87 -5
- package/dist/esm/components/editor/ChatPanel/ChatInput.js.map +1 -1
- package/dist/esm/components/editor/ChatPanel/ChatMessage.js +87 -2
- package/dist/esm/components/editor/ChatPanel/ChatMessage.js.map +1 -1
- package/dist/esm/components/editor/PropertyInspector/PropertyRow.js +87 -3
- package/dist/esm/components/editor/PropertyInspector/PropertyRow.js.map +1 -1
- package/dist/esm/components/editor/PropertyInspector/PropertySection.js +87 -3
- package/dist/esm/components/editor/PropertyInspector/PropertySection.js.map +1 -1
- package/dist/esm/components/feedback/Alert/Alert.css.js +1 -1
- package/dist/esm/components/feedback/Alert/Alert.js +3 -2
- package/dist/esm/components/feedback/Alert/Alert.js.map +1 -1
- package/dist/esm/components/feedback/CommandPalette/CommandPalette.css.js +20 -0
- package/dist/esm/components/feedback/CommandPalette/CommandPalette.css.js.map +1 -0
- package/dist/esm/components/feedback/CommandPalette/CommandPalette.js +261 -0
- package/dist/esm/components/feedback/CommandPalette/CommandPalette.js.map +1 -0
- package/dist/esm/components/feedback/CommandPalette/fuzzySearch.js +86 -0
- package/dist/esm/components/feedback/CommandPalette/fuzzySearch.js.map +1 -0
- package/dist/esm/components/feedback/CommandPalette/useRecentItems.js +63 -0
- package/dist/esm/components/feedback/CommandPalette/useRecentItems.js.map +1 -0
- package/dist/esm/components/feedback/Dialog/DialogHeader.js +2 -1
- package/dist/esm/components/feedback/Dialog/DialogHeader.js.map +1 -1
- package/dist/esm/components/feedback/Drawer/Drawer.css.js +17 -0
- package/dist/esm/components/feedback/Drawer/Drawer.css.js.map +1 -0
- package/dist/esm/components/feedback/Drawer/Drawer.js +120 -0
- package/dist/esm/components/feedback/Drawer/Drawer.js.map +1 -0
- package/dist/esm/components/feedback/Drawer/useDrawerAnimation.js +74 -0
- package/dist/esm/components/feedback/Drawer/useDrawerAnimation.js.map +1 -0
- package/dist/esm/components/feedback/Skeleton/SkeletonLayout.css.js +18 -0
- package/dist/esm/components/feedback/Skeleton/SkeletonLayout.css.js.map +1 -0
- package/dist/esm/components/feedback/Skeleton/SkeletonLayout.js +95 -0
- package/dist/esm/components/feedback/Skeleton/SkeletonLayout.js.map +1 -0
- package/dist/esm/components/feedback/Stat/Stat.css.js +15 -0
- package/dist/esm/components/feedback/Stat/Stat.css.js.map +1 -0
- package/dist/esm/components/feedback/Stat/Stat.js +55 -0
- package/dist/esm/components/feedback/Stat/Stat.js.map +1 -0
- package/dist/esm/components/feedback/Toast/ToastItem.js +12 -15
- package/dist/esm/components/feedback/Toast/ToastItem.js.map +1 -1
- package/dist/esm/components/layout/Accordion/Accordion.js +2 -1
- package/dist/esm/components/layout/Accordion/Accordion.js.map +1 -1
- package/dist/esm/components/layout/Accordion/AccordionTrigger.js +2 -3
- package/dist/esm/components/layout/Accordion/AccordionTrigger.js.map +1 -1
- package/dist/esm/components/layout/Card/Card.css.js +18 -0
- package/dist/esm/components/layout/Card/Card.css.js.map +1 -0
- package/dist/esm/components/layout/Card/Card.js +66 -0
- package/dist/esm/components/layout/Card/Card.js.map +1 -0
- package/dist/esm/components/navigation/Breadcrumbs/BreadcrumbEllipsis.js +1 -0
- package/dist/esm/components/navigation/Breadcrumbs/BreadcrumbEllipsis.js.map +1 -1
- package/dist/esm/components/navigation/Breadcrumbs/BreadcrumbItem.js +1 -0
- package/dist/esm/components/navigation/Breadcrumbs/BreadcrumbItem.js.map +1 -1
- package/dist/esm/components/navigation/Breadcrumbs/Breadcrumbs.js +5 -0
- package/dist/esm/components/navigation/Breadcrumbs/Breadcrumbs.js.map +1 -1
- package/dist/esm/components/navigation/Pagination/Pagination.css.js +12 -0
- package/dist/esm/components/navigation/Pagination/Pagination.css.js.map +1 -0
- package/dist/esm/components/navigation/Pagination/Pagination.js +107 -0
- package/dist/esm/components/navigation/Pagination/Pagination.js.map +1 -0
- package/dist/esm/components/navigation/Pagination/usePagination.js +143 -0
- package/dist/esm/components/navigation/Pagination/usePagination.js.map +1 -0
- package/dist/esm/components/primitives/Avatar/Avatar.js +87 -1
- package/dist/esm/components/primitives/Avatar/Avatar.js.map +1 -1
- package/dist/esm/components/primitives/Badge/Badge.js +87 -1
- package/dist/esm/components/primitives/Badge/Badge.js.map +1 -1
- package/dist/esm/components/primitives/Checkbox/Checkbox.js +5 -2
- package/dist/esm/components/primitives/Checkbox/Checkbox.js.map +1 -1
- package/dist/esm/components/primitives/Collapsible/Collapsible.js +2 -3
- package/dist/esm/components/primitives/Collapsible/Collapsible.js.map +1 -1
- package/dist/esm/components/primitives/HoverCard/HoverCard.css.js +7 -0
- package/dist/esm/components/primitives/HoverCard/HoverCard.css.js.map +1 -0
- package/dist/esm/components/primitives/HoverCard/HoverCard.js +169 -0
- package/dist/esm/components/primitives/HoverCard/HoverCard.js.map +1 -0
- package/dist/esm/components/primitives/Icon/Icon.js +16 -2
- package/dist/esm/components/primitives/Icon/Icon.js.map +1 -1
- package/dist/esm/components/primitives/Link/Link.js +3 -3
- package/dist/esm/components/primitives/Link/Link.js.map +1 -1
- package/dist/esm/components/primitives/Popover/PopoverClose.js +2 -3
- package/dist/esm/components/primitives/Popover/PopoverClose.js.map +1 -1
- package/dist/esm/components/primitives/Radio/Radio.js +1 -1
- package/dist/esm/hooks/useBreakpoint/useBreakpoint.js +44 -0
- package/dist/esm/hooks/useBreakpoint/useBreakpoint.js.map +1 -0
- package/dist/esm/hooks/useDebounced/useDebouncedCallback.js +97 -0
- package/dist/esm/hooks/useDebounced/useDebouncedCallback.js.map +1 -0
- package/dist/esm/hooks/useDebounced/useDebouncedValue.js +35 -0
- package/dist/esm/hooks/useDebounced/useDebouncedValue.js.map +1 -0
- package/dist/esm/hooks/useIntersectionObserver/useIntersectionObserver.js +73 -0
- package/dist/esm/hooks/useIntersectionObserver/useIntersectionObserver.js.map +1 -0
- package/dist/esm/hooks/useListboxNav/useListboxNav.js +181 -0
- package/dist/esm/hooks/useListboxNav/useListboxNav.js.map +1 -0
- package/dist/esm/hooks/useMediaQuery/useMediaQuery.js +54 -0
- package/dist/esm/hooks/useMediaQuery/useMediaQuery.js.map +1 -0
- package/dist/esm/hooks/useThrottledCallback/useThrottledCallback.js +78 -0
- package/dist/esm/hooks/useThrottledCallback/useThrottledCallback.js.map +1 -0
- package/dist/esm/index.js +25 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/theme/breakpoints.js +27 -0
- package/dist/esm/theme/breakpoints.js.map +1 -0
- package/dist/esm/theme/index.js +1 -0
- package/dist/esm/theme/index.js.map +1 -1
- package/dist/tokens/tokens.dark.css +1 -1
- package/dist/tokens/tokens.json +1 -1
- package/dist/tokens/tokens.light.css +1 -1
- package/dist/types/components/Icons/CloudUploadIcon.d.ts +27 -0
- package/dist/types/components/Icons/ExternalLinkIcon.d.ts +29 -0
- package/dist/types/components/Icons/FirstIcon.d.ts +26 -0
- package/dist/types/components/Icons/LastIcon.d.ts +26 -0
- package/dist/types/components/Icons/UnlinkIcon.d.ts +29 -0
- package/dist/types/components/controls/Combobox/Combobox.d.ts +29 -0
- package/dist/types/components/controls/Combobox/Combobox.types.d.ts +109 -0
- package/dist/types/components/controls/FileUploader/FileUploader.d.ts +34 -0
- package/dist/types/components/controls/FileUploader/FileUploader.types.d.ts +94 -0
- package/dist/types/components/controls/MultiSelect/MultiSelect.d.ts +31 -0
- package/dist/types/components/controls/MultiSelect/MultiSelect.types.d.ts +85 -0
- package/dist/types/components/controls/TagInput/TagInput.d.ts +24 -0
- package/dist/types/components/controls/TagInput/TagInput.types.d.ts +100 -0
- package/dist/types/components/data/DataTable/DataTable.d.ts +8 -0
- package/dist/types/components/data/DataTable/DataTable.types.d.ts +159 -0
- package/dist/types/components/feedback/Alert/Alert.d.ts +1 -0
- package/dist/types/components/feedback/Alert/Alert.types.d.ts +7 -0
- package/dist/types/components/feedback/CommandPalette/CommandPalette.d.ts +29 -0
- package/dist/types/components/feedback/CommandPalette/CommandPalette.types.d.ts +61 -0
- package/dist/types/components/feedback/CommandPalette/fuzzySearch.d.ts +6 -0
- package/dist/types/components/feedback/Drawer/Drawer.d.ts +12 -0
- package/dist/types/components/feedback/Drawer/Drawer.types.d.ts +70 -0
- package/dist/types/components/feedback/Skeleton/Skeleton.types.d.ts +44 -1
- package/dist/types/components/feedback/Skeleton/SkeletonLayout.d.ts +314 -0
- package/dist/types/components/feedback/Stat/Stat.d.ts +23 -0
- package/dist/types/components/feedback/Stat/Stat.types.d.ts +38 -0
- package/dist/types/components/layout/Accordion/Accordion.types.d.ts +7 -0
- package/dist/types/components/layout/Card/Card.d.ts +12 -0
- package/dist/types/components/layout/Card/Card.types.d.ts +54 -0
- package/dist/types/components/navigation/Pagination/Pagination.d.ts +22 -0
- package/dist/types/components/navigation/Pagination/Pagination.types.d.ts +49 -0
- package/dist/types/components/primitives/Button/Button.d.ts +1 -1
- package/dist/types/components/primitives/HoverCard/HoverCard.d.ts +10 -0
- package/dist/types/components/primitives/HoverCard/HoverCard.types.d.ts +64 -0
- package/dist/types/components/primitives/Icon/Icon.d.ts +14 -1
- package/dist/types/components/primitives/IconButton/IconButton.d.ts +1 -1
- package/dist/types/hooks/useBreakpoint/useBreakpoint.d.ts +19 -0
- package/dist/types/hooks/useBreakpoint/useBreakpoint.types.d.ts +20 -0
- package/dist/types/hooks/useDebounced/useDebounced.types.d.ts +15 -0
- package/dist/types/hooks/useDebounced/useDebouncedCallback.d.ts +22 -0
- package/dist/types/hooks/useDebounced/useDebouncedValue.d.ts +16 -0
- package/dist/types/hooks/useIntersectionObserver/useIntersectionObserver.d.ts +22 -0
- package/dist/types/hooks/useIntersectionObserver/useIntersectionObserver.types.d.ts +22 -0
- package/dist/types/hooks/useListboxNav/useListboxNav.d.ts +75 -0
- package/dist/types/hooks/useMediaQuery/useMediaQuery.d.ts +19 -0
- package/dist/types/hooks/useMediaQuery/useMediaQuery.types.d.ts +6 -0
- package/dist/types/hooks/useThrottledCallback/useThrottledCallback.d.ts +23 -0
- package/dist/types/hooks/useThrottledCallback/useThrottledCallback.types.d.ts +13 -0
- package/dist/types/index.d.ts +43 -1
- package/dist/types/theme/breakpoints.d.ts +22 -0
- package/dist/types/theme/index.d.ts +1 -0
- package/package.json +3 -1
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
|
|
3
|
+
import { useId, useRef, useState, useEffect, useMemo, useCallback } from 'react';
|
|
4
|
+
import { createPortal } from 'react-dom';
|
|
5
|
+
import { SearchIcon } from '../../Icons/SearchIcon.js';
|
|
6
|
+
import { Kbd } from '../../primitives/Kbd/Kbd.js';
|
|
7
|
+
import { useDebouncedValue } from '../../../hooks/useDebounced/useDebouncedValue.js';
|
|
8
|
+
import { cx } from '../../../utils/cx.js';
|
|
9
|
+
import { overlayStyle, inputWrapperStyle, searchIconStyle, inputStyle, emptyStyle, listStyle, groupHeaderStyle, itemSelectedStyle, itemStyle, panelStyle, itemIconStyle, itemTextStyle, itemLabelStyle, itemDescriptionStyle, itemShortcutStyle } from './CommandPalette.css.js';
|
|
10
|
+
import { fuzzyFilter } from './fuzzySearch.js';
|
|
11
|
+
import { useRecentItems } from './useRecentItems.js';
|
|
12
|
+
|
|
13
|
+
const DEFAULT_GROUP = 'default';
|
|
14
|
+
const RECENT_GROUP = '__recent__';
|
|
15
|
+
function getItemSearchStrings(item) {
|
|
16
|
+
const out = [item.label];
|
|
17
|
+
if (item.description)
|
|
18
|
+
out.push(item.description);
|
|
19
|
+
if (item.keywords)
|
|
20
|
+
out.push(...item.keywords);
|
|
21
|
+
return out;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Search-driven command list shown as a centred floating dialog. Type to
|
|
25
|
+
* fuzzy-filter, ArrowUp/ArrowDown to navigate, Enter to select, Escape to
|
|
26
|
+
* close. Recent selections are tracked in localStorage when `recentKey` is
|
|
27
|
+
* set. The component does not bind a global hotkey — wire `useHotkey` in
|
|
28
|
+
* the consumer.
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* ```tsx
|
|
32
|
+
* useHotkey('Mod+K', () => { setOpen(true); });
|
|
33
|
+
*
|
|
34
|
+
* <CommandPalette
|
|
35
|
+
* open={open}
|
|
36
|
+
* onClose={() => setOpen(false)}
|
|
37
|
+
* items={[
|
|
38
|
+
* { id: 'open', label: 'Open File', shortcut: 'Cmd+O', group: 'File' },
|
|
39
|
+
* { id: 'save', label: 'Save', shortcut: 'Cmd+S', group: 'File' },
|
|
40
|
+
* ]}
|
|
41
|
+
* onSelect={(item) => runCommand(item.id)}
|
|
42
|
+
* recentKey="myapp:command-palette"
|
|
43
|
+
* />
|
|
44
|
+
* ```
|
|
45
|
+
*/
|
|
46
|
+
const CommandPalette = ({ open, onClose, items, onSelect, placeholder = 'Type a command or search...', emptyState, recentLabel = 'Recent', recentKey, maxRecent = 5, debounceMs = 150, renderItem, portal = true, maxHeight = 400, width = 640, className, style, testId, id, ...rest }) => {
|
|
47
|
+
const autoId = useId();
|
|
48
|
+
const inputId = id ?? `command-palette-${autoId}`;
|
|
49
|
+
const listboxId = `${inputId}-listbox`;
|
|
50
|
+
const inputRef = useRef(null);
|
|
51
|
+
const listRef = useRef(null);
|
|
52
|
+
const [query, setQuery] = useState('');
|
|
53
|
+
const [activeIndex, setActiveIndex] = useState(0);
|
|
54
|
+
const debouncedQuery = useDebouncedValue(query, debounceMs);
|
|
55
|
+
const { recentIds, pushRecent } = useRecentItems({
|
|
56
|
+
storageKey: recentKey,
|
|
57
|
+
maxRecent,
|
|
58
|
+
});
|
|
59
|
+
// Reset state when the palette opens.
|
|
60
|
+
useEffect(() => {
|
|
61
|
+
if (open) {
|
|
62
|
+
setQuery('');
|
|
63
|
+
setActiveIndex(0);
|
|
64
|
+
}
|
|
65
|
+
}, [open]);
|
|
66
|
+
// Focus the input on open.
|
|
67
|
+
useEffect(() => {
|
|
68
|
+
if (open) {
|
|
69
|
+
const t = setTimeout(() => {
|
|
70
|
+
inputRef.current?.focus();
|
|
71
|
+
}, 0);
|
|
72
|
+
return () => {
|
|
73
|
+
clearTimeout(t);
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
return undefined;
|
|
77
|
+
}, [open]);
|
|
78
|
+
// Resolve groups + filtering.
|
|
79
|
+
const { rows, selectableItems } = useMemo(() => {
|
|
80
|
+
const groupOrder = [];
|
|
81
|
+
const groupMap = new Map();
|
|
82
|
+
if (debouncedQuery === '' && recentIds.length > 0) {
|
|
83
|
+
const byId = new Map(items.map(it => [it.id, it]));
|
|
84
|
+
const recent = [];
|
|
85
|
+
for (const id of recentIds) {
|
|
86
|
+
const it = byId.get(id);
|
|
87
|
+
if (it && !it.disabled)
|
|
88
|
+
recent.push(it);
|
|
89
|
+
}
|
|
90
|
+
if (recent.length > 0) {
|
|
91
|
+
groupOrder.push(RECENT_GROUP);
|
|
92
|
+
groupMap.set(RECENT_GROUP, recent);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
const filtered = fuzzyFilter(debouncedQuery, items, getItemSearchStrings);
|
|
96
|
+
const filteredItems = filtered.map(r => r.item);
|
|
97
|
+
for (const item of filteredItems) {
|
|
98
|
+
const g = item.group ?? DEFAULT_GROUP;
|
|
99
|
+
if (!groupMap.has(g)) {
|
|
100
|
+
groupOrder.push(g);
|
|
101
|
+
groupMap.set(g, []);
|
|
102
|
+
}
|
|
103
|
+
groupMap.get(g)?.push(item);
|
|
104
|
+
}
|
|
105
|
+
const rows = [];
|
|
106
|
+
const flat = [];
|
|
107
|
+
let nextIndex = 0;
|
|
108
|
+
for (const groupKey of groupOrder) {
|
|
109
|
+
const list = groupMap.get(groupKey);
|
|
110
|
+
if (!list || list.length === 0)
|
|
111
|
+
continue;
|
|
112
|
+
const groupLabel = groupKey === RECENT_GROUP
|
|
113
|
+
? recentLabel
|
|
114
|
+
: groupKey === DEFAULT_GROUP
|
|
115
|
+
? ''
|
|
116
|
+
: groupKey;
|
|
117
|
+
if (groupLabel) {
|
|
118
|
+
rows.push({
|
|
119
|
+
type: 'header',
|
|
120
|
+
group: groupKey,
|
|
121
|
+
groupLabel,
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
for (const item of list) {
|
|
125
|
+
const index = nextIndex++;
|
|
126
|
+
rows.push({
|
|
127
|
+
type: 'item',
|
|
128
|
+
group: groupKey,
|
|
129
|
+
groupLabel,
|
|
130
|
+
item,
|
|
131
|
+
index,
|
|
132
|
+
});
|
|
133
|
+
flat.push(item);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return { rows, selectableItems: flat };
|
|
137
|
+
}, [items, debouncedQuery, recentIds, recentLabel]);
|
|
138
|
+
// Clamp activeIndex when the result list shrinks.
|
|
139
|
+
useEffect(() => {
|
|
140
|
+
setActiveIndex(prev => {
|
|
141
|
+
if (selectableItems.length === 0)
|
|
142
|
+
return 0;
|
|
143
|
+
if (prev >= selectableItems.length)
|
|
144
|
+
return selectableItems.length - 1;
|
|
145
|
+
return prev;
|
|
146
|
+
});
|
|
147
|
+
}, [selectableItems.length]);
|
|
148
|
+
const moveActive = useCallback((delta) => {
|
|
149
|
+
setActiveIndex(prev => {
|
|
150
|
+
const len = selectableItems.length;
|
|
151
|
+
if (len === 0)
|
|
152
|
+
return 0;
|
|
153
|
+
let next = prev + delta;
|
|
154
|
+
if (next < 0)
|
|
155
|
+
next = len - 1;
|
|
156
|
+
if (next >= len)
|
|
157
|
+
next = 0;
|
|
158
|
+
return next;
|
|
159
|
+
});
|
|
160
|
+
}, [selectableItems.length]);
|
|
161
|
+
const runSelect = useCallback((item) => {
|
|
162
|
+
if (item.disabled)
|
|
163
|
+
return;
|
|
164
|
+
pushRecent(item.id);
|
|
165
|
+
if (item.onSelect) {
|
|
166
|
+
item.onSelect();
|
|
167
|
+
}
|
|
168
|
+
else {
|
|
169
|
+
onSelect?.(item);
|
|
170
|
+
}
|
|
171
|
+
onClose();
|
|
172
|
+
}, [onClose, onSelect, pushRecent]);
|
|
173
|
+
const handleKeyDown = useCallback((e) => {
|
|
174
|
+
if (e.key === 'Escape') {
|
|
175
|
+
e.preventDefault();
|
|
176
|
+
onClose();
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
if (e.key === 'ArrowDown') {
|
|
180
|
+
e.preventDefault();
|
|
181
|
+
moveActive(1);
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
if (e.key === 'ArrowUp') {
|
|
185
|
+
e.preventDefault();
|
|
186
|
+
moveActive(-1);
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
if (e.key === 'Home') {
|
|
190
|
+
e.preventDefault();
|
|
191
|
+
setActiveIndex(0);
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
if (e.key === 'End') {
|
|
195
|
+
e.preventDefault();
|
|
196
|
+
setActiveIndex(Math.max(0, selectableItems.length - 1));
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
if (e.key === 'Enter') {
|
|
200
|
+
e.preventDefault();
|
|
201
|
+
const item = selectableItems[activeIndex];
|
|
202
|
+
if (item)
|
|
203
|
+
runSelect(item);
|
|
204
|
+
}
|
|
205
|
+
}, [activeIndex, moveActive, onClose, runSelect, selectableItems]);
|
|
206
|
+
// Scroll active item into view when it changes.
|
|
207
|
+
useEffect(() => {
|
|
208
|
+
if (!open)
|
|
209
|
+
return;
|
|
210
|
+
const list = listRef.current;
|
|
211
|
+
if (!list)
|
|
212
|
+
return;
|
|
213
|
+
const el = list.querySelector(`[data-row-index="${String(activeIndex)}"]`);
|
|
214
|
+
if (el && typeof el.scrollIntoView === 'function') {
|
|
215
|
+
el.scrollIntoView({ block: 'nearest' });
|
|
216
|
+
}
|
|
217
|
+
}, [activeIndex, open]);
|
|
218
|
+
const handleOverlayClick = useCallback((e) => {
|
|
219
|
+
if (e.target === e.currentTarget) {
|
|
220
|
+
onClose();
|
|
221
|
+
}
|
|
222
|
+
}, [onClose]);
|
|
223
|
+
if (!open)
|
|
224
|
+
return null;
|
|
225
|
+
const widthCss = typeof width === 'number' ? `${String(width)}px` : width;
|
|
226
|
+
const activeId = activeIndex < selectableItems.length
|
|
227
|
+
? `${listboxId}-item-${String(activeIndex)}`
|
|
228
|
+
: undefined;
|
|
229
|
+
const content = (jsxs(Fragment, { children: [jsx("div", { className: overlayStyle, "data-testid": testId ? `${testId}-overlay` : undefined, onClick: handleOverlayClick }), jsxs("div", { role: "dialog", "aria-modal": "true", "aria-label": "Command palette", className: cx(panelStyle, className), style: { width: widthCss, ...style }, "data-testid": testId, onKeyDown: handleKeyDown, ...rest, children: [jsxs("div", { className: inputWrapperStyle, children: [jsx("span", { className: searchIconStyle, "aria-hidden": "true", children: jsx(SearchIcon, {}) }), jsx("input", { ref: inputRef, id: inputId, type: "text", role: "combobox", "aria-expanded": "true", "aria-controls": listboxId, "aria-activedescendant": activeId, "aria-autocomplete": "list", className: inputStyle, placeholder: placeholder, value: query, onChange: e => {
|
|
230
|
+
setQuery(e.target.value);
|
|
231
|
+
setActiveIndex(0);
|
|
232
|
+
}, "data-testid": testId ? `${testId}-input` : undefined })] }), selectableItems.length === 0 ? (jsx("div", { className: emptyStyle, "data-testid": testId ? `${testId}-empty` : undefined, children: emptyState ?? `No matches for "${debouncedQuery}"` })) : (jsx("ul", { ref: listRef, id: listboxId, role: "listbox", className: listStyle, style: { maxHeight }, children: rows.map((row, rowIndex) => {
|
|
233
|
+
if (row.type === 'header') {
|
|
234
|
+
return (jsx("li", { role: "presentation", className: groupHeaderStyle, children: row.groupLabel }, `hdr-${row.group}-${String(rowIndex)}`));
|
|
235
|
+
}
|
|
236
|
+
const item = row.item;
|
|
237
|
+
const itemIndex = row.index;
|
|
238
|
+
if (!item || itemIndex === undefined)
|
|
239
|
+
return null;
|
|
240
|
+
const isSelected = itemIndex === activeIndex;
|
|
241
|
+
const itemDomId = `${listboxId}-item-${String(itemIndex)}`;
|
|
242
|
+
return (jsx("li", { id: itemDomId, role: "option", "aria-selected": isSelected, "aria-disabled": item.disabled ? true : undefined, className: cx(itemStyle, isSelected && itemSelectedStyle), "data-disabled": item.disabled ? 'true' : undefined, "data-row-index": itemIndex, "data-testid": testId ? `${testId}-item-${item.id}` : undefined, onMouseEnter: () => {
|
|
243
|
+
if (!item.disabled)
|
|
244
|
+
setActiveIndex(itemIndex);
|
|
245
|
+
}, onClick: () => {
|
|
246
|
+
runSelect(item);
|
|
247
|
+
}, children: renderItem ? (renderItem(item, {
|
|
248
|
+
selected: isSelected,
|
|
249
|
+
query: debouncedQuery,
|
|
250
|
+
})) : (jsx(DefaultItemContent, { item: item })) }, `${row.group}-${item.id}`));
|
|
251
|
+
}) }))] })] }));
|
|
252
|
+
if (portal && typeof document !== 'undefined') {
|
|
253
|
+
return createPortal(content, document.body);
|
|
254
|
+
}
|
|
255
|
+
return content;
|
|
256
|
+
};
|
|
257
|
+
CommandPalette.displayName = 'CommandPalette';
|
|
258
|
+
const DefaultItemContent = ({ item }) => (jsxs(Fragment, { children: [item.icon && jsx("span", { className: itemIconStyle, children: item.icon }), jsxs("span", { className: itemTextStyle, children: [jsx("span", { className: itemLabelStyle, children: item.label }), item.description && (jsx("span", { className: itemDescriptionStyle, children: item.description }))] }), item.shortcut && (jsx("span", { className: itemShortcutStyle, children: jsx(Kbd, { size: "sm", children: item.shortcut }) }))] }));
|
|
259
|
+
|
|
260
|
+
export { CommandPalette };
|
|
261
|
+
//# sourceMappingURL=CommandPalette.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CommandPalette.js","sources":["../../../../../../src/components/feedback/CommandPalette/CommandPalette.tsx"],"sourcesContent":[null],"names":[],"mappings":";;;;;;;;;;;;AAoCA;AACA;AAUA;AACE;;AACsB;;;AAEtB;AACF;AAEA;;;;;;;;;;;;;;;;;;;;;;AAsBG;AACI;AAqBL;AACA;AACA;AAEA;AACA;;;;AAMA;AACE;;AAED;;;;;;;AAQD;;;;AAKI;AACE;;AAEF;;AAEA;;AAEF;AACF;;;;AAKE;;;;AAKE;;AAEE;AAAwB;;AAE1B;AACE;AACA;;;;AAKJ;AAEA;AACE;;AAEE;AACA;;;;;;;AASJ;;AAEE;;AACA;AAEI;;AAEE;;;;AAIF;AACA;;AAED;;AAEH;AACE;;AAEE;AACA;;;;AAID;AACD;;;AAIJ;;;;;AAME;AAAkC;AAClC;AAAoC;AACpC;AACF;AACF;AAEA;;AAGM;;AACe;AACf;;AACc;;;AAEd;AACF;AACF;AAIF;;;AAGI;AACA;;;;AAGE;;AAEF;;AAKJ;AAEI;;AAEE;;;AAGF;;;;;AAKA;;AAEE;;;AAGF;;;;;AAKA;;AAEE;;;AAGF;;AAEE;AACA;;;AAEJ;;;AAMA;;AACA;AACA;;AACA;;;;AAMF;AAEA;;AAGM;;AAEJ;AAIF;AAAW;AAEX;AACA;;;;AAuCY;;AAEF;AAqBE;;;AAWA;AACA;AACA;AAAsC;AACtC;;AAEA;;;AAaI;;;AAOI;AACA;;AAOV;AAOV;;;AAGA;AACF;AAEA;AAEA;;"}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Score how well `query` matches `target`. Higher = better. Zero = no match.
|
|
3
|
+
*
|
|
4
|
+
* Heuristics, in priority order:
|
|
5
|
+
* 1. Exact case-insensitive match (1000).
|
|
6
|
+
* 2. Target starts with the query (800 + length bonus).
|
|
7
|
+
* 3. Target contains the query as a contiguous substring (500 + position bonus).
|
|
8
|
+
* 4. Subsequence match: every query char appears in target in order; bonuses
|
|
9
|
+
* for consecutive matches, word-boundary matches, and earlier positions.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```ts
|
|
13
|
+
* fuzzyScore('opn', 'open file') // > 0, subsequence match
|
|
14
|
+
* fuzzyScore('zzz', 'open file') // 0
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
function fuzzyScore(query, target) {
|
|
18
|
+
if (query === '')
|
|
19
|
+
return 1;
|
|
20
|
+
if (target === '')
|
|
21
|
+
return 0;
|
|
22
|
+
const q = query.toLowerCase();
|
|
23
|
+
const t = target.toLowerCase();
|
|
24
|
+
if (q === t)
|
|
25
|
+
return 1000;
|
|
26
|
+
if (t.startsWith(q))
|
|
27
|
+
return 800 + Math.max(0, 50 - target.length);
|
|
28
|
+
const containsAt = t.indexOf(q);
|
|
29
|
+
if (containsAt !== -1)
|
|
30
|
+
return 500 - containsAt + Math.max(0, 30 - target.length);
|
|
31
|
+
let score = 0;
|
|
32
|
+
let qi = 0;
|
|
33
|
+
let lastMatch = -2;
|
|
34
|
+
for (let ti = 0; ti < t.length && qi < q.length; ti++) {
|
|
35
|
+
const ch = t[ti];
|
|
36
|
+
if (ch === q[qi]) {
|
|
37
|
+
// Earlier matches contribute more.
|
|
38
|
+
score += Math.max(1, 30 - ti);
|
|
39
|
+
if (lastMatch === ti - 1) {
|
|
40
|
+
score += 25;
|
|
41
|
+
}
|
|
42
|
+
// Bonus when the match begins a "word" (after space/-/_/.)
|
|
43
|
+
const prev = ti > 0 ? t[ti - 1] : '';
|
|
44
|
+
if (ti === 0 ||
|
|
45
|
+
prev === ' ' ||
|
|
46
|
+
prev === '-' ||
|
|
47
|
+
prev === '_' ||
|
|
48
|
+
prev === '.') {
|
|
49
|
+
score += 20;
|
|
50
|
+
}
|
|
51
|
+
lastMatch = ti;
|
|
52
|
+
qi++;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
if (qi !== q.length)
|
|
56
|
+
return 0;
|
|
57
|
+
return score;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Score a list of items against a query. Items with score 0 are dropped, the
|
|
61
|
+
* rest are sorted by descending score. The `getStrings` accessor returns one
|
|
62
|
+
* or more strings per item to score against; the highest-scoring string wins.
|
|
63
|
+
*/
|
|
64
|
+
function fuzzyFilter(query, items, getStrings) {
|
|
65
|
+
if (query === '') {
|
|
66
|
+
return items.map(item => ({ item, score: 1 }));
|
|
67
|
+
}
|
|
68
|
+
const result = [];
|
|
69
|
+
for (const item of items) {
|
|
70
|
+
const strings = getStrings(item);
|
|
71
|
+
let best = 0;
|
|
72
|
+
for (const s of strings) {
|
|
73
|
+
const score = fuzzyScore(query, s);
|
|
74
|
+
if (score > best)
|
|
75
|
+
best = score;
|
|
76
|
+
}
|
|
77
|
+
if (best > 0) {
|
|
78
|
+
result.push({ item, score: best });
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
result.sort((a, b) => b.score - a.score);
|
|
82
|
+
return result;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export { fuzzyFilter, fuzzyScore };
|
|
86
|
+
//# sourceMappingURL=fuzzySearch.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fuzzySearch.js","sources":["../../../../../../src/components/feedback/CommandPalette/fuzzySearch.ts"],"sourcesContent":[null],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;AAeG;AACG,SAAU,UAAU,CAAC,KAAa,EAAE,MAAc,EAAA;IACtD,IAAI,KAAK,KAAK,EAAE;AAAE,QAAA,OAAO,CAAC;IAC1B,IAAI,MAAM,KAAK,EAAE;AAAE,QAAA,OAAO,CAAC;AAE3B,IAAA,MAAM,CAAC,GAAG,KAAK,CAAC,WAAW,EAAE;AAC7B,IAAA,MAAM,CAAC,GAAG,MAAM,CAAC,WAAW,EAAE;IAE9B,IAAI,CAAC,KAAK,CAAC;AAAE,QAAA,OAAO,IAAI;AACxB,IAAA,IAAI,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC;AAAE,QAAA,OAAO,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,GAAG,MAAM,CAAC,MAAM,CAAC;IACjE,MAAM,UAAU,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC;IAC/B,IAAI,UAAU,KAAK,EAAE;AACnB,QAAA,OAAO,GAAG,GAAG,UAAU,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,GAAG,MAAM,CAAC,MAAM,CAAC;IAE3D,IAAI,KAAK,GAAG,CAAC;IACb,IAAI,EAAE,GAAG,CAAC;AACV,IAAA,IAAI,SAAS,GAAG,EAAE;IAClB,KAAK,IAAI,EAAE,GAAG,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC,MAAM,IAAI,EAAE,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,EAAE,EAAE;AACrD,QAAA,MAAM,EAAE,GAAG,CAAC,CAAC,EAAE,CAAC;AAChB,QAAA,IAAI,EAAE,KAAK,CAAC,CAAC,EAAE,CAAC,EAAE;;YAEhB,KAAK,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,GAAG,EAAE,CAAC;AAC7B,YAAA,IAAI,SAAS,KAAK,EAAE,GAAG,CAAC,EAAE;gBACxB,KAAK,IAAI,EAAE;YACb;;AAEA,YAAA,MAAM,IAAI,GAAG,EAAE,GAAG,CAAC,GAAG,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,GAAG,EAAE;YACpC,IACE,EAAE,KAAK,CAAC;AACR,gBAAA,IAAI,KAAK,GAAG;AACZ,gBAAA,IAAI,KAAK,GAAG;AACZ,gBAAA,IAAI,KAAK,GAAG;gBACZ,IAAI,KAAK,GAAG,EACZ;gBACA,KAAK,IAAI,EAAE;YACb;YACA,SAAS,GAAG,EAAE;AACd,YAAA,EAAE,EAAE;QACN;IACF;AAEA,IAAA,IAAI,EAAE,KAAK,CAAC,CAAC,MAAM;AAAE,QAAA,OAAO,CAAC;AAC7B,IAAA,OAAO,KAAK;AACd;AAOA;;;;AAIG;SACa,WAAW,CACzB,KAAa,EACb,KAAmB,EACnB,UAA0C,EAAA;AAE1C,IAAA,IAAI,KAAK,KAAK,EAAE,EAAE;AAChB,QAAA,OAAO,KAAK,CAAC,GAAG,CAAC,IAAI,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC;IAChD;IACA,MAAM,MAAM,GAAoB,EAAE;AAClC,IAAA,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE;AACxB,QAAA,MAAM,OAAO,GAAG,UAAU,CAAC,IAAI,CAAC;QAChC,IAAI,IAAI,GAAG,CAAC;AACZ,QAAA,KAAK,MAAM,CAAC,IAAI,OAAO,EAAE;YACvB,MAAM,KAAK,GAAG,UAAU,CAAC,KAAK,EAAE,CAAC,CAAC;YAClC,IAAI,KAAK,GAAG,IAAI;gBAAE,IAAI,GAAG,KAAK;QAChC;AACA,QAAA,IAAI,IAAI,GAAG,CAAC,EAAE;YACZ,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;QACpC;IACF;AACA,IAAA,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC;AACxC,IAAA,OAAO,MAAM;AACf;;;;"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useState, useEffect, useCallback } from 'react';
|
|
3
|
+
|
|
4
|
+
function readStorage(key) {
|
|
5
|
+
if (typeof window === 'undefined')
|
|
6
|
+
return [];
|
|
7
|
+
try {
|
|
8
|
+
const raw = window.localStorage.getItem(key);
|
|
9
|
+
if (!raw)
|
|
10
|
+
return [];
|
|
11
|
+
const parsed = JSON.parse(raw);
|
|
12
|
+
if (Array.isArray(parsed)) {
|
|
13
|
+
return parsed.filter((v) => typeof v === 'string');
|
|
14
|
+
}
|
|
15
|
+
return [];
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return [];
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
function writeStorage(key, ids) {
|
|
22
|
+
if (typeof window === 'undefined')
|
|
23
|
+
return;
|
|
24
|
+
try {
|
|
25
|
+
window.localStorage.setItem(key, JSON.stringify(ids));
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
// localStorage may be unavailable (private mode, full quota); silently ignore.
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Track recently selected command ids. Backed by localStorage when
|
|
33
|
+
* `storageKey` is provided; otherwise the list is in-memory only.
|
|
34
|
+
*/
|
|
35
|
+
function useRecentItems({ storageKey, maxRecent = 5, }) {
|
|
36
|
+
const [recentIds, setRecentIds] = useState(() => storageKey ? readStorage(storageKey).slice(0, maxRecent) : []);
|
|
37
|
+
// Re-read storage if the key changes after mount.
|
|
38
|
+
useEffect(() => {
|
|
39
|
+
if (storageKey) {
|
|
40
|
+
setRecentIds(readStorage(storageKey).slice(0, maxRecent));
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
setRecentIds([]);
|
|
44
|
+
}
|
|
45
|
+
}, [storageKey, maxRecent]);
|
|
46
|
+
const pushRecent = useCallback((id) => {
|
|
47
|
+
setRecentIds(prev => {
|
|
48
|
+
const next = [id, ...prev.filter(v => v !== id)].slice(0, maxRecent);
|
|
49
|
+
if (storageKey)
|
|
50
|
+
writeStorage(storageKey, next);
|
|
51
|
+
return next;
|
|
52
|
+
});
|
|
53
|
+
}, [storageKey, maxRecent]);
|
|
54
|
+
const clearRecent = useCallback(() => {
|
|
55
|
+
setRecentIds([]);
|
|
56
|
+
if (storageKey)
|
|
57
|
+
writeStorage(storageKey, []);
|
|
58
|
+
}, [storageKey]);
|
|
59
|
+
return { recentIds, pushRecent, clearRecent };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export { useRecentItems };
|
|
63
|
+
//# sourceMappingURL=useRecentItems.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useRecentItems.js","sources":["../../../../../../src/components/feedback/CommandPalette/useRecentItems.ts"],"sourcesContent":[null],"names":[],"mappings":";;;AAiBA;;AACqC;AACnC;;AAEE;AAAU;;AAEV;AACE;;AAEF;;AACA;AACA;;AAEJ;AAEA;;;AAEE;AACE;;AACA;;;AAGJ;AAEA;;;AAGG;AACG;AAIJ;;;;AAOI;;;;;AAIJ;AAEA;;;AAIM;AAAgB;AAChB;AACF;AACF;AAIF;;AAEE;AAAgB;AAClB;AAEA;AACF;;"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
3
|
+
import { CloseIcon } from '../../Icons/CloseIcon.js';
|
|
3
4
|
import { useDialogContext } from './Dialog.js';
|
|
4
5
|
import { cx } from '../../../utils/cx.js';
|
|
5
6
|
import { dialogHeaderContentStyle, dialogTitleStyle, dialogDescriptionStyle, dialogCloseButtonStyle, dialogHeaderStyle } from './Dialog.css.js';
|
|
@@ -18,7 +19,7 @@ import { dialogHeaderContentStyle, dialogTitleStyle, dialogDescriptionStyle, dia
|
|
|
18
19
|
*/
|
|
19
20
|
const DialogHeader = ({ children, showClose = true, description, className, style, testId, ref, ...rest }) => {
|
|
20
21
|
const { onClose, titleId, descriptionId } = useDialogContext();
|
|
21
|
-
return (jsxs("div", { ref: ref, className: cx(dialogHeaderStyle, className), style: style, "data-testid": testId, ...rest, children: [jsxs("div", { className: dialogHeaderContentStyle, children: [jsx("div", { id: titleId, className: dialogTitleStyle, children: children }), description && (jsx("div", { id: descriptionId, className: dialogDescriptionStyle, children: description }))] }), showClose && (jsx("button", { type: "button", onClick: onClose, "aria-label": "Close dialog", className: dialogCloseButtonStyle, "data-testid": testId ? `${testId}-close` : undefined, children: jsx(
|
|
22
|
+
return (jsxs("div", { ref: ref, className: cx(dialogHeaderStyle, className), style: style, "data-testid": testId, ...rest, children: [jsxs("div", { className: dialogHeaderContentStyle, children: [jsx("div", { id: titleId, className: dialogTitleStyle, children: children }), description && (jsx("div", { id: descriptionId, className: dialogDescriptionStyle, children: description }))] }), showClose && (jsx("button", { type: "button", onClick: onClose, "aria-label": "Close dialog", className: dialogCloseButtonStyle, "data-testid": testId ? `${testId}-close` : undefined, children: jsx(CloseIcon, { size: "sm", decorative: true }) }))] }));
|
|
22
23
|
};
|
|
23
24
|
DialogHeader.displayName = 'DialogHeader';
|
|
24
25
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DialogHeader.js","sources":["../../../../../../src/components/feedback/Dialog/DialogHeader.tsx"],"sourcesContent":[null],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"DialogHeader.js","sources":["../../../../../../src/components/feedback/Dialog/DialogHeader.tsx"],"sourcesContent":[null],"names":[],"mappings":";;;;;;;AAeA;AAEA;;;;;;;;;;AAUG;AACI;;;AA2CP;AAEA;;"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import './../../../assets/src/components/feedback/Drawer/Drawer.css.ts.vanilla-CLPTOUrA.css';
|
|
2
|
+
import { createRuntimeFn } from '@vanilla-extract/recipes/createRuntimeFn';
|
|
3
|
+
|
|
4
|
+
var DRAWER_ANIMATION_MS = 220;
|
|
5
|
+
var DRAWER_SIZE_MAP = {sm:'280px',md:'360px',lg:'480px',xl:'640px'};
|
|
6
|
+
var drawerBodyStyle = 'Drawer_drawerBodyStyle__x57sw1x';
|
|
7
|
+
var drawerCloseButtonStyle = 'Drawer_drawerCloseButtonStyle__x57sw113';
|
|
8
|
+
var drawerDescriptionStyle = 'Drawer_drawerDescriptionStyle__x57sw1w';
|
|
9
|
+
var drawerFooterRecipe = createRuntimeFn({defaultClassName:'Drawer_drawerFooterRecipe__x57sw1y',variantClassNames:{align:{left:'Drawer_drawerFooterRecipe_align_left__x57sw1z',center:'Drawer_drawerFooterRecipe_align_center__x57sw110',right:'Drawer_drawerFooterRecipe_align_right__x57sw111','space-between':'Drawer_drawerFooterRecipe_align_space-between__x57sw112'}},defaultVariants:{align:'right'},compoundVariants:[]});
|
|
10
|
+
var drawerHeaderContentStyle = 'Drawer_drawerHeaderContentStyle__x57sw1u';
|
|
11
|
+
var drawerHeaderStyle = 'Drawer_drawerHeaderStyle__x57sw1t';
|
|
12
|
+
var drawerPanelRecipe = createRuntimeFn({defaultClassName:'Drawer_drawerPanelBase__x57sw1d',variantClassNames:{anchor:{left:'Drawer_drawerPanelRecipe_anchor_left__x57sw1f',right:'Drawer_drawerPanelRecipe_anchor_right__x57sw1g',top:'Drawer_drawerPanelRecipe_anchor_top__x57sw1h',bottom:'Drawer_drawerPanelRecipe_anchor_bottom__x57sw1i'},state:{open:'Drawer_drawerPanelRecipe_state_open__x57sw1j',closing:'Drawer_drawerPanelRecipe_state_closing__x57sw1k'}},defaultVariants:{anchor:'right',state:'open'},compoundVariants:[[{anchor:'left',state:'open'},'Drawer_drawerPanelRecipe_compound_0__x57sw1l'],[{anchor:'left',state:'closing'},'Drawer_drawerPanelRecipe_compound_1__x57sw1m'],[{anchor:'right',state:'open'},'Drawer_drawerPanelRecipe_compound_2__x57sw1n'],[{anchor:'right',state:'closing'},'Drawer_drawerPanelRecipe_compound_3__x57sw1o'],[{anchor:'top',state:'open'},'Drawer_drawerPanelRecipe_compound_4__x57sw1p'],[{anchor:'top',state:'closing'},'Drawer_drawerPanelRecipe_compound_5__x57sw1q'],[{anchor:'bottom',state:'open'},'Drawer_drawerPanelRecipe_compound_6__x57sw1r'],[{anchor:'bottom',state:'closing'},'Drawer_drawerPanelRecipe_compound_7__x57sw1s']]});
|
|
13
|
+
var drawerTitleStyle = 'Drawer_drawerTitleStyle__x57sw1v';
|
|
14
|
+
var overlayRecipe = createRuntimeFn({defaultClassName:'Drawer_overlayRecipe__x57sw1a',variantClassNames:{closing:{true:'Drawer_overlayRecipe_closing_true__x57sw1b',false:'Drawer_overlayRecipe_closing_false__x57sw1c'}},defaultVariants:{closing:false},compoundVariants:[]});
|
|
15
|
+
|
|
16
|
+
export { DRAWER_ANIMATION_MS, DRAWER_SIZE_MAP, drawerBodyStyle, drawerCloseButtonStyle, drawerDescriptionStyle, drawerFooterRecipe, drawerHeaderContentStyle, drawerHeaderStyle, drawerPanelRecipe, drawerTitleStyle, overlayRecipe };
|
|
17
|
+
//# sourceMappingURL=Drawer.css.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Drawer.css.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;"}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
3
|
+
import { useId, useRef, useCallback, createContext, useContext } from 'react';
|
|
4
|
+
import { createPortal } from 'react-dom';
|
|
5
|
+
import { CloseIcon } from '../../Icons/CloseIcon.js';
|
|
6
|
+
import { useFocusTrap } from '../../../hooks/useFocusTrap/useFocusTrap.js';
|
|
7
|
+
import { useMergedRef } from '../../../hooks/useMergedRef/useMergedRef.js';
|
|
8
|
+
import { cx } from '../../../utils/cx.js';
|
|
9
|
+
import { overlayRecipe, drawerPanelRecipe, drawerHeaderContentStyle, drawerTitleStyle, drawerDescriptionStyle, drawerCloseButtonStyle, drawerHeaderStyle, drawerBodyStyle, drawerFooterRecipe, DRAWER_SIZE_MAP } from './Drawer.css.js';
|
|
10
|
+
import { useDrawerAnimation } from './useDrawerAnimation.js';
|
|
11
|
+
|
|
12
|
+
const DrawerContext = /*#__PURE__*/ createContext(null);
|
|
13
|
+
function useDrawerContext() {
|
|
14
|
+
const ctx = useContext(DrawerContext);
|
|
15
|
+
if (!ctx) {
|
|
16
|
+
throw new Error('Drawer sub-components must be used within <Drawer>');
|
|
17
|
+
}
|
|
18
|
+
return ctx;
|
|
19
|
+
}
|
|
20
|
+
function resolveSize(size, anchor) {
|
|
21
|
+
const isHorizontal = anchor === 'left' || anchor === 'right';
|
|
22
|
+
const dimension = isHorizontal ? 'width' : 'height';
|
|
23
|
+
let value;
|
|
24
|
+
if (typeof size === 'number') {
|
|
25
|
+
value = `${String(size)}px`;
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
value = DRAWER_SIZE_MAP[size] ?? size;
|
|
29
|
+
}
|
|
30
|
+
return {
|
|
31
|
+
[dimension]: value,
|
|
32
|
+
maxWidth: '100vw',
|
|
33
|
+
maxHeight: '100vh',
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Anchored sliding panel for secondary navigation, filters, or detail views.
|
|
38
|
+
* Modal mode renders an overlay backdrop and traps focus; non-modal mode
|
|
39
|
+
* leaves the rest of the page interactive.
|
|
40
|
+
*
|
|
41
|
+
* @example
|
|
42
|
+
* ```tsx
|
|
43
|
+
* <Drawer open={isOpen} onClose={close} anchor="right" size="md">
|
|
44
|
+
* <Drawer.Header>Filters</Drawer.Header>
|
|
45
|
+
* <Drawer.Body>...</Drawer.Body>
|
|
46
|
+
* <Drawer.Footer>
|
|
47
|
+
* <Button onClick={close}>Cancel</Button>
|
|
48
|
+
* <Button variant="filled">Apply</Button>
|
|
49
|
+
* </Drawer.Footer>
|
|
50
|
+
* </Drawer>
|
|
51
|
+
* ```
|
|
52
|
+
*/
|
|
53
|
+
const DrawerRoot = ({ open, onClose, anchor = 'right', size = 'md', modal = true, closeOnOverlayClick = true, closeOnEscape = true, trapFocus, initialFocusRef, portal = true, title, description, className, style, testId, id, children, ref, ...rest }) => {
|
|
54
|
+
const autoId = useId();
|
|
55
|
+
const titleId = `drawer-title-${autoId}`;
|
|
56
|
+
const descriptionId = `drawer-desc-${autoId}`;
|
|
57
|
+
const panelRef = useRef(null);
|
|
58
|
+
const shouldTrapFocus = trapFocus ?? modal;
|
|
59
|
+
const { mounted, closing } = useDrawerAnimation({
|
|
60
|
+
open,
|
|
61
|
+
panelRef,
|
|
62
|
+
initialFocusRef,
|
|
63
|
+
shouldFocus: shouldTrapFocus,
|
|
64
|
+
});
|
|
65
|
+
const handleFocusTrap = useFocusTrap({
|
|
66
|
+
containerRef: panelRef,
|
|
67
|
+
enabled: shouldTrapFocus,
|
|
68
|
+
});
|
|
69
|
+
const setPanelRef = useMergedRef(panelRef, ref);
|
|
70
|
+
const handleKeyDown = useCallback((e) => {
|
|
71
|
+
if (closeOnEscape && e.key === 'Escape') {
|
|
72
|
+
e.stopPropagation();
|
|
73
|
+
onClose();
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
handleFocusTrap(e);
|
|
77
|
+
}, [closeOnEscape, onClose, handleFocusTrap]);
|
|
78
|
+
const handleOverlayClick = useCallback((e) => {
|
|
79
|
+
if (closeOnOverlayClick && e.target === e.currentTarget) {
|
|
80
|
+
onClose();
|
|
81
|
+
}
|
|
82
|
+
}, [closeOnOverlayClick, onClose]);
|
|
83
|
+
if (!mounted)
|
|
84
|
+
return null;
|
|
85
|
+
const contextValue = {
|
|
86
|
+
onClose,
|
|
87
|
+
titleId,
|
|
88
|
+
descriptionId,
|
|
89
|
+
};
|
|
90
|
+
const sizeStyle = resolveSize(size, anchor);
|
|
91
|
+
const drawerContent = (jsxs(DrawerContext.Provider, { value: contextValue, children: [modal && (jsx("div", { className: overlayRecipe({ closing }), onClick: handleOverlayClick, "data-testid": testId ? `${testId}-overlay` : undefined })), jsxs("div", { ref: setPanelRef, role: "dialog", "aria-modal": modal ? 'true' : undefined, "aria-labelledby": title ? titleId : undefined, "aria-describedby": description ? descriptionId : undefined, tabIndex: -1, className: cx(drawerPanelRecipe({ anchor, state: closing ? 'closing' : 'open' }), className), style: { ...sizeStyle, ...style }, id: id, "data-testid": testId, "data-anchor": anchor, onKeyDown: handleKeyDown, ...rest, children: [title && (jsx("span", { id: titleId, style: { display: 'none' }, children: title })), description && (jsx("span", { id: descriptionId, style: { display: 'none' }, children: description })), children] })] }));
|
|
92
|
+
if (portal && typeof document !== 'undefined') {
|
|
93
|
+
return createPortal(drawerContent, document.body);
|
|
94
|
+
}
|
|
95
|
+
return drawerContent;
|
|
96
|
+
};
|
|
97
|
+
DrawerRoot.displayName = 'Drawer';
|
|
98
|
+
const DrawerHeader = ({ children, showClose = true, description, className, style, testId, ref, ...rest }) => {
|
|
99
|
+
const { onClose, titleId, descriptionId } = useDrawerContext();
|
|
100
|
+
return (jsxs("div", { ref: ref, className: cx(drawerHeaderStyle, className), style: style, "data-testid": testId, ...rest, children: [jsxs("div", { className: drawerHeaderContentStyle, children: [jsx("div", { id: titleId, className: drawerTitleStyle, children: children }), description && (jsx("div", { id: descriptionId, className: drawerDescriptionStyle, children: description }))] }), showClose && (jsx("button", { type: "button", onClick: onClose, "aria-label": "Close drawer", className: drawerCloseButtonStyle, "data-testid": testId ? `${testId}-close` : undefined, children: jsx(CloseIcon, { size: "sm", decorative: true }) }))] }));
|
|
101
|
+
};
|
|
102
|
+
DrawerHeader.displayName = 'Drawer.Header';
|
|
103
|
+
const DrawerBody = ({ children, className, style, testId, ref, ...rest }) => (jsx("div", { ref: ref, className: cx(drawerBodyStyle, className), style: style, "data-testid": testId, ...rest, children: children }));
|
|
104
|
+
DrawerBody.displayName = 'Drawer.Body';
|
|
105
|
+
const DrawerFooter = ({ children, align = 'right', className, style, testId, ref, ...rest }) => (jsx("div", { ref: ref, className: cx(drawerFooterRecipe({ align }), className), style: style, "data-testid": testId, ...rest, children: children }));
|
|
106
|
+
DrawerFooter.displayName = 'Drawer.Footer';
|
|
107
|
+
const DrawerCloseButton = ({ children, className, style, testId, 'aria-label': ariaLabel = 'Close drawer', ref, ...rest }) => {
|
|
108
|
+
const { onClose } = useDrawerContext();
|
|
109
|
+
return (jsx("button", { ref: ref, type: "button", onClick: onClose, "aria-label": ariaLabel, className: cx(drawerCloseButtonStyle, className), style: style, "data-testid": testId, ...rest, children: children ?? jsx(CloseIcon, { size: "sm", decorative: true }) }));
|
|
110
|
+
};
|
|
111
|
+
DrawerCloseButton.displayName = 'Drawer.CloseButton';
|
|
112
|
+
const Drawer = Object.assign(DrawerRoot, {
|
|
113
|
+
Header: DrawerHeader,
|
|
114
|
+
Body: DrawerBody,
|
|
115
|
+
Footer: DrawerFooter,
|
|
116
|
+
CloseButton: DrawerCloseButton,
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
export { Drawer };
|
|
120
|
+
//# sourceMappingURL=Drawer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Drawer.js","sources":["../../../../../../src/components/feedback/Drawer/Drawer.tsx"],"sourcesContent":[null],"names":[],"mappings":";;;;;;;;;;;AAqCA;AAIA;AACE;;AAEE;;AAEF;AACF;AAEA;;;AAME;AACA;AACE;;;AAEA;;;;AAIA;AACA;;AAEJ;AAEA;;;;;;;;;;;;;;;;AAgBG;AACH;AAqBE;AACA;AACA;AACA;AAEA;AAEA;;;;AAIE;AACD;;AAGC;AACA;AACD;;AAID;;;AAIM;;;;;AAQN;;AAGM;;AAEJ;AAIF;AAAc;AAEd;;;;;;AAQA;AA0CA;;;AAGA;AACF;AAEA;AAEA;;;AA0CA;AAEA;AAEA;AAmBA;AAEA;AAoBA;AAEA;AASE;;AAeF;AAEA;;AAUE;AACA;AACA;AACA;AACD;;"}
|