feat: better search UX on the page
This commit is contained in:
@@ -18,13 +18,25 @@
|
||||
<%= I18n.t('public_portal.search.results') %>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
|
||||
<h1 class="text-3xl font-semibold leading-normal md:tracking-normal md:text-4xl text-slate-900 dark:text-white">
|
||||
<%= I18n.t('public_portal.search.results_for', query: @query) %>
|
||||
</h1>
|
||||
|
||||
|
||||
<!-- Search form for the results page -->
|
||||
<div id="search-wrap" class="w-full my-4"></div>
|
||||
<form class="w-full my-4" action="<%= request.path %>" method="GET" data-search-form>
|
||||
<input type="hidden" name="locale" value="<%= params[:locale] %>">
|
||||
<input type="text"
|
||||
name="query"
|
||||
value="<%= @query %>"
|
||||
placeholder="<%= I18n.t('public_portal.search.search_placeholder') %>"
|
||||
data-search-input
|
||||
autofocus
|
||||
class="w-full px-4 py-3 border border-slate-200 dark:border-slate-700 rounded-lg bg-white dark:bg-slate-800 text-slate-900 dark:text-slate-100 placeholder-slate-500 dark:placeholder-slate-400 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent">
|
||||
<button type="submit" class="sr-only">
|
||||
<%= I18n.t('public_portal.search.submit') %>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -42,16 +54,73 @@
|
||||
<%= I18n.t('public_portal.search.results') %>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
|
||||
<h1 class="text-3xl font-semibold leading-normal md:tracking-normal md:text-4xl text-slate-900 dark:text-white">
|
||||
<%= I18n.t('public_portal.search.results_for', query: @query) %>
|
||||
</h1>
|
||||
|
||||
|
||||
<!-- Search form for the results page -->
|
||||
<div id="search-wrap" class="w-full my-4"></div>
|
||||
<form class="w-full my-4" action="<%= request.path %>" method="GET" data-search-form>
|
||||
<input type="hidden" name="locale" value="<%= params[:locale] %>">
|
||||
<input type="text"
|
||||
name="query"
|
||||
value="<%= @query %>"
|
||||
placeholder="<%= I18n.t('public_portal.search.search_placeholder') %>"
|
||||
data-search-input
|
||||
autofocus
|
||||
class="w-full px-4 py-3 border border-slate-200 dark:border-slate-700 rounded-lg bg-white dark:bg-slate-800 text-slate-900 dark:text-slate-100 placeholder-slate-500 dark:placeholder-slate-400 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent">
|
||||
<button type="submit" class="sr-only">
|
||||
<%= I18n.t('public_portal.search.submit') %>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<script>
|
||||
document.addEventListener('turbo:load', function() {
|
||||
const searchInputs = document.querySelectorAll('[data-search-input]');
|
||||
|
||||
searchInputs.forEach(function(input) {
|
||||
let debounceTimer;
|
||||
|
||||
// Move cursor to end of input text after autofocus
|
||||
if (input.value.length > 0) {
|
||||
setTimeout(function() {
|
||||
input.setSelectionRange(input.value.length, input.value.length);
|
||||
}, 0);
|
||||
}
|
||||
|
||||
input.addEventListener('input', function() {
|
||||
const form = input.closest('[data-search-form]');
|
||||
const query = input.value.trim();
|
||||
|
||||
// Clear existing timer
|
||||
clearTimeout(debounceTimer);
|
||||
|
||||
// Only search if there's a query and it's different from current
|
||||
if (query.length > 0) {
|
||||
debounceTimer = setTimeout(function() {
|
||||
// Check if the query has actually changed
|
||||
const currentQuery = new URLSearchParams(window.location.search).get('query') || '';
|
||||
if (query !== currentQuery) {
|
||||
// Use Turbo.visit for SPA-like navigation
|
||||
const formData = new FormData(form);
|
||||
const searchParams = new URLSearchParams(formData);
|
||||
const url = form.action + '?' + searchParams.toString();
|
||||
Turbo.visit(url);
|
||||
}
|
||||
}, 500); // 500ms debounce
|
||||
}
|
||||
});
|
||||
|
||||
// Handle manual form submission
|
||||
input.closest('[data-search-form]').addEventListener('submit', function(e) {
|
||||
clearTimeout(debounceTimer);
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="max-w-5xl w-full mx-auto px-4 md:px-8 py-6 flex flex-col items-center justify-center flex-grow">
|
||||
<div class="w-full flex flex-col gap-6 flex-grow">
|
||||
<% if @articles.empty? %>
|
||||
@@ -62,7 +131,7 @@
|
||||
<p class="text-sm text-slate-600 dark:text-slate-400">
|
||||
<%= I18n.t('public_portal.search.found_results', count: @articles.size) %>
|
||||
</p>
|
||||
|
||||
|
||||
<% @articles.each do |article| %>
|
||||
<div class="border border-solid border-slate-100 dark:border-slate-800 rounded-lg">
|
||||
<a class="p-4 text-slate-800 dark:text-slate-50 flex justify-between content-center hover:cursor-pointer"
|
||||
@@ -91,7 +160,7 @@
|
||||
</a>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
|
||||
<!-- Pagination -->
|
||||
<% if @articles.respond_to?(:total_pages) && @articles.total_pages > 1 %>
|
||||
<div class="flex justify-center mt-6">
|
||||
@@ -101,7 +170,7 @@
|
||||
<%= I18n.t('public_portal.common.previous') %>
|
||||
</a>
|
||||
<% end %>
|
||||
|
||||
|
||||
<% if @articles.next_page %>
|
||||
<a href="<%= url_for(page: @articles.next_page) %>" class="px-3 py-2 border border-slate-200 dark:border-slate-700 rounded-r-md text-sm font-medium text-slate-700 dark:text-slate-300 hover:bg-slate-50 dark:hover:bg-slate-800">
|
||||
<%= I18n.t('public_portal.common.next') %>
|
||||
@@ -112,4 +181,4 @@
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user