From b4c9785aa7b4434ccb9e6f829f51d9d477a18746 Mon Sep 17 00:00:00 2001 From: rcourtman Date: Fri, 26 Dec 2025 10:53:11 +0000 Subject: [PATCH] refactor(storage): match filter bar to Backups pattern - Full-width search bar on its own row - Smart Reset button (only shows when filters active) - Simplified sort options dropdown - Consistent flex-wrap layout with gap-x-2 gap-y-2 Maintains consistency across Proxmox pages. --- .../src/components/Storage/StorageFilter.tsx | 458 +++++++++--------- 1 file changed, 223 insertions(+), 235 deletions(-) diff --git a/frontend-modern/src/components/Storage/StorageFilter.tsx b/frontend-modern/src/components/Storage/StorageFilter.tsx index df5cab7fd3..c9fe946e72 100644 --- a/frontend-modern/src/components/Storage/StorageFilter.tsx +++ b/frontend-modern/src/components/Storage/StorageFilter.tsx @@ -22,12 +22,9 @@ interface StorageFilterProps { export const StorageFilter: Component = (props) => { const sortOptions = props.sortOptions ?? [ { value: 'name', label: 'Name' }, - { value: 'node', label: 'Node' }, - { value: 'type', label: 'Type' }, - { value: 'status', label: 'Status' }, { value: 'usage', label: 'Usage %' }, - { value: 'free', label: 'Free Capacity' }, - { value: 'total', label: 'Total Capacity' }, + { value: 'free', label: 'Free' }, + { value: 'total', label: 'Total' }, ]; const historyManager = createSearchHistoryManager(STORAGE_KEYS.STORAGE_SEARCH_HISTORY); @@ -96,205 +93,202 @@ export const StorageFilter: Component = (props) => { }); }; + const hasActiveFilters = () => + props.search().trim() !== '' || + props.sortKey() !== 'name' || + props.sortDirection() !== 'asc' || + (props.groupBy && props.groupBy() !== 'node') || + (props.statusFilter && props.statusFilter() !== 'all'); + return ( -
- {/* Search Bar */} -
-
- { - searchInputEl = el; - props.searchInputRef?.(el); - }} - type="text" - placeholder="Search storage or node:nodename" - value={props.search()} - onInput={(e) => props.setSearch(e.currentTarget.value)} - onKeyDown={(e) => { - if (e.key === 'Enter') { - commitSearchToHistory(e.currentTarget.value); - closeHistory(); - } else if (e.key === 'ArrowDown' && searchHistory().length > 0) { - e.preventDefault(); - setIsHistoryOpen(true); - } - }} - onBlur={(e) => { - if (suppressBlurCommit) { - return; - } - const next = e.relatedTarget as HTMLElement | null; - const interactingWithHistory = next - ? historyMenuRef?.contains(next) || historyToggleRef?.contains(next) - : false; - const interactingWithTips = - next?.getAttribute('aria-controls') === 'storage-search-help'; - if (!interactingWithHistory && !interactingWithTips) { - commitSearchToHistory(e.currentTarget.value); - } - }} - class="w-full pl-9 pr-16 py-1.5 text-sm border border-gray-300 dark:border-gray-600 rounded-lg - bg-white dark:bg-gray-900 text-gray-800 dark:text-gray-200 placeholder-gray-400 dark:placeholder-gray-500 - focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 dark:focus:border-blue-400 outline-none transition-all" - title="Search storage by name or filter by node" +
+ {/* Row 1: Search Bar - Full width */} +
+ { + searchInputEl = el; + props.searchInputRef?.(el); + }} + type="text" + placeholder="Search storage... (e.g., local, nfs, node:pve1)" + value={props.search()} + onInput={(e) => props.setSearch(e.currentTarget.value)} + onKeyDown={(e) => { + if (e.key === 'Enter') { + commitSearchToHistory(e.currentTarget.value); + closeHistory(); + } else if (e.key === 'ArrowDown' && searchHistory().length > 0) { + e.preventDefault(); + setIsHistoryOpen(true); + } + }} + onBlur={(e) => { + if (suppressBlurCommit) { + return; + } + const next = e.relatedTarget as HTMLElement | null; + const interactingWithHistory = next + ? historyMenuRef?.contains(next) || historyToggleRef?.contains(next) + : false; + const interactingWithTips = + next?.getAttribute('aria-controls') === 'storage-search-help'; + if (!interactingWithHistory && !interactingWithTips) { + commitSearchToHistory(e.currentTarget.value); + } + }} + class="w-full pl-9 pr-20 py-2 text-sm border border-gray-300 dark:border-gray-600 rounded-lg + bg-white dark:bg-gray-900 text-gray-800 dark:text-gray-200 placeholder-gray-400 dark:placeholder-gray-500 + focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 dark:focus:border-blue-400 outline-none transition-all" + title="Search storage by name or filter by node" + /> + + - + + - - - -
- - -
- -
(historyMenuRef = el)} - class="absolute left-0 right-0 top-full z-50 mt-2 w-full overflow-hidden rounded-lg border border-gray-200 bg-white text-sm shadow-xl dark:border-gray-700 dark:bg-gray-800" - role="listbox" - > - 0} - fallback={ -
- Your recent storage searches will show here. -
+ + + + +
+
+ - -
- )} - -
- -
-
- + return next; + }) + } + onMouseDown={markSuppressCommit} + aria-haspopup="listbox" + aria-expanded={isHistoryOpen()} + title={ + searchHistory().length > 0 + ? 'Show recent searches' + : 'No recent searches yet' + } + > + + + + Show search history + +
+ +
(historyMenuRef = el)} + class="absolute left-0 right-0 top-full z-50 mt-2 w-full overflow-hidden rounded-lg border border-gray-200 bg-white text-sm shadow-xl dark:border-gray-700 dark:bg-gray-800" + role="listbox" + > + 0} + fallback={ +
+ Your recent storage searches will show here. +
+ } + > +
+ + {(entry) => ( +
+ + +
+ )} +
+
+ +
+
+
- {/* Filters */} -
+ {/* Row 2: Filters - Compact horizontal layout */} +
{/* Group By Filter */}
@@ -355,17 +349,15 @@ export const StorageFilter: Component = (props) => { Offline
+ {/* Sort controls */} -
- - Sort - +