Dashboard chrome: an icon picker, one segmented shape, a rail without bars

- IconPicker replaces the three icon selects (rail icon, icon-widget rule,
  "Otherwise"): the glyphs in a grid, and a button that clears back to none —
  which a Radix SelectItem could never offer.
- ModePicker/StylePicker drop out in favour of a shared ui/Segmented, the same
  sliding-thumb shape RangePicker and the widget-side control already wear.
- PanelRail draws no scrollbars at all: hiding them also takes back the gutter
  a vertical bar claimed from a column exactly as wide as its buttons, which is
  what pushed a horizontal bar under them.
- The panels dialog can re-pair one screen (POST /panels/{id}/unpair) without
  deleting the panel it hangs on.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018tULRZJUkZsw7rMJ3h4xvu
This commit is contained in:
2026-08-22 12:16:17 +02:00
co-authored by Claude Opus 5
parent 3674099758
commit 6be6f18dc2
5 changed files with 264 additions and 118 deletions
@@ -13,6 +13,7 @@ import {
dashboardsQueryOptions,
panelsQueryOptions,
useSavePanels,
useUnpairPanel,
} from "@/components/Dashboard/queries"
import { Button } from "@/components/ui/button"
import { Checkbox } from "@/components/ui/checkbox"
@@ -201,6 +202,12 @@ function PanelRow({
}) {
const { showSuccessToast, showErrorToast } = useCustomToast()
const [code, setCode] = useState("")
// Unpairing cannot be undone from here — the screen has to be at hand to
// read out a new code — so the button asks a second time before it fires.
// ponytail: a two-step button rather than a dialog, since this one already
// lives inside a dialog.
const [confirmUnpair, setConfirmUnpair] = useState(false)
const unpair = useUnpairPanel(panel.id)
const assigned = panel.dashboards ?? []
const typed = code.trim().toUpperCase()
@@ -364,6 +371,41 @@ function PanelRow({
: "No device is waiting on that code."}
</p>
) : null}
<div className="flex flex-wrap items-center gap-2">
<Button
variant={confirmUnpair ? "destructive" : "outline"}
disabled={unpair.isPending}
data-testid={`unpair-${panel.id}`}
onBlur={() => setConfirmUnpair(false)}
onClick={() => {
if (!confirmUnpair) {
setConfirmUnpair(true)
return
}
setConfirmUnpair(false)
unpair.mutate(undefined, {
onSuccess: () =>
showSuccessToast(
"Unpaired — the screen asks for a new code.",
),
onError: (error) =>
handleError.call(showErrorToast, error as ApiError),
})
}}
>
{confirmUnpair
? "Confirm — sign this screen out"
: "Unpair screen"}
</Button>
<p className="min-w-0 flex-1 text-xs text-muted-foreground">
Sends the screen hanging here back to a pairing code and keeps
the panel, its dashboards and their arrangement unlike
removing the panel, which throws all three away. A screen paired
through the portal holds a credential this does not reach; that
one is revoked at the portal.
</p>
</div>
</>
) : null}
</div>