feat: implement WooCommerce AJAX product search

This commit is contained in:
XShop
2026-09-13 20:01:15 +03:30
parent 00439fcea9
commit f5c283af25
14 changed files with 760 additions and 37 deletions
@@ -0,0 +1,76 @@
<?php
/**
* AJAX Search — reusable, accessible, combobox-like (without over-engineered ARIA).
*
* Reusable by: desktop header, mobile header, future overlay.
* JS: assets/js/search.js handles fetch, debounce, stale protection, keyboard.
*
* Args: ['placeholder'=>string, 'action'=>string, 'id'=>string|null]
*
* @package XShop
*/
declare(strict_types=1);
defined('ABSPATH') || exit;
$uid = $args['id'] ?? ('xshop-ajax-search-' . wp_unique_id());
$placeholder = $args['placeholder'] ?? esc_html__('Search products…', 'xshop');
$action = $args['action'] ?? home_url('/');
$minLength = 2;
$limit = 6;
?>
<div class="xshop-search" data-xshop-search>
<form role="search" method="get" action="<?php echo esc_url($action); ?>" class="xshop-search__form" data-xshop-search-form>
<label class="xshop-sr-only" for="<?php echo esc_attr($uid); ?>"><?php esc_html_e('Search products', 'xshop'); ?></label>
<input
type="search"
id="<?php echo esc_attr($uid); ?>"
name="s"
value="<?php echo esc_attr(get_search_query()); ?>"
placeholder="<?php echo esc_attr($placeholder); ?>"
autocomplete="off"
aria-label="<?php esc_attr_e('Search products', 'xshop'); ?>"
aria-expanded="false"
aria-controls="<?php echo esc_attr($uid); ?>-listbox"
aria-autocomplete="list"
role="combobox"
data-xshop-search-input
data-min-length="<?php echo esc_attr((string) $minLength); ?>"
data-limit="<?php echo esc_attr((string) $limit); ?>"
/>
<?php if (xshop_has_woocommerce()): ?>
<input type="hidden" name="post_type" value="product" />
<?php endif; ?>
<button type="submit" class="xshop-btn xshop-btn--primary" aria-label="<?php esc_attr_e('Search', 'xshop'); ?>"><?php esc_html_e('Search', 'xshop'); ?></button>
</form>
<div
id="<?php echo esc_attr($uid); ?>-listbox"
class="xshop-search__dropdown"
role="listbox"
hidden
data-xshop-search-dropdown
>
<div class="xshop-search__status xshop-sr-only" aria-live="polite" aria-atomic="true" data-xshop-search-status></div>
<div class="xshop-search__loading" hidden data-xshop-search-loading>
<span class="xshop-loading__spinner" aria-hidden="true"></span>
<span><?php esc_html_e('Searching…', 'xshop'); ?></span>
</div>
<ul class="xshop-search__results" role="presentation" data-xshop-search-results></ul>
<div class="xshop-search__empty" hidden data-xshop-search-empty>
<p><?php esc_html_e('No products found.', 'xshop'); ?></p>
</div>
<div class="xshop-search__error" hidden data-xshop-search-error>
<p><?php esc_html_e('Search is temporarily unavailable. Press Enter to search.', 'xshop'); ?></p>
</div>
<a href="#" class="xshop-search__viewall" hidden data-xshop-search-viewall>
<?php esc_html_e('View all results', 'xshop'); ?> →
</a>
</div>
</div>
@@ -42,7 +42,7 @@ $hasLogo = has_custom_logo();
<div class="xshop-header__actions">
<div class="xshop-header__search xshop-header__search--desktop">
<?php get_template_part('template-parts/components/search-form', null, ['placeholder' => esc_html__('Search…', 'xshop')]); ?>
<?php get_template_part('template-parts/components/ajax-search', null, ['placeholder' => esc_html__('Search products…', 'xshop')]); ?>
</div>
<?php if (xshop_has_woocommerce()): ?>
@@ -75,6 +75,6 @@ $hasLogo = has_custom_logo();
]);
?>
<div style="margin-block-start:var(--xshop-space-4)">
<?php get_template_part('template-parts/components/search-form', null, ['placeholder' => esc_html__('Search…', 'xshop')]); ?>
<?php get_template_part('template-parts/components/ajax-search', null, ['placeholder' => esc_html__('Search products…', 'xshop'), 'id' => 'xshop-ajax-search-mobile-' . wp_unique_id()]); ?>
</div>
</nav>