]> danix's work - danix.xyz-2.git/commitdiff
feat: create desktop search modal partial
authorDanilo M. <redacted>
Mon, 20 Apr 2026 11:53:24 +0000 (13:53 +0200)
committerDanilo M. <redacted>
Mon, 20 Apr 2026 11:53:24 +0000 (13:53 +0200)
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 <redacted>
themes/danix-xyz-hacker/layouts/partials/search-modal.html [new file with mode: 0644]

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 (file)
index 0000000..22f68af
--- /dev/null
@@ -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>