@seekora-ai/docsearch-react 0.1.1

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 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
@@ -0,0 +1,395 @@
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
+ lvl3?: string;
63
+ lvl4?: string;
64
+ lvl5?: string;
65
+ };
66
+ highlight?: {
67
+ title?: string;
68
+ content?: string;
69
+ };
70
+ _source?: string;
71
+ route?: string;
72
+ parentTitle?: string;
73
+ type?: string;
74
+ anchor?: string;
75
+ }
76
+ /**
77
+ * Search source configuration for multi-source search
78
+ */
79
+ interface SearchSource {
80
+ /** Unique identifier for this source */
81
+ id: string;
82
+ /** Display name for the source (e.g., "Pages", "Documentation") */
83
+ name: string;
84
+ /** API endpoint for this source */
85
+ endpoint: string;
86
+ /** Optional API key for this source */
87
+ apiKey?: string;
88
+ /** Maximum results from this source */
89
+ maxResults?: number;
90
+ /** Minimum query length to trigger search for this source */
91
+ minQueryLength?: number;
92
+ /** Transform function to normalize results */
93
+ transformResults?: (data: any) => DocSearchSuggestion[];
94
+ /** Whether to open links in new tab */
95
+ openInNewTab?: boolean;
96
+ /** Icon for this source (optional) */
97
+ icon?: string;
98
+ }
99
+ interface DocSearchResponse {
100
+ hits: DocSearchHit[];
101
+ query: string;
102
+ total?: number;
103
+ page?: number;
104
+ nbPages?: number;
105
+ }
106
+ interface DocSearchSuggestionsResponse {
107
+ suggestions: DocSearchSuggestion[];
108
+ query: string;
109
+ }
110
+ interface DocSearchProps {
111
+ /**
112
+ * Seekora Store ID for search
113
+ * When provided, uses the Seekora SDK for searching
114
+ */
115
+ storeId?: string;
116
+ /**
117
+ * Seekora Store Secret for authentication
118
+ * Optional for public stores
119
+ */
120
+ storeSecret?: string;
121
+ /**
122
+ * Seekora API endpoint override
123
+ * Defaults to production (https://api.seekora.com/api)
124
+ * Can be 'local', 'stage', 'production', or a custom URL
125
+ */
126
+ seekoraApiEndpoint?: string;
127
+ /**
128
+ * @deprecated Use storeId and storeSecret instead
129
+ * Legacy API endpoint for search (e.g., "https://api.example.com/v1/docs") - used if sources not provided
130
+ */
131
+ apiEndpoint?: string;
132
+ /**
133
+ * @deprecated Use storeId and storeSecret instead
134
+ * Optional API key for authentication - used if sources not provided
135
+ */
136
+ apiKey?: string;
137
+ /** Multiple search sources for unified search */
138
+ sources?: SearchSource[];
139
+ /** Index name (optional, some APIs may not need this) */
140
+ indexName?: string;
141
+ /** Placeholder text for the search input */
142
+ placeholder?: string;
143
+ /** Maximum number of results to show */
144
+ maxResults?: number;
145
+ /** Debounce delay in milliseconds */
146
+ debounceMs?: number;
147
+ /** Callback when a result is selected */
148
+ onSelect?: (hit: DocSearchHit | DocSearchSuggestion) => void;
149
+ /** Callback when the modal is closed */
150
+ onClose?: () => void;
151
+ /** Custom translations */
152
+ translations?: DocSearchTranslations;
153
+ /** Whether to render the button trigger */
154
+ renderButton?: boolean;
155
+ /** Custom button component */
156
+ buttonComponent?: React.ComponentType<DocSearchButtonProps>;
157
+ /** Initial open state */
158
+ initialOpen?: boolean;
159
+ /** Disable keyboard shortcut */
160
+ disableShortcut?: boolean;
161
+ /** Custom keyboard shortcut key (default: 'k') */
162
+ shortcutKey?: string;
163
+ }
164
+ interface DocSearchButtonProps {
165
+ onClick: () => void;
166
+ placeholder?: string;
167
+ }
168
+ interface DocSearchTranslations {
169
+ buttonText?: string;
170
+ buttonAriaLabel?: string;
171
+ searchPlaceholder?: string;
172
+ noResultsText?: string;
173
+ loadingText?: string;
174
+ errorText?: string;
175
+ footerText?: string;
176
+ closeText?: string;
177
+ searchByText?: string;
178
+ }
179
+ interface DocSearchState {
180
+ query: string;
181
+ results: DocSearchHit[];
182
+ suggestions: DocSearchSuggestion[];
183
+ isLoading: boolean;
184
+ error: string | null;
185
+ selectedIndex: number;
186
+ mode: 'suggestions' | 'results';
187
+ }
188
+ type DocSearchAction = {
189
+ type: 'SET_QUERY';
190
+ payload: string;
191
+ } | {
192
+ type: 'SET_RESULTS';
193
+ payload: DocSearchHit[];
194
+ } | {
195
+ type: 'SET_SUGGESTIONS';
196
+ payload: DocSearchSuggestion[];
197
+ } | {
198
+ type: 'SET_LOADING';
199
+ payload: boolean;
200
+ } | {
201
+ type: 'SET_ERROR';
202
+ payload: string | null;
203
+ } | {
204
+ type: 'SET_SELECTED_INDEX';
205
+ payload: number;
206
+ } | {
207
+ type: 'SELECT_NEXT';
208
+ } | {
209
+ type: 'SELECT_PREV';
210
+ } | {
211
+ type: 'SET_MODE';
212
+ payload: 'suggestions' | 'results';
213
+ } | {
214
+ type: 'RESET';
215
+ };
216
+
217
+ declare function DocSearch({ storeId, storeSecret, seekoraApiEndpoint, apiEndpoint, apiKey, sources, placeholder, maxResults, debounceMs, onSelect, onClose, translations, renderButton, buttonComponent: ButtonComponent, initialOpen, disableShortcut, shortcutKey, }: DocSearchProps): react_jsx_runtime.JSX.Element;
218
+
219
+ declare function DocSearchButton({ onClick, placeholder, }: DocSearchButtonProps): react_jsx_runtime.JSX.Element;
220
+
221
+ interface ModalProps {
222
+ isOpen: boolean;
223
+ onClose: () => void;
224
+ children: React$1.ReactNode;
225
+ }
226
+ /**
227
+ * Modal component that renders via React Portal to document.body.
228
+ * This ensures the search overlay renders independently of its integration context,
229
+ * avoiding clipping/positioning issues from parent overflow, transform, or stacking contexts.
230
+ */
231
+ declare function Modal({ isOpen, onClose, children }: ModalProps): React$1.ReactPortal | null;
232
+
233
+ interface SearchBoxProps {
234
+ value: string;
235
+ onChange: (value: string) => void;
236
+ onKeyDown: (event: React$1.KeyboardEvent) => void;
237
+ placeholder?: string;
238
+ isLoading?: boolean;
239
+ onClear?: () => void;
240
+ }
241
+ declare function SearchBox({ value, onChange, onKeyDown, placeholder, isLoading, onClear, }: SearchBoxProps): react_jsx_runtime.JSX.Element;
242
+
243
+ interface GroupedHits {
244
+ source: SearchSource;
245
+ items: DocSearchSuggestion[];
246
+ }
247
+ interface ResultsProps {
248
+ hits: (DocSearchHit | DocSearchSuggestion)[];
249
+ groupedHits?: GroupedHits[];
250
+ selectedIndex: number;
251
+ onSelect: (hit: DocSearchHit | DocSearchSuggestion) => void;
252
+ onHover: (index: number) => void;
253
+ query: string;
254
+ isLoading: boolean;
255
+ error: string | null;
256
+ translations?: DocSearchTranslations;
257
+ sources?: SearchSource[];
258
+ }
259
+ declare function Results({ hits, groupedHits, selectedIndex, onSelect, onHover, query, isLoading, error, translations, sources: _sources, }: ResultsProps): react_jsx_runtime.JSX.Element;
260
+
261
+ interface HitProps {
262
+ hit: DocSearchHit | DocSearchSuggestion;
263
+ isSelected: boolean;
264
+ onClick: () => void;
265
+ onMouseEnter: () => void;
266
+ openInNewTab?: boolean;
267
+ isChild?: boolean;
268
+ isLastChild?: boolean;
269
+ /** Hierarchy type from API (lvl1, lvl2, lvl3, etc.) - source of truth for icon and display */
270
+ hierarchyType?: string;
271
+ }
272
+ declare function Hit({ hit, isSelected, onClick, onMouseEnter, openInNewTab, isChild, isLastChild, hierarchyType }: HitProps): react_jsx_runtime.JSX.Element;
273
+
274
+ interface HighlightProps {
275
+ value: string;
276
+ highlightedValue?: string;
277
+ }
278
+ /**
279
+ * Renders text with highlighted matches.
280
+ * Expects highlightedValue to contain <mark> tags around matches.
281
+ */
282
+ declare function Highlight({ value, highlightedValue }: HighlightProps): react_jsx_runtime.JSX.Element;
283
+ /**
284
+ * Truncate content and highlight the matched portion.
285
+ */
286
+ declare function truncateAroundMatch(content: string, maxLength?: number): string;
287
+
288
+ interface FooterProps {
289
+ translations?: DocSearchTranslations;
290
+ }
291
+ declare function Footer({ translations }: FooterProps): react_jsx_runtime.JSX.Element;
292
+
293
+ interface UseDocSearchOptions {
294
+ /** Single API endpoint (legacy mode) */
295
+ apiEndpoint?: string;
296
+ /** Single API key (legacy mode) */
297
+ apiKey?: string;
298
+ /** Multiple search sources */
299
+ sources?: SearchSource[];
300
+ maxResults?: number;
301
+ debounceMs?: number;
302
+ }
303
+ declare function useDocSearch(options: UseDocSearchOptions): {
304
+ sources: SearchSource[];
305
+ setQuery: (query: string) => void;
306
+ search: (query: string) => Promise<void>;
307
+ fetchSuggestions: (query: string) => Promise<void>;
308
+ selectNext: () => void;
309
+ selectPrev: () => void;
310
+ setSelectedIndex: (index: number) => void;
311
+ reset: () => void;
312
+ getSelectedItem: () => DocSearchHit | DocSearchSuggestion | null;
313
+ groupedSuggestions: {
314
+ source: SearchSource;
315
+ items: DocSearchSuggestion[];
316
+ }[];
317
+ query: string;
318
+ results: DocSearchHit[];
319
+ suggestions: DocSearchSuggestion[];
320
+ isLoading: boolean;
321
+ error: string | null;
322
+ selectedIndex: number;
323
+ mode: "suggestions" | "results";
324
+ };
325
+
326
+ interface UseSeekoraSearchOptions {
327
+ /** Seekora Store ID */
328
+ storeId: string;
329
+ /** Seekora Store Secret (optional for public stores) */
330
+ storeSecret?: string;
331
+ /** API endpoint override - can be 'local', 'stage', 'production', or a custom URL */
332
+ apiEndpoint?: string;
333
+ /** Maximum results to return */
334
+ maxResults?: number;
335
+ /** Debounce delay in milliseconds */
336
+ debounceMs?: number;
337
+ /** Analytics tags to include with searches */
338
+ analyticsTags?: string[];
339
+ /** Group results by this field (e.g., 'hierarchy.lvl0' for doc search) */
340
+ groupField?: string;
341
+ /** Number of results per group */
342
+ groupSize?: number;
343
+ }
344
+ interface UseSeekoraSearchResult {
345
+ /** Current query */
346
+ query: string;
347
+ /** Search suggestions/results */
348
+ suggestions: DocSearchSuggestion[];
349
+ /** Whether a search is in progress */
350
+ isLoading: boolean;
351
+ /** Error message if any */
352
+ error: string | null;
353
+ /** Currently selected index for keyboard navigation */
354
+ selectedIndex: number;
355
+ /** Set the query and trigger search */
356
+ setQuery: (query: string) => void;
357
+ /** Select next result */
358
+ selectNext: () => void;
359
+ /** Select previous result */
360
+ selectPrev: () => void;
361
+ /** Set selected index directly */
362
+ setSelectedIndex: (index: number) => void;
363
+ /** Reset state */
364
+ reset: () => void;
365
+ /** Get currently selected item */
366
+ getSelectedItem: () => DocSearchSuggestion | null;
367
+ }
368
+ /**
369
+ * Hook for searching using the Seekora SDK
370
+ *
371
+ * This hook provides a seamless integration with the Seekora Search SDK,
372
+ * handling client initialization, debouncing, and result transformation.
373
+ */
374
+ declare function useSeekoraSearch(options: UseSeekoraSearchOptions): UseSeekoraSearchResult;
375
+
376
+ interface UseKeyboardOptions {
377
+ isOpen: boolean;
378
+ onOpen: () => void;
379
+ onClose: () => void;
380
+ onSelectNext: () => void;
381
+ onSelectPrev: () => void;
382
+ onEnter: () => void;
383
+ disableShortcut?: boolean;
384
+ shortcutKey?: string;
385
+ }
386
+ declare function useKeyboard(options: UseKeyboardOptions): {
387
+ handleModalKeyDown: (event: KeyboardEvent | React.KeyboardEvent) => void;
388
+ };
389
+ /**
390
+ * Get the keyboard shortcut display text based on platform
391
+ */
392
+ declare function getShortcutText(key?: string): string;
393
+
394
+ export { DocSearch, DocSearchButton, Footer, Highlight, Hit, Modal, Results, SearchBox, getShortcutText, truncateAroundMatch, useDocSearch, useKeyboard, useSeekoraSearch };
395
+ export type { DocSearchAction, DocSearchButtonProps, DocSearchHit, DocSearchProps, DocSearchResponse, DocSearchState, DocSearchSuggestion, DocSearchSuggestionsResponse, DocSearchTranslations, SearchSource, UseSeekoraSearchOptions, UseSeekoraSearchResult };