fix: restore translucent dashboard styling
This commit is contained in:
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));
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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"}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
Reference in new issue
Block a user