Put the dashboard panels away on a click off the widgets
Mirrors the flow editor's onPaneClick: clicking the canvas margin or the empty surface closes the widget settings and the dashboard panel. Widget frames, grid resize handles and menus portalled out of the canvas keep it open. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01K1moruzue2kTJd3uVisgNk
This commit is contained in:
@@ -144,6 +144,14 @@ const AUTOSAVE_MS = 800
|
|||||||
const INTERACTIVE =
|
const INTERACTIVE =
|
||||||
"button, a, input, select, textarea, [role='switch'], [role='combobox'], [role='slider'], .react-resizable-handle, .widget-grip"
|
"button, a, input, select, textarea, [role='switch'], [role='combobox'], [role='slider'], .react-resizable-handle, .widget-grip"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* What a click may land on without counting as a click off the widgets: the
|
||||||
|
* widget itself, and the handles the grid draws around it — resizing a tile is
|
||||||
|
* still working on it. The grid item covers both; the frame is what a stacked
|
||||||
|
* dashboard has instead.
|
||||||
|
*/
|
||||||
|
const ON_WIDGET = "[data-testid=widget-frame], .react-grid-item"
|
||||||
|
|
||||||
function nextId(dashboard: DashboardDef_Output, type: string): string {
|
function nextId(dashboard: DashboardDef_Output, type: string): string {
|
||||||
const taken = new Set(widgetsOf(dashboard).map((widget) => widget.id))
|
const taken = new Set(widgetsOf(dashboard).map((widget) => widget.id))
|
||||||
let candidate = type
|
let candidate = type
|
||||||
@@ -524,12 +532,26 @@ export function DashboardEditor({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<LookProvider dashboard={draft}>
|
<LookProvider dashboard={draft}>
|
||||||
|
{/* Clicking off the widgets is how you put a panel away, as it is on the
|
||||||
|
flow pane — the dashboard is what you went back to look at. A menu a
|
||||||
|
widget portals to `body` lands outside this element but still bubbles
|
||||||
|
through React's tree, so only a press that landed in the subtree
|
||||||
|
counts. */}
|
||||||
|
{/* biome-ignore lint/a11y/useKeyWithClickEvents: Escape closes the panel; this is a pointer shortcut, not a control. */}
|
||||||
|
{/* biome-ignore lint/a11y/noStaticElementInteractions: see above. */}
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"absolute inset-0 overflow-hidden px-4 pb-24 pt-20 transition-[padding] duration-200",
|
"absolute inset-0 overflow-hidden px-4 pb-24 pt-20 transition-[padding] duration-200",
|
||||||
panelOpen && "md:pr-[27rem]",
|
panelOpen && "md:pr-[27rem]",
|
||||||
)}
|
)}
|
||||||
data-testid="dashboard-canvas"
|
data-testid="dashboard-canvas"
|
||||||
|
onClick={(event) => {
|
||||||
|
const target = event.target as Element
|
||||||
|
if (!event.currentTarget.contains(target)) return
|
||||||
|
if (target.closest(ON_WIDGET)) return
|
||||||
|
setSelected(null)
|
||||||
|
setSettingsOpen(false)
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<PaletteProvider dashboard={draft}>{body}</PaletteProvider>
|
<PaletteProvider dashboard={draft}>{body}</PaletteProvider>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -135,6 +135,21 @@ test("the drag handle moves a widget without selecting it", async ({
|
|||||||
await expect(settings).toBeVisible()
|
await expect(settings).toBeVisible()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test("clicking off the widgets puts the settings panel away", async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
await openEditor(page)
|
||||||
|
|
||||||
|
const settings = page.getByTestId("widget-settings")
|
||||||
|
await page.getByTestId("widget-frame").filter({ hasText: "Top" }).click()
|
||||||
|
await expect(settings).toBeVisible()
|
||||||
|
|
||||||
|
// The margin around the canvas is not a widget, the way the flow pane is not
|
||||||
|
// a node.
|
||||||
|
await page.getByTestId("dashboard-canvas").click({ position: { x: 8, y: 8 } })
|
||||||
|
await expect(settings).toBeHidden()
|
||||||
|
})
|
||||||
|
|
||||||
test("a widget without its title can still be dragged and picked", async ({
|
test("a widget without its title can still be dragged and picked", async ({
|
||||||
page,
|
page,
|
||||||
}) => {
|
}) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user