Compare commits

...
Author SHA1 Message Date
ajnart d04c8c437f style: auto-fix lint and format 2026-07-21 15:59:33 +00:00
ajnart 28fefcd23f fix(widgets): fix hover card disappearing and remove group wrapper
- Remove wrapper div that broke HoverCard hover tracking between
  target and dropdown (root cause of instant disappear)
- Remove useElementSize, always show notification dots
- Increase closeDelay to 400ms (per Mantine interactive-elements
  pattern) so users can move mouse into dropdown with link buttons
- Remove HoverCard.Group from component.tsx per feedback
2026-07-21 17:58:19 +02:00
ajnart c08054736e fix(widgets): fix missing event dots and instant open in calendar
- Move useElementSize ref to wrapper div so HoverCard.Target's ref
  merging doesn't break element measurement (dots were hidden because
  height returned 0, triggering isTooSmallForIndicators)
- Add pos='relative' to Container so NotificationIndicator anchors
  to the day cell
- Add openDelay/closeDelay directly to each HoverCard as fallback in
  case HoverCard.Group context doesn't propagate through Mantine
  Calendar's internal rendering
2026-07-21 17:49:44 +02:00
ajnart 2e20fd7da5 fix(widgets): remove redundant calendar day CSS module
The border highlight was redundant — HoverCard's arrow already indicates
which day is active. Removes the CSS file and className import entirely.
2026-07-21 16:22:42 +02:00
ajnart 1856d20a47 fix(widgets): wrap calendar days in HoverCard.Group for shared delays
Uses HoverCard.Group with openDelay={350} and closeDelay={200} so all
calendar day hover cards share synchronized delays. The closeDelay
gives users time to move from the day cell to the dropdown where
interactive link buttons live (per Mantine's interactive elements
pattern).
2026-07-21 16:16:07 +02:00
ajnart 6df2f92268 fix(widgets): use HoverCard with openDelay for calendar day events
Replaces Popover with Mantine HoverCard for built-in hover behavior:
- openDelay={350} prevents triggering when dragging across days
- closeOnClickOutside and closeOnEscape are true by default
- No manual mouse handlers or state needed
2026-07-21 16:14:34 +02:00
ajnart c30f525a83 fix(widgets): simplify calendar day popover to uncontrolled click-to-open
Replaces the hover+pin+timeout implementation with Mantine Popover's
built-in uncontrolled behavior (click to open, click outside/escape to
close). Removes all component state, mouse handlers, timeout management,
and the cross-popover close machinery from the parent.

- 56 lines removed, 13 added across 3 files
- Border highlight now via CSS aria-expanded selector
- Resolves the messy hover navigation issue (#2637)
2026-07-21 16:02:18 +02:00
2 changed files with 14 additions and 63 deletions

No files matched your search

+13 -56
View File
@@ -1,6 +1,4 @@
import { useCallback, useState } from "react";
import { Box, Container, Flex, Popover, Text, useMantineTheme } from "@mantine/core";
import { useElementSize } from "@mantine/hooks";
import { Box, Container, Flex, HoverCard, Text, useMantineTheme } from "@mantine/core";
import { useRequiredBoard } from "@homarr/boards/context";
import type { CalendarEvent } from "@homarr/integrations/types";
@@ -13,14 +11,9 @@ interface CalendarDayProps {
disabled: boolean;
rootWidth: number;
rootHeight: number;
onOpen: (close: () => void) => void;
}
export const CalendarDay = ({ date, events, disabled, rootHeight, rootWidth, onOpen }: CalendarDayProps) => {
const [opened, setOpened] = useState(false);
const [pinned, setPinned] = useState(false);
const { primaryColor } = useMantineTheme();
const { ref, height } = useElementSize();
export const CalendarDay = ({ date, events, disabled, rootHeight, rootWidth }: CalendarDayProps) => {
const board = useRequiredBoard();
const mantineTheme = useMantineTheme();
const actualItemRadius = mantineTheme.radius[board.itemRadius];
@@ -29,39 +22,19 @@ export const CalendarDay = ({ date, events, disabled, rootHeight, rootWidth, onO
const shouldScaleDown = minAxisSize < 350;
const isSmall = rootHeight < 256;
const isTooSmallForIndicators = height < 30;
const handleMouseEnter = useCallback(() => {
if (disabled) return;
onOpen(() => {
setOpened(false);
setPinned(false);
});
setOpened(true);
}, [disabled, onOpen]);
const handleMouseLeave = useCallback(() => {
if (!pinned) setOpened(false);
}, [pinned]);
return (
<Popover
<HoverCard
position="bottom"
withArrow
withinPortal
radius="lg"
shadow="sm"
transitionProps={{
transition: "pop",
}}
onChange={(value) => {
setOpened(value);
if (!value) setPinned(false);
}}
opened={opened}
transitionProps={{ transition: "pop" }}
openDelay={350}
closeDelay={400}
disabled={disabled}
>
<Popover.Target>
<HoverCard.Target>
<Container
h="100%"
w="100%"
@@ -69,38 +42,23 @@ export const CalendarDay = ({ date, events, disabled, rootHeight, rootWidth, onO
pt={isSmall ? 0 : 10}
pb={isSmall ? 0 : 10}
m={0}
ref={ref}
bd={`2px solid ${opened && !disabled ? primaryColor : "transparent"}`}
pos="relative"
style={{
alignContent: "center",
borderRadius: actualItemRadius,
cursor: disabled ? "default" : "pointer",
}}
onClick={() => {
if (!disabled) setPinned((prev) => !prev);
}}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
>
<Text ta={"center"} size={shouldScaleDown ? "xs" : "md"} lh={1}>
{date.getDate()}
</Text>
{!isTooSmallForIndicators && <NotificationIndicator events={events} isSmall={isSmall} />}
<NotificationIndicator events={events} isSmall={isSmall} />
</Container>
</Popover.Target>
{/* Popover has some offset on the left side, padding is removed because of scrollarea paddings */}
<Popover.Dropdown
maw="calc(100vw - 24px)"
w={512}
pe={4}
pb={0}
style={{ overflow: "hidden" }}
onMouseEnter={() => setOpened(true)}
onMouseLeave={handleMouseLeave}
>
</HoverCard.Target>
<HoverCard.Dropdown maw="calc(100vw - 24px)" w={512} pe={4} pb={0} style={{ overflow: "hidden" }}>
<CalendarEventList events={events} />
</Popover.Dropdown>
</Popover>
</HoverCard.Dropdown>
</HoverCard>
);
};
@@ -111,7 +69,6 @@ interface NotificationIndicatorProps {
const NotificationIndicator = ({ events, isSmall }: NotificationIndicatorProps) => {
const notificationEvents = [...new Set(events.map((event) => event.indicatorColor))].filter(String);
/* position bottom is lower when small to not be on top of number*/
return (
<Flex
w="75%"
+1 -7
View File
@@ -1,6 +1,6 @@
"use client";
import { useCallback, useMemo, useRef, useState } from "react";
import { useMemo, useState } from "react";
import { useParams } from "next/navigation";
import { useMantineTheme } from "@mantine/core";
import { Calendar } from "@mantine/dates";
@@ -65,11 +65,6 @@ const CalendarBase = ({ isEditMode, events, month, setMonth, options }: Calendar
const isSmall = width < 256;
const normalizedEvents = useMemo(() => splitEvents(events), [events]);
const activeCloseRef = useRef<(() => void) | null>(null);
const onDayOpen = useCallback((close: () => void) => {
activeCloseRef.current?.();
activeCloseRef.current = close;
}, []);
return (
<Calendar
@@ -146,7 +141,6 @@ const CalendarBase = ({ isEditMode, events, month, setMonth, options }: Calendar
disabled={isEditMode || eventsForDate.length === 0}
rootWidth={width}
rootHeight={height}
onOpen={onDayOpen}
/>
);
}}