|
149 | 149 | }); |
150 | 150 | </script> |
151 | 151 |
|
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" |
| 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" |
159 | 171 | > |
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" |
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> |
203 | | - |
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 |
| 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" |
207 | 193 | > |
| 194 | + <X size={20} aria-hidden="true" /> |
| 195 | + </button> |
| 196 | + </div> |
208 | 197 |
|
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)} |
232 | | - </div> |
233 | | - {#if result.match.excerpt} |
234 | | - <div class="search-result-excerpt"> |
235 | | - {@html highlightMatches(result.match.excerpt, query)} |
236 | | - </div> |
237 | | - {/if} |
| 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 |
| 201 | + > |
| 202 | + |
| 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)} |
238 | 226 | </div> |
239 | | - {#if index === selectedIndex} |
240 | | - <CornerDownLeft size={16} class="search-result-enter-icon" aria-hidden="true" /> |
| 227 | + {#if result.match.excerpt} |
| 228 | + <div class="search-result-excerpt"> |
| 229 | + {@html highlightMatches(result.match.excerpt, query)} |
| 230 | + </div> |
241 | 231 | {/if} |
242 | | - </button> |
243 | | - {/each} |
244 | | - {/if} |
245 | | - </div> |
| 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> |
246 | 240 |
|
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> |
| 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> |
| 251 | + </div> |
| 252 | + <div class="search-modal-hint"> |
| 253 | + <kbd>ESC</kbd> |
| 254 | + <span>Close</span> |
262 | 255 | </div> |
263 | 256 | </div> |
264 | | - {/if} |
265 | | -</div> |
| 257 | + </div> |
| 258 | +{/if} |
266 | 259 |
|
267 | 260 | <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 | | -
|
281 | 261 | /* Visually hidden but accessible to screen readers */ |
282 | 262 | .visually-hidden { |
283 | 263 | position: absolute; |
|
293 | 273 |
|
294 | 274 | /* Trigger Button */ |
295 | 275 | .search-trigger { |
296 | | - position: relative; |
| 276 | + position: fixed; |
297 | 277 | top: 20px; |
298 | 278 | right: 20px; |
299 | | - margin-left: auto; |
| 279 | + z-index: 100; |
300 | 280 | display: flex; |
301 | 281 | align-items: center; |
302 | 282 | gap: var(--docs-spacing-sm, 0.5rem); |
|
0 commit comments