@@ -18,11 +18,31 @@ import { HitsList } from "./hits-list";
1818import { SparklesIcon , SearchIcon , AlgoliaLogo } from "./icons" ;
1919
2020import "./index.css" ;
21- import { API_KEY , APPLICATION_ID , INDEX_NAME } from "./constants" ;
2221import { Modal } from "./search-modal" ;
2322import { SearchButton } from "./search-button" ;
2423
25- const searchClient = algoliasearch ( APPLICATION_ID , API_KEY ) ;
24+ export interface SearchExperienceConfig {
25+ /** Algolia Application ID (required) */
26+ applicationId : string ;
27+ /** Algolia API Key (required) */
28+ apiKey : string ;
29+ /** Algolia Index Name (required) */
30+ indexName : string ;
31+ /** AI Assistant ID (required for chat functionality) */
32+ assistantId : string ;
33+ /** Base URL for AI chat API (optional, defaults to beta endpoint) */
34+ baseAskaiUrl ?: string ;
35+ /** Placeholder text for search input (optional, defaults to "What are you looking for?") */
36+ placeholder ?: string ;
37+ /** Number of hits per page (optional, defaults to 8) */
38+ hitsPerPage ?: number ;
39+ /** Keyboard shortcut to open search (optional, defaults to "cmd+k") */
40+ keyboardShortcut ?: string ;
41+ /** Custom search button text (optional) */
42+ buttonText ?: string ;
43+ /** Custom search button props (optional) */
44+ buttonProps ?: React . ComponentProps < typeof SearchButton > ;
45+ }
2646
2747interface SearchBoxProps {
2848 query ?: string ;
@@ -89,9 +109,10 @@ interface ResultsPanelProps {
89109 initialQuestion ?: string ;
90110 selectedIndex : number ;
91111 refine : ( query : string ) => void ;
112+ config : SearchExperienceConfig ;
92113}
93114
94- const ResultsPanel = memo ( function ResultsPanel ( { showChat, inputRef, setShowChat, query, initialQuestion, selectedIndex, refine } : ResultsPanelProps ) {
115+ const ResultsPanel = memo ( function ResultsPanel ( { showChat, inputRef, setShowChat, query, initialQuestion, selectedIndex, refine, config } : ResultsPanelProps ) {
95116 const { items } = useHits ( ) ;
96117 const containerRef = useRef < HTMLDivElement > ( null ) ;
97118
@@ -114,7 +135,7 @@ const ResultsPanel = memo(function ResultsPanel({ showChat, inputRef, setShowCha
114135 } , [ selectedIndex , showChat , items . length ] ) ;
115136
116137 if ( showChat ) {
117- return < ChatWidget initialQuestion = { initialQuestion } inputRef = { inputRef } /> ;
138+ return < ChatWidget initialQuestion = { initialQuestion } inputRef = { inputRef } config = { config } /> ;
118139 }
119140
120141 return (
@@ -135,9 +156,10 @@ const ResultsPanel = memo(function ResultsPanel({ showChat, inputRef, setShowCha
135156
136157interface PopSearchProps {
137158 onClose ?: ( ) => void ;
159+ config : SearchExperienceConfig ;
138160}
139161
140- export function PopSearch ( { onClose } : PopSearchProps = { } ) {
162+ export function PopSearch ( { onClose, config } : PopSearchProps ) {
141163 const { query, refine } = useSearchBox ( ) ;
142164 const inputRef = useRef < HTMLInputElement | null > ( null ) ;
143165
@@ -162,11 +184,11 @@ export function PopSearch({ onClose }: PopSearchProps = {}) {
162184
163185 return (
164186 < >
165- < Configure hitsPerPage = { 8 } />
187+ < Configure hitsPerPage = { config . hitsPerPage || 8 } />
166188 < div className = "search-panel" >
167189 < SearchBox
168190 query = { query }
169- placeholder = "What are you looking for?"
191+ placeholder = { config . placeholder || "What are you looking for?" }
170192 className = "qs-searchbox-form"
171193 refine = { refine }
172194 showChat = { showChat }
@@ -190,6 +212,7 @@ export function PopSearch({ onClose }: PopSearchProps = {}) {
190212 initialQuestion = { initialQuestion }
191213 selectedIndex = { selectedIndex }
192214 refine = { refine }
215+ config = { config }
193216 />
194217 ) }
195218 { noResults && query && ! showChat && (
@@ -238,36 +261,51 @@ const Footer = memo(function Footer({ showResultsPanel, showChat }: { showResult
238261} ) ;
239262
240263
241- export default function SearchExperience ( ) {
242-
264+ export default function SearchExperience ( config : SearchExperienceConfig ) {
265+ const searchClient = algoliasearch ( config . applicationId , config . apiKey ) ;
243266 const [ isModalOpen , setIsModalOpen ] = useState ( false ) ;
244267
245268 const openModal = ( ) => setIsModalOpen ( true ) ;
246269 const closeModal = ( ) => setIsModalOpen ( false ) ;
247270
271+ // Parse keyboard shortcut (defaults to cmd+k)
272+ const shortcut = config . keyboardShortcut || "cmd+k" ;
273+ const [ modifierKey , key ] = shortcut . toLowerCase ( ) . split ( "+" ) ;
274+
248275 useEffect ( ( ) => {
249276 const handleKeyDown = ( event : KeyboardEvent ) => {
250- if ( ( event . metaKey || event . ctrlKey ) && event . key === 'k' ) {
277+ const isModifierPressed = modifierKey === "cmd"
278+ ? ( event . metaKey || event . ctrlKey )
279+ : event . getModifierState ( modifierKey . charAt ( 0 ) . toUpperCase ( ) + modifierKey . slice ( 1 ) ) ;
280+
281+ if ( isModifierPressed && event . key . toLowerCase ( ) === key ) {
251282 event . preventDefault ( ) ;
252283 openModal ( ) ;
253284 }
254285 } ;
255286
256287 document . addEventListener ( 'keydown' , handleKeyDown ) ;
257288 return ( ) => document . removeEventListener ( 'keydown' , handleKeyDown ) ;
258- } , [ ] ) ;
289+ } , [ modifierKey , key ] ) ;
290+
291+ const buttonProps = {
292+ ...config . buttonProps ,
293+ onClick : openModal ,
294+ } ;
259295
260296 return (
261297 < >
262- < SearchButton onClick = { openModal } />
298+ < SearchButton { ...buttonProps } >
299+ { config . buttonText }
300+ </ SearchButton >
263301 < Modal isOpen = { isModalOpen } onClose = { closeModal } >
264302 < InstantSearch
265303 searchClient = { searchClient }
266- indexName = { INDEX_NAME }
304+ indexName = { config . indexName }
267305 future = { { preserveSharedStateOnUnmount : true } }
268306 insights
269307 >
270- < PopSearch onClose = { closeModal } />
308+ < PopSearch onClose = { closeModal } config = { config } />
271309 </ InstantSearch >
272310 </ Modal >
273311 </ >
0 commit comments