@seekora-ai/docsearch-react 1.0.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/README.md +108 -0
- package/dist/index.d.ts +309 -0
- package/dist/index.esm.js +661 -0
- package/dist/index.esm.js.map +1 -0
- package/dist/index.js +674 -0
- package/dist/index.js.map +1 -0
- package/package.json +61 -0
package/README.md
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# @seekora/docsearch-react
|
|
2
|
+
|
|
3
|
+
React component for Seekora DocSearch - documentation search modal with keyboard navigation.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @seekora/docsearch-react @seekora/docsearch-css
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
import { DocSearch } from '@seekora/docsearch-react';
|
|
15
|
+
import '@seekora/docsearch-css';
|
|
16
|
+
|
|
17
|
+
function App() {
|
|
18
|
+
return (
|
|
19
|
+
<DocSearch
|
|
20
|
+
apiEndpoint="https://api.example.com/v1/docs"
|
|
21
|
+
placeholder="Search documentation..."
|
|
22
|
+
onSelect={(hit) => {
|
|
23
|
+
// Custom navigation logic
|
|
24
|
+
window.location.href = hit.url;
|
|
25
|
+
}}
|
|
26
|
+
/>
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Props
|
|
32
|
+
|
|
33
|
+
| Prop | Type | Default | Description |
|
|
34
|
+
|------|------|---------|-------------|
|
|
35
|
+
| `apiEndpoint` | `string` | required | Base URL for the search API |
|
|
36
|
+
| `apiKey` | `string` | - | Optional API key for authentication |
|
|
37
|
+
| `indexName` | `string` | - | Optional index name |
|
|
38
|
+
| `placeholder` | `string` | "Search documentation..." | Input placeholder |
|
|
39
|
+
| `maxResults` | `number` | 10 | Maximum results |
|
|
40
|
+
| `debounceMs` | `number` | 200 | Debounce delay |
|
|
41
|
+
| `onSelect` | `(hit) => void` | - | Selection callback |
|
|
42
|
+
| `onClose` | `() => void` | - | Modal close callback |
|
|
43
|
+
| `translations` | `object` | - | Custom UI text |
|
|
44
|
+
| `renderButton` | `boolean` | true | Show trigger button |
|
|
45
|
+
| `initialOpen` | `boolean` | false | Start with modal open |
|
|
46
|
+
| `disableShortcut` | `boolean` | false | Disable Cmd+K shortcut |
|
|
47
|
+
| `shortcutKey` | `string` | "k" | Custom shortcut key |
|
|
48
|
+
|
|
49
|
+
## Hooks
|
|
50
|
+
|
|
51
|
+
### useDocSearch
|
|
52
|
+
|
|
53
|
+
Access search state and actions programmatically:
|
|
54
|
+
|
|
55
|
+
```tsx
|
|
56
|
+
import { useDocSearch } from '@seekora/docsearch-react';
|
|
57
|
+
|
|
58
|
+
function CustomSearch() {
|
|
59
|
+
const {
|
|
60
|
+
query,
|
|
61
|
+
setQuery,
|
|
62
|
+
suggestions,
|
|
63
|
+
isLoading,
|
|
64
|
+
selectedIndex,
|
|
65
|
+
selectNext,
|
|
66
|
+
selectPrev,
|
|
67
|
+
} = useDocSearch({
|
|
68
|
+
apiEndpoint: 'https://api.example.com/v1/docs',
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
return (
|
|
72
|
+
<input
|
|
73
|
+
value={query}
|
|
74
|
+
onChange={(e) => setQuery(e.target.value)}
|
|
75
|
+
onKeyDown={(e) => {
|
|
76
|
+
if (e.key === 'ArrowDown') selectNext();
|
|
77
|
+
if (e.key === 'ArrowUp') selectPrev();
|
|
78
|
+
}}
|
|
79
|
+
/>
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### useKeyboard
|
|
85
|
+
|
|
86
|
+
Handle keyboard shortcuts:
|
|
87
|
+
|
|
88
|
+
```tsx
|
|
89
|
+
import { useKeyboard, getShortcutText } from '@seekora/docsearch-react';
|
|
90
|
+
|
|
91
|
+
function MyComponent() {
|
|
92
|
+
const { handleModalKeyDown } = useKeyboard({
|
|
93
|
+
isOpen,
|
|
94
|
+
onOpen: () => setIsOpen(true),
|
|
95
|
+
onClose: () => setIsOpen(false),
|
|
96
|
+
onSelectNext: () => {},
|
|
97
|
+
onSelectPrev: () => {},
|
|
98
|
+
onEnter: () => {},
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
// getShortcutText returns "⌘K" on Mac, "Ctrl+K" on Windows
|
|
102
|
+
console.log(getShortcutText('K'));
|
|
103
|
+
}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## License
|
|
107
|
+
|
|
108
|
+
MIT
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import React$1 from 'react';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Seekora DocSearch Types
|
|
6
|
+
*/
|
|
7
|
+
interface DocSearchHit {
|
|
8
|
+
objectID: string;
|
|
9
|
+
url: string;
|
|
10
|
+
anchor?: string;
|
|
11
|
+
title: string;
|
|
12
|
+
content: string;
|
|
13
|
+
section_level: number;
|
|
14
|
+
hierarchy: {
|
|
15
|
+
lvl0?: string;
|
|
16
|
+
lvl1?: string;
|
|
17
|
+
lvl2?: string;
|
|
18
|
+
lvl3?: string;
|
|
19
|
+
lvl4?: string;
|
|
20
|
+
lvl5?: string;
|
|
21
|
+
};
|
|
22
|
+
_highlightResult?: {
|
|
23
|
+
title?: {
|
|
24
|
+
value: string;
|
|
25
|
+
matchLevel: string;
|
|
26
|
+
};
|
|
27
|
+
content?: {
|
|
28
|
+
value: string;
|
|
29
|
+
matchLevel: string;
|
|
30
|
+
};
|
|
31
|
+
hierarchy?: {
|
|
32
|
+
lvl0?: {
|
|
33
|
+
value: string;
|
|
34
|
+
matchLevel: string;
|
|
35
|
+
};
|
|
36
|
+
lvl1?: {
|
|
37
|
+
value: string;
|
|
38
|
+
matchLevel: string;
|
|
39
|
+
};
|
|
40
|
+
lvl2?: {
|
|
41
|
+
value: string;
|
|
42
|
+
matchLevel: string;
|
|
43
|
+
};
|
|
44
|
+
lvl3?: {
|
|
45
|
+
value: string;
|
|
46
|
+
matchLevel: string;
|
|
47
|
+
};
|
|
48
|
+
};
|
|
49
|
+
};
|
|
50
|
+
_source?: string;
|
|
51
|
+
}
|
|
52
|
+
interface DocSearchSuggestion {
|
|
53
|
+
url: string;
|
|
54
|
+
title: string;
|
|
55
|
+
content?: string;
|
|
56
|
+
description?: string;
|
|
57
|
+
category?: string;
|
|
58
|
+
hierarchy?: {
|
|
59
|
+
lvl0?: string;
|
|
60
|
+
lvl1?: string;
|
|
61
|
+
lvl2?: string;
|
|
62
|
+
};
|
|
63
|
+
highlight?: {
|
|
64
|
+
title?: string;
|
|
65
|
+
content?: string;
|
|
66
|
+
};
|
|
67
|
+
_source?: string;
|
|
68
|
+
route?: string;
|
|
69
|
+
parentTitle?: string;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Search source configuration for multi-source search
|
|
73
|
+
*/
|
|
74
|
+
interface SearchSource {
|
|
75
|
+
/** Unique identifier for this source */
|
|
76
|
+
id: string;
|
|
77
|
+
/** Display name for the source (e.g., "Pages", "Documentation") */
|
|
78
|
+
name: string;
|
|
79
|
+
/** API endpoint for this source */
|
|
80
|
+
endpoint: string;
|
|
81
|
+
/** Optional API key for this source */
|
|
82
|
+
apiKey?: string;
|
|
83
|
+
/** Maximum results from this source */
|
|
84
|
+
maxResults?: number;
|
|
85
|
+
/** Minimum query length to trigger search for this source */
|
|
86
|
+
minQueryLength?: number;
|
|
87
|
+
/** Transform function to normalize results */
|
|
88
|
+
transformResults?: (data: any) => DocSearchSuggestion[];
|
|
89
|
+
/** Whether to open links in new tab */
|
|
90
|
+
openInNewTab?: boolean;
|
|
91
|
+
/** Icon for this source (optional) */
|
|
92
|
+
icon?: string;
|
|
93
|
+
}
|
|
94
|
+
interface DocSearchResponse {
|
|
95
|
+
hits: DocSearchHit[];
|
|
96
|
+
query: string;
|
|
97
|
+
total?: number;
|
|
98
|
+
page?: number;
|
|
99
|
+
nbPages?: number;
|
|
100
|
+
}
|
|
101
|
+
interface DocSearchSuggestionsResponse {
|
|
102
|
+
suggestions: DocSearchSuggestion[];
|
|
103
|
+
query: string;
|
|
104
|
+
}
|
|
105
|
+
interface DocSearchProps {
|
|
106
|
+
/** API endpoint for search (e.g., "https://api.example.com/v1/docs") - used if sources not provided */
|
|
107
|
+
apiEndpoint?: string;
|
|
108
|
+
/** Optional API key for authentication - used if sources not provided */
|
|
109
|
+
apiKey?: string;
|
|
110
|
+
/** Multiple search sources for unified search */
|
|
111
|
+
sources?: SearchSource[];
|
|
112
|
+
/** Index name (optional, some APIs may not need this) */
|
|
113
|
+
indexName?: string;
|
|
114
|
+
/** Placeholder text for the search input */
|
|
115
|
+
placeholder?: string;
|
|
116
|
+
/** Maximum number of results to show */
|
|
117
|
+
maxResults?: number;
|
|
118
|
+
/** Debounce delay in milliseconds */
|
|
119
|
+
debounceMs?: number;
|
|
120
|
+
/** Callback when a result is selected */
|
|
121
|
+
onSelect?: (hit: DocSearchHit | DocSearchSuggestion) => void;
|
|
122
|
+
/** Callback when the modal is closed */
|
|
123
|
+
onClose?: () => void;
|
|
124
|
+
/** Custom translations */
|
|
125
|
+
translations?: DocSearchTranslations;
|
|
126
|
+
/** Whether to render the button trigger */
|
|
127
|
+
renderButton?: boolean;
|
|
128
|
+
/** Custom button component */
|
|
129
|
+
buttonComponent?: React.ComponentType<DocSearchButtonProps>;
|
|
130
|
+
/** Initial open state */
|
|
131
|
+
initialOpen?: boolean;
|
|
132
|
+
/** Disable keyboard shortcut */
|
|
133
|
+
disableShortcut?: boolean;
|
|
134
|
+
/** Custom keyboard shortcut key (default: 'k') */
|
|
135
|
+
shortcutKey?: string;
|
|
136
|
+
}
|
|
137
|
+
interface DocSearchButtonProps {
|
|
138
|
+
onClick: () => void;
|
|
139
|
+
placeholder?: string;
|
|
140
|
+
}
|
|
141
|
+
interface DocSearchTranslations {
|
|
142
|
+
buttonText?: string;
|
|
143
|
+
buttonAriaLabel?: string;
|
|
144
|
+
searchPlaceholder?: string;
|
|
145
|
+
noResultsText?: string;
|
|
146
|
+
loadingText?: string;
|
|
147
|
+
errorText?: string;
|
|
148
|
+
footerText?: string;
|
|
149
|
+
closeText?: string;
|
|
150
|
+
searchByText?: string;
|
|
151
|
+
}
|
|
152
|
+
interface DocSearchState {
|
|
153
|
+
query: string;
|
|
154
|
+
results: DocSearchHit[];
|
|
155
|
+
suggestions: DocSearchSuggestion[];
|
|
156
|
+
isLoading: boolean;
|
|
157
|
+
error: string | null;
|
|
158
|
+
selectedIndex: number;
|
|
159
|
+
mode: 'suggestions' | 'results';
|
|
160
|
+
}
|
|
161
|
+
type DocSearchAction = {
|
|
162
|
+
type: 'SET_QUERY';
|
|
163
|
+
payload: string;
|
|
164
|
+
} | {
|
|
165
|
+
type: 'SET_RESULTS';
|
|
166
|
+
payload: DocSearchHit[];
|
|
167
|
+
} | {
|
|
168
|
+
type: 'SET_SUGGESTIONS';
|
|
169
|
+
payload: DocSearchSuggestion[];
|
|
170
|
+
} | {
|
|
171
|
+
type: 'SET_LOADING';
|
|
172
|
+
payload: boolean;
|
|
173
|
+
} | {
|
|
174
|
+
type: 'SET_ERROR';
|
|
175
|
+
payload: string | null;
|
|
176
|
+
} | {
|
|
177
|
+
type: 'SET_SELECTED_INDEX';
|
|
178
|
+
payload: number;
|
|
179
|
+
} | {
|
|
180
|
+
type: 'SELECT_NEXT';
|
|
181
|
+
} | {
|
|
182
|
+
type: 'SELECT_PREV';
|
|
183
|
+
} | {
|
|
184
|
+
type: 'SET_MODE';
|
|
185
|
+
payload: 'suggestions' | 'results';
|
|
186
|
+
} | {
|
|
187
|
+
type: 'RESET';
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
declare function DocSearch({ apiEndpoint, apiKey, sources, indexName, placeholder, maxResults, debounceMs, onSelect, onClose, translations, renderButton, buttonComponent: ButtonComponent, initialOpen, disableShortcut, shortcutKey, }: DocSearchProps): react_jsx_runtime.JSX.Element;
|
|
191
|
+
|
|
192
|
+
declare function DocSearchButton({ onClick, placeholder, }: DocSearchButtonProps): react_jsx_runtime.JSX.Element;
|
|
193
|
+
|
|
194
|
+
interface ModalProps {
|
|
195
|
+
isOpen: boolean;
|
|
196
|
+
onClose: () => void;
|
|
197
|
+
children: React$1.ReactNode;
|
|
198
|
+
}
|
|
199
|
+
declare function Modal({ isOpen, onClose, children }: ModalProps): react_jsx_runtime.JSX.Element | null;
|
|
200
|
+
|
|
201
|
+
interface SearchBoxProps {
|
|
202
|
+
value: string;
|
|
203
|
+
onChange: (value: string) => void;
|
|
204
|
+
onKeyDown: (event: React$1.KeyboardEvent) => void;
|
|
205
|
+
placeholder?: string;
|
|
206
|
+
isLoading?: boolean;
|
|
207
|
+
onClear?: () => void;
|
|
208
|
+
}
|
|
209
|
+
declare function SearchBox({ value, onChange, onKeyDown, placeholder, isLoading, onClear, }: SearchBoxProps): react_jsx_runtime.JSX.Element;
|
|
210
|
+
|
|
211
|
+
interface GroupedHits {
|
|
212
|
+
source: SearchSource;
|
|
213
|
+
items: DocSearchSuggestion[];
|
|
214
|
+
}
|
|
215
|
+
interface ResultsProps {
|
|
216
|
+
hits: (DocSearchHit | DocSearchSuggestion)[];
|
|
217
|
+
groupedHits?: GroupedHits[];
|
|
218
|
+
selectedIndex: number;
|
|
219
|
+
onSelect: (hit: DocSearchHit | DocSearchSuggestion) => void;
|
|
220
|
+
onHover: (index: number) => void;
|
|
221
|
+
query: string;
|
|
222
|
+
isLoading: boolean;
|
|
223
|
+
error: string | null;
|
|
224
|
+
translations?: DocSearchTranslations;
|
|
225
|
+
sources?: SearchSource[];
|
|
226
|
+
}
|
|
227
|
+
declare function Results({ hits, groupedHits, selectedIndex, onSelect, onHover, query, isLoading, error, translations, sources, }: ResultsProps): react_jsx_runtime.JSX.Element;
|
|
228
|
+
|
|
229
|
+
interface HitProps {
|
|
230
|
+
hit: DocSearchHit | DocSearchSuggestion;
|
|
231
|
+
isSelected: boolean;
|
|
232
|
+
onClick: () => void;
|
|
233
|
+
onMouseEnter: () => void;
|
|
234
|
+
openInNewTab?: boolean;
|
|
235
|
+
}
|
|
236
|
+
declare function Hit({ hit, isSelected, onClick, onMouseEnter, openInNewTab }: HitProps): react_jsx_runtime.JSX.Element;
|
|
237
|
+
|
|
238
|
+
interface HighlightProps {
|
|
239
|
+
value: string;
|
|
240
|
+
highlightedValue?: string;
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Renders text with highlighted matches.
|
|
244
|
+
* Expects highlightedValue to contain <mark> tags around matches.
|
|
245
|
+
*/
|
|
246
|
+
declare function Highlight({ value, highlightedValue }: HighlightProps): react_jsx_runtime.JSX.Element;
|
|
247
|
+
/**
|
|
248
|
+
* Truncate content and highlight the matched portion.
|
|
249
|
+
*/
|
|
250
|
+
declare function truncateAroundMatch(content: string, maxLength?: number): string;
|
|
251
|
+
|
|
252
|
+
interface FooterProps {
|
|
253
|
+
translations?: DocSearchTranslations;
|
|
254
|
+
}
|
|
255
|
+
declare function Footer({ translations }: FooterProps): react_jsx_runtime.JSX.Element;
|
|
256
|
+
|
|
257
|
+
interface UseDocSearchOptions {
|
|
258
|
+
/** Single API endpoint (legacy mode) */
|
|
259
|
+
apiEndpoint?: string;
|
|
260
|
+
/** Single API key (legacy mode) */
|
|
261
|
+
apiKey?: string;
|
|
262
|
+
/** Multiple search sources */
|
|
263
|
+
sources?: SearchSource[];
|
|
264
|
+
maxResults?: number;
|
|
265
|
+
debounceMs?: number;
|
|
266
|
+
}
|
|
267
|
+
declare function useDocSearch(options: UseDocSearchOptions): {
|
|
268
|
+
sources: SearchSource[];
|
|
269
|
+
setQuery: (query: string) => void;
|
|
270
|
+
search: (query: string) => Promise<void>;
|
|
271
|
+
fetchSuggestions: (query: string) => Promise<void>;
|
|
272
|
+
selectNext: () => void;
|
|
273
|
+
selectPrev: () => void;
|
|
274
|
+
setSelectedIndex: (index: number) => void;
|
|
275
|
+
reset: () => void;
|
|
276
|
+
getSelectedItem: () => DocSearchHit | DocSearchSuggestion | null;
|
|
277
|
+
groupedSuggestions: {
|
|
278
|
+
source: SearchSource;
|
|
279
|
+
items: DocSearchSuggestion[];
|
|
280
|
+
}[];
|
|
281
|
+
query: string;
|
|
282
|
+
results: DocSearchHit[];
|
|
283
|
+
suggestions: DocSearchSuggestion[];
|
|
284
|
+
isLoading: boolean;
|
|
285
|
+
error: string | null;
|
|
286
|
+
selectedIndex: number;
|
|
287
|
+
mode: "suggestions" | "results";
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
interface UseKeyboardOptions {
|
|
291
|
+
isOpen: boolean;
|
|
292
|
+
onOpen: () => void;
|
|
293
|
+
onClose: () => void;
|
|
294
|
+
onSelectNext: () => void;
|
|
295
|
+
onSelectPrev: () => void;
|
|
296
|
+
onEnter: () => void;
|
|
297
|
+
disableShortcut?: boolean;
|
|
298
|
+
shortcutKey?: string;
|
|
299
|
+
}
|
|
300
|
+
declare function useKeyboard(options: UseKeyboardOptions): {
|
|
301
|
+
handleModalKeyDown: (event: KeyboardEvent | React.KeyboardEvent) => void;
|
|
302
|
+
};
|
|
303
|
+
/**
|
|
304
|
+
* Get the keyboard shortcut display text based on platform
|
|
305
|
+
*/
|
|
306
|
+
declare function getShortcutText(key?: string): string;
|
|
307
|
+
|
|
308
|
+
export { DocSearch, DocSearchButton, Footer, Highlight, Hit, Modal, Results, SearchBox, getShortcutText, truncateAroundMatch, useDocSearch, useKeyboard };
|
|
309
|
+
export type { DocSearchAction, DocSearchButtonProps, DocSearchHit, DocSearchProps, DocSearchResponse, DocSearchState, DocSearchSuggestion, DocSearchSuggestionsResponse, DocSearchTranslations, SearchSource };
|