|
149 | 149 | }); |
150 | 150 | </script> |
151 | 151 |
|
152 | | -<!-- Trigger Button --> |
153 | | -<button class="search-trigger" onclick={openModal} type="button" aria-label="Search documentation"> |
154 | | - <Search size={18} /> |
155 | | - <span class="search-trigger-text">Search...</span> |
156 | | - <kbd class="search-trigger-kbd"> |
157 | | - <Command size={12} /> |
158 | | - <span>K</span> |
159 | | - </kbd> |
160 | | -</button> |
161 | | - |
162 | | -<!-- Modal --> |
163 | | -{#if isOpen} |
164 | | - <div class="search-modal-overlay" onclick={closeModal}></div> |
165 | | - <div |
166 | | - class="search-modal" |
167 | | - role="dialog" |
168 | | - aria-modal="true" |
169 | | - aria-labelledby="search-modal-title" |
170 | | - aria-describedby="search-modal-description" |
| 152 | +<div class="search-container"> |
| 153 | + <!-- Trigger Button --> |
| 154 | + <button |
| 155 | + class="search-trigger" |
| 156 | + onclick={openModal} |
| 157 | + type="button" |
| 158 | + aria-label="Search documentation" |
171 | 159 | > |
172 | | - <!-- Search Input --> |
173 | | - <div class="search-modal-header"> |
174 | | - <Search size={20} class="search-modal-icon" aria-hidden="true" /> |
175 | | - <label for="search-modal-input" class="visually-hidden" id="search-modal-title" |
176 | | - >Search documentation</label |
177 | | - > |
178 | | - <input |
179 | | - id="search-modal-input" |
180 | | - bind:value={query} |
181 | | - class="search-modal-input" |
182 | | - type="text" |
183 | | - {placeholder} |
184 | | - autocomplete="off" |
185 | | - spellcheck="false" |
186 | | - aria-describedby="search-modal-description" |
187 | | - /> |
188 | | - <button |
189 | | - class="search-modal-close" |
190 | | - onclick={closeModal} |
191 | | - type="button" |
192 | | - aria-label="Close search" |
193 | | - > |
194 | | - <X size={20} aria-hidden="true" /> |
195 | | - </button> |
196 | | - </div> |
197 | | - |
198 | | - <!-- Hidden description for screen readers --> |
199 | | - <span id="search-modal-description" class="visually-hidden" |
200 | | - >Use arrow keys to navigate results, enter to select, escape to close</span |
| 160 | + <Search size={18} /> |
| 161 | + <span class="search-trigger-text">Search...</span> |
| 162 | + <kbd class="search-trigger-kbd"> |
| 163 | + <Command size={12} /> |
| 164 | + <span>K</span> |
| 165 | + </kbd> |
| 166 | + </button> |
| 167 | + |
| 168 | + <!-- Modal --> |
| 169 | + {#if isOpen} |
| 170 | + <div class="search-modal-overlay" onclick={closeModal}></div> |
| 171 | + <div |
| 172 | + class="search-modal" |
| 173 | + role="dialog" |
| 174 | + aria-modal="true" |
| 175 | + aria-labelledby="search-modal-title" |
| 176 | + aria-describedby="search-modal-description" |
201 | 177 | > |
| 178 | + <!-- Search Input --> |
| 179 | + <div class="search-modal-header"> |
| 180 | + <Search size={20} class="search-modal-icon" aria-hidden="true" /> |
| 181 | + <label for="search-modal-input" class="visually-hidden" id="search-modal-title" |
| 182 | + >Search documentation</label |
| 183 | + > |
| 184 | + <input |
| 185 | + id="search-modal-input" |
| 186 | + bind:value={query} |
| 187 | + class="search-modal-input" |
| 188 | + type="text" |
| 189 | + {placeholder} |
| 190 | + autocomplete="off" |
| 191 | + spellcheck="false" |
| 192 | + aria-describedby="search-modal-description" |
| 193 | + /> |
| 194 | + <button |
| 195 | + class="search-modal-close" |
| 196 | + onclick={closeModal} |
| 197 | + type="button" |
| 198 | + aria-label="Close search" |
| 199 | + > |
| 200 | + <X size={20} aria-hidden="true" /> |
| 201 | + </button> |
| 202 | + </div> |
202 | 203 |
|
203 | | - <!-- Results --> |
204 | | - <div class="search-modal-results" role="region" aria-live="polite" aria-atomic="false"> |
205 | | - {#if isLoading} |
206 | | - <div class="search-modal-loading">Loading search index...</div> |
207 | | - {:else if query.trim() && results.length === 0} |
208 | | - <div class="search-modal-empty"> |
209 | | - <p>No results found for "{query}"</p> |
210 | | - <p class="search-modal-empty-hint">Try a different search term</p> |
211 | | - </div> |
212 | | - {:else if results.length > 0} |
213 | | - {#each results as result, index (result.id)} |
214 | | - <button |
215 | | - class="search-result {index === selectedIndex ? 'selected' : ''}" |
216 | | - onclick={() => handleResultClick(result)} |
217 | | - type="button" |
218 | | - role="option" |
219 | | - aria-selected={index === selectedIndex} |
220 | | - aria-label="{result.title} in {result.section}" |
221 | | - > |
222 | | - <div class="search-result-content"> |
223 | | - <div class="search-result-section">{result.section}</div> |
224 | | - <div class="search-result-title"> |
225 | | - {@html highlightMatches(result.title, query)} |
226 | | - </div> |
227 | | - {#if result.match.excerpt} |
228 | | - <div class="search-result-excerpt"> |
229 | | - {@html highlightMatches(result.match.excerpt, query)} |
| 204 | + <!-- Hidden description for screen readers --> |
| 205 | + <span id="search-modal-description" class="visually-hidden" |
| 206 | + >Use arrow keys to navigate results, enter to select, escape to close</span |
| 207 | + > |
| 208 | + |
| 209 | + <!-- Results --> |
| 210 | + <div class="search-modal-results" role="region" aria-live="polite" aria-atomic="false"> |
| 211 | + {#if isLoading} |
| 212 | + <div class="search-modal-loading">Loading search index...</div> |
| 213 | + {:else if query.trim() && results.length === 0} |
| 214 | + <div class="search-modal-empty"> |
| 215 | + <p>No results found for "{query}"</p> |
| 216 | + <p class="search-modal-empty-hint">Try a different search term</p> |
| 217 | + </div> |
| 218 | + {:else if results.length > 0} |
| 219 | + {#each results as result, index (result.id)} |
| 220 | + <button |
| 221 | + class="search-result {index === selectedIndex ? 'selected' : ''}" |
| 222 | + onclick={() => handleResultClick(result)} |
| 223 | + type="button" |
| 224 | + role="option" |
| 225 | + aria-selected={index === selectedIndex} |
| 226 | + aria-label="{result.title} in {result.section}" |
| 227 | + > |
| 228 | + <div class="search-result-content"> |
| 229 | + <div class="search-result-section">{result.section}</div> |
| 230 | + <div class="search-result-title"> |
| 231 | + {@html highlightMatches(result.title, query)} |
230 | 232 | </div> |
| 233 | + {#if result.match.excerpt} |
| 234 | + <div class="search-result-excerpt"> |
| 235 | + {@html highlightMatches(result.match.excerpt, query)} |
| 236 | + </div> |
| 237 | + {/if} |
| 238 | + </div> |
| 239 | + {#if index === selectedIndex} |
| 240 | + <CornerDownLeft size={16} class="search-result-enter-icon" aria-hidden="true" /> |
231 | 241 | {/if} |
232 | | - </div> |
233 | | - {#if index === selectedIndex} |
234 | | - <CornerDownLeft size={16} class="search-result-enter-icon" aria-hidden="true" /> |
235 | | - {/if} |
236 | | - </button> |
237 | | - {/each} |
238 | | - {/if} |
239 | | - </div> |
240 | | - |
241 | | - <!-- Footer with keyboard hints --> |
242 | | - <div class="search-modal-footer"> |
243 | | - <div class="search-modal-hint"> |
244 | | - <kbd><ArrowUp size={12} /></kbd> |
245 | | - <kbd><ArrowDown size={12} /></kbd> |
246 | | - <span>Navigate</span> |
247 | | - </div> |
248 | | - <div class="search-modal-hint"> |
249 | | - <kbd><CornerDownLeft size={12} /></kbd> |
250 | | - <span>Select</span> |
| 242 | + </button> |
| 243 | + {/each} |
| 244 | + {/if} |
251 | 245 | </div> |
252 | | - <div class="search-modal-hint"> |
253 | | - <kbd>ESC</kbd> |
254 | | - <span>Close</span> |
| 246 | + |
| 247 | + <!-- Footer with keyboard hints --> |
| 248 | + <div class="search-modal-footer"> |
| 249 | + <div class="search-modal-hint"> |
| 250 | + <kbd><ArrowUp size={12} /></kbd> |
| 251 | + <kbd><ArrowDown size={12} /></kbd> |
| 252 | + <span>Navigate</span> |
| 253 | + </div> |
| 254 | + <div class="search-modal-hint"> |
| 255 | + <kbd><CornerDownLeft size={12} /></kbd> |
| 256 | + <span>Select</span> |
| 257 | + </div> |
| 258 | + <div class="search-modal-hint"> |
| 259 | + <kbd>ESC</kbd> |
| 260 | + <span>Close</span> |
| 261 | + </div> |
255 | 262 | </div> |
256 | 263 | </div> |
257 | | - </div> |
258 | | -{/if} |
| 264 | + {/if} |
| 265 | +</div> |
259 | 266 |
|
260 | 267 | <style> |
| 268 | + /* Container - doesn't affect layout */ |
| 269 | + .search-container { |
| 270 | + position: fixed; |
| 271 | + top: 0; |
| 272 | + right: 0; |
| 273 | + pointer-events: none; |
| 274 | + z-index: 100; |
| 275 | + } |
| 276 | +
|
| 277 | + .search-container > * { |
| 278 | + pointer-events: auto; |
| 279 | + } |
| 280 | +
|
261 | 281 | /* Visually hidden but accessible to screen readers */ |
262 | 282 | .visually-hidden { |
263 | 283 | position: absolute; |
|
273 | 293 |
|
274 | 294 | /* Trigger Button */ |
275 | 295 | .search-trigger { |
276 | | - position: fixed; |
| 296 | + position: relative; |
277 | 297 | top: 20px; |
278 | 298 | right: 20px; |
| 299 | + margin-left: auto; |
279 | 300 | display: flex; |
280 | 301 | align-items: center; |
281 | 302 | gap: var(--docs-spacing-sm, 0.5rem); |
|
288 | 309 | transition: all 0.2s ease; |
289 | 310 | font-size: 0.875rem; |
290 | 311 | min-width: 240px; |
291 | | - z-index: 100; |
292 | 312 | } |
293 | 313 |
|
294 | 314 | .search-trigger:hover { |
|
0 commit comments