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:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user