diff options
| author | Danilo M. <danix@danix.xyz> | 2026-04-20 13:53:24 +0200 |
|---|---|---|
| committer | Danilo M. <danix@danix.xyz> | 2026-04-20 13:53:24 +0200 |
| commit | 25cf50085ad1ce1e0820ec5ccb728ddeef5fb6fb (patch) | |
| tree | 142c0f9b68e597a897600e2c7cd4e49d68650afa | |
| parent | aefc3d8c3994ba0eb1e3dfced3564ba2b0f8b73f (diff) | |
| download | danixxyz-25cf50085ad1ce1e0820ec5ccb728ddeef5fb6fb.tar.gz danixxyz-25cf50085ad1ce1e0820ec5ccb728ddeef5fb6fb.zip | |
feat: create desktop search modal partial
Implement full-screen overlay modal with:
- Search input with auto-focus on open
- Results display (max 5 results) with title/date
- Three display states: results, no results, no query
- WCAG 2.1 AA accessibility: role="dialog", aria-modal, aria-live="polite"
- Keyboard support: ESC to close, handled via handleEscape()
- Click backdrop to close, click close button
- All text localized via i18n keys
- Alpine.js 3.x integration with searchOverlay() component
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
| -rw-r--r-- | themes/danix-xyz-hacker/layouts/partials/search-modal.html | 86 |
1 files changed, 86 insertions, 0 deletions
diff --git a/themes/danix-xyz-hacker/layouts/partials/search-modal.html b/themes/danix-xyz-hacker/layouts/partials/search-modal.html new file mode 100644 index 0000000..22f68af --- /dev/null +++ b/themes/danix-xyz-hacker/layouts/partials/search-modal.html @@ -0,0 +1,86 @@ +<!-- Desktop Search Modal (hidden on mobile, shown via Alpine) --> +<div + x-cloak + x-data="searchOverlay()" + @keydown.escape.window="handleEscape($event)" + class="fixed inset-0 z-50" + :class="{ 'flex items-center justify-center': isOpen, 'hidden': !isOpen }" + x-show="isOpen" +> + <!-- Overlay backdrop --> + <div + class="absolute inset-0 bg-black/50" + @click="close()" + aria-hidden="true" + ></div> + + <!-- Modal content --> + <div + class="relative bg-bg border-2 border-accent rounded-lg shadow-xl max-w-2xl mx-4 w-full" + role="dialog" + aria-labelledby="search-modal-title" + aria-modal="true" + > + <!-- Header with close button --> + <div class="flex items-center justify-between p-6 border-b border-border"> + <h2 id="search-modal-title" class="text-xl font-bold text-accent"> + {{ i18n "searchArticles" }} + </h2> + <button + @click="close()" + aria-label="Close search" + class="p-2 rounded hover:bg-surface transition-colors focus:outline-none focus:ring-2 focus:ring-accent" + > + <i data-feather="x" class="w-5 h-5" aria-hidden="true"></i> + </button> + </div> + + <!-- Search input --> + <div class="p-6 border-b border-border"> + <label for="search-input-desktop" class="sr-only"> + {{ i18n "searchPlaceholder" }} + </label> + <input + id="search-input-desktop" + type="text" + :value="searchQuery" + @input="filterArticles($el.value)" + placeholder="{{ i18n "searchPlaceholder" }}" + class="w-full px-4 py-3 border-2 border-border rounded focus:outline-none focus:ring-2 focus:ring-accent focus:border-transparent bg-bg text-text" + aria-describedby="search-results" + /> + </div> + + <!-- Results container --> + <div id="search-results" class="max-h-96 overflow-y-auto p-6"> + <!-- Results list --> + <div x-show="filteredArticles.length > 0" class="space-y-3" role="region" aria-live="polite"> + <template x-for="article in filteredArticles" :key="article.url"> + <div class="p-4 border-l-4 border-accent bg-bg/50 hover:bg-bg/70 transition-colors rounded"> + <a :href="article.url" class="block focus:outline-none focus:ring-2 focus:ring-accent rounded px-2 py-1"> + <h3 class="font-bold text-accent hover:underline" x-text="article.title"></h3> + <p class="text-sm text-text-dim mt-1" x-text="article.date"></p> + </a> + </div> + </template> + </div> + + <!-- Empty state --> + <div + x-show="searchQuery && filteredArticles.length === 0" + class="text-center py-8 text-text-dim" + role="status" + > + {{ i18n "noSearchResults" }} + </div> + + <!-- No query state --> + <div + x-show="!searchQuery" + class="text-center py-8 text-text-dim" + > + {{ i18n "searchPlaceholder" }} + </div> + </div> + </div> +</div> |
