fix: restore translucent dashboard styling

This commit is contained in:
ajnart committed 2026-08-22 12:40:11 +02:00
1 parent 7591f7e313
commit 3f9ab07a5e
29 files changed
+277 -145

No files matched your search

@@ -23,6 +23,8 @@
* zoom and enlarges everything together.
*/
--mantine-scale: var(--board-canvas-ui-scale, 1);
/* Keep the legacy card inset visually stable while the fixed canvas is zoomed. */
--board-grid-item-inset: calc(10px * var(--board-canvas-inverse-scale, 1) - 12px);
--mantine-spacing-xs: calc(0.625rem * var(--board-canvas-ui-scale, 1));
--mantine-spacing-sm: calc(0.75rem * var(--board-canvas-ui-scale, 1));
--mantine-spacing-md: calc(1rem * var(--board-canvas-ui-scale, 1));
@@ -51,7 +51,7 @@
.board-grid-entry[data-board-item-selected="true"]::after {
position: absolute;
inset: 0;
inset: var(--board-grid-item-inset, 0px);
z-index: 100;
content: "";
background: color-mix(in srgb, var(--mantine-primary-color-filled) 12%, transparent);
@@ -121,6 +121,7 @@ body[data-board-grid-interacting="resize"] .board-grid-entry[data-dnd-active="tr
.board-grid-placeholder {
z-index: 0;
clip-path: inset(var(--board-grid-item-inset, 0px));
background: color-mix(in srgb, var(--mantine-primary-color-filled) 16%, transparent);
border: 2px dashed var(--mantine-primary-color-filled);
border-radius: var(--mantine-radius-md);
@@ -139,6 +140,7 @@ body[data-board-grid-interacting="resize"] .board-grid-entry[data-dnd-active="tr
.board-grid-resize-outline {
z-index: 1002;
clip-path: inset(var(--board-grid-item-inset, 0px));
background: color-mix(in srgb, var(--mantine-primary-color-filled) 6%, transparent);
border: 2px solid var(--mantine-primary-color-filled);
border-radius: var(--mantine-radius-md);
@@ -240,11 +242,11 @@ body[data-board-grid-interacting="resize"] .board-grid-entry[data-dnd-active="tr
}
.board-grid-resize-handle[data-grid-resize-handle="n"] {
top: -0.25rem;
top: calc(var(--board-grid-item-inset, 0px) - 0.25rem);
}
.board-grid-resize-handle[data-grid-resize-handle="s"] {
bottom: -0.25rem;
bottom: calc(var(--board-grid-item-inset, 0px) - 0.25rem);
}
.board-grid-resize-handle[data-grid-resize-handle="e"],
@@ -258,11 +260,11 @@ body[data-board-grid-interacting="resize"] .board-grid-entry[data-dnd-active="tr
}
.board-grid-resize-handle[data-grid-resize-handle="e"] {
right: -0.25rem;
right: calc(var(--board-grid-item-inset, 0px) - 0.25rem);
}
.board-grid-resize-handle[data-grid-resize-handle="w"] {
left: -0.25rem;
left: calc(var(--board-grid-item-inset, 0px) - 0.25rem);
}
.board-grid-resize-handle[data-grid-resize-handle="ne"],
@@ -275,42 +277,30 @@ body[data-board-grid-interacting="resize"] .board-grid-entry[data-dnd-active="tr
}
.board-grid-resize-handle[data-grid-resize-handle="ne"] {
top: -0.625rem;
right: -0.625rem;
top: calc(var(--board-grid-item-inset, 0px) - 0.625rem);
right: calc(var(--board-grid-item-inset, 0px) - 0.625rem);
cursor: nesw-resize;
}
.board-grid-resize-handle[data-grid-resize-handle="se"] {
right: -0.625rem;
bottom: -0.625rem;
right: calc(var(--board-grid-item-inset, 0px) - 0.625rem);
bottom: calc(var(--board-grid-item-inset, 0px) - 0.625rem);
cursor: nwse-resize;
}
.board-grid-resize-handle[data-grid-resize-handle="sw"] {
bottom: -0.625rem;
left: -0.625rem;
bottom: calc(var(--board-grid-item-inset, 0px) - 0.625rem);
left: calc(var(--board-grid-item-inset, 0px) - 0.625rem);
cursor: nesw-resize;
}
.board-grid-resize-handle[data-grid-resize-handle="nw"] {
top: -0.625rem;
left: -0.625rem;
top: calc(var(--board-grid-item-inset, 0px) - 0.625rem);
left: calc(var(--board-grid-item-inset, 0px) - 0.625rem);
cursor: nwse-resize;
/* Leaves a thin draggable border around the corner instead of a solid 44x44 hit area,
so it doesn't sit on top of the container menu button anchored in this same corner. */
clip-path: polygon(
evenodd,
0 0,
100% 0,
100% 100%,
0 100%,
0 0,
4px 4px,
4px 40px,
40px 40px,
40px 4px,
4px 4px
);
clip-path: polygon(evenodd, 0 0, 100% 0, 100% 100%, 0 100%, 0 0, 4px 4px, 4px 40px, 40px 40px, 40px 4px, 4px 4px);
}
@media (prefers-reduced-motion: reduce) {
@@ -46,9 +46,7 @@
.contentMount {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
inset: var(--board-grid-item-inset, 0px);
overflow: visible;
}
@@ -1,3 +1,7 @@
.progress {
opacity: max(0.6, var(--opacity, 1));
}
.clickableCard {
position: relative;
transition: background-color 150ms;
@@ -159,6 +159,7 @@ const MetricValue = ({ value, progress, size }: Pick<MetricRowProps, "value" | "
</Text>
{progress && (
<Progress
className={classes.progress}
value={progress.value}
color={progress.color}
size={getProgressTrackSize(size.progressSize)}
@@ -1,3 +1,7 @@
.progress {
opacity: max(0.6, var(--opacity, 1));
}
.target {
position: relative;
z-index: 2;
@@ -63,7 +63,12 @@ export const DiskUsage = ({ system, fontSize, progressSize, valueMiw, valueGap =
}}
>
<Box pos="relative" style={{ flex: 1, minWidth: 24 }}>
<Progress value={system.disk} color={thresholdColor(system.disk)} size={trackSize} />
<Progress
className={classes.progress}
value={system.disk}
color={thresholdColor(system.disk)}
size={trackSize}
/>
<Group
gap={dotGap}
wrap="nowrap"
@@ -101,7 +106,13 @@ export const DiskUsage = ({ system, fontSize, progressSize, valueMiw, valueGap =
<Text size="sm" fw={600} miw={48} lh={1.25}>
{formatPercent(value)}
</Text>
<Progress value={value} color={thresholdColor(value)} size="xs" style={{ flex: 1 }} />
<Progress
className={classes.progress}
value={value}
color={thresholdColor(value)}
size="xs"
style={{ flex: 1 }}
/>
</Group>
</Stack>
))}
@@ -111,7 +122,12 @@ export const DiskUsage = ({ system, fontSize, progressSize, valueMiw, valueGap =
) : (
<Box style={{ display: "flex", alignItems: "center", flex: 1, minWidth: 24 }}>
<Box pos="relative" style={{ flex: 1, minWidth: 24 }}>
<Progress value={system.disk} color={thresholdColor(system.disk)} size={trackSize} />
<Progress
className={classes.progress}
value={system.disk}
color={thresholdColor(system.disk)}
size={trackSize}
/>
</Box>
</Box>
)}
@@ -3,3 +3,11 @@
display: flex;
flex-direction: column;
}
.errorIndicator {
z-index: 1;
padding: 2px;
border: 1px solid rgb(from var(--mantine-primary-color-filled) r g b / calc(var(--opacity, 1) * 0.45));
border-radius: var(--mantine-radius-sm);
background-color: rgb(from var(--mantine-color-secondaryColor-filled) r g b / calc(var(--opacity, 1) * 0.18));
}
+10 -8
View File
@@ -180,14 +180,10 @@ const CalendarBase = ({
);
}
const hasErrors = failedIntegrations.length > 0 || Boolean(queryError);
return (
<Stack ref={ref} h="100%" w="100%" gap="xs" style={{ overflow: "hidden" }}>
{(failedIntegrations.length > 0 || Boolean(queryError)) && (
<Group px="xs" justify="flex-end">
<IntegrationErrorIndicator results={failedIntegrations} />
<WidgetQueryErrorIndicator error={queryError} label={t("name")} />
</Group>
)}
<Box ref={ref} h="100%" w="100%" pos="relative" style={{ overflow: "hidden" }}>
<Calendar
defaultDate={new Date()}
onPreviousMonth={(previousMonth) => setMonth(new Date(previousMonth))}
@@ -260,7 +256,13 @@ const CalendarBase = ({
);
}}
/>
</Stack>
{hasErrors && (
<Group className={classes.errorIndicator} gap={0} pos="absolute" top={4} right={4}>
<IntegrationErrorIndicator results={failedIntegrations} />
<WidgetQueryErrorIndicator error={queryError} label={t("name")} />
</Group>
)}
</Box>
);
};
@@ -195,6 +195,9 @@ const StatCard = ({ item, data, usePiHoleColors, t }: StatCardProps) => {
const board = useRequiredBoard();
const label = translateIfNecessary(t, item.label);
const value = item.value(data, width <= 64 ? "sm" : "md");
const backgroundColor = usePiHoleColors
? `rgb(from ${item.color} r g b / calc(var(--opacity, 1) * 0.4))`
: "rgb(from var(--mantine-color-primaryColor-filled) r g b / calc(var(--opacity, 1) * 0.12))";
return (
<Tooltip label={tooltip} disabled={!tooltip} w={250} multiline events={{ hover: true, focus: true, touch: true }}>
@@ -206,9 +209,11 @@ const StatCard = ({ item, data, usePiHoleColors, t }: StatCardProps) => {
className="summary-card"
p="sm"
radius={board.itemRadius}
bg={usePiHoleColors ? item.color : "var(--mantine-color-default-hover)"}
bg={backgroundColor}
style={{
flex: 1,
border:
"1px solid rgb(from var(--mantine-color-secondaryColor-filled) r g b / calc(var(--opacity, 1) * 0.45))",
}}
>
<Flex
+6 -2
View File
@@ -1,5 +1,7 @@
"use client";
import "./styles.css";
import { useCallback, useEffect, useMemo, useState } from "react";
import {
ActionIcon,
@@ -146,8 +148,9 @@ const createColumns = (
<Group gap="xs" wrap="nowrap" style={{ overflow: "hidden" }}>
{inlineState && <ContainerStateDot state={container.state} />}
<Avatar
radius="xl"
size={24}
variant="outline"
radius="sm"
size={20}
styles={{ image: { objectFit: "contain" } }}
src={container.iconUrl}
style={{ flexShrink: 0 }}
@@ -432,6 +435,7 @@ export default function DockerWidget({
<HomarrDataTable
isEditMode={isEditMode}
cellPadding="2px 8px"
className="docker-table"
rowCursor="default"
fetching={isFetching && containers.length === 0}
fz={width < 400 ? "xs" : "sm"}
+24
View File
@@ -0,0 +1,24 @@
/* Keep Docker rows translucent so board wallpaper and the inherited item opacity remain visible. */
.homarr-data-table.docker-table {
--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;
}
+2 -8
View File
@@ -143,11 +143,6 @@ const stateColorMap: Record<DownloadState, string> = {
unknown: "gray",
};
const rowBgAlpha: Partial<Record<DownloadState, string>> = {
failed: "var(--mantine-color-red-light)",
paused: "var(--mantine-color-yellow-light)",
};
const columnVisibilityChecks: Partial<Record<string, (ctx: ColumnContext) => boolean>> = {
upSpeed: (ctx) => ctx.hasTorrents && ctx.size.showSpeedColumns,
ratio: (ctx) => ctx.hasTorrents,
@@ -606,7 +601,7 @@ export default function DownloadClientsWidget({
title: t("items.state.columnTitle"),
width: 90,
render: (record) => (
<Badge size="xs" variant="light" color={stateColorMap[record.state]}>
<Badge size="xs" variant="dot" color={stateColorMap[record.state]}>
{t(`states.${record.state}`)}
</Badge>
),
@@ -784,7 +779,6 @@ export default function DownloadClientsWidget({
className="downloads-table"
customRowAttributes={(_, index) => ({ "data-row-index": index })}
onRowContextMenu={rowContextMenuHandler}
rowBackgroundColor={(record) => rowBgAlpha[record.state]}
rowExpansion={{
trigger: "click",
allowMultiple: true,
@@ -900,7 +894,7 @@ function ExpandedRow({ item, collapse }: { item: ExtendedDownloadClientItem; col
<Text size="sm" fw={600} truncate style={{ minWidth: 0 }}>
{item.name}
</Text>
<Badge size="xs" variant="light" color={stateColorMap[item.state]} ml="auto" style={{ flexShrink: 0 }}>
<Badge size="xs" variant="dot" color={stateColorMap[item.state]} ml="auto" style={{ flexShrink: 0 }}>
{t(`states.${item.state}`)}
</Badge>
</Group>
+19 -3
View File
@@ -1,4 +1,20 @@
/* Row expansion styling */
.downloads-table .mantine-datatable-row-expansion-cell {
background-color: color-mix(in srgb, var(--mantine-color-body) 40%, transparent) !important;
/* 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;
}
@@ -1,13 +1,22 @@
.statItem {
padding: 12px;
border-radius: var(--mantine-radius-md);
background-color: light-dark(var(--mantine-color-gray-0), var(--mantine-color-dark-6));
padding: 8px 10px;
border: 0.0625rem solid rgb(from var(--mantine-color-secondaryColor-filled) r g b / calc(var(--opacity, 1) * 0.38));
border-radius: var(--mantine-radius-sm);
background-color: transparent;
}
.statItemDense {
min-width: 0;
min-height: 44px;
padding: 4px 6px;
min-height: 40px;
padding: 3px 6px;
border: 0.0625rem solid rgb(from var(--mantine-color-secondaryColor-filled) r g b / calc(var(--opacity, 1) * 0.32));
border-radius: var(--mantine-radius-sm);
background-color: light-dark(var(--mantine-color-gray-0), var(--mantine-color-dark-6));
background-color: transparent;
}
@media (forced-colors: active) {
.statItem,
.statItemDense {
border-color: CanvasText;
}
}
@@ -55,42 +55,49 @@ export default function ImmichServerStatsWidget({
Number(statVisibility.showStorage);
const statsLayout = getImmichStatsLayout(width, height, statCount, isAdvanced);
const statsContent = (
<SimpleGrid cols={statsLayout.columns} spacing={statsLayout.dense ? 4 : "sm"}>
{statVisibility.showUsers && (
<StatItem
icon={<IconUsers size="var(--mantine-font-size-xl)" />}
label={t("users")}
value={stats.userCount.toLocaleString(locale)}
dense={statsLayout.dense}
/>
)}
{statVisibility.showPhotos && (
<StatItem
icon={<IconPhoto size="var(--mantine-font-size-xl)" />}
label={t("photos")}
value={stats.photoCount.toLocaleString(locale)}
dense={statsLayout.dense}
/>
)}
{statVisibility.showVideos && (
<StatItem
icon={<IconVideo size="var(--mantine-font-size-xl)" />}
label={t("videos")}
value={stats.videoCount.toLocaleString(locale)}
dense={statsLayout.dense}
/>
)}
{statVisibility.showStorage && (
<StatItem
icon={<IconDatabase size="var(--mantine-font-size-xl)" />}
label={t("storage")}
value={formatBytes(stats.totalLibraryUsageInBytes)}
dense={statsLayout.dense}
/>
)}
</SimpleGrid>
);
const statItems = [
{
key: "users",
enabled: statVisibility.showUsers,
icon: <IconUsers size={statsLayout.dense ? 16 : 18} />,
label: t("users"),
value: stats.userCount.toLocaleString(locale),
},
{
key: "photos",
enabled: statVisibility.showPhotos,
icon: <IconPhoto size={statsLayout.dense ? 16 : 18} />,
label: t("photos"),
value: stats.photoCount.toLocaleString(locale),
},
{
key: "videos",
enabled: statVisibility.showVideos,
icon: <IconVideo size={statsLayout.dense ? 16 : 18} />,
label: t("videos"),
value: stats.videoCount.toLocaleString(locale),
},
{
key: "storage",
enabled: statVisibility.showStorage,
icon: <IconDatabase size={statsLayout.dense ? 16 : 18} />,
label: t("storage"),
value: formatBytes(stats.totalLibraryUsageInBytes),
},
]
.filter((item) => item.enabled)
.map(({ key, icon, label, value }) => (
<StatItem key={key} icon={icon} label={label} value={value} dense={statsLayout.dense} />
));
const statsContent =
isAdvanced || statsLayout.dense ? (
<SimpleGrid cols={statsLayout.columns} spacing={statsLayout.dense ? 4 : "sm"}>
{statItems}
</SimpleGrid>
) : (
<Stack gap="xs">{statItems}</Stack>
);
if (!isAdvanced) {
return (
@@ -1,9 +1,6 @@
[data-mantine-color-scheme="light"] .card {
background-color: var(--mantine-color-gray-1);
}
[data-mantine-color-scheme="dark"] .card {
background-color: var(--mantine-color-dark-7);
.card {
background-color: transparent;
border: 1px solid rgb(from var(--mantine-color-secondaryColor-filled) r g b / calc(var(--opacity, 1) * 0.38));
}
.root {
@@ -31,7 +28,7 @@
&:hover,
&:focus-within {
background-color: var(--mantine-color-default-hover);
background-color: transparent;
}
}
@@ -1,14 +1,15 @@
.card {
min-width: 0;
background-color: light-dark(var(--mantine-color-white), var(--mantine-color-dark-7));
background-color: transparent;
border-color: rgb(from var(--mantine-color-secondaryColor-filled) r g b / calc(var(--opacity, 1) * 0.38));
transition:
background-color 120ms ease,
border-color 120ms ease;
&:hover,
&:focus-within {
border-color: var(--mantine-primary-color-filled);
background-color: var(--mantine-color-default-hover);
border-color: rgb(from var(--mantine-color-secondaryColor-filled) r g b / calc(var(--opacity, 1) * 0.62));
background-color: transparent;
}
}
@@ -149,7 +149,7 @@ const MediaRequestCard = ({
<Image
className="mediaRequests-list-item-poster"
src={request.posterImagePath}
h={isDense ? 36 : 44}
h={isDense ? 32 : 40}
w="auto"
radius="sm"
alt=""
@@ -157,7 +157,7 @@ const MediaRequestCard = ({
/>
)}
<Stack gap={2} w="100%" miw={0}>
<Stack gap={0} w="100%" miw={0}>
<Group gap="xs" justify="space-between" wrap="nowrap" className="mediaRequests-list-item-top-group">
<Anchor
className="mediaRequests-list-item-info-second-line mediaRequests-list-item-media-title"
@@ -274,7 +274,7 @@ const DecisionButtons = ({ requestId, integrationId, canInteract, alwaysVisible
className={`mediaRequests-list-item-pending-button-approve ${actionTargetClasses.root}`}
variant="light"
color="green"
size="sm"
size="xs"
disabled={!canInteract || isPending}
aria-label={t("pending.approve")}
onClick={() => {
@@ -289,7 +289,7 @@ const DecisionButtons = ({ requestId, integrationId, canInteract, alwaysVisible
className={`mediaRequests-list-item-pending-button-decline ${actionTargetClasses.root}`}
variant="light"
color="red"
size="sm"
size="xs"
disabled={!canInteract || isPending}
aria-label={t("pending.decline")}
onClick={() => {
@@ -2,11 +2,16 @@
min-width: 0;
color: inherit;
text-decoration: none;
transition: background-color 120ms ease;
background-color: transparent;
border: 1px solid rgb(from var(--mantine-color-secondaryColor-filled) r g b / calc(var(--opacity, 1) * 0.38));
transition:
background-color 120ms ease,
border-color 120ms ease;
&:hover,
&:focus-visible {
background-color: var(--mantine-color-default-hover);
background-color: transparent;
border-color: rgb(from var(--mantine-color-secondaryColor-filled) r g b / calc(var(--opacity, 1) * 0.62));
}
&:focus-visible {
@@ -24,3 +29,9 @@
transition: none;
}
}
@media (forced-colors: active) {
.entry {
border-color: CanvasText;
}
}
+1 -1
View File
@@ -219,7 +219,7 @@ export const getRssEntryDisplay = ({
descriptionLines,
}: RssEntryDisplayInput) => ({
showDescription: isAdvanced || (!hideDescription && !isTiny),
showImage: isAdvanced || (showPosterImage && !isTiny),
showImage: isAdvanced || (showPosterImage && !isDense),
showSource: isAdvanced || !isDense,
descriptionLineClamp: isAdvanced ? undefined : descriptionLines,
});
@@ -1,6 +1,6 @@
"use client";
import { Box, Flex, Text, Tooltip, VisuallyHidden } from "@mantine/core";
import { Card, Flex, Text, Tooltip, VisuallyHidden } from "@mantine/core";
import { useElementSize } from "@mantine/hooks";
import { useRequiredBoard } from "@homarr/boards/context";
@@ -8,7 +8,7 @@ import type { TablerIcon } from "@homarr/ui";
export interface SpeedStatCardProps {
icon: TablerIcon;
color: string;
color: "blue" | "teal" | "orange" | "green" | "red";
value: string;
label: string;
compact?: boolean;
@@ -19,15 +19,22 @@ export function SpeedStatCard({ icon: Icon, color, value, label, compact = false
const board = useRequiredBoard();
const isWide = width > height + 20;
const hideLabel = height > 0 && height <= 38;
const surfaceColor = `var(--mantine-color-${color}-filled)`;
const accentColor = `var(--mantine-color-${color}-5)`;
const surfaceAlpha = compact ? 0.1 : 0.14;
const surfaceBackground = `rgb(from ${surfaceColor} r g b / calc(var(--opacity, 1) * ${surfaceAlpha}))`;
const surfaceBorder = "rgb(from var(--mantine-color-default-border) r g b / calc(var(--opacity, 1) * 0.45))";
return (
<Tooltip label={`${label}: ${value}`} withArrow>
<Box
<Card
ref={ref}
p={compact ? "xs" : "sm"}
bg={`var(--mantine-color-${color}-light)`}
radius={board.itemRadius}
withBorder
bg={surfaceBackground}
h="100%"
style={{ flex: 1, borderRadius: `var(--mantine-radius-${board.itemRadius})`, minWidth: 0 }}
style={{ flex: 1, minWidth: 0, borderColor: surfaceBorder }}
>
<Flex
h="100%"
@@ -38,7 +45,7 @@ export function SpeedStatCard({ icon: Icon, color, value, label, compact = false
gap={isWide ? 8 : 4}
style={{ minWidth: 0 }}
>
<Icon size={compact ? 16 : 20} color={`var(--mantine-color-${color}-5)`} style={{ flexShrink: 0 }} />
<Icon size={compact ? 16 : 20} color={accentColor} style={{ flexShrink: 0 }} />
<Flex direction="column" align={isWide ? "flex-start" : "center"} gap={0} style={{ minWidth: 0 }}>
<Text size={compact ? "sm" : "md"} fw={700} ta="center" lh={1.1} truncate w="100%">
{value}
@@ -51,7 +58,7 @@ export function SpeedStatCard({ icon: Icon, color, value, label, compact = false
{hideLabel && <VisuallyHidden>{label}</VisuallyHidden>}
</Flex>
</Flex>
</Box>
</Card>
</Tooltip>
);
}
@@ -1,7 +1,7 @@
"use client";
import { useEffect, useRef, useState } from "react";
import { Box, Card, Group, ScrollArea, SimpleGrid, Text, Tooltip, useMantineColorScheme } from "@mantine/core";
import { Box, Card, Group, ScrollArea, SimpleGrid, Text, Tooltip } from "@mantine/core";
import { clientApi } from "@homarr/api/client";
import { useRequiredBoard } from "@homarr/boards/context";
@@ -85,7 +85,6 @@ const SystemDiskCard = ({
showTemperature,
}: SystemDiskCardProps) => {
const board = useRequiredBoard();
const scheme = useMantineColorScheme();
const t = useI18n("widget.systemDisks");
const valueRef = useRef<HTMLParagraphElement>(null);
const [valueFits, setValueFits] = useState(true);
@@ -120,6 +119,8 @@ const SystemDiskCard = ({
]
.filter(Boolean)
.join(" · ");
const backgroundColor = "rgb(from var(--mantine-color-primaryColor-filled) r g b / calc(var(--opacity, 1) * 0.12))";
const borderColor = "rgb(from var(--mantine-color-secondaryColor-filled) r g b / calc(var(--opacity, 1) * 0.45))";
return (
<Tooltip
@@ -131,8 +132,9 @@ const SystemDiskCard = ({
<Card
radius={board.itemRadius}
py="xs"
bg={scheme.colorScheme === "dark" ? "dark.7" : "gray.1"}
style={{ overflow: "hidden", position: "relative" }}
withBorder
bg={backgroundColor}
style={{ overflow: "hidden", position: "relative", borderColor }}
>
<Group justify="space-between" wrap="nowrap" style={{ zIndex: 1, minWidth: 0 }}>
<div style={{ minWidth: 0, flex: 1 }}>
@@ -241,7 +243,7 @@ export default function SystemResources({
const isAdvanced = displayMode === "advanced";
const minimumCardWidth = 260;
const columns = Math.max(1, Math.min(disks.length, Math.floor(width / minimumCardWidth)));
const columns = isAdvanced ? Math.max(1, Math.min(disks.length, Math.floor(width / minimumCardWidth))) : 1;
const cellWidth = width / columns;
return (
@@ -2,7 +2,7 @@
import type { ReactNode } from "react";
import type { AreaChartSeries } from "@mantine/charts";
import { AreaChart, LineChart } from "@mantine/charts";
import { Card, Center, Group, Stack, Text, useComputedColorScheme, useMantineTheme } from "@mantine/core";
import { Card, Center, Group, Stack, Text, useMantineTheme } from "@mantine/core";
import { useElementSize, useHover, useMergedRef } from "@mantine/hooks";
import type { TooltipProps, YAxisProps } from "recharts";
@@ -36,13 +36,12 @@ export const CommonChart = ({
}) => {
const { ref: elementSizeRef, height } = useElementSize();
const theme = useMantineTheme();
const colorScheme = useComputedColorScheme("light");
const board = useRequiredBoard();
const { hovered, ref: hoverRef } = useHover();
const ref = useMergedRef(elementSizeRef, hoverRef);
const opacity = board.opacity / 100;
const backgroundColor = colorScheme === "dark" ? `rgba(57, 57, 57, ${opacity})` : `rgba(246, 247, 248, ${opacity})`;
const backgroundColor = "rgb(from var(--mantine-color-primaryColor-filled) r g b / calc(var(--opacity, 1) * 0.12))";
const borderColor = "rgb(from var(--mantine-color-secondaryColor-filled) r g b / calc(var(--opacity, 1) * 0.45))";
const ChartComponent = chartType === "line" ? LineChart : AreaChart;
const showIcon = labelDisplayMode === "icon" || labelDisplayMode === "textWithIcon";
@@ -53,7 +52,7 @@ export const CommonChart = ({
ref={ref}
h={"100%"}
pos={"relative"}
style={{ overflow: "visible" }}
style={{ overflow: "visible", border: `1px solid ${borderColor}` }}
p={0}
bg={backgroundColor}
radius={board.itemRadius}
@@ -119,7 +118,13 @@ export const CommonChart = ({
withYAxis={false}
withDots={false}
bg={backgroundColor}
styles={{ root: { padding: 5, borderRadius: theme.radius[board.itemRadius] } }}
styles={{
root: {
padding: 5,
borderRadius: theme.radius[board.itemRadius],
backgroundColor,
},
}}
tooltipAnimationDuration={200}
tooltipProps={tooltipProps}
withTooltip={height >= 64}
@@ -232,10 +232,10 @@ const SystemCharts = ({
const chartColumns = isAdvanced && width >= 560 ? 2 : 1;
const chartHeight = isAdvanced
? 180
: `calc((100% - ${Math.max(0, chartCount - 1) * 10}px) / ${Math.max(1, chartCount)})`;
: `calc((100% - ${Math.max(0, chartCount - 1) * 8}px) / ${Math.max(1, chartCount)})`;
return (
<Stack gap="xs" h={isAdvanced ? "auto" : height} miw={0}>
<Stack gap={isAdvanced ? "xs" : 8} h={isAdvanced ? "auto" : height} miw={0}>
{showTitle && (
<Text size="sm" fw={600} truncate="end">
{integrationName}
@@ -0,0 +1,21 @@
.chartSurface {
min-width: 0;
min-height: 0;
background-color: transparent !important;
}
.chartSurface :global(.mantine-ScrollArea-viewport),
.chartSurface :global(.mantine-ScrollArea-scrollbar),
.chartSurface :global(.recharts-wrapper),
.chartSurface :global(.recharts-surface) {
background-color: transparent !important;
}
.listSurface {
min-width: 0;
min-height: 0;
padding: 4px 6px;
border: 1px solid rgb(from var(--mantine-color-secondaryColor-filled) r g b / calc(var(--opacity, 1) * 0.32));
border-radius: var(--mantine-radius-sm);
background-color: transparent;
}
+9 -6
View File
@@ -7,6 +7,7 @@ import { clientApi } from "@homarr/api/client";
import { formatDuration } from "@homarr/common";
import type { UmamiVisitorStats } from "@homarr/integrations/types";
import { useCurrentIntlLocale, useI18n } from "@homarr/translation/client";
import classes from "./component.module.css";
import { UmamiEventsContent } from "./umami-events-content";
import { getUmamiLayout } from "./umami-layout";
@@ -161,7 +162,7 @@ export function UmamiContent({
);
const advancedLists = (
<SimpleGrid cols={viewMode === "topPages" || viewMode === "topReferrers" ? 1 : 2} spacing="md" mih={260}>
<SimpleGrid cols={viewMode === "topPages" || viewMode === "topReferrers" ? 1 : 2} spacing="xs" mih={180}>
{viewMode !== "topPages" && (
<UmamiTopPagesContent
integrationIds={integrationIds}
@@ -182,7 +183,7 @@ export function UmamiContent({
);
const advancedContent = stackAdvancedContent ? (
<ScrollArea h="100%">
<ScrollArea className={classes.chartSurface} h="100%">
<Stack gap="md">
<Box h={Math.max(280, Math.min(420, height - 160))}>{selectedView}</Box>
{advancedLists}
@@ -190,15 +191,17 @@ export function UmamiContent({
</ScrollArea>
) : (
<SimpleGrid cols={2} spacing="md" h="100%" style={{ gridTemplateRows: "minmax(0, 1fr)" }}>
<Box h="100%" mih={260}>
<Box className={classes.chartSurface} h="100%" mih={260}>
{selectedView}
</Box>
<ScrollArea h="100%">{advancedLists}</ScrollArea>
<ScrollArea className={classes.chartSurface} h="100%">
{advancedLists}
</ScrollArea>
</SimpleGrid>
);
return (
<Stack gap={isDense ? 2 : 4} p={isDense ? 4 : "xs"} h="100%" pos="relative">
<Stack gap={isDense ? 1 : 4} p={isDense ? 2 : "xs"} h="100%" pos="relative">
<Group justify="space-between" align="baseline" wrap="nowrap">
<Text size="xs" c="dimmed" truncate="end" style={{ maxWidth: "55%" }}>
{visitorStats.domain} ({formatTimeFrameLabel(timeFrame, t)})
@@ -264,7 +267,7 @@ export function UmamiContent({
)}
</Group>
)}
<Box mt={4} style={{ flex: 1, minHeight: 0 }}>
<Box className={classes.chartSurface} mt={4} style={{ flex: 1, minHeight: 0 }}>
{displayMode === "advanced" ? advancedContent : selectedView}
</Box>
{(statsQuery.error || activeVisitorsQuery.error || multiEventQuery.error) && (
@@ -5,6 +5,7 @@ import { Group, ScrollArea, Stack, Text } from "@mantine/core";
import { clientApi } from "@homarr/api/client";
import type { UmamiMetricItem } from "@homarr/integrations/types";
import { useCurrentIntlLocale, useI18n } from "@homarr/translation/client";
import classes from "./component.module.css";
import { umamiQueryOptions } from "./umami-utils";
import { getUsableWidgetQueryData } from "../common/query-state";
@@ -52,7 +53,7 @@ function UmamiTopList({
const locale = useCurrentIntlLocale();
return (
<Stack gap={2} h="100%">
<Stack className={classes.listSurface} gap={2} h="100%">
<Text size="xs" c="dimmed" fw={500}>
{heading}
</Text>
@@ -31,8 +31,8 @@
.dailyCard,
.detailMetric,
.solarDetails {
background: color-mix(in srgb, var(--mantine-color-body) 72%, transparent);
border: 0.0625rem solid color-mix(in srgb, var(--mantine-color-default-border) 72%, transparent);
background-color: transparent;
border: 0.0625rem solid rgb(from var(--mantine-color-secondaryColor-filled) r g b / calc(var(--opacity, 1) * 0.45));
}
.dailyCard {
@@ -43,8 +43,8 @@
.dailyCardButton:hover .dailyCard,
.dailyCardButton:focus-visible .dailyCard {
background: color-mix(in srgb, var(--mantine-primary-color-light) 30%, var(--mantine-color-body));
border-color: color-mix(in srgb, var(--mantine-primary-color-filled) 45%, var(--mantine-color-default-border));
background-color: transparent;
border-color: rgb(from var(--mantine-color-secondaryColor-filled) r g b / calc(var(--opacity, 1) * 0.72));
}
.dailyDetailsDropdown {
@@ -74,7 +74,7 @@
}
.solarDetails {
background: color-mix(in srgb, var(--mantine-color-yellow-light) 24%, var(--mantine-color-body));
background-color: transparent;
}
.detailsButton,