fix: stabilize widgets in board edit mode

This commit is contained in:
ajnart committed 2026-08-23 01:56:31 +02:00
1 parent 1b843817a4
commit 978b3c7f33
51 files changed
+46 -351

No files matched your search

@@ -149,8 +149,8 @@ export function HomarrDataTable<T>({
defaultColumnProps={{
...defaultColumnProps,
noWrap: defaultColumnProps?.noWrap ?? true,
draggable: !isEditMode,
resizable: !isEditMode,
draggable: true,
resizable: true,
cellsStyle: defaultColumnProps?.cellsStyle ?? (() => ({ padding: cellPadding })),
}}
scrollAreaProps={{ type: "auto", scrollbarSize: 6, ...scrollAreaProps }}
@@ -5,11 +5,10 @@ import { createRoot } from "react-dom/client";
import { MantineProvider } from "@mantine/core";
import { afterEach, describe, expect, test, vi } from "vitest";
import { WidgetQueryErrorIndicator, WidgetQueryLoadingState } from "./query-state-indicator";
import { WidgetQueryLoadingState } from "./query-state-indicator";
vi.mock("@homarr/translation/client", () => ({
useI18n: () => (key: string, values?: { widget?: string }) => {
if (key === "stale") return `Could not refresh ${values?.widget}; showing saved data`;
useI18n: () => (key: string) => {
if (key === "loading") return "Loading widget data";
return key;
},
@@ -26,7 +25,7 @@ const matchMedia = (query: string) => ({
dispatchEvent: () => false,
});
describe("WidgetQueryErrorIndicator", () => {
describe("WidgetQueryLoadingState", () => {
let host: HTMLDivElement | undefined;
let root: ReturnType<typeof createRoot> | undefined;
@@ -36,28 +35,6 @@ describe("WidgetQueryErrorIndicator", () => {
vi.unstubAllGlobals();
});
test("uses a localized label without exposing the raw failure", async () => {
vi.stubGlobal("IS_REACT_ACT_ENVIRONMENT", true);
vi.stubGlobal("matchMedia", matchMedia);
host = document.createElement("div");
document.body.append(host);
root = createRoot(host);
const secret = "http://admin:password@private.local/path?token=secret";
await act(async () =>
root?.render(
createElement(
MantineProvider,
null,
createElement(WidgetQueryErrorIndicator, { error: new Error(secret), label: "Weather" }),
),
),
);
expect(host.querySelector("[aria-label='Could not refresh Weather; showing saved data']")).not.toBeNull();
expect(host.textContent).not.toMatch(/password|private\.local|token=secret/);
});
test("announces the loading state to assistive technology", async () => {
vi.stubGlobal("IS_REACT_ACT_ENVIRONMENT", true);
vi.stubGlobal("matchMedia", matchMedia);
@@ -1,39 +1,9 @@
"use client";
import { ActionIcon, Center, Loader, Tooltip, VisuallyHidden } from "@mantine/core";
import { IconAlertTriangle } from "@tabler/icons-react";
import { Center, Loader, VisuallyHidden } from "@mantine/core";
import { useI18n } from "@homarr/translation/client";
interface WidgetQueryErrorIndicatorProps {
error: unknown;
label: string;
}
/** Localized warning for stale or optional-query failures. Never renders the raw error. */
export function WidgetQueryErrorIndicator({ error, label }: WidgetQueryErrorIndicatorProps) {
const t = useI18n("common.widgetQuery");
if (!error) return null;
const accessibleLabel = t("stale", { widget: label });
return (
<Tooltip label={accessibleLabel} position="left" withArrow>
<ActionIcon
component="span"
color="orange"
variant="subtle"
size="sm"
role="note"
aria-label={accessibleLabel}
tabIndex={0}
style={{ cursor: "help" }}
>
<IconAlertTriangle aria-hidden size="var(--mantine-font-size-sm)" />
</ActionIcon>
</Tooltip>
);
}
export function WidgetQueryLoadingState() {
const t = useI18n("common.widgetQuery");
return (