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 && (