diff --git a/blocks/header/header.js b/blocks/header/header.js
index c373678..bdff3ee 100644
--- a/blocks/header/header.js
+++ b/blocks/header/header.js
@@ -175,6 +175,27 @@ function decorateNav(section) {
});
}
+/**
+ * Resolve the locale prefix from the current URL path.
+ * @returns {string} Locale path segment (e.g. en, fr)
+ */
+function getLocaleFromPath() {
+ const segment = window.location.pathname.split('/').filter(Boolean)[0];
+ if (segment && /^[a-z]{2}(-[a-z]{2})?$/i.test(segment)) {
+ return segment.split('-')[0].toLowerCase();
+ }
+ return 'en';
+}
+
+/**
+ * Whether the page includes a search results widget.
+ * @returns {boolean}
+ */
+function pageHasSearchWidget() {
+ return !!window.hlx?.searchWidgetOnPage
+ || !!document.querySelector('.widget.search, .search.widget, .widget a[href*="/widgets/search/"]');
+}
+
/**
* Builds a search form with an input and submit button inside the section.
* @param {Element} section - The search section element
@@ -205,14 +226,35 @@ function decorateSearch(section) {
section.textContent = '';
section.append(form);
+ const locale = getLocaleFromPath();
+ const searchResultsPath = `/${locale}/search`;
+
+ const params = new URLSearchParams(window.location.search);
+ if (params.get('search')) {
+ input.value = params.get('search');
+ }
+
form.addEventListener('submit', (e) => {
e.preventDefault();
const query = input.value.trim();
- if (query) {
- const q = encodeURIComponent(query);
- const path = encodeURIComponent(window.location.pathname);
- window.location.href = `/en/search/search-results.html?search=${q}&pagePath=${path}`;
+ if (!query) return;
+ if (pageHasSearchWidget()) {
+ input.dispatchEvent(new Event('input', { bubbles: true }));
+ return;
}
+ window.location.href = `${searchResultsPath}?search=${encodeURIComponent(query)}`;
+ });
+
+ document.dispatchEvent(new CustomEvent('header-search-ready', { detail: { input } }));
+ window.hlx = window.hlx || {};
+ window.hlx.headerSearch = input;
+
+ const searchModulePath = `${window.hlx?.codeBasePath || ''}/widgets/search/search.js`;
+ import(searchModulePath).then((mod) => {
+ mod.initHeaderSearch(input, {
+ anchor: section,
+ resultsPath: searchResultsPath,
+ });
});
}
diff --git a/widgets/search/search.css b/widgets/search/search.css
new file mode 100644
index 0000000..e8cc8b6
--- /dev/null
+++ b/widgets/search/search.css
@@ -0,0 +1,133 @@
+/* container */
+.search {
+ max-width: 1200px;
+ margin: 0 auto;
+ scroll-margin-top: calc(var(--nav-height) + 1em);
+}
+
+.search ol,
+.search ul {
+ list-style: none;
+ margin: 0;
+ padding: 0;
+}
+
+/* results count */
+.search .info {
+ margin: 0 0 1em;
+ padding-bottom: 1em;
+ border-bottom: 1px solid var(--color-border);
+ color: var(--color-text);
+}
+
+.search .info[hidden],
+.search .search-prompt[hidden],
+.search .no-results[hidden] {
+ display: none;
+}
+
+.search .search-prompt {
+ color: var(--color-muted);
+}
+
+/* results list */
+.search .results {
+ display: flex;
+ flex-direction: column;
+ gap: 1em;
+ margin-bottom: 1.5em;
+}
+
+.search .result {
+ display: flex;
+ gap: 1em;
+ align-items: start;
+ position: relative;
+ border: 1px solid var(--color-border);
+ border-radius: 4px;
+ padding: 1.25em;
+ background: var(--color-bg);
+ transition: box-shadow 0.2s;
+}
+
+.search .result:hover {
+ box-shadow: var(--shadow-s);
+}
+
+.search .result .media-wrapper {
+ flex-shrink: 0;
+ width: 64px;
+ height: 64px;
+ overflow: hidden;
+ border-radius: var(--radius-s);
+}
+
+.search .result .media-wrapper picture,
+.search .result .media-wrapper picture > img {
+ display: block;
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+}
+
+.search .result .body-wrapper {
+ min-width: 0;
+}
+
+.search .result .title {
+ margin: 0 0 0.4em;
+ font-family: var(--heading-font-family);
+ font-size: var(--heading-s);
+ font-weight: 600;
+ line-height: 1.3;
+}
+
+.search .result .desc {
+ margin: 0;
+ color: var(--color-muted);
+ font-size: var(--text-s);
+ line-height: 1.5;
+}
+
+.search .result .link::before {
+ content: '';
+ position: absolute;
+ inset: 0;
+}
+
+/* highlight */
+.search .results mark {
+ outline: 1px solid var(--color-brand);
+ background-color: var(--color-brand);
+}
+
+/* pagination */
+.search .pagination {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ justify-content: center;
+ gap: 0.5em 1em;
+}
+
+.search .pagination[hidden] {
+ display: none;
+}
+
+.search .pagination .ellipsis {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.search .pagination ol {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 0.5em;
+}
+
+.search .pagination ol button[aria-current] {
+ border-color: var(--color-text);
+ background-color: var(--color-text);
+ color: var(--color-bg);
+}
diff --git a/widgets/search/search.html b/widgets/search/search.html
new file mode 100644
index 0000000..5cdd8ff
--- /dev/null
+++ b/widgets/search/search.html
@@ -0,0 +1,16 @@
+
+ 0-0
+ Results For
+ ""
+
+Use the search box in the header to find content across the site.
+
+ No results found
+ Try different keywords or check your spelling.
+
+
+
diff --git a/widgets/search/search.js b/widgets/search/search.js
new file mode 100644
index 0000000..014f8e4
--- /dev/null
+++ b/widgets/search/search.js
@@ -0,0 +1,874 @@
+import { createOptimizedPicture, loadCSS } from '../../scripts/aem.js';
+
+/**
+ * Load widget copy from the widget's local JSON (same name as the script).
+ * @param {string} lang - Language key (e.g. en)
+ * @returns {Promise