Skip to content

Commit e92637d

Browse files
fix: update component
1 parent 51359d2 commit e92637d

3 files changed

Lines changed: 78 additions & 29 deletions

File tree

src/App.tsx

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import React from "react";
22

3-
import SearchExperience from "./experiences/pop-search";
3+
import SearchExperience, { SearchExperienceConfig } from "./experiences/pop-search";
4+
import { API_KEY, APPLICATION_ID, INDEX_NAME, ASSISTANT_ID, BASE_ASKAI_URL } from "./experiences/pop-search/constants";
45

56
import "./App.css";
67
import "./experiences/pop-search/index.css";
@@ -18,7 +19,15 @@ function Landing() {
1819
<h2>Pop Search</h2>
1920
<p>A compact button that opens search in a modal.</p>
2021
<div className="qs-modal-demo">
21-
<SearchExperience />
22+
<SearchExperience
23+
applicationId={APPLICATION_ID}
24+
apiKey={API_KEY}
25+
indexName={INDEX_NAME}
26+
assistantId={ASSISTANT_ID}
27+
baseAskaiUrl={BASE_ASKAI_URL}
28+
placeholder="What are you looking for?"
29+
buttonText="Search..."
30+
/>
2231
</div>
2332
</article>
2433
</div>

src/experiences/pop-search/chat.tsx

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,6 @@ import {
1515
UIDataTypes,
1616
UIMessagePart,
1717
} from "ai";
18-
import {
19-
API_KEY,
20-
APPLICATION_ID,
21-
ASSISTANT_ID,
22-
BASE_ASKAI_URL,
23-
INDEX_NAME,
24-
} from "./constants";
2518
import { getValidToken } from "./askai";
2619
import { Streamdown } from "streamdown";
2720
import { CopyIcon, LikeIcon, DislikeIcon, SearchIcon, BrainIcon, CheckIcon } from "./icons";
@@ -51,6 +44,13 @@ function useKeyboardListener(
5144
interface ChatWidgetProps {
5245
initialQuestion?: string;
5346
inputRef: React.RefObject<HTMLInputElement | null>;
47+
config: {
48+
applicationId: string;
49+
apiKey: string;
50+
indexName: string;
51+
assistantId: string;
52+
baseAskaiUrl?: string;
53+
};
5454
}
5555

5656
export interface SearchIndexTool {
@@ -87,21 +87,23 @@ interface Exchange {
8787
export const ChatWidget = memo(function ChatWidget({
8888
initialQuestion,
8989
inputRef,
90+
config,
9091
}: ChatWidgetProps) {
9192
const { copyText } = useClipboard();
9293
const { refine } = useSearchBox();
9394
const [copiedExchangeId, setCopiedExchangeId] = useState<string | null>(null);
9495
const copyResetTimeoutRef = useRef<number | null>(null);
9596

97+
const baseUrl = config.baseAskaiUrl || "https://beta-askai.algolia.com";
9698
const transport = new DefaultChatTransport({
97-
api: `${BASE_ASKAI_URL}/chat`,
99+
api: `${baseUrl}/chat`,
98100
headers: async () => {
99-
const token = await getValidToken({ assistantId: ASSISTANT_ID });
101+
const token = await getValidToken({ assistantId: config.assistantId });
100102
return {
101-
"x-algolia-api-key": API_KEY,
102-
"x-algolia-application-id": APPLICATION_ID,
103-
"x-algolia-index-name": INDEX_NAME,
104-
"x-algolia-assistant-id": ASSISTANT_ID,
103+
"x-algolia-api-key": config.apiKey,
104+
"x-algolia-application-id": config.applicationId,
105+
"x-algolia-index-name": config.indexName,
106+
"x-algolia-assistant-id": config.assistantId,
105107
"x-ai-sdk-version": "v5",
106108
authorization: `TOKEN ${token}`,
107109
};

src/experiences/pop-search/index.tsx

Lines changed: 52 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,31 @@ import { HitsList } from "./hits-list";
1818
import { SparklesIcon, SearchIcon, AlgoliaLogo } from "./icons";
1919

2020
import "./index.css";
21-
import { API_KEY, APPLICATION_ID, INDEX_NAME } from "./constants";
2221
import { Modal } from "./search-modal";
2322
import { 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

2747
interface 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

136157
interface 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

Comments
 (0)