Compare commits

...
Author SHA1 Message Date
ajnart 79269aa055 fix: align data table scrollbars 2026-08-23 01:44:06 +02:00
ajnart 94e784f81f fix: cover all widget scroll areas 2026-08-23 01:16:00 +02:00
ajnart 60ffc34e73 fix: unify widget scroll areas 2026-08-23 01:09:02 +02:00
20 changed files with 36 additions and 23 deletions

No files matched your search

+1 -1
View File
@@ -2,7 +2,7 @@ import type { MantineProviderProps } from "@mantine/core";
import { theme } from "./theme"; import { theme } from "./theme";
export { theme } from "./theme"; export { scrollAreaDefaultProps, theme } from "./theme";
export { modalSizeForm, modalSizeSelect } from "./theme/modal"; export { modalSizeForm, modalSizeSelect } from "./theme/modal";
export * from "./components"; export * from "./components";
+13
View File
@@ -2,6 +2,13 @@ import { createTheme, rem } from "@mantine/core";
import { modalComponent } from "./theme/modal"; import { modalComponent } from "./theme/modal";
export const scrollAreaDefaultProps = {
type: "hover",
scrollbarSize: 2,
scrollHideDelay: 500,
offsetScrollbars: true,
} as const;
export const theme = createTheme({ export const theme = createTheme({
primaryColor: "red", primaryColor: "red",
autoContrast: true, autoContrast: true,
@@ -96,6 +103,12 @@ export const theme = createTheme({
style: { borderRadius: 5 }, style: { borderRadius: 5 },
}, },
}, },
ScrollArea: {
defaultProps: scrollAreaDefaultProps,
},
ScrollAreaAutosize: {
defaultProps: scrollAreaDefaultProps,
},
LoadingOverlay: { LoadingOverlay: {
defaultProps: { defaultProps: {
zIndex: 1000, zIndex: 1000,
@@ -324,7 +324,7 @@ export function JsonPathTreePicker({
</Combobox.Header> </Combobox.Header>
)} )}
<Combobox.Options> <Combobox.Options>
<ScrollArea.Autosize mah={420} type="scroll"> <ScrollArea.Autosize mah={420}>
{!hasData && <Combobox.Empty>{noDataHint}</Combobox.Empty>} {!hasData && <Combobox.Empty>{noDataHint}</Combobox.Empty>}
{hasData && visibleNodes.length === 0 && <Combobox.Empty>No matching paths</Combobox.Empty>} {hasData && visibleNodes.length === 0 && <Combobox.Empty>No matching paths</Combobox.Empty>}
{visibleNodes.map((node) => ( {visibleNodes.map((node) => (
@@ -177,7 +177,7 @@ const AdvancedAirQuality = ({ airQuality, options, width }: AirQualityViewProps
]; ];
return ( return (
<ScrollArea h="100%" w="100%" type="auto" offsetScrollbars scrollbarSize={6}> <ScrollArea h="100%" w="100%">
<Stack gap="md" p="md"> <Stack gap="md" p="md">
<Group justify="space-between" align="flex-start" wrap="wrap"> <Group justify="space-between" align="flex-start" wrap="wrap">
<Stack gap={2}> <Stack gap={2}>
@@ -163,7 +163,7 @@ export const BookmarkAddButton: SortableItemListInput<BookmarkSelectionItem, str
</Combobox.DropdownTarget> </Combobox.DropdownTarget>
<Combobox.Dropdown> <Combobox.Dropdown>
<ScrollArea.Autosize mah={280} type="auto"> <ScrollArea.Autosize mah={280}>
<Combobox.Options> <Combobox.Options>
{pendingUrl && !selectedValues.has(pendingUrl.id) ? ( {pendingUrl && !selectedValues.has(pendingUrl.id) ? (
<Combobox.Option value={createOptionValue}> <Combobox.Option value={createOptionValue}>
@@ -148,11 +148,7 @@ export default function BookmarksWidget({
</Center> </Center>
) : ( ) : (
<ScrollArea <ScrollArea
type="hover"
scrollbars={plan.horizontalScroll ? "x" : "y"} scrollbars={plan.horizontalScroll ? "x" : "y"}
scrollbarSize={2}
scrollHideDelay={500}
offsetScrollbars
style={{ flex: 1, minHeight: 0 }} style={{ flex: 1, minHeight: 0 }}
> >
<Box miw="100%" mih="100%" pb={2}> <Box miw="100%" mih="100%" pb={2}>
@@ -56,7 +56,6 @@ export const CalendarEventList = ({
return ( return (
<ScrollArea <ScrollArea
offsetScrollbars
pt={5} pt={5}
w="100%" w="100%"
h={fillHeight ? "100%" : undefined} h={fillHeight ? "100%" : undefined}
@@ -68,9 +68,6 @@ export const AdvancedClockView = ({
<ScrollArea <ScrollArea
h="100%" h="100%"
w="100%" w="100%"
type="auto"
offsetScrollbars
scrollbarSize={6}
aria-label={t("worldClock.advancedLabel")} aria-label={t("worldClock.advancedLabel")}
> >
<Stack gap="md" p={{ base: "sm", sm: "md" }}> <Stack gap="md" p={{ base: "sm", sm: "md" }}>
@@ -22,6 +22,16 @@
background-color: color-mix(in srgb, var(--mantine-color-default-hover) 40%, transparent) !important; background-color: color-mix(in srgb, var(--mantine-color-default-hover) 40%, transparent) !important;
} }
.homarr-data-table .mantine-datatable-scroll-area-scrollbar[data-state="visible"]:hover {
@mixin light {
background-color: var(--mantine-color-gray-0);
}
@mixin dark {
background-color: var(--mantine-color-dark-8);
}
}
.homarr-data-table th { .homarr-data-table th {
padding: 0; padding: 0;
white-space: nowrap; white-space: nowrap;
@@ -6,6 +6,8 @@ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import type { DataTableColumn, DataTableProps } from "mantine-datatable"; import type { DataTableColumn, DataTableProps } from "mantine-datatable";
import { DataTable } from "mantine-datatable"; import { DataTable } from "mantine-datatable";
import { scrollAreaDefaultProps } from "@homarr/ui";
const fallbackColumnWidth = 60; const fallbackColumnWidth = 60;
const getColumnWidth = (width: string | number | undefined): number => { const getColumnWidth = (width: string | number | undefined): number => {
@@ -153,7 +155,7 @@ export function HomarrDataTable<T>({
resizable: !isEditMode, resizable: !isEditMode,
cellsStyle: defaultColumnProps?.cellsStyle ?? (() => ({ padding: cellPadding })), cellsStyle: defaultColumnProps?.cellsStyle ?? (() => ({ padding: cellPadding })),
}} }}
scrollAreaProps={{ type: "auto", scrollbarSize: 6, ...scrollAreaProps }} scrollAreaProps={{ ...scrollAreaDefaultProps, ...scrollAreaProps }}
/> />
); );
} }
+1 -1
View File
@@ -184,7 +184,7 @@ export default function CountdownWidget({
onNotificationsChange={setNotifications} onNotificationsChange={setNotifications}
/> />
<ScrollArea type="auto" style={{ flex: 1, minHeight: 0 }}> <ScrollArea style={{ flex: 1, minHeight: 0 }}>
<Stack gap="md" pb="xs"> <Stack gap="md" pb="xs">
<EventSection <EventSection
title={t("upcoming")} title={t("upcoming")}
@@ -104,7 +104,7 @@ export default function DnsHoleSummaryWidget({
)} )}
</SimpleGrid> </SimpleGrid>
{showSourceStatuses && ( {showSourceStatuses && (
<ScrollArea type="auto" scrollbarSize={4} px="xs" pb="xs"> <ScrollArea px="xs" pb="xs">
<Group gap="xs" wrap="nowrap"> <Group gap="xs" wrap="nowrap">
{successfulSummaries.map(({ integration, summary }) => ( {successfulSummaries.map(({ integration, summary }) => (
<Badge <Badge
@@ -228,7 +228,7 @@ function Carousel({
)} )}
</Box> </Box>
{advanced && ( {advanced && (
<ScrollArea type="never" scrollbarSize={0} px="xs" py={6}> <ScrollArea px="xs" py={6}>
<Group gap={6} wrap="nowrap"> <Group gap={6} wrap="nowrap">
{assets.map((asset, index) => ( {assets.map((asset, index) => (
<UnstyledButton <UnstyledButton
@@ -126,7 +126,6 @@ export default function MediaMissingWidget({
) => ( ) => (
<ScrollArea <ScrollArea
h="100%" h="100%"
scrollbarSize={4}
startScrollPosition={tabScrollPositionsRef.current[tab]} startScrollPosition={tabScrollPositionsRef.current[tab]}
onScrollPositionChange={(position) => { onScrollPositionChange={(position) => {
tabScrollPositionsRef.current[tab] = position; tabScrollPositionsRef.current[tab] = position;
@@ -61,7 +61,6 @@ export default function MediaServerWidget({
)} )}
<ScrollArea <ScrollArea
className="mediaRequests-list-scrollArea" className="mediaRequests-list-scrollArea"
scrollbarSize="md"
style={{ flex: 1, minHeight: 0, pointerEvents: isEditMode ? "none" : undefined }} style={{ flex: 1, minHeight: 0, pointerEvents: isEditMode ? "none" : undefined }}
> >
<Stack className="mediaRequests-list-list" gap="xs" p="sm"> <Stack className="mediaRequests-list-list" gap="xs" p="sm">
@@ -500,7 +500,6 @@ export function Notebook({
<ScrollArea <ScrollArea
mih="4rem" mih="4rem"
offsetScrollbars
pl={12} pl={12}
pt={12} pt={12}
styles={{ styles={{
+1 -1
View File
@@ -387,7 +387,7 @@ const AdvancedTimer = ({
const t = useI18n("widget.timer"); const t = useI18n("widget.timer");
const phaseLabel = getTimerPhaseLabel(runtime, t); const phaseLabel = getTimerPhaseLabel(runtime, t);
return ( return (
<ScrollArea h="100%" w="100%" type="auto" offsetScrollbars scrollbarSize={6}> <ScrollArea h="100%" w="100%">
<Stack gap="md" p="md"> <Stack gap="md" p="md">
<Paper <Paper
withBorder withBorder
@@ -5,7 +5,6 @@
} }
.chartSurface :global(.mantine-ScrollArea-viewport), .chartSurface :global(.mantine-ScrollArea-viewport),
.chartSurface :global(.mantine-ScrollArea-scrollbar),
.chartSurface :global(.recharts-wrapper), .chartSurface :global(.recharts-wrapper),
.chartSurface :global(.recharts-surface) { .chartSurface :global(.recharts-surface) {
background-color: transparent !important; background-color: transparent !important;
+1 -1
View File
@@ -46,7 +46,7 @@ export default function VpnWidget({ options, integrationIds, width, height }: Wi
} }
return ( return (
<ScrollArea className="scroll-area-w100" w="100%" h="100%" offsetScrollbars p="xs"> <ScrollArea className="scroll-area-w100" w="100%" h="100%" p="xs">
<SimpleGrid cols={width >= 640 ? 2 : 1} spacing="sm"> <SimpleGrid cols={width >= 640 ? 2 : 1} spacing="sm">
{integrations.map((result) => ( {integrations.map((result) => (
<Stack key={result.integration.id} gap={4}> <Stack key={result.integration.id} gap={4}>
+1 -1
View File
@@ -143,7 +143,7 @@ export const AdvancedWeather = ({ height, options, weather, width }: AdvancedWea
const ticks = hourly.filter((_, index) => index % layout.hourlyTickStep === 0).map((hour) => hour.observedAt); const ticks = hourly.filter((_, index) => index % layout.hourlyTickStep === 0).map((hour) => hour.observedAt);
return ( return (
<ScrollArea h="100%" w="100%" type="auto" offsetScrollbars scrollbarSize={6}> <ScrollArea h="100%" w="100%">
<Stack className={classes.advancedRoot} gap="md" p="md"> <Stack className={classes.advancedRoot} gap="md" p="md">
<Group justify="space-between" align="flex-start" gap="md" wrap="wrap"> <Group justify="space-between" align="flex-start" gap="md" wrap="wrap">
<Group gap="md" wrap="nowrap"> <Group gap="md" wrap="nowrap">