From dc600fc5c8032489e96abcd67718d1322eeded97 Mon Sep 17 00:00:00 2001 From: ajnart Date: Sat, 29 Aug 2026 07:30:41 +0200 Subject: [PATCH] fix(board): improve widget scaling and edit layering --- .../board/sections/grid/grid-editor.css | 2 +- .../beszel-system-stats/component.module.css | 9 +- .../src/beszel-system-stats/component.tsx | 139 +++++++++--------- packages/widgets/src/beszel/_shared/chart.tsx | 6 +- .../widgets/src/beszel/_shared/stats-view.tsx | 13 +- .../src/uptime-kuma/component.module.css | 34 ++--- .../widgets/src/uptime-kuma/component.tsx | 7 +- 7 files changed, 110 insertions(+), 100 deletions(-) diff --git a/apps/nextjs/src/components/board/sections/grid/grid-editor.css b/apps/nextjs/src/components/board/sections/grid/grid-editor.css index 0de7580b3..4b7e47054 100644 --- a/apps/nextjs/src/components/board/sections/grid/grid-editor.css +++ b/apps/nextjs/src/components/board/sections/grid/grid-editor.css @@ -13,7 +13,7 @@ .board-grid-entry:has([data-board-widget-header]):hover, .board-grid-entry:has([data-board-widget-header]):focus-within { - z-index: var(--homarr-z-index-widget-focus); + z-index: calc(var(--homarr-z-index-board-header) - 1); } .board-grid-entry[data-grid-preview="true"] { diff --git a/packages/widgets/src/beszel-system-stats/component.module.css b/packages/widgets/src/beszel-system-stats/component.module.css index f9e9b584b..d41f57deb 100644 --- a/packages/widgets/src/beszel-system-stats/component.module.css +++ b/packages/widgets/src/beszel-system-stats/component.module.css @@ -1,11 +1,16 @@ .beszelStatsControls { + position: absolute; + z-index: 2; + top: var(--mantine-spacing-sm); + right: var(--mantine-spacing-sm); + left: var(--mantine-spacing-sm); opacity: 0; transition: opacity 150ms ease-out; pointer-events: none; } -.beszelStatsContainer:hover .beszelStatsControls, -.beszelStatsContainer:focus-within .beszelStatsControls { +.beszelStatsRoot:hover .beszelStatsControls, +.beszelStatsRoot:focus-within .beszelStatsControls { opacity: 1; pointer-events: auto; } diff --git a/packages/widgets/src/beszel-system-stats/component.tsx b/packages/widgets/src/beszel-system-stats/component.tsx index 2a209e85e..eb24774eb 100644 --- a/packages/widgets/src/beszel-system-stats/component.tsx +++ b/packages/widgets/src/beszel-system-stats/component.tsx @@ -23,9 +23,8 @@ import { getUsableWidgetQueryData } from "../common/query-state"; import { BeszelStatsView } from "../beszel/_shared/stats-view"; import { createBeszelSystemChoices, resolveBeszelSystemChoice } from "./selection"; -const CONTROLS_ROW_HEIGHT = 36; -const CONTROLS_ROW_GAP = 16; const STACK_PADDING = 24; +const SCROLL_FIT_BUFFER = 2; export default function BeszelSystemStatsWidget({ options, @@ -33,6 +32,7 @@ export default function BeszelSystemStatsWidget({ isEditMode, width, height, + displayScale = 1, boardId, itemId, setOptions, @@ -71,6 +71,12 @@ export default function BeszelSystemStatsWidget({ { value: "1w", label: t("period.oneWeek") }, { value: "30d", label: t("period.thirtyDays") }, ]; + let responsiveWidth = width; + let responsiveHeight = height; + if (Number.isFinite(displayScale) && displayScale > 0) { + responsiveWidth *= displayScale; + responsiveHeight *= displayScale; + } useWidgetRuntimeQueries( widgetRuntimeRef, selectedSystem && options.timePeriod !== "1m" @@ -185,86 +191,83 @@ export default function BeszelSystemStatsWidget({ } return ( - + + {!isEditMode && ( + + {systems.length > 1 && ( + + + + + + {systems.map((system) => ( + handleSelectSystem(system.value)} + > + {system.label} + + ))} + + + )} + {responsiveWidth >= 560 ? ( + + {periodOptions.map((period) => ( + + ))} + + ) : ( + value && handleTimePeriod(value)} - disabled={isSelectionSavePending} - data={periodOptions} - className={classes.beszelStatsPeriodToggle} - /> - )} - - )} + 600 ? 2 : 1} - availableHeight={Math.max( - 0, - height - STACK_PADDING - (isEditMode ? 0 : CONTROLS_ROW_HEIGHT + CONTROLS_ROW_GAP), - )} + columns={responsiveWidth > 600 ? 2 : 1} + availableHeight={Math.max(0, responsiveHeight - STACK_PADDING - SCROLL_FIT_BUFFER)} visibility={{ cpu: options.showCpu, memory: options.showMemory, diff --git a/packages/widgets/src/beszel/_shared/chart.tsx b/packages/widgets/src/beszel/_shared/chart.tsx index 7a9949b6d..89dacb535 100644 --- a/packages/widgets/src/beszel/_shared/chart.tsx +++ b/packages/widgets/src/beszel/_shared/chart.tsx @@ -101,12 +101,12 @@ interface BeszelChartPanelProps { export const BeszelChartPanel = memo(({ title, subtitle, chartProps }: BeszelChartPanelProps) => ( - - + + {title} {subtitle && ( - + {subtitle} )} diff --git a/packages/widgets/src/beszel/_shared/stats-view.tsx b/packages/widgets/src/beszel/_shared/stats-view.tsx index ef67f40ee..91996d266 100644 --- a/packages/widgets/src/beszel/_shared/stats-view.tsx +++ b/packages/widgets/src/beszel/_shared/stats-view.tsx @@ -35,13 +35,13 @@ const tooltips = { bytesTotal: makeTooltipProps(formatStorageBytes, true), }; -const ChartSkeleton = () => ( +const ChartSkeleton = ({ height }: { height: number }) => ( - + ); @@ -67,7 +67,7 @@ interface BeszelStatsViewProps { } const MIN_CHART_HEIGHT = 100; -const PANEL_ROW_OVERHEAD = 28; +const PANEL_ROW_OVERHEAD = 25; const GRID_ROW_GAP = 16; const computeChartHeight = (availableHeight: number | undefined, rowCount: number) => { @@ -238,10 +238,13 @@ export function BeszelStatsView({ if (!activeStats) { const visibleChartCount = Object.values(visibility).filter(Boolean).length; + const effectiveColumns = Math.min(columns, Math.max(1, visibleChartCount)) as 1 | 2; + const rowCount = Math.ceil(visibleChartCount / effectiveColumns); + const chartHeight = computeChartHeight(availableHeight, rowCount); return ( - + {Array.from({ length: visibleChartCount }, (_, index) => ( - + ))} ); diff --git a/packages/widgets/src/uptime-kuma/component.module.css b/packages/widgets/src/uptime-kuma/component.module.css index f56a12bda..57fb16aed 100644 --- a/packages/widgets/src/uptime-kuma/component.module.css +++ b/packages/widgets/src/uptime-kuma/component.module.css @@ -4,8 +4,8 @@ flex-direction: column; height: 100%; min-height: 0; - padding: clamp(6px, 3cqi, 14px); - gap: clamp(4px, 2cqi, 10px); + padding: clamp(6px, 3cqmin, 14px); + gap: clamp(4px, 2cqmin, 10px); overflow: hidden; } @@ -13,11 +13,10 @@ display: flex; align-items: center; justify-content: space-between; - gap: clamp(8px, 3cqi, 18px); + gap: clamp(8px, 3cqmin, 18px); flex-shrink: 0; - padding-bottom: clamp(4px, 2cqi, 10px); - border-bottom: 1px solid - rgb(from var(--mantine-color-default-border) r g b / calc(var(--opacity, 1) * 0.45)); + padding-bottom: clamp(4px, 2cqmin, 10px); + border-bottom: 1px solid rgb(from var(--mantine-color-default-border) r g b / calc(var(--opacity, 1) * 0.45)); } .heroExpanded { @@ -37,25 +36,25 @@ } .heroTextOnly .heroValue { - font-size: clamp(22px, 10cqi, 36px); + font-size: clamp(22px, 10cqmin, 36px); } .heroText { display: flex; flex-direction: column; - gap: clamp(2px, 1cqi, 4px); + gap: clamp(2px, 1cqmin, 4px); min-width: 0; flex: 1; } .heroLabel { - font-size: clamp(9px, 2.8cqi, 13px); + font-size: clamp(9px, 2.8cqmin, 13px); color: var(--mantine-color-dimmed); line-height: 1.2; } .heroValue { - font-size: clamp(18px, 8cqi, 32px); + font-size: clamp(18px, 8cqmin, 32px); font-weight: 700; line-height: 1.1; } @@ -68,7 +67,7 @@ display: grid; flex: 1; min-height: 0; - gap: clamp(4px, 2cqi, 10px); + gap: clamp(4px, 2cqmin, 10px); grid-template-columns: repeat(var(--stat-cols, 2), minmax(0, 1fr)); grid-auto-rows: 1fr; align-content: stretch; @@ -81,11 +80,10 @@ justify-content: center; min-height: 0; min-width: 0; - gap: clamp(2px, 1.5cqi, 5px); - padding: clamp(4px, 2.5cqi, 10px); + gap: clamp(2px, 1.5cqmin, 5px); + padding: clamp(4px, 2.5cqmin, 10px); border-radius: var(--mantine-radius-md); - border: 1px solid - rgb(from var(--mantine-color-default-border) r g b / calc(var(--opacity, 1) * 0.45)); + border: 1px solid rgb(from var(--mantine-color-default-border) r g b / calc(var(--opacity, 1) * 0.45)); background-color: transparent; overflow: hidden; } @@ -95,14 +93,14 @@ } .statValue { - font-size: clamp(14px, 5.5cqi, 24px); + font-size: clamp(14px, 5.5cqmin, 24px); font-weight: 700; line-height: 1.1; text-align: center; } .statLabel { - font-size: clamp(8px, 2.5cqi, 12px); + font-size: clamp(8px, 2.5cqmin, 12px); color: var(--mantine-color-dimmed); line-height: 1.2; text-align: center; @@ -119,6 +117,6 @@ justify-content: center; min-height: 0; color: var(--mantine-color-dimmed); - font-size: clamp(11px, 3cqi, 14px); + font-size: clamp(11px, 3cqmin, 14px); text-align: center; } diff --git a/packages/widgets/src/uptime-kuma/component.tsx b/packages/widgets/src/uptime-kuma/component.tsx index 908d8ed52..6108fec8f 100644 --- a/packages/widgets/src/uptime-kuma/component.tsx +++ b/packages/widgets/src/uptime-kuma/component.tsx @@ -129,8 +129,9 @@ function UptimeKumaContent({ integrationIds, options, width, height, displayMode ); const uptimeValue = clampPercent(combined.averageUptimePercent); const uptimeColor = getUptimeColor(uptimeValue); - const ringSize = getRingSize(width); - const iconSize = getIconSize(width); + const visualScale = Math.min(width, height); + const ringSize = getRingSize(visualScale); + const iconSize = getIconSize(visualScale); const gridCols = getGridCols(width); const isAdvanced = displayMode === "advanced"; @@ -247,7 +248,7 @@ function UptimeKumaContent({ integrationIds, options, width, height, displayMode - {summaryContent} + {summaryContent} {monitorList}