From fe01b72541bd78f16eb70da5f145f49f80f01988 Mon Sep 17 00:00:00 2001 From: rcourtman Date: Wed, 1 Oct 2025 16:57:43 +0000 Subject: [PATCH] Refine search tips popovers --- .../src/components/Backups/BackupsFilter.tsx | 30 ++++++---- .../components/Dashboard/DashboardFilter.tsx | 37 ++++++------ .../src/components/Storage/StorageFilter.tsx | 30 ++++++---- .../components/shared/SearchTipsPopover.tsx | 60 +++++++++++++++++-- 4 files changed, 110 insertions(+), 47 deletions(-) diff --git a/frontend-modern/src/components/Backups/BackupsFilter.tsx b/frontend-modern/src/components/Backups/BackupsFilter.tsx index 506d3e2e5..bc40cde6d 100644 --- a/frontend-modern/src/components/Backups/BackupsFilter.tsx +++ b/frontend-modern/src/components/Backups/BackupsFilter.tsx @@ -46,7 +46,7 @@ export const BackupsFilter: Component = (props) => { placeholder="Search backups or node:nodename" value={props.search()} onInput={(e) => props.setSearch(e.currentTarget.value)} - class="w-full pl-9 pr-9 py-1.5 text-sm border border-gray-300 dark:border-gray-600 rounded-lg + 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 backups by name or filter by node" @@ -64,19 +64,23 @@ export const BackupsFilter: Component = (props) => { d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" /> +
+ +
- {/* Filters */} diff --git a/frontend-modern/src/components/Dashboard/DashboardFilter.tsx b/frontend-modern/src/components/Dashboard/DashboardFilter.tsx index 2d6c1ca82..1216c21aa 100644 --- a/frontend-modern/src/components/Dashboard/DashboardFilter.tsx +++ b/frontend-modern/src/components/Dashboard/DashboardFilter.tsx @@ -30,7 +30,7 @@ export const DashboardFilter: Component = (props) => { placeholder="Search or filter..." value={props.search()} onInput={(e) => props.setSearch(e.currentTarget.value)} - class={`w-full pl-9 pr-9 py-1.5 text-sm border border-gray-300 dark:border-gray-600 rounded-lg + 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`} /> @@ -50,7 +50,7 @@ export const DashboardFilter: Component = (props) => { +
+ 80', description: 'Highlight guests using more than 80% CPU' }, + { code: 'memory<20', description: 'Find guests under 20% memory usage' }, + { code: 'tags:prod', description: 'Filter by tag' }, + { code: 'node:pve1', description: 'Show guests on a specific node' }, + ]} + footerHighlight="node:pve1 cpu>60" + footerText="Stack filters to get laser-focused results." + triggerVariant="icon" + buttonLabel="Search tips" + openOnHover + /> +
- - 80', description: 'Highlight guests using more than 80% CPU' }, - { code: 'memory<20', description: 'Find guests under 20% memory usage' }, - { code: 'tags:prod', description: 'Filter by tag' }, - { code: 'node:pve1', description: 'Show guests on a specific node' }, - ]} - footerHighlight="node:pve1 cpu>60" - footerText="Stack filters to get laser-focused results." - /> {/* Filters */} diff --git a/frontend-modern/src/components/Storage/StorageFilter.tsx b/frontend-modern/src/components/Storage/StorageFilter.tsx index 14a4ee449..99be91939 100644 --- a/frontend-modern/src/components/Storage/StorageFilter.tsx +++ b/frontend-modern/src/components/Storage/StorageFilter.tsx @@ -38,7 +38,7 @@ export const StorageFilter: Component = (props) => { placeholder="Search storage or node:nodename" value={props.search()} onInput={(e) => props.setSearch(e.currentTarget.value)} - class="w-full pl-9 pr-9 py-1.5 text-sm border border-gray-300 dark:border-gray-600 rounded-lg + 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" @@ -56,19 +56,23 @@ export const StorageFilter: Component = (props) => { d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" /> +
+ +
- {/* Filters */} diff --git a/frontend-modern/src/components/shared/SearchTipsPopover.tsx b/frontend-modern/src/components/shared/SearchTipsPopover.tsx index 841d43936..33815285f 100644 --- a/frontend-modern/src/components/shared/SearchTipsPopover.tsx +++ b/frontend-modern/src/components/shared/SearchTipsPopover.tsx @@ -15,12 +15,15 @@ interface SearchTipsPopoverProps { popoverId?: string; align?: 'left' | 'right'; class?: string; + triggerVariant?: 'button' | 'link' | 'icon'; + openOnHover?: boolean; } export const SearchTipsPopover: Component = (props) => { const [open, setOpen] = createSignal(false); let popoverRef: HTMLDivElement | undefined; let triggerRef: HTMLButtonElement | undefined; + let pointerInside = false; const close = () => setOpen(false); @@ -57,17 +60,66 @@ export const SearchTipsPopover: Component = (props) => { const popoverPositionClass = props.align === 'left' ? 'left-0' : 'right-0'; const popoverId = props.popoverId ?? 'search-tips-popover'; + const triggerVariant = props.triggerVariant ?? 'button'; + const openOnHover = props.openOnHover ?? false; + const buttonLabel = props.buttonLabel ?? 'Search tips'; + + const triggerBaseClasses = + 'text-xs font-medium focus:outline-none focus:ring-2 focus:ring-blue-500/40 focus:ring-offset-1 focus:ring-offset-white dark:focus:ring-blue-400/40 dark:focus:ring-offset-gray-900'; + + const triggerClasses = + triggerVariant === 'button' + ? `rounded-md border border-gray-200 px-2.5 py-1 text-gray-600 transition-colors hover:bg-gray-100 dark:border-gray-600 dark:text-gray-300 dark:hover:bg-gray-700 ${triggerBaseClasses}` + : triggerVariant === 'link' + ? `rounded px-1 py-0.5 text-gray-500 underline decoration-dotted underline-offset-4 transition-colors hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200 ${triggerBaseClasses}` + : `flex h-5 w-5 items-center justify-center rounded-full text-gray-400 transition-colors hover:text-gray-600 dark:text-gray-400 dark:hover:text-gray-100 ${triggerBaseClasses}`; + + const handleMouseEnter = () => { + pointerInside = true; + setOpen(true); + }; + + const handleMouseLeave = () => { + pointerInside = false; + setOpen(false); + }; + return ( -
+