@seekora-ai/ui-sdk-react 0.2.0 → 0.2.5
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/dist/components/FederatedDropdown.d.ts +6 -0
- package/dist/components/FederatedDropdown.d.ts.map +1 -1
- package/dist/components/FederatedDropdown.js +4 -2
- package/dist/components/SearchBarWithSuggestions.d.ts +4 -0
- package/dist/components/SearchBarWithSuggestions.d.ts.map +1 -1
- package/dist/components/SearchBarWithSuggestions.js +2 -2
- package/dist/components/SearchResults.d.ts.map +1 -1
- package/dist/components/SearchResults.js +24 -58
- package/dist/docsearch/components/DocSearch.d.ts +4 -0
- package/dist/docsearch/components/DocSearch.d.ts.map +1 -0
- package/dist/docsearch/components/DocSearch.js +91 -0
- package/dist/docsearch/components/DocSearchButton.d.ts +4 -0
- package/dist/docsearch/components/DocSearchButton.d.ts.map +1 -0
- package/dist/docsearch/components/DocSearchButton.js +12 -0
- package/dist/docsearch/components/Footer.d.ts +8 -0
- package/dist/docsearch/components/Footer.d.ts.map +1 -0
- package/dist/docsearch/components/Footer.js +23 -0
- package/dist/docsearch/components/Highlight.d.ts +9 -0
- package/dist/docsearch/components/Highlight.d.ts.map +1 -0
- package/dist/docsearch/components/Highlight.js +48 -0
- package/dist/docsearch/components/Hit.d.ts +15 -0
- package/dist/docsearch/components/Hit.d.ts.map +1 -0
- package/dist/docsearch/components/Hit.js +96 -0
- package/dist/docsearch/components/Modal.d.ts +9 -0
- package/dist/docsearch/components/Modal.d.ts.map +1 -0
- package/dist/docsearch/components/Modal.js +54 -0
- package/dist/docsearch/components/Results.d.ts +23 -0
- package/dist/docsearch/components/Results.d.ts.map +1 -0
- package/dist/docsearch/components/Results.js +145 -0
- package/dist/docsearch/components/SearchBox.d.ts +12 -0
- package/dist/docsearch/components/SearchBox.d.ts.map +1 -0
- package/dist/docsearch/components/SearchBox.js +18 -0
- package/dist/docsearch/hooks/useDocSearch.d.ts +33 -0
- package/dist/docsearch/hooks/useDocSearch.d.ts.map +1 -0
- package/dist/docsearch/hooks/useDocSearch.js +211 -0
- package/dist/docsearch/hooks/useKeyboard.d.ts +17 -0
- package/dist/docsearch/hooks/useKeyboard.d.ts.map +1 -0
- package/dist/docsearch/hooks/useKeyboard.js +71 -0
- package/dist/docsearch/hooks/useSeekoraSearch.d.ts +27 -0
- package/dist/docsearch/hooks/useSeekoraSearch.d.ts.map +1 -0
- package/dist/docsearch/hooks/useSeekoraSearch.js +207 -0
- package/dist/docsearch/index.d.ts +13 -0
- package/dist/docsearch/index.d.ts.map +1 -0
- package/dist/docsearch/index.js +11 -0
- package/dist/docsearch/types.d.ts +172 -0
- package/dist/docsearch/types.d.ts.map +1 -0
- package/dist/docsearch/types.js +4 -0
- package/dist/docsearch.css +237 -0
- package/dist/hooks/useAnalytics.d.ts +8 -4
- package/dist/hooks/useAnalytics.d.ts.map +1 -1
- package/dist/hooks/useAnalytics.js +14 -9
- package/dist/hooks/useQuerySuggestionsEnhanced.js +1 -1
- package/dist/hooks/useSuggestionsAnalytics.d.ts +3 -1
- package/dist/hooks/useSuggestionsAnalytics.d.ts.map +1 -1
- package/dist/hooks/useSuggestionsAnalytics.js +11 -9
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11 -0
- package/dist/index.umd.js +1 -1
- package/dist/src/index.d.ts +271 -7
- package/dist/src/index.esm.js +1705 -84
- package/dist/src/index.esm.js.map +1 -1
- package/dist/src/index.js +1712 -83
- package/dist/src/index.js.map +1 -1
- package/package.json +9 -6
- package/src/docsearch/docsearch.css +237 -0
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import React, { useEffect, useRef } from 'react';
|
|
2
|
+
import { createPortal } from 'react-dom';
|
|
3
|
+
export function Modal({ isOpen, onClose, children }) {
|
|
4
|
+
const overlayRef = useRef(null);
|
|
5
|
+
const containerRef = useRef(null);
|
|
6
|
+
useEffect(() => {
|
|
7
|
+
const handleClickOutside = (event) => {
|
|
8
|
+
if (containerRef.current && !containerRef.current.contains(event.target)) {
|
|
9
|
+
onClose();
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
if (isOpen)
|
|
13
|
+
document.addEventListener('mousedown', handleClickOutside);
|
|
14
|
+
return () => document.removeEventListener('mousedown', handleClickOutside);
|
|
15
|
+
}, [isOpen, onClose]);
|
|
16
|
+
useEffect(() => {
|
|
17
|
+
if (isOpen) {
|
|
18
|
+
const originalOverflow = document.body.style.overflow;
|
|
19
|
+
document.body.style.overflow = 'hidden';
|
|
20
|
+
return () => { document.body.style.overflow = originalOverflow; };
|
|
21
|
+
}
|
|
22
|
+
}, [isOpen]);
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
if (!isOpen || !containerRef.current)
|
|
25
|
+
return;
|
|
26
|
+
const container = containerRef.current;
|
|
27
|
+
const focusableElements = container.querySelectorAll('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])');
|
|
28
|
+
const firstElement = focusableElements[0];
|
|
29
|
+
const lastElement = focusableElements[focusableElements.length - 1];
|
|
30
|
+
const handleTabKey = (e) => {
|
|
31
|
+
if (e.key !== 'Tab')
|
|
32
|
+
return;
|
|
33
|
+
if (e.shiftKey) {
|
|
34
|
+
if (document.activeElement === firstElement) {
|
|
35
|
+
e.preventDefault();
|
|
36
|
+
lastElement?.focus();
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
if (document.activeElement === lastElement) {
|
|
41
|
+
e.preventDefault();
|
|
42
|
+
firstElement?.focus();
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
container.addEventListener('keydown', handleTabKey);
|
|
47
|
+
return () => container.removeEventListener('keydown', handleTabKey);
|
|
48
|
+
}, [isOpen]);
|
|
49
|
+
if (!isOpen || typeof document === 'undefined')
|
|
50
|
+
return null;
|
|
51
|
+
const modalContent = (React.createElement("div", { ref: overlayRef, className: "seekora-docsearch-overlay", role: "dialog", "aria-modal": "true", "aria-label": "Search documentation" },
|
|
52
|
+
React.createElement("div", { ref: containerRef, className: "seekora-docsearch-container" }, children)));
|
|
53
|
+
return createPortal(modalContent, document.body);
|
|
54
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { DocSearchHit, DocSearchSuggestion, DocSearchTranslations, SearchSource } from '../types';
|
|
3
|
+
interface GroupedHits {
|
|
4
|
+
source: SearchSource;
|
|
5
|
+
items: DocSearchSuggestion[];
|
|
6
|
+
}
|
|
7
|
+
interface ResultsProps {
|
|
8
|
+
hits: (DocSearchHit | DocSearchSuggestion)[];
|
|
9
|
+
groupedHits?: GroupedHits[];
|
|
10
|
+
selectedIndex: number;
|
|
11
|
+
onSelect: (hit: DocSearchHit | DocSearchSuggestion) => void;
|
|
12
|
+
onHover: (index: number) => void;
|
|
13
|
+
/** When true, scroll the selected item into view (set by keyboard nav only; avoids scroll-on-hover jump) */
|
|
14
|
+
scrollSelectionIntoViewRef?: React.MutableRefObject<boolean>;
|
|
15
|
+
query: string;
|
|
16
|
+
isLoading: boolean;
|
|
17
|
+
error: string | null;
|
|
18
|
+
translations?: DocSearchTranslations;
|
|
19
|
+
sources?: SearchSource[];
|
|
20
|
+
}
|
|
21
|
+
export declare function Results({ hits, groupedHits, selectedIndex, onSelect, onHover, scrollSelectionIntoViewRef, query, isLoading, error, translations, sources: _sources, }: ResultsProps): React.JSX.Element;
|
|
22
|
+
export {};
|
|
23
|
+
//# sourceMappingURL=Results.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Results.d.ts","sourceRoot":"","sources":["../../../src/docsearch/components/Results.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA4B,MAAM,OAAO,CAAC;AAEjD,OAAO,KAAK,EAAE,YAAY,EAAE,mBAAmB,EAAE,qBAAqB,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAEvG,UAAU,WAAW;IACnB,MAAM,EAAE,YAAY,CAAC;IACrB,KAAK,EAAE,mBAAmB,EAAE,CAAC;CAC9B;AAED,UAAU,YAAY;IACpB,IAAI,EAAE,CAAC,YAAY,GAAG,mBAAmB,CAAC,EAAE,CAAC;IAC7C,WAAW,CAAC,EAAE,WAAW,EAAE,CAAC;IAC5B,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,CAAC,GAAG,EAAE,YAAY,GAAG,mBAAmB,KAAK,IAAI,CAAC;IAC5D,OAAO,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACjC,4GAA4G;IAC5G,0BAA0B,CAAC,EAAE,KAAK,CAAC,gBAAgB,CAAC,OAAO,CAAC,CAAC;IAC7D,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,OAAO,CAAC;IACnB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,YAAY,CAAC,EAAE,qBAAqB,CAAC;IACrC,OAAO,CAAC,EAAE,YAAY,EAAE,CAAC;CAC1B;AAuGD,wBAAgB,OAAO,CAAC,EACtB,IAAI,EACJ,WAAW,EACX,aAAa,EACb,QAAQ,EACR,OAAO,EACP,0BAA0B,EAC1B,KAAK,EACL,SAAS,EACT,KAAK,EACL,YAAiB,EACjB,OAAO,EAAE,QAAa,GACvB,EAAE,YAAY,qBAsGd"}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import React, { useEffect, useRef } from 'react';
|
|
2
|
+
import { Hit } from './Hit';
|
|
3
|
+
function getTypeLevel(type) {
|
|
4
|
+
if (!type)
|
|
5
|
+
return 1;
|
|
6
|
+
const match = type.match(/^lvl(\d+)$/);
|
|
7
|
+
return match ? parseInt(match[1], 10) : 1;
|
|
8
|
+
}
|
|
9
|
+
function isChildType(type) {
|
|
10
|
+
return getTypeLevel(type) >= 2;
|
|
11
|
+
}
|
|
12
|
+
/** Build a stable grouping key from all hierarchy levels (lvl0 through lvl5). */
|
|
13
|
+
function getHierarchyKey(hit) {
|
|
14
|
+
const h = hit.hierarchy ?? {};
|
|
15
|
+
const parts = [
|
|
16
|
+
h.lvl0 ?? '',
|
|
17
|
+
h.lvl1 ?? '',
|
|
18
|
+
h.lvl2 ?? '',
|
|
19
|
+
h.lvl3 ?? '',
|
|
20
|
+
h.lvl4 ?? '',
|
|
21
|
+
h.lvl5 ?? '',
|
|
22
|
+
];
|
|
23
|
+
return parts.join('\0');
|
|
24
|
+
}
|
|
25
|
+
/** Build a display breadcrumb from hierarchy, skipping consecutive duplicates (e.g. lvl0 === lvl1). */
|
|
26
|
+
function getHierarchyBreadcrumb(hit) {
|
|
27
|
+
const h = hit.hierarchy ?? {};
|
|
28
|
+
const parts = [h.lvl0, h.lvl1, h.lvl2, h.lvl3, h.lvl4, h.lvl5].filter((v) => typeof v === 'string' && v.length > 0);
|
|
29
|
+
const deduped = [];
|
|
30
|
+
for (const p of parts) {
|
|
31
|
+
if (deduped[deduped.length - 1] !== p)
|
|
32
|
+
deduped.push(p);
|
|
33
|
+
}
|
|
34
|
+
return deduped.join(' › ');
|
|
35
|
+
}
|
|
36
|
+
function groupHitsByHierarchy(hits) {
|
|
37
|
+
const hitToIndex = new Map();
|
|
38
|
+
hits.forEach((h, i) => hitToIndex.set(h, i));
|
|
39
|
+
const groups = new Map();
|
|
40
|
+
for (const hit of hits) {
|
|
41
|
+
const key = getHierarchyKey(hit);
|
|
42
|
+
if (!groups.has(key))
|
|
43
|
+
groups.set(key, []);
|
|
44
|
+
groups.get(key).push(hit);
|
|
45
|
+
}
|
|
46
|
+
const entries = Array.from(groups.entries());
|
|
47
|
+
entries.sort(([, aHits], [, bHits]) => {
|
|
48
|
+
const aMin = Math.min(...aHits.map(h => hitToIndex.get(h) ?? 0));
|
|
49
|
+
const bMin = Math.min(...bHits.map(h => hitToIndex.get(h) ?? 0));
|
|
50
|
+
return aMin - bMin;
|
|
51
|
+
});
|
|
52
|
+
const result = [];
|
|
53
|
+
for (const [, groupHits] of entries) {
|
|
54
|
+
const sortedHits = [...groupHits].sort((a, b) => {
|
|
55
|
+
const aType = a.type;
|
|
56
|
+
const bType = b.type;
|
|
57
|
+
return getTypeLevel(aType) - getTypeLevel(bType);
|
|
58
|
+
});
|
|
59
|
+
const markedHits = sortedHits.map((hit, index) => {
|
|
60
|
+
const suggestion = hit;
|
|
61
|
+
const isChild = isChildType(suggestion.type);
|
|
62
|
+
const nextHit = sortedHits[index + 1];
|
|
63
|
+
const isLastChild = isChild && (!nextHit || !isChildType(nextHit.type));
|
|
64
|
+
return { ...hit, isChild, isLastChild };
|
|
65
|
+
});
|
|
66
|
+
const category = getHierarchyBreadcrumb(groupHits[0]);
|
|
67
|
+
result.push({ category: category || null, hits: markedHits });
|
|
68
|
+
}
|
|
69
|
+
return result;
|
|
70
|
+
}
|
|
71
|
+
function getGlobalIndexMulti(groups, groupIndex, hitIndex) {
|
|
72
|
+
let index = 0;
|
|
73
|
+
for (let i = 0; i < groupIndex; i++)
|
|
74
|
+
index += groups[i].hits.length;
|
|
75
|
+
return index + hitIndex;
|
|
76
|
+
}
|
|
77
|
+
function getHitKey(hit, index) {
|
|
78
|
+
if ('objectID' in hit)
|
|
79
|
+
return hit.objectID;
|
|
80
|
+
return `suggestion-${hit.url}-${index}`;
|
|
81
|
+
}
|
|
82
|
+
export function Results({ hits, groupedHits, selectedIndex, onSelect, onHover, scrollSelectionIntoViewRef, query, isLoading, error, translations = {}, sources: _sources = [], }) {
|
|
83
|
+
void _sources;
|
|
84
|
+
const listRef = useRef(null);
|
|
85
|
+
useEffect(() => {
|
|
86
|
+
if (!listRef.current || hits.length === 0)
|
|
87
|
+
return;
|
|
88
|
+
// Only scroll when selection changed via keyboard (avoids scroll-to-selected on hover)
|
|
89
|
+
if (scrollSelectionIntoViewRef && !scrollSelectionIntoViewRef.current)
|
|
90
|
+
return;
|
|
91
|
+
// listRef's direct children are groups (li.seekora-docsearch-results-group), not hits.
|
|
92
|
+
// Find the actual hit element at flat selectedIndex.
|
|
93
|
+
const groupEls = listRef.current.querySelectorAll('.seekora-docsearch-results-group');
|
|
94
|
+
let idx = 0;
|
|
95
|
+
for (const groupEl of groupEls) {
|
|
96
|
+
const itemList = groupEl.querySelector('.seekora-docsearch-results-group-items');
|
|
97
|
+
if (!itemList)
|
|
98
|
+
continue;
|
|
99
|
+
const items = itemList.children;
|
|
100
|
+
for (let i = 0; i < items.length; i++) {
|
|
101
|
+
if (idx === selectedIndex) {
|
|
102
|
+
items[i].scrollIntoView({ block: 'nearest' });
|
|
103
|
+
if (scrollSelectionIntoViewRef)
|
|
104
|
+
scrollSelectionIntoViewRef.current = false;
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
idx++;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
if (scrollSelectionIntoViewRef)
|
|
111
|
+
scrollSelectionIntoViewRef.current = false;
|
|
112
|
+
}, [selectedIndex, hits.length, scrollSelectionIntoViewRef]);
|
|
113
|
+
if (!query) {
|
|
114
|
+
return (React.createElement("div", { className: "seekora-docsearch-empty" },
|
|
115
|
+
React.createElement("p", { className: "seekora-docsearch-empty-text" }, translations.searchPlaceholder || 'Type to start searching...')));
|
|
116
|
+
}
|
|
117
|
+
if (isLoading && hits.length === 0) {
|
|
118
|
+
return (React.createElement("div", { className: "seekora-docsearch-loading" },
|
|
119
|
+
React.createElement("div", { className: "seekora-docsearch-loading-spinner" },
|
|
120
|
+
React.createElement("svg", { width: "24", height: "24", viewBox: "0 0 24 24", "aria-hidden": "true" },
|
|
121
|
+
React.createElement("circle", { cx: "12", cy: "12", r: "10", stroke: "currentColor", strokeWidth: "2", fill: "none", strokeDasharray: "50", strokeDashoffset: "15" },
|
|
122
|
+
React.createElement("animateTransform", { attributeName: "transform", type: "rotate", from: "0 12 12", to: "360 12 12", dur: "0.8s", repeatCount: "indefinite" })))),
|
|
123
|
+
React.createElement("p", { className: "seekora-docsearch-loading-text" }, translations.loadingText || 'Searching...')));
|
|
124
|
+
}
|
|
125
|
+
if (error) {
|
|
126
|
+
return (React.createElement("div", { className: "seekora-docsearch-error" },
|
|
127
|
+
React.createElement("p", { className: "seekora-docsearch-error-text" }, translations.errorText || error)));
|
|
128
|
+
}
|
|
129
|
+
if (hits.length === 0 && query) {
|
|
130
|
+
return (React.createElement("div", { className: "seekora-docsearch-no-results" },
|
|
131
|
+
React.createElement("p", { className: "seekora-docsearch-no-results-text" }, translations.noResultsText || `No results found for "${query}"`)));
|
|
132
|
+
}
|
|
133
|
+
const displayGroups = groupedHits && groupedHits.length > 0
|
|
134
|
+
? groupedHits.map(g => ({ category: g.source.name, sourceId: g.source.id, openInNewTab: g.source.openInNewTab, hits: g.items }))
|
|
135
|
+
: groupHitsByHierarchy(hits).map(g => ({ category: g.category, sourceId: 'default', openInNewTab: false, hits: g.hits }));
|
|
136
|
+
return (React.createElement("div", { className: "seekora-docsearch-results" },
|
|
137
|
+
React.createElement("ul", { ref: listRef, id: "seekora-docsearch-results", className: "seekora-docsearch-results-list", role: "listbox" }, displayGroups.map((group, groupIndex) => (React.createElement("li", { key: group.sourceId + '-' + groupIndex, className: "seekora-docsearch-results-group" },
|
|
138
|
+
group.category && React.createElement("div", { className: "seekora-docsearch-results-group-header" }, group.category),
|
|
139
|
+
React.createElement("ul", { className: "seekora-docsearch-results-group-items" }, group.hits.map((hit, hitIndex) => {
|
|
140
|
+
const globalIndex = getGlobalIndexMulti(displayGroups, groupIndex, hitIndex);
|
|
141
|
+
const extHit = hit;
|
|
142
|
+
return (React.createElement("li", { key: getHitKey(hit, hitIndex) },
|
|
143
|
+
React.createElement(Hit, { hit: hit, isSelected: globalIndex === selectedIndex, onClick: () => onSelect(hit), onMouseEnter: () => onHover(globalIndex), openInNewTab: group.openInNewTab, isChild: extHit.isChild, isLastChild: extHit.isLastChild, hierarchyType: hit.type })));
|
|
144
|
+
}))))))));
|
|
145
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
interface SearchBoxProps {
|
|
3
|
+
value: string;
|
|
4
|
+
onChange: (value: string) => void;
|
|
5
|
+
onKeyDown: (event: React.KeyboardEvent) => void;
|
|
6
|
+
placeholder?: string;
|
|
7
|
+
isLoading?: boolean;
|
|
8
|
+
onClear?: () => void;
|
|
9
|
+
}
|
|
10
|
+
export declare function SearchBox({ value, onChange, onKeyDown, placeholder, isLoading, onClear, }: SearchBoxProps): React.JSX.Element;
|
|
11
|
+
export {};
|
|
12
|
+
//# sourceMappingURL=SearchBox.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SearchBox.d.ts","sourceRoot":"","sources":["../../../src/docsearch/components/SearchBox.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA4B,MAAM,OAAO,CAAC;AAEjD,UAAU,cAAc;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,SAAS,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,aAAa,KAAK,IAAI,CAAC;IAChD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;CACtB;AAED,wBAAgB,SAAS,CAAC,EACxB,KAAK,EACL,QAAQ,EACR,SAAS,EACT,WAAuC,EACvC,SAAiB,EACjB,OAAO,GACR,EAAE,cAAc,qBAiDhB"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import React, { useEffect, useRef } from 'react';
|
|
2
|
+
export function SearchBox({ value, onChange, onKeyDown, placeholder = 'Search documentation...', isLoading = false, onClear, }) {
|
|
3
|
+
const inputRef = useRef(null);
|
|
4
|
+
useEffect(() => { if (inputRef.current)
|
|
5
|
+
inputRef.current.focus(); }, []);
|
|
6
|
+
const handleChange = (event) => onChange(event.target.value);
|
|
7
|
+
const handleClear = () => { onChange(''); onClear?.(); inputRef.current?.focus(); };
|
|
8
|
+
return (React.createElement("div", { className: "seekora-docsearch-searchbox" },
|
|
9
|
+
React.createElement("label", { className: "seekora-docsearch-searchbox-icon", htmlFor: "seekora-docsearch-input" }, isLoading ? (React.createElement("span", { className: "seekora-docsearch-spinner", "aria-hidden": "true" },
|
|
10
|
+
React.createElement("svg", { width: "20", height: "20", viewBox: "0 0 20 20" },
|
|
11
|
+
React.createElement("circle", { cx: "10", cy: "10", r: "8", stroke: "currentColor", strokeWidth: "2", fill: "none", strokeDasharray: "40", strokeDashoffset: "10" },
|
|
12
|
+
React.createElement("animateTransform", { attributeName: "transform", type: "rotate", from: "0 10 10", to: "360 10 10", dur: "0.8s", repeatCount: "indefinite" }))))) : (React.createElement("svg", { width: "20", height: "20", viewBox: "0 0 20 20", fill: "none", xmlns: "http://www.w3.org/2000/svg", "aria-hidden": "true" },
|
|
13
|
+
React.createElement("path", { d: "M9 3.5a5.5 5.5 0 100 11 5.5 5.5 0 000-11zM2 9a7 7 0 1112.452 4.391l3.328 3.329a.75.75 0 11-1.06 1.06l-3.329-3.328A7 7 0 012 9z", fill: "currentColor" })))),
|
|
14
|
+
React.createElement("input", { ref: inputRef, id: "seekora-docsearch-input", className: "seekora-docsearch-input", type: "text", value: value, onChange: handleChange, onKeyDown: onKeyDown, placeholder: placeholder, autoComplete: "off", autoCorrect: "off", autoCapitalize: "off", spellCheck: false, "aria-autocomplete": "list", "aria-controls": "seekora-docsearch-results" }),
|
|
15
|
+
value && (React.createElement("button", { type: "button", className: "seekora-docsearch-clear", onClick: handleClear, "aria-label": "Clear search" },
|
|
16
|
+
React.createElement("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", xmlns: "http://www.w3.org/2000/svg", "aria-hidden": "true" },
|
|
17
|
+
React.createElement("path", { d: "M4.28 3.22a.75.75 0 00-1.06 1.06L6.94 8l-3.72 3.72a.75.75 0 101.06 1.06L8 9.06l3.72 3.72a.75.75 0 101.06-1.06L9.06 8l3.72-3.72a.75.75 0 00-1.06-1.06L8 6.94 4.28 3.22z", fill: "currentColor" }))))));
|
|
18
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { DocSearchHit, DocSearchSuggestion, SearchSource } from '../types';
|
|
2
|
+
interface UseDocSearchOptions {
|
|
3
|
+
apiEndpoint?: string;
|
|
4
|
+
apiKey?: string;
|
|
5
|
+
sources?: SearchSource[];
|
|
6
|
+
maxResults?: number;
|
|
7
|
+
debounceMs?: number;
|
|
8
|
+
processGroupedResults?: (sourceId: string, items: DocSearchSuggestion[]) => DocSearchSuggestion[];
|
|
9
|
+
}
|
|
10
|
+
export declare function useDocSearch(options: UseDocSearchOptions): {
|
|
11
|
+
sources: SearchSource[];
|
|
12
|
+
setQuery: (query: string) => void;
|
|
13
|
+
search: (q: string) => Promise<void>;
|
|
14
|
+
fetchSuggestions: (query: string) => Promise<void>;
|
|
15
|
+
selectNext: () => void;
|
|
16
|
+
selectPrev: () => void;
|
|
17
|
+
setSelectedIndex: (index: number) => void;
|
|
18
|
+
reset: () => void;
|
|
19
|
+
getSelectedItem: () => DocSearchHit | DocSearchSuggestion | null;
|
|
20
|
+
groupedSuggestions: {
|
|
21
|
+
source: SearchSource;
|
|
22
|
+
items: DocSearchSuggestion[];
|
|
23
|
+
}[];
|
|
24
|
+
query: string;
|
|
25
|
+
results: DocSearchHit[];
|
|
26
|
+
suggestions: DocSearchSuggestion[];
|
|
27
|
+
isLoading: boolean;
|
|
28
|
+
error: string | null;
|
|
29
|
+
selectedIndex: number;
|
|
30
|
+
mode: "suggestions" | "results";
|
|
31
|
+
};
|
|
32
|
+
export {};
|
|
33
|
+
//# sourceMappingURL=useDocSearch.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useDocSearch.d.ts","sourceRoot":"","sources":["../../../src/docsearch/hooks/useDocSearch.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAGV,YAAY,EACZ,mBAAmB,EACnB,YAAY,EACb,MAAM,UAAU,CAAC;AA0DlB,UAAU,mBAAmB;IAC3B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,YAAY,EAAE,CAAC;IACzB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,qBAAqB,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,mBAAmB,EAAE,KAAK,mBAAmB,EAAE,CAAC;CACnG;AAED,wBAAgB,YAAY,CAAC,OAAO,EAAE,mBAAmB;;sBAuI7C,MAAM;gBAHe,MAAM;8BAlCrB,MAAM;;;8BA+CuB,MAAM;;2BASX,YAAY,GAAG,mBAAmB,GAAG,IAAI;wBA1N7D;QAAE,MAAM,EAAE,YAAY,CAAC;QAAC,KAAK,EAAE,mBAAmB,EAAE,CAAA;KAAE,EAAE;;;;;;;;EAmP7E"}
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import { useCallback, useEffect, useReducer, useRef } from 'react';
|
|
2
|
+
const initialState = {
|
|
3
|
+
query: '',
|
|
4
|
+
results: [],
|
|
5
|
+
suggestions: [],
|
|
6
|
+
groupedSuggestions: [],
|
|
7
|
+
isLoading: false,
|
|
8
|
+
error: null,
|
|
9
|
+
selectedIndex: 0,
|
|
10
|
+
mode: 'suggestions',
|
|
11
|
+
};
|
|
12
|
+
function reducer(state, action) {
|
|
13
|
+
switch (action.type) {
|
|
14
|
+
case 'SET_QUERY':
|
|
15
|
+
return { ...state, query: action.payload, selectedIndex: 0 };
|
|
16
|
+
case 'SET_RESULTS':
|
|
17
|
+
return { ...state, results: action.payload, mode: 'results' };
|
|
18
|
+
case 'SET_SUGGESTIONS':
|
|
19
|
+
return { ...state, suggestions: action.payload, mode: 'suggestions' };
|
|
20
|
+
case 'SET_GROUPED_SUGGESTIONS': {
|
|
21
|
+
const flatSuggestions = action.payload.flatMap(group => group.items.map(item => ({ ...item, _source: group.source.id })));
|
|
22
|
+
return { ...state, groupedSuggestions: action.payload, suggestions: flatSuggestions, mode: 'suggestions' };
|
|
23
|
+
}
|
|
24
|
+
case 'SET_LOADING':
|
|
25
|
+
return { ...state, isLoading: action.payload };
|
|
26
|
+
case 'SET_ERROR':
|
|
27
|
+
return { ...state, error: action.payload };
|
|
28
|
+
case 'SET_SELECTED_INDEX':
|
|
29
|
+
return { ...state, selectedIndex: action.payload };
|
|
30
|
+
case 'SELECT_NEXT': {
|
|
31
|
+
const items = state.mode === 'results' ? state.results : state.suggestions;
|
|
32
|
+
const maxIndex = items.length - 1;
|
|
33
|
+
return { ...state, selectedIndex: state.selectedIndex >= maxIndex ? 0 : state.selectedIndex + 1 };
|
|
34
|
+
}
|
|
35
|
+
case 'SELECT_PREV': {
|
|
36
|
+
const items = state.mode === 'results' ? state.results : state.suggestions;
|
|
37
|
+
const maxIndex = items.length - 1;
|
|
38
|
+
return { ...state, selectedIndex: state.selectedIndex <= 0 ? maxIndex : state.selectedIndex - 1 };
|
|
39
|
+
}
|
|
40
|
+
case 'SET_MODE':
|
|
41
|
+
return { ...state, mode: action.payload, selectedIndex: 0 };
|
|
42
|
+
case 'RESET':
|
|
43
|
+
return initialState;
|
|
44
|
+
default:
|
|
45
|
+
return state;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
export function useDocSearch(options) {
|
|
49
|
+
const { apiEndpoint, apiKey, sources, maxResults = 10, debounceMs = 200, processGroupedResults } = options;
|
|
50
|
+
const searchSources = sources || (apiEndpoint ? [{
|
|
51
|
+
id: 'default',
|
|
52
|
+
name: 'Results',
|
|
53
|
+
endpoint: apiEndpoint,
|
|
54
|
+
apiKey,
|
|
55
|
+
maxResults,
|
|
56
|
+
}] : []);
|
|
57
|
+
const [state, dispatch] = useReducer(reducer, initialState);
|
|
58
|
+
const abortControllersRef = useRef(new Map());
|
|
59
|
+
const debounceTimerRef = useRef(null);
|
|
60
|
+
const defaultTransform = (data, sourceId) => {
|
|
61
|
+
const items = data.data?.suggestions || data.data?.results || data.suggestions || data.results || data.hits || [];
|
|
62
|
+
return items.map((item) => ({
|
|
63
|
+
url: item.url || item.route || '',
|
|
64
|
+
title: item.title?.replace?.(/<\/?mark>/g, '') || item.title || '',
|
|
65
|
+
content: item.content?.replace?.(/<\/?mark>/g, '')?.substring?.(0, 100) || item.description || '',
|
|
66
|
+
description: item.description || item.content?.substring?.(0, 100) || '',
|
|
67
|
+
category: item.category || item.hierarchy?.lvl0 || '',
|
|
68
|
+
hierarchy: item.hierarchy,
|
|
69
|
+
route: item.route,
|
|
70
|
+
parentTitle: item.parent_title || item.parentTitle,
|
|
71
|
+
_source: sourceId,
|
|
72
|
+
}));
|
|
73
|
+
};
|
|
74
|
+
const transformPublicSearchResults = useCallback((data, sourceId) => {
|
|
75
|
+
const results = data?.data?.results || [];
|
|
76
|
+
return results.map((item) => {
|
|
77
|
+
const doc = item.document || item;
|
|
78
|
+
return {
|
|
79
|
+
url: doc.url || doc.route || '',
|
|
80
|
+
title: (doc.title || doc.name || '').replace?.(/<\/?mark>/g, '') || '',
|
|
81
|
+
content: (doc.content || doc.description || '').replace?.(/<\/?mark>/g, '')?.substring?.(0, 100) || '',
|
|
82
|
+
description: doc.description || doc.content?.substring?.(0, 100) || '',
|
|
83
|
+
category: doc.category || doc.hierarchy?.lvl0 || '',
|
|
84
|
+
hierarchy: doc.hierarchy,
|
|
85
|
+
route: doc.route,
|
|
86
|
+
parentTitle: doc.parent_title || doc.parentTitle,
|
|
87
|
+
_source: sourceId,
|
|
88
|
+
};
|
|
89
|
+
});
|
|
90
|
+
}, []);
|
|
91
|
+
const fetchFromSource = useCallback(async (source, query, signal) => {
|
|
92
|
+
const minLength = source.minQueryLength ?? 1;
|
|
93
|
+
if (query.length < minLength)
|
|
94
|
+
return [];
|
|
95
|
+
try {
|
|
96
|
+
if (source.storeId) {
|
|
97
|
+
const baseUrl = source.endpoint.replace(/\/$/, '');
|
|
98
|
+
const searchUrl = `${baseUrl}/api/v1/search`;
|
|
99
|
+
const headers = {
|
|
100
|
+
'Content-Type': 'application/json',
|
|
101
|
+
'x-storeid': source.storeId,
|
|
102
|
+
...(source.storeSecret && { 'x-storesecret': source.storeSecret }),
|
|
103
|
+
};
|
|
104
|
+
const response = await fetch(searchUrl, {
|
|
105
|
+
method: 'POST',
|
|
106
|
+
headers,
|
|
107
|
+
body: JSON.stringify({ q: query.trim() || '*', per_page: source.maxResults || 8 }),
|
|
108
|
+
signal,
|
|
109
|
+
});
|
|
110
|
+
if (!response.ok)
|
|
111
|
+
return [];
|
|
112
|
+
const data = await response.json();
|
|
113
|
+
if (source.transformResults) {
|
|
114
|
+
return source.transformResults(data).map((item) => ({ ...item, _source: source.id }));
|
|
115
|
+
}
|
|
116
|
+
return transformPublicSearchResults(data, source.id);
|
|
117
|
+
}
|
|
118
|
+
const url = new URL(source.endpoint);
|
|
119
|
+
url.searchParams.set('query', query);
|
|
120
|
+
url.searchParams.set('limit', String(source.maxResults || 8));
|
|
121
|
+
const headers = { 'Content-Type': 'application/json' };
|
|
122
|
+
if (source.apiKey)
|
|
123
|
+
headers['X-Docs-API-Key'] = source.apiKey;
|
|
124
|
+
const response = await fetch(url.toString(), { method: 'GET', headers, signal });
|
|
125
|
+
if (!response.ok)
|
|
126
|
+
return [];
|
|
127
|
+
const data = await response.json();
|
|
128
|
+
if (source.transformResults) {
|
|
129
|
+
return source.transformResults(data).map((item) => ({ ...item, _source: source.id }));
|
|
130
|
+
}
|
|
131
|
+
return defaultTransform(data, source.id);
|
|
132
|
+
}
|
|
133
|
+
catch (error) {
|
|
134
|
+
if (error instanceof Error && error.name === 'AbortError')
|
|
135
|
+
throw error;
|
|
136
|
+
return [];
|
|
137
|
+
}
|
|
138
|
+
}, [transformPublicSearchResults]);
|
|
139
|
+
const fetchSuggestions = useCallback(async (query) => {
|
|
140
|
+
if (!query.trim()) {
|
|
141
|
+
dispatch({ type: 'SET_GROUPED_SUGGESTIONS', payload: [] });
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
abortControllersRef.current.forEach(c => c.abort());
|
|
145
|
+
abortControllersRef.current.clear();
|
|
146
|
+
dispatch({ type: 'SET_LOADING', payload: true });
|
|
147
|
+
dispatch({ type: 'SET_ERROR', payload: null });
|
|
148
|
+
try {
|
|
149
|
+
const results = await Promise.all(searchSources.map(async (source) => {
|
|
150
|
+
const controller = new AbortController();
|
|
151
|
+
abortControllersRef.current.set(source.id, controller);
|
|
152
|
+
let items = await fetchFromSource(source, query, controller.signal);
|
|
153
|
+
if (processGroupedResults) {
|
|
154
|
+
items = processGroupedResults(source.id, items);
|
|
155
|
+
}
|
|
156
|
+
return { source, items };
|
|
157
|
+
}));
|
|
158
|
+
const groupedResults = results.filter(r => r.items.length > 0);
|
|
159
|
+
dispatch({ type: 'SET_GROUPED_SUGGESTIONS', payload: groupedResults });
|
|
160
|
+
}
|
|
161
|
+
catch (error) {
|
|
162
|
+
if (error instanceof Error && error.name === 'AbortError')
|
|
163
|
+
return;
|
|
164
|
+
dispatch({ type: 'SET_ERROR', payload: error instanceof Error ? error.message : 'Search failed' });
|
|
165
|
+
}
|
|
166
|
+
finally {
|
|
167
|
+
dispatch({ type: 'SET_LOADING', payload: false });
|
|
168
|
+
}
|
|
169
|
+
}, [searchSources, fetchFromSource, processGroupedResults]);
|
|
170
|
+
const search = useCallback((q) => fetchSuggestions(q), [fetchSuggestions]);
|
|
171
|
+
const setQuery = useCallback((query) => {
|
|
172
|
+
dispatch({ type: 'SET_QUERY', payload: query });
|
|
173
|
+
if (debounceTimerRef.current)
|
|
174
|
+
clearTimeout(debounceTimerRef.current);
|
|
175
|
+
debounceTimerRef.current = setTimeout(() => fetchSuggestions(query), debounceMs);
|
|
176
|
+
}, [fetchSuggestions, debounceMs]);
|
|
177
|
+
const selectNext = useCallback(() => dispatch({ type: 'SELECT_NEXT' }), []);
|
|
178
|
+
const selectPrev = useCallback(() => dispatch({ type: 'SELECT_PREV' }), []);
|
|
179
|
+
const setSelectedIndex = useCallback((index) => dispatch({ type: 'SET_SELECTED_INDEX', payload: index }), []);
|
|
180
|
+
const reset = useCallback(() => {
|
|
181
|
+
abortControllersRef.current.forEach(c => c.abort());
|
|
182
|
+
abortControllersRef.current.clear();
|
|
183
|
+
if (debounceTimerRef.current)
|
|
184
|
+
clearTimeout(debounceTimerRef.current);
|
|
185
|
+
dispatch({ type: 'RESET' });
|
|
186
|
+
}, []);
|
|
187
|
+
const getSelectedItem = useCallback(() => {
|
|
188
|
+
const items = state.mode === 'results' ? state.results : state.suggestions;
|
|
189
|
+
return items[state.selectedIndex] || null;
|
|
190
|
+
}, [state.mode, state.results, state.suggestions, state.selectedIndex]);
|
|
191
|
+
useEffect(() => {
|
|
192
|
+
return () => {
|
|
193
|
+
abortControllersRef.current.forEach(c => c.abort());
|
|
194
|
+
abortControllersRef.current.clear();
|
|
195
|
+
if (debounceTimerRef.current)
|
|
196
|
+
clearTimeout(debounceTimerRef.current);
|
|
197
|
+
};
|
|
198
|
+
}, []);
|
|
199
|
+
return {
|
|
200
|
+
...state,
|
|
201
|
+
sources: searchSources,
|
|
202
|
+
setQuery,
|
|
203
|
+
search,
|
|
204
|
+
fetchSuggestions,
|
|
205
|
+
selectNext,
|
|
206
|
+
selectPrev,
|
|
207
|
+
setSelectedIndex,
|
|
208
|
+
reset,
|
|
209
|
+
getSelectedItem,
|
|
210
|
+
};
|
|
211
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
interface UseKeyboardOptions {
|
|
3
|
+
isOpen: boolean;
|
|
4
|
+
onOpen: () => void;
|
|
5
|
+
onClose: () => void;
|
|
6
|
+
onSelectNext: () => void;
|
|
7
|
+
onSelectPrev: () => void;
|
|
8
|
+
onEnter: () => void;
|
|
9
|
+
disableShortcut?: boolean;
|
|
10
|
+
shortcutKey?: string;
|
|
11
|
+
}
|
|
12
|
+
export declare function useKeyboard(options: UseKeyboardOptions): {
|
|
13
|
+
handleModalKeyDown: (event: KeyboardEvent | React.KeyboardEvent) => void;
|
|
14
|
+
};
|
|
15
|
+
export declare function getShortcutText(key?: string): string;
|
|
16
|
+
export {};
|
|
17
|
+
//# sourceMappingURL=useKeyboard.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useKeyboard.d.ts","sourceRoot":"","sources":["../../../src/docsearch/hooks/useKeyboard.ts"],"names":[],"mappings":"AAAA,OAAO,KAAiC,MAAM,OAAO,CAAC;AAEtD,UAAU,kBAAkB;IAC1B,MAAM,EAAE,OAAO,CAAC;IAChB,MAAM,EAAE,MAAM,IAAI,CAAC;IACnB,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,YAAY,EAAE,MAAM,IAAI,CAAC;IACzB,YAAY,EAAE,MAAM,IAAI,CAAC;IACzB,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,wBAAgB,WAAW,CAAC,OAAO,EAAE,kBAAkB;gCAyC3C,aAAa,GAAG,KAAK,CAAC,aAAa;EAyC9C;AAED,wBAAgB,eAAe,CAAC,GAAG,GAAE,MAAY,GAAG,MAAM,CAMzD"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { useCallback, useEffect } from 'react';
|
|
2
|
+
export function useKeyboard(options) {
|
|
3
|
+
const { isOpen, onOpen, onClose, onSelectNext, onSelectPrev, onEnter, disableShortcut = false, shortcutKey = 'k', } = options;
|
|
4
|
+
const handleGlobalKeyDown = useCallback((event) => {
|
|
5
|
+
if (!disableShortcut && (event.metaKey || event.ctrlKey) && event.key.toLowerCase() === shortcutKey) {
|
|
6
|
+
event.preventDefault();
|
|
7
|
+
if (isOpen) {
|
|
8
|
+
onClose();
|
|
9
|
+
}
|
|
10
|
+
else {
|
|
11
|
+
onOpen();
|
|
12
|
+
}
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
if (!disableShortcut && event.key === '/' && !isOpen) {
|
|
16
|
+
const target = event.target;
|
|
17
|
+
const isInput = target.tagName === 'INPUT' ||
|
|
18
|
+
target.tagName === 'TEXTAREA' ||
|
|
19
|
+
target.isContentEditable;
|
|
20
|
+
if (!isInput) {
|
|
21
|
+
event.preventDefault();
|
|
22
|
+
onOpen();
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}, [isOpen, onOpen, onClose, disableShortcut, shortcutKey]);
|
|
26
|
+
const handleModalKeyDown = useCallback((event) => {
|
|
27
|
+
switch (event.key) {
|
|
28
|
+
case 'Escape':
|
|
29
|
+
event.preventDefault();
|
|
30
|
+
onClose();
|
|
31
|
+
break;
|
|
32
|
+
case 'ArrowDown':
|
|
33
|
+
event.preventDefault();
|
|
34
|
+
onSelectNext();
|
|
35
|
+
break;
|
|
36
|
+
case 'ArrowUp':
|
|
37
|
+
event.preventDefault();
|
|
38
|
+
onSelectPrev();
|
|
39
|
+
break;
|
|
40
|
+
case 'Enter':
|
|
41
|
+
event.preventDefault();
|
|
42
|
+
onEnter();
|
|
43
|
+
break;
|
|
44
|
+
case 'Tab':
|
|
45
|
+
if (event.shiftKey) {
|
|
46
|
+
onSelectPrev();
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
onSelectNext();
|
|
50
|
+
}
|
|
51
|
+
event.preventDefault();
|
|
52
|
+
break;
|
|
53
|
+
}
|
|
54
|
+
}, [onClose, onSelectNext, onSelectPrev, onEnter]);
|
|
55
|
+
useEffect(() => {
|
|
56
|
+
document.addEventListener('keydown', handleGlobalKeyDown);
|
|
57
|
+
return () => {
|
|
58
|
+
document.removeEventListener('keydown', handleGlobalKeyDown);
|
|
59
|
+
};
|
|
60
|
+
}, [handleGlobalKeyDown]);
|
|
61
|
+
return {
|
|
62
|
+
handleModalKeyDown,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
export function getShortcutText(key = 'K') {
|
|
66
|
+
if (typeof navigator === 'undefined') {
|
|
67
|
+
return `⌘${key}`;
|
|
68
|
+
}
|
|
69
|
+
const isMac = navigator.platform.toLowerCase().includes('mac');
|
|
70
|
+
return isMac ? `⌘${key}` : `Ctrl+${key}`;
|
|
71
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { DocSearchSuggestion } from '../types';
|
|
2
|
+
export interface UseSeekoraSearchOptions {
|
|
3
|
+
storeId: string;
|
|
4
|
+
storeSecret?: string;
|
|
5
|
+
apiEndpoint?: string;
|
|
6
|
+
maxResults?: number;
|
|
7
|
+
debounceMs?: number;
|
|
8
|
+
analyticsTags?: string[];
|
|
9
|
+
groupField?: string;
|
|
10
|
+
groupSize?: number;
|
|
11
|
+
}
|
|
12
|
+
export interface UseSeekoraSearchResult {
|
|
13
|
+
query: string;
|
|
14
|
+
suggestions: DocSearchSuggestion[];
|
|
15
|
+
isLoading: boolean;
|
|
16
|
+
error: string | null;
|
|
17
|
+
selectedIndex: number;
|
|
18
|
+
setQuery: (query: string) => void;
|
|
19
|
+
selectNext: () => void;
|
|
20
|
+
selectPrev: () => void;
|
|
21
|
+
setSelectedIndex: (index: number) => void;
|
|
22
|
+
reset: () => void;
|
|
23
|
+
getSelectedItem: () => DocSearchSuggestion | null;
|
|
24
|
+
trackDocClick: (hit: DocSearchSuggestion, position: number) => void | Promise<void>;
|
|
25
|
+
}
|
|
26
|
+
export declare function useSeekoraSearch(options: UseSeekoraSearchOptions): UseSeekoraSearchResult;
|
|
27
|
+
//# sourceMappingURL=useSeekoraSearch.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useSeekoraSearch.d.ts","sourceRoot":"","sources":["../../../src/docsearch/hooks/useSeekoraSearch.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,UAAU,CAAC;AAEpD,MAAM,WAAW,uBAAuB;IACtC,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,sBAAsB;IACrC,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,mBAAmB,EAAE,CAAC;IACnC,SAAS,EAAE,OAAO,CAAC;IACnB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,UAAU,EAAE,MAAM,IAAI,CAAC;IACvB,UAAU,EAAE,MAAM,IAAI,CAAC;IACvB,gBAAgB,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1C,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,eAAe,EAAE,MAAM,mBAAmB,GAAG,IAAI,CAAC;IAClD,aAAa,EAAE,CAAC,GAAG,EAAE,mBAAmB,EAAE,QAAQ,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACrF;AA0DD,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,uBAAuB,GAAG,sBAAsB,CA2KzF"}
|