From 055f4d7d978b6e44e55d3f468ea6960884fa6a94 Mon Sep 17 00:00:00 2001 From: stroblme Date: Tue, 25 Aug 2026 18:26:49 +0200 Subject: [PATCH] Filter the icon grid, and ask before removing a panel Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01UytviPMJbXzD8P84nLvXcq --- .../src/components/Dashboard/PanelsDialog.tsx | 29 +++++++++++++-- frontend/src/components/Dashboard/panels.tsx | 37 ++++++++++++++++--- 2 files changed, 56 insertions(+), 10 deletions(-) diff --git a/frontend/src/components/Dashboard/PanelsDialog.tsx b/frontend/src/components/Dashboard/PanelsDialog.tsx index e9de859..dda5281 100644 --- a/frontend/src/components/Dashboard/PanelsDialog.tsx +++ b/frontend/src/components/Dashboard/PanelsDialog.tsx @@ -27,6 +27,7 @@ import { Input } from "@/components/ui/input" import { Separator } from "@/components/ui/separator" import useAuth from "@/hooks/useAuth" import useCustomToast from "@/hooks/useCustomToast" +import { cn } from "@/lib/utils" import { handleError } from "@/utils" /** As many characters as a device puts on the wall. */ @@ -207,6 +208,9 @@ function PanelRow({ // ponytail: a two-step button rather than a dialog, since this one already // lives inside a dialog. const [confirmUnpair, setConfirmUnpair] = useState(false) + // Removing the panel throws away more than unpairing does, so it asks the + // same way — the icon turns destructive and only the second press fires. + const [confirmRemove, setConfirmRemove] = useState(false) const unpair = useUnpairPanel(panel.id) const assigned = panel.dashboards ?? [] const typed = code.trim().toUpperCase() @@ -247,6 +251,11 @@ function PanelRow({ const link = host ? `${host}/panel/${panel.id}` : "" const remoteLink = remoteHost ? `${remoteHost}/panel` : "" + // The trash icon carries no text of its own, so the confirm step says what + // the next press does through the label a reader or a hover gets. + const removeLabel = confirmRemove + ? `Confirm — remove ${panel.id}` + : `Remove ${panel.id}` return (
@@ -264,13 +273,25 @@ function PanelRow({ {panel.id} diff --git a/frontend/src/components/Dashboard/panels.tsx b/frontend/src/components/Dashboard/panels.tsx index 2f948f3..c824185 100644 --- a/frontend/src/components/Dashboard/panels.tsx +++ b/frontend/src/components/Dashboard/panels.tsx @@ -307,8 +307,8 @@ const CHART_SOURCES = [ * Radix forbids an empty `SelectItem` value — which is why the selects this * replaces could set an icon but never take one back. * - * ponytail: no filter field. `ICONS` is a few dozen and the grid shows all of - * it without scrolling; add one when the map outgrows a popover. + * The field at the top narrows the grid by name, so the glyphs stay findable + * as the map grows past what a popover can show at once. */ function IconPicker({ value, @@ -328,10 +328,21 @@ function IconPicker({ onChange: (icon: string) => void }) { const [open, setOpen] = useState(false) + // Cleared whenever the popover opens or closes: a filter left over from last + // time hides icons that are still there. + const [filter, setFilter] = useState("") const Current = ICONS[value] + const needle = filter.trim().toLowerCase() + const shown = ICON_NAMES.filter((name) => name.toLowerCase().includes(needle)) return ( - + { + setOpen(next) + setFilter("") + }} + > - -
- {ICON_NAMES.map((name) => { + {/* Fixed width rather than auto: the grid would otherwise shrink under + the cursor as the filter narrows it. */} + + setFilter(event.target.value)} + /> +
+ {shown.map((name) => { const Glyph = ICONS[name] return (