fix widget focus interactions
This commit is contained in:
1 parent
53185701ce
commit
758c300d9e
14 files changed
+83
-12
No files matched your search
@@ -4,8 +4,6 @@ description: "Card grid view of all Beszel-monitored systems with real-time metr
|
||||
hide_title: true
|
||||
---
|
||||
|
||||
import { WidgetInteractionGuide } from "@site/src/components/widgets/interactions";
|
||||
|
||||
import { WidgetHeader } from "@site/src/components/widgets/header";
|
||||
import { AddingWidget } from "@site/src/components/widgets/adding";
|
||||
import { WidgetConfig } from "@site/src/components/widgets/configuration";
|
||||
@@ -36,8 +34,6 @@ Select a system card to open the complete statistics view, including system, fil
|
||||
]}
|
||||
/>
|
||||
|
||||
<WidgetInteractionGuide />
|
||||
|
||||
### Adding the widget
|
||||
|
||||
<AddingWidget />
|
||||
|
||||
@@ -15,9 +15,9 @@ import { beszelIntegration } from "@site/docs/integrations/beszel";
|
||||
|
||||
<WidgetHeader widget={beszelSystemTableWidget} categories={["System Monitoring"]} />
|
||||
|
||||
This widget displays all your Beszel-monitored systems in a sortable data table. Each row shows system metrics like CPU, memory, disk, network, temperature, and more. Columns can be toggled on or off, dragged into a different order, and resized from their headers. Column order and widths are saved automatically for each widget. When Beszel reports extra filesystems, the disk column adds a hover breakdown for each mount alongside the root disk.
|
||||
This widget displays all your Beszel-monitored systems in a sortable data table. Each row shows system metrics like CPU, memory, disk, network, temperature, and more. System and load-average status dots are displayed inline with their values. Columns can be toggled on or off, dragged into a different order, and resized from their headers. Column order and widths are saved automatically for each widget. When Beszel reports extra filesystems, the disk column adds a hover breakdown for each mount alongside the root disk.
|
||||
|
||||
Select a system row to open the complete statistics view, including system, filesystem, Docker, historical, and realtime charts.
|
||||
Select a system row to open the complete statistics view.
|
||||
|
||||
### Interactions
|
||||
|
||||
@@ -37,7 +37,7 @@ Select a system row to open the complete statistics view, including system, file
|
||||
]}
|
||||
/>
|
||||
|
||||
<WidgetInteractionGuide />
|
||||
<WidgetInteractionGuide advancedView={false} />
|
||||
|
||||
### Adding the widget
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
.surfaceHostAdvanced {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: calc(var(--mantine-z-index-modal) - 1);
|
||||
z-index: calc(var(--mantine-z-index-popover) - 1);
|
||||
width: auto;
|
||||
height: auto;
|
||||
pointer-events: none;
|
||||
|
||||
@@ -201,7 +201,7 @@ export const BoardAdvancedFocusProvider = ({ children }: PropsWithChildren) => {
|
||||
data-advanced-focus-overlay
|
||||
backgroundOpacity={0.38}
|
||||
blur={0}
|
||||
zIndex="calc(var(--mantine-z-index-modal) - 2)"
|
||||
zIndex="calc(var(--mantine-z-index-popover) - 2)"
|
||||
className={`${classes.backdrop} ${active.phase === "closing" ? classes.backdropClosing : ""}`}
|
||||
aria-hidden
|
||||
/>
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
const canScrollVertically = (element: HTMLElement, delta: number) => {
|
||||
if (element.scrollHeight <= element.clientHeight) return false;
|
||||
|
||||
const overflowY = getComputedStyle(element).overflowY;
|
||||
if (overflowY !== "auto" && overflowY !== "scroll") return false;
|
||||
|
||||
return delta < 0 ? element.scrollTop > 0 : element.scrollTop + element.clientHeight < element.scrollHeight;
|
||||
};
|
||||
|
||||
export const redirectShiftWheel = (root: HTMLElement, target: EventTarget | null, delta: number) => {
|
||||
if (delta === 0) return false;
|
||||
|
||||
let current = target instanceof HTMLElement ? target : null;
|
||||
while (current && root.contains(current)) {
|
||||
if (canScrollVertically(current, delta)) {
|
||||
current.scrollTop += delta;
|
||||
return true;
|
||||
}
|
||||
if (current === root) break;
|
||||
current = current.parentElement;
|
||||
}
|
||||
|
||||
const viewport = [...root.querySelectorAll<HTMLElement>("[data-scrollbars='y'], [data-scrollbars='xy']")].find(
|
||||
(element) => canScrollVertically(element, delta),
|
||||
);
|
||||
if (!viewport) return false;
|
||||
|
||||
viewport.scrollTop += delta;
|
||||
return true;
|
||||
};
|
||||
@@ -115,6 +115,18 @@ describe("Board advanced interactions", () => {
|
||||
await expect(page.getByRole("button", { name: /advanced view is pinned|keep advanced view open/i })).toHaveCount(
|
||||
0,
|
||||
);
|
||||
await advancedSurface.evaluate((element) => {
|
||||
const probe = document.createElement("div");
|
||||
probe.dataset.shiftScrollProbe = "true";
|
||||
probe.style.cssText = "height:80px;overflow-y:auto";
|
||||
probe.innerHTML = '<div style="height:600px"></div>';
|
||||
element.append(probe);
|
||||
});
|
||||
const scrollProbe = advancedSurface.locator("[data-shift-scroll-probe]");
|
||||
await page.keyboard.down("Shift");
|
||||
await scrollProbe.dispatchEvent("wheel", { shiftKey: true, deltaX: 120, deltaY: 0 });
|
||||
await page.keyboard.up("Shift");
|
||||
expect(await scrollProbe.evaluate((element) => element.scrollTop)).toBeGreaterThan(0);
|
||||
await page.keyboard.press("Escape");
|
||||
await expect(advancedSurface).toBeHidden();
|
||||
await expect(widget).toBeFocused();
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
export interface StreamSession {
|
||||
sessionId: string;
|
||||
sessionName: string;
|
||||
user: {
|
||||
user?: {
|
||||
userId: string;
|
||||
username: string;
|
||||
profilePictureUrl: string | null;
|
||||
|
||||
@@ -3488,6 +3488,7 @@
|
||||
"items": {
|
||||
"currentlyPlaying": "Currently playing",
|
||||
"user": "User",
|
||||
"unknownUser": "Unknown user",
|
||||
"name": "Name",
|
||||
"id": "Id",
|
||||
"status": "Status",
|
||||
|
||||
@@ -15,6 +15,7 @@ import { optionsBuilder } from "../options";
|
||||
|
||||
export const { definition, componentLoader } = createWidgetDefinition("app", {
|
||||
icon: IconApps,
|
||||
supportsAdvancedFocus: false,
|
||||
queryKeys: [[["app", "byId"]], [["widget", "app", "ping"]]],
|
||||
createOptions(settings) {
|
||||
return optionsBuilder.from(
|
||||
|
||||
@@ -6,6 +6,7 @@ import { optionsBuilder } from "../options";
|
||||
|
||||
export const { definition, componentLoader } = createWidgetDefinition("beszelSystemGrid", {
|
||||
icon: IconLayoutGrid,
|
||||
supportsAdvancedFocus: false,
|
||||
queryKey: [["widget", "beszel", "getSystems"]],
|
||||
supportedIntegrations: ["beszel", "mock"],
|
||||
integrationsRequired: true,
|
||||
|
||||
@@ -25,6 +25,7 @@ const sortDirectionOptions = [
|
||||
|
||||
export const { definition, componentLoader } = createWidgetDefinition("beszelSystemTable", {
|
||||
icon: IconTable,
|
||||
supportsAdvancedFocus: false,
|
||||
queryKeys: [[["widget", "beszel", "getSystems"]], [["widget", "beszel", "getSystemStats"]]],
|
||||
queryMatcher: (query, scope) =>
|
||||
query.path.at(-1) === "getSystems"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Box, Group, HoverCard, Progress, Stack, Text, UnstyledButton } from "@mantine/core";
|
||||
import { Box, getDefaultZIndex, Group, HoverCard, Progress, Stack, Text, UnstyledButton } from "@mantine/core";
|
||||
|
||||
import type { BeszelSystemRow } from "./types";
|
||||
import { thresholdColor } from "./colors";
|
||||
@@ -28,6 +28,7 @@ export const DiskUsage = ({ system, fontSize, progressSize, valueMiw, valueGap =
|
||||
{formatPercent(system.disk)}
|
||||
</Text>
|
||||
<HoverCard
|
||||
zIndex={getDefaultZIndex("popover") - 3}
|
||||
position="right"
|
||||
withArrow
|
||||
shadow="md"
|
||||
|
||||
@@ -1,6 +1,14 @@
|
||||
import { describe, expect, test } from "vitest";
|
||||
|
||||
import { getWidgetQueryKeys, getWidgetRuntimeQueries, setWidgetRuntimeQueries } from "./definition";
|
||||
import { definition as appDefinition } from "./app";
|
||||
import { definition as beszelSystemGridDefinition } from "./beszel-system-grid";
|
||||
import { definition as beszelSystemTableDefinition } from "./beszel-system-table";
|
||||
import {
|
||||
getWidgetQueryKeys,
|
||||
getWidgetRuntimeQueries,
|
||||
setWidgetRuntimeQueries,
|
||||
supportsAdvancedFocus,
|
||||
} from "./definition";
|
||||
|
||||
const togglePolling = () => undefined;
|
||||
|
||||
@@ -32,3 +40,22 @@ describe("getWidgetQueryKeys", () => {
|
||||
expect(getWidgetRuntimeQueries(widgetStateRef)).toEqual(runtimeQueries);
|
||||
});
|
||||
});
|
||||
|
||||
describe("supportsAdvancedFocus", () => {
|
||||
const definition = { supportsAdvancedFocus: undefined } as Parameters<typeof supportsAdvancedFocus>[0];
|
||||
|
||||
test("defaults to enabled", () => {
|
||||
expect(supportsAdvancedFocus(definition)).toBe(true);
|
||||
});
|
||||
|
||||
test("allows widgets to opt out", () => {
|
||||
expect(supportsAdvancedFocus({ ...definition, supportsAdvancedFocus: false })).toBe(false);
|
||||
});
|
||||
|
||||
test.each([appDefinition, beszelSystemGridDefinition, beszelSystemTableDefinition])(
|
||||
"keeps non-enhanced widgets compact",
|
||||
(widgetDefinition) => {
|
||||
expect(supportsAdvancedFocus(widgetDefinition)).toBe(false);
|
||||
},
|
||||
);
|
||||
});
|
||||
@@ -79,6 +79,7 @@ export {
|
||||
getWidgetQueryKeys,
|
||||
getWidgetRuntimeQueries,
|
||||
normalizeWidgetQuery,
|
||||
supportsAdvancedFocus,
|
||||
widgetQueryValueEquals,
|
||||
} from "./definition";
|
||||
export type { WidgetComponentProps };
|
||||
|
||||
Reference in new issue
Block a user