diff --git a/apps/nextjs/src/components/board/sections/container-section.tsx b/apps/nextjs/src/components/board/sections/container-section.tsx index 1c21a311e..0c585259d 100644 --- a/apps/nextjs/src/components/board/sections/container-section.tsx +++ b/apps/nextjs/src/components/board/sections/container-section.tsx @@ -42,18 +42,7 @@ export const BoardContainerSection = ({ section }: Props) => { const labelRight = isEditMode ? 48 : options.showOpenAll ? 40 : 8; return ( - + { styles={{ root: { overflow: "visible", + "--opacity": board.opacity / 100, + "--border-color": options.borderColor || undefined, }, }} radius={board.itemRadius} @@ -110,6 +101,31 @@ export const BoardContainerSection = ({ section }: Props) => { )} )} + {!isVisuallyCollapsed && !options.collapsible && options.showLabel && options.title && ( + + {options.title} + + )} {options.showOpenAll && !isEditMode && ( { - {!isVisuallyCollapsed && !options.collapsible && options.showLabel && options.title && ( - - {options.title} - - )} {isEditMode && } ); diff --git a/apps/nextjs/src/components/board/sections/item.module.css b/apps/nextjs/src/components/board/sections/item.module.css index 006e98e25..319eb7b07 100644 --- a/apps/nextjs/src/components/board/sections/item.module.css +++ b/apps/nextjs/src/components/board/sections/item.module.css @@ -1,4 +1,4 @@ -.containerRoot { +.itemCard { @mixin dark { --background-color: rgb(from var(--mantine-color-dark-6) r g b / var(--opacity)); --border-color: rgb(from var(--mantine-color-dark-4) r g b / var(--opacity)); @@ -7,9 +7,6 @@ --background-color: rgb(from var(--mantine-color-white) r g b / var(--opacity)); --border-color: rgb(from var(--mantine-color-gray-3) r g b / var(--opacity)); } -} - -.itemCard { background-color: var(--background-color) !important; border-color: var(--border-color) !important; } @@ -45,21 +42,6 @@ white-space: nowrap; } -.containerLabel { - overflow: visible; - pointer-events: none; - background-color: var(--background-color); - border-color: var(--border-color); -} - -.containerLabel :global(.mantine-Badge-label) { - min-width: 0; - overflow: hidden; - line-height: 1.2; - text-overflow: ellipsis; - white-space: nowrap; -} - .containerBody { visibility: visible; opacity: 1; diff --git a/packages/widgets/src/calendar/component.module.css b/packages/widgets/src/calendar/component.module.css index f9ff6ab2d..2ae544f49 100644 --- a/packages/widgets/src/calendar/component.module.css +++ b/packages/widgets/src/calendar/component.module.css @@ -5,6 +5,8 @@ } .errorIndicator { + top: calc(var(--mantine-spacing-md) + var(--mantine-spacing-xl)); + inset-inline-end: 4px; z-index: 1; padding: 2px; border: 1px solid rgb(from var(--mantine-primary-color-filled) r g b / calc(var(--opacity, 1) * 0.45)); diff --git a/packages/widgets/src/calendar/component.tsx b/packages/widgets/src/calendar/component.tsx index 6291f8a4a..4a46d8f21 100644 --- a/packages/widgets/src/calendar/component.tsx +++ b/packages/widgets/src/calendar/component.tsx @@ -257,7 +257,7 @@ const CalendarBase = ({ }} /> {hasErrors && ( - + diff --git a/packages/widgets/src/docker/styles.css b/packages/widgets/src/docker/styles.css index 85133f2c7..5e24b55ca 100644 --- a/packages/widgets/src/docker/styles.css +++ b/packages/widgets/src/docker/styles.css @@ -3,22 +3,3 @@ --mantine-datatable-background-color: transparent !important; --mantine-datatable-striped-color: transparent !important; } - -.homarr-data-table.docker-table, -.homarr-data-table.docker-table .mantine-datatable-table, -.homarr-data-table.docker-table .mantine-datatable-table thead, -.homarr-data-table.docker-table .mantine-datatable-table tbody, -.homarr-data-table.docker-table .mantine-datatable-table tfoot, -.homarr-data-table.docker-table .mantine-datatable-table tr, -.homarr-data-table.docker-table th, -.homarr-data-table.docker-table td { - background: transparent !important; -} - -.homarr-data-table.docker-table .mantine-datatable-table tbody tr:hover { - background: transparent !important; -} - -.homarr-data-table.docker-table th { - backdrop-filter: none; -} diff --git a/packages/widgets/src/downloads/styles.css b/packages/widgets/src/downloads/styles.css index 66c8d44bc..0caffa099 100644 --- a/packages/widgets/src/downloads/styles.css +++ b/packages/widgets/src/downloads/styles.css @@ -1,20 +1,4 @@ -/* Keep the downloads table translucent so the board wallpaper and opacity remain visible. */ -.homarr-data-table.downloads-table, -.homarr-data-table.downloads-table .mantine-datatable-table, -.homarr-data-table.downloads-table .mantine-datatable-table thead, -.homarr-data-table.downloads-table .mantine-datatable-table tbody, -.homarr-data-table.downloads-table .mantine-datatable-table tfoot, -.homarr-data-table.downloads-table .mantine-datatable-table tr, -.homarr-data-table.downloads-table th, -.homarr-data-table.downloads-table td, -.homarr-data-table.downloads-table .mantine-datatable-row-expansion-cell { - background-color: transparent !important; -} - -.homarr-data-table.downloads-table .mantine-datatable-table tbody tr:hover { - background-color: transparent !important; -} - -.homarr-data-table.downloads-table th { - backdrop-filter: none; +/* Row expansion styling */ +.downloads-table .mantine-datatable-row-expansion-cell { + background-color: color-mix(in srgb, var(--mantine-color-body) 40%, transparent) !important; } diff --git a/packages/widgets/src/rssFeed/component.tsx b/packages/widgets/src/rssFeed/component.tsx index e84e8f2c3..2d2cb92bf 100644 --- a/packages/widgets/src/rssFeed/component.tsx +++ b/packages/widgets/src/rssFeed/component.tsx @@ -219,7 +219,7 @@ export const getRssEntryDisplay = ({ descriptionLines, }: RssEntryDisplayInput) => ({ showDescription: isAdvanced || (!hideDescription && !isTiny), - showImage: isAdvanced || (showPosterImage && !isDense), + showImage: isAdvanced || (showPosterImage && !isTiny), showSource: isAdvanced || !isDense, descriptionLineClamp: isAdvanced ? undefined : descriptionLines, }); diff --git a/packages/widgets/src/system-disks/component.tsx b/packages/widgets/src/system-disks/component.tsx index 9313cc602..956d8c00d 100644 --- a/packages/widgets/src/system-disks/component.tsx +++ b/packages/widgets/src/system-disks/component.tsx @@ -1,6 +1,6 @@ "use client"; -import { useEffect, useRef, useState } from "react"; +import { useEffect, useMemo, useRef, useState } from "react"; import { Box, Card, Group, ScrollArea, SimpleGrid, Text, Tooltip } from "@mantine/core"; import { clientApi } from "@homarr/api/client"; @@ -18,6 +18,34 @@ import { NoIntegrationDataError } from "../errors/no-data-integration"; type DiskDisplayMode = WidgetComponentProps<"systemDisks">["options"]["displayMode"]; +const advancedColumnBreakpoints = [ + { minimumWidth: 1820, columns: 7 }, + { minimumWidth: 1560, columns: 6 }, + { minimumWidth: 1300, columns: 5 }, + { minimumWidth: 1040, columns: 4 }, + { minimumWidth: 780, columns: 3 }, + { minimumWidth: 520, columns: 2 }, + { minimumWidth: 0, columns: 1 }, +] as const; + +const getSystemDisksLayout = (width: number, height: number, diskCount: number, isAdvanced: boolean) => { + if (!isAdvanced) { + return { + columns: 1, + showSecondaryText: height >= 160 && width >= 260, + showTemperature: height >= 100 && width >= 220, + }; + } + + const breakpoint = advancedColumnBreakpoints.find((candidate) => width >= candidate.minimumWidth); + const columns = Math.min(diskCount, breakpoint?.columns ?? 1); + return { + columns: Math.max(1, columns), + showSecondaryText: true, + showTemperature: true, + }; +}; + export const clampPercentage = (percentage: number): number => Math.min(100, Math.max(0, percentage)); export const getDisplayText = ( @@ -201,6 +229,27 @@ export default function SystemResources({ const data = results.filter( (entry): entry is typeof entry & { healthInfo: NonNullable } => entry.healthInfo !== null, ); + const disks = data.flatMap((entry) => { + const fileSystem = filterStorageVolumes( + entry.healthInfo.fileSystem, + options.visibleStorageVolumes, + entry.integrationId, + ); + const smart = filterStorageVolumes(entry.healthInfo.smart, options.visibleStorageVolumes, entry.integrationId); + return fileSystem.map((item) => ({ + integrationId: entry.integrationId, + integrationName: entry.integrationName, + item, + smartItem: smart.find( + (candidate) => normalizeStorageDeviceName(candidate.deviceName) === normalizeStorageDeviceName(item.deviceName), + ), + })); + }); + const isAdvanced = displayMode === "advanced"; + const layout = useMemo( + () => getSystemDisksLayout(width, height, disks.length, isAdvanced), + [disks.length, height, isAdvanced, width], + ); const queryIndicators = ( @@ -219,40 +268,18 @@ export default function SystemResources({ if (isInitialWidgetQueryPending(healthQuery)) return ; if (data.length === 0) return emptyState; - const disks = data.flatMap((entry) => { - const fileSystem = filterStorageVolumes( - entry.healthInfo.fileSystem, - options.visibleStorageVolumes, - entry.integrationId, - ); - const smart = filterStorageVolumes(entry.healthInfo.smart, options.visibleStorageVolumes, entry.integrationId); - return fileSystem.map((item) => ({ - integrationId: entry.integrationId, - integrationName: entry.integrationName, - item, - smartItem: smart.find( - (candidate) => normalizeStorageDeviceName(candidate.deviceName) === normalizeStorageDeviceName(item.deviceName), - ), - })); - }); - const hasNoFileSystems = data.every((entry) => entry.healthInfo.fileSystem.length === 0); const hasFailedSource = results.some((entry) => Boolean(entry.error)); if (hasNoFileSystems && !hasFailedSource && !healthQuery.error) throw new NoIntegrationDataError(); if (hasNoFileSystems || disks.length === 0) return emptyState; - const isAdvanced = displayMode === "advanced"; - const minimumCardWidth = 260; - const columns = isAdvanced ? Math.max(1, Math.min(disks.length, Math.floor(width / minimumCardWidth))) : 1; - const cellWidth = width / columns; - return ( {queryIndicators} - + {disks.map(({ integrationId, integrationName, item, smartItem }) => { const freeText = t("status.free", { percentage: String(Math.round(100 - clampPercentage(item.percentage))), @@ -275,8 +302,8 @@ export default function SystemResources({ freeText={advancedDisplayTexts.free} smartStatus={smartItem?.overallStatus} isAdvanced={isAdvanced} - showSecondaryText={isAdvanced || (height >= 160 && cellWidth >= 260)} - showTemperature={isAdvanced || (height >= 100 && cellWidth >= 220)} + showSecondaryText={layout.showSecondaryText} + showTemperature={layout.showTemperature} /> ); })} diff --git a/packages/widgets/src/system-resources/component.tsx b/packages/widgets/src/system-resources/component.tsx index ec665d306..65b8714b8 100644 --- a/packages/widgets/src/system-resources/component.tsx +++ b/packages/widgets/src/system-resources/component.tsx @@ -230,18 +230,19 @@ const SystemCharts = ({ const labelDisplayMode = isAdvanced ? "textWithIcon" : options.labelDisplayMode; const chartCount = visibleCharts.length; const chartColumns = isAdvanced && width >= 560 ? 2 : 1; + const compactChartGap = 8; const chartHeight = isAdvanced ? 180 - : `calc((100% - ${Math.max(0, chartCount - 1) * 8}px) / ${Math.max(1, chartCount)})`; + : `calc((100% - ${Math.max(0, chartCount - 1) * compactChartGap}px) / ${Math.max(1, chartCount)})`; return ( - + {showTitle && ( {integrationName} )} - + {chartCount === 0 && (