fix: address widget review findings

This commit is contained in:
Thomas Camlong authored and ajnart committed 2026-08-14 11:07:08 +02:00
1 parent 842e86012d
commit e7e44375ef
21 files changed
+190 -86

No files matched your search

@@ -22,6 +22,7 @@ export const categoryTranslationKeys: Record<string, string> = {
smartHomeServer: "integration.category.smartHomeServer",
indexerManager: "integration.category.indexerManager",
healthMonitoring: "integration.category.healthMonitoring",
beszel: "integration.category.beszel",
search: "integration.category.search",
mediaTranscoding: "integration.category.mediaTranscoding",
networkController: "integration.category.networkController",
@@ -18,6 +18,7 @@ import { useScopedI18n } from "@homarr/translation/client";
import { WidgetEmptyState } from "../common/empty-state";
import type { WidgetComponentProps } from "../definition";
import actionTargetClasses from "../common/action-target.module.css";
import "react-quill-new/dist/quill.snow.css";
import "./anchor-note.css";
@@ -353,7 +354,14 @@ const AnchorNoteWidgetContent = ({
</Button>
) : (
<Tooltip label={t("edit")}>
<ActionIcon aria-label={t("edit")} size="md" variant="light" onClick={handleEdit} disabled={isUpdating}>
<ActionIcon
className={actionTargetClasses.root}
aria-label={t("edit")}
size="md"
variant="light"
onClick={handleEdit}
disabled={isUpdating}
>
<IconEdit size={16} />
</ActionIcon>
</Tooltip>
@@ -14,7 +14,7 @@
pointer-events: auto;
}
@media (hover: none) {
@media (any-pointer: coarse) {
.beszelStatsSystemToggle {
opacity: 1;
pointer-events: auto;
@@ -0,0 +1,11 @@
.root {
min-width: 24px;
min-height: 24px;
}
@media (any-pointer: coarse) {
.root {
min-width: 44px;
min-height: 44px;
}
}
@@ -3,6 +3,7 @@
import { ActionIcon, Anchor, Group, Indicator, Stack, Text } from "@mantine/core";
import { IconFileText, IconLink } from "@tabler/icons-react";
import actionTargetClasses from "../common/action-target.module.css";
import { cleanFqdn, getResourceTimestamp, getStatusColor, parseStatus } from "./coolify-utils";
interface ResourceRowProps {
@@ -58,6 +59,7 @@ export function ResourceRow({ item, baseUrl, isTiny, resourceType }: ResourceRow
<Group wrap="nowrap" gap={4} ml={16}>
{cleanFqdn(item.fqdn) && (
<ActionIcon
className={actionTargetClasses.root}
component="a"
href={cleanFqdn(item.fqdn)}
target="_blank"
@@ -72,6 +74,7 @@ export function ResourceRow({ item, baseUrl, isTiny, resourceType }: ResourceRow
)}
{!isTiny && logsUrl && (
<ActionIcon
className={actionTargetClasses.root}
component="a"
href={logsUrl}
target="_blank"
@@ -6,6 +6,7 @@ import { IconEye, IconEyeOff, IconServer } from "@tabler/icons-react";
import type { CoolifyServer } from "@homarr/integrations/types";
import { useScopedI18n } from "@homarr/translation/client";
import actionTargetClasses from "../common/action-target.module.css";
import { getBadgeColor } from "./coolify-utils";
interface ServersSectionProps {
@@ -40,6 +41,7 @@ export function ServersSection({
</Badge>
</Group>
<ActionIcon
className={actionTargetClasses.root}
aria-label={`${showIp ? tCommon("action.hide") : tCommon("action.show")} IP`}
size="xs"
variant="subtle"
@@ -33,6 +33,7 @@ import { MaskedOrNormalImage } from "@homarr/ui";
import type { widgetKind } from ".";
import type { WidgetComponentProps } from "../../definition";
import actionTargetClasses from "../../common/action-target.module.css";
import classes from "./component.module.css";
import TimerModal from "./TimerModal";
@@ -353,6 +354,7 @@ const ControlsCard: React.FC<ControlsCardProps> = ({
<Group gap="xs" grow wrap="nowrap" w="100%">
{!isEnabled ? (
<ActionIcon
className={actionTargetClasses.root}
aria-label={`${t("widget.dnsHoleControls.controls.enableAll")}: ${data.integration.name}`}
onClick={() => void toggleDns(data.integration.id).catch(() => undefined)}
disabled={!controlEnabled}
@@ -364,6 +366,7 @@ const ControlsCard: React.FC<ControlsCardProps> = ({
</ActionIcon>
) : (
<ActionIcon
className={actionTargetClasses.root}
aria-label={`${t("widget.dnsHoleControls.controls.disableAll")}: ${data.integration.name}`}
onClick={() => void toggleDns(data.integration.id).catch(() => undefined)}
disabled={!controlEnabled}
@@ -375,6 +378,7 @@ const ControlsCard: React.FC<ControlsCardProps> = ({
</ActionIcon>
)}
<ActionIcon
className={actionTargetClasses.root}
aria-label={`${t("widget.dnsHoleControls.controls.setTimer")}: ${data.integration.name}`}
onClick={() => {
setSelectedIntegrationIds([data.integration.id]);
@@ -440,7 +444,7 @@ const ControlsCard: React.FC<ControlsCardProps> = ({
{layout === "md" && (
<ActionIcon
aria-label={`${t("widget.dnsHoleControls.controls.setTimer")}: ${data.integration.name}`}
className="dns-hole-controls-item-timer-button"
className={combineClasses("dns-hole-controls-item-timer-button", actionTargetClasses.root)}
display={isInteractPermitted ? undefined : "none"}
disabled={!controlEnabled || !isEnabled}
color="yellow"
@@ -25,7 +25,7 @@
pointer-events: auto;
}
@media (hover: none) {
@media (any-pointer: coarse) {
.infoActionCompact {
opacity: 1;
pointer-events: auto;
@@ -42,6 +42,7 @@ import { useI18n } from "@homarr/translation/client";
import { filterStorageVolumes, normalizeStorageDeviceName } from "../filter-storage-volumes";
import { WidgetEmptyState } from "../common/empty-state";
import type { WidgetComponentProps } from "../definition";
import actionTargetClasses from "../common/action-target.module.css";
import { CpuRing } from "./rings/cpu-ring";
import { CpuTempRing } from "./rings/cpu-temp-ring";
import { GpuRing } from "./rings/gpu-ring";
@@ -118,7 +119,7 @@ export const SystemHealthMonitoring = ({
disabled={!healthInfo.rebootRequired && healthInfo.availablePkgUpdates === 0}
>
<ActionIcon
className="health-monitoring-information-icon-avatar"
className={combineClasses("health-monitoring-information-icon-avatar", actionTargetClasses.root)}
variant={"light"}
color="var(--mantine-color-text)"
size="sm"
@@ -35,7 +35,7 @@
}
}
@media (hover: none) {
@media (any-pointer: coarse) {
.testAction {
opacity: 1;
}
@@ -10,6 +10,7 @@ import { useRequiredBoard } from "@homarr/boards/context";
import { useI18n } from "@homarr/translation/client";
import type { WidgetComponentProps } from "../definition";
import actionTargetClasses from "../common/action-target.module.css";
import classes from "./component.module.css";
export default function IndexerManagerWidget({
@@ -83,7 +84,7 @@ export default function IndexerManagerWidget({
)}
<Tooltip label={t("widget.indexerManager.testAll")}>
<ActionIcon
className={combineClasses("indexer-manager-test-action-icon", classes.testAction)}
className={combineClasses("indexer-manager-test-action-icon", classes.testAction, actionTargetClasses.root)}
size="sm"
radius={board.itemRadius}
variant="light"
@@ -24,19 +24,12 @@
opacity: 1;
}
@media (hover: none) {
@media (any-pointer: coarse) {
.pendingActions {
opacity: 1;
}
}
@media (pointer: coarse) {
.pendingActions button {
min-width: 36px;
min-height: 36px;
}
}
@media (prefers-reduced-motion: reduce) {
.card,
.pendingActions {
@@ -14,6 +14,7 @@ import { openMediaRequestSearch } from "@homarr/spotlight";
import { useScopedI18n } from "@homarr/translation/client";
import { WidgetEmptyState } from "../../common/empty-state";
import actionTargetClasses from "../../common/action-target.module.css";
import { IntegrationErrorIndicator } from "../../common/integration-error-indicator";
import type { WidgetComponentProps } from "../../definition";
import { NoIntegrationDataError } from "../../errors/no-data-integration";
@@ -83,7 +84,7 @@ const MediaRequestSearchButton = ({ integrationIds }: { integrationIds: string[]
return (
<Tooltip label={t("action.search.label")}>
<ActionIcon
className={searchClasses.searchButton}
className={`${searchClasses.searchButton} ${actionTargetClasses.root}`}
variant="light"
size="sm"
aria-label={t("action.search.label")}
@@ -255,7 +256,7 @@ const DecisionButtons = ({ requestId, integrationId, canInteract, alwaysVisible
>
<Tooltip label={t("pending.approve")}>
<ActionIcon
className="mediaRequests-list-item-pending-button-approve"
className={`mediaRequests-list-item-pending-button-approve ${actionTargetClasses.root}`}
variant="light"
color="green"
size="sm"
@@ -270,7 +271,7 @@ const DecisionButtons = ({ requestId, integrationId, canInteract, alwaysVisible
</Tooltip>
<Tooltip label={t("pending.decline")}>
<ActionIcon
className="mediaRequests-list-item-pending-button-decline"
className={`mediaRequests-list-item-pending-button-decline ${actionTargetClasses.root}`}
variant="light"
color="red"
size="sm"
@@ -17,7 +17,11 @@
opacity: 1;
}
@media (hover: none) {
@media (any-pointer: coarse) {
.searchRoot {
padding-top: 52px;
}
.searchButton {
opacity: 1;
}
@@ -22,6 +22,7 @@ import { openMediaRequestSearch } from "@homarr/spotlight";
import { useScopedI18n } from "@homarr/translation/client";
import { WidgetEmptyState } from "../../common/empty-state";
import actionTargetClasses from "../../common/action-target.module.css";
import { IntegrationErrorIndicator } from "../../common/integration-error-indicator";
import type { WidgetComponentProps } from "../../definition";
import { NoIntegrationDataError } from "../../errors/no-data-integration";
@@ -220,7 +221,7 @@ const MediaRequestSearchButton = ({ integrationIds }: { integrationIds: string[]
return (
<Tooltip label={t("action.search.label")}>
<ActionIcon
className={searchClasses.searchButton}
className={`${searchClasses.searchButton} ${actionTargetClasses.root}`}
variant="light"
size="sm"
aria-label={t("action.search.label")}
@@ -6,6 +6,8 @@ import { IconHeartbeat } from "@tabler/icons-react";
import type { TdarrStatistics } from "@homarr/integrations";
import { useI18n } from "@homarr/translation/client";
import actionTargetClasses from "../common/action-target.module.css";
interface HealthCheckStatusProps {
statistics: TdarrStatistics;
}
@@ -23,7 +25,7 @@ export function HealthCheckStatus(props: HealthCheckStatusProps) {
return (
<Popover position="bottom" width={250} shadow="sm" withArrow>
<Popover.Target>
<ActionIcon variant="subtle" size="sm" aria-label={t("title")}>
<ActionIcon className={actionTargetClasses.root} variant="subtle" size="sm" aria-label={t("title")}>
<Indicator color={textColor(indicatorColor, colorScheme)} size={6} display="flex">
<IconHeartbeat size={16} />
</Indicator>
+26 -19
View File
@@ -68,6 +68,7 @@ import { useI18n, useScopedI18n } from "@homarr/translation/client";
import type { TablerIcon } from "@homarr/ui";
import type { WidgetComponentProps } from "../definition";
import actionTargetClasses from "../common/action-target.module.css";
import { createReadOnlyTaskItemTransaction, ReadOnlyTaskItem } from "./read-only-task-item";
import { NotebookTextDirection, setTextDirection } from "./text-direction";
@@ -526,18 +527,12 @@ export function Notebook({
</Group>
)}
{canChange && (
<>
<Stack pos="absolute" top={7} right={7} gap={7} style={{ zIndex: 1 }}>
<ActionIcon
className="homarr-notebook-action"
className={`homarr-notebook-action ${actionTargetClasses.root}`}
data-visible={isEditing || displayMode === "advanced" || undefined}
title={isEditing ? t("common.action.save") : t("common.action.edit")}
aria-label={isEditing ? t("common.action.save") : t("common.action.edit")}
style={{
zIndex: 1,
}}
top={7}
right={7}
pos="absolute"
color={primaryColor}
variant="light"
size={30}
@@ -549,16 +544,10 @@ export function Notebook({
</ActionIcon>
{isEditing && (
<ActionIcon
className="homarr-notebook-action"
className={`homarr-notebook-action ${actionTargetClasses.root}`}
data-visible
title={t("common.action.cancel")}
aria-label={t("common.action.cancel")}
style={{
zIndex: 1,
}}
top={44}
right={7}
pos="absolute"
color={primaryColor}
variant="light"
size={30}
@@ -568,7 +557,7 @@ export function Notebook({
<IconX {...iconProps} />
</ActionIcon>
)}
</>
</Stack>
)}
</Box>
);
@@ -746,13 +735,31 @@ const ColorControl = ({ defaultColor, getCurrent, update, icon: Icon, ariaLabel
<Stack gap={8}>
<ColorPicker value={color} onChange={setColor} format="hexa" swatches={palette} swatchesPerRow={6} />
<Group justify="right" gap={8}>
<ActionIcon title={t("common.action.cancel")} variant="default" onClick={close}>
<ActionIcon
className={actionTargetClasses.root}
title={t("common.action.cancel")}
aria-label={t("common.action.cancel")}
variant="default"
onClick={close}
>
<IconX stroke={1.5} size="1rem" />
</ActionIcon>
<ActionIcon title={t("common.action.apply")} variant="default" onClick={handleApplyColor}>
<ActionIcon
className={actionTargetClasses.root}
title={t("common.action.apply")}
aria-label={t("common.action.apply")}
variant="default"
onClick={handleApplyColor}
>
<IconCheck stroke={1.5} size="1rem" />
</ActionIcon>
<ActionIcon title={t("widget.notebook.popover.clearColor")} variant="default" onClick={handleClearColor}>
<ActionIcon
className={actionTargetClasses.root}
title={t("widget.notebook.popover.clearColor")}
aria-label={t("widget.notebook.popover.clearColor")}
variant="default"
onClick={handleClearColor}
>
<IconCircleOff stroke={1.5} size="1rem" />
</ActionIcon>
</Group>
+16 -2
View File
@@ -221,6 +221,10 @@ export default function ReleasesWidget({ options, itemId, width, displayMode }:
})}
onClick={() => toggleExpandedDisplay(repository)}
aria-expanded={isActive}
aria-label={t(isActive ? "collapseDetails" : "expandDetails", {
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
name: repository.name || repository.identifier,
})}
>
<Group p="xs">
<MaskedOrNormalImage
@@ -321,7 +325,11 @@ export default function ReleasesWidget({ options, itemId, width, displayMode }:
</Group>
</UnstyledButton>
{(options.showDetails || isAdvanced) && (
<DetailsDisplay repository={repository} toggleExpandedDisplay={toggleExpandedDisplay} />
<DetailsDisplay
repository={repository}
isExpanded={isActive}
toggleExpandedDisplay={toggleExpandedDisplay}
/>
)}
{isActive && (
<ExpandedDisplay
@@ -342,10 +350,11 @@ export default function ReleasesWidget({ options, itemId, width, displayMode }:
interface DetailsDisplayProps {
repository: ReleasesRepositoryResponse;
isExpanded: boolean;
toggleExpandedDisplay: (repository: ReleasesRepositoryResponse) => void;
}
const DetailsDisplay = ({ repository, toggleExpandedDisplay }: DetailsDisplayProps) => {
const DetailsDisplay = ({ repository, isExpanded, toggleExpandedDisplay }: DetailsDisplayProps) => {
const t = useScopedI18n("widget.releases");
const formatter = useFormatter();
@@ -355,6 +364,11 @@ const DetailsDisplay = ({ repository, toggleExpandedDisplay }: DetailsDisplayPro
<UnstyledButton
className={combineClasses("releases-repository-details", classes.releasesRepositoryDetails)}
onClick={() => toggleExpandedDisplay(repository)}
aria-expanded={isExpanded}
aria-label={t(isExpanded ? "collapseDetails" : "expandDetails", {
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
name: repository.name || repository.identifier,
})}
>
<Group justify="space-between" p={5}>
<Group className="releases-repository-details-icon-wrapper">
+4 -2
View File
@@ -2,7 +2,7 @@
import { Sparkline } from "@mantine/charts";
import { Flex, Stack, Text, Title, useMantineTheme } from "@mantine/core";
import { IconTrendingDown, IconTrendingUp } from "@tabler/icons-react";
import { IconMinus, IconTrendingDown, IconTrendingUp } from "@tabler/icons-react";
import { clientApi } from "@homarr/api/client";
import { useScopedI18n } from "@homarr/translation/client";
@@ -105,8 +105,10 @@ export default function StockPriceWidget({ options, width, height, displayMode }
<Text size="xl" fw={700} lh="0.715">
{stockValuesChange > 0 ? (
<IconTrendingUp size="1.5rem" color={theme.colors.green[7]} />
) : (
) : stockValuesChange < 0 ? (
<IconTrendingDown size="1.5rem" color={theme.colors.red[7]} />
) : (
<IconMinus size="1.5rem" color={theme.colors.gray[6]} />
)}
{data.symbol}
</Text>
@@ -0,0 +1,31 @@
.detailsButton,
.forecastButton {
color: inherit;
border-radius: var(--mantine-radius-sm);
}
.detailsButton {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 24px;
min-height: 24px;
}
.forecastButton {
display: block;
}
.detailsButton:focus-visible,
.forecastButton:focus-visible {
outline: 2px solid var(--mantine-primary-color-filled);
outline-offset: 2px;
}
@media (any-pointer: coarse) {
.detailsButton,
.forecastButton {
min-width: 44px;
min-height: 44px;
}
}
+60 -42
View File
@@ -1,7 +1,7 @@
"use client";
import { useId } from "react";
import { Box, Group, HoverCard, Stack, Table, Text } from "@mantine/core";
import { Group, Popover, Stack, Table, Text, UnstyledButton } from "@mantine/core";
import {
IconArrowDownRight,
IconArrowUpRight,
@@ -23,6 +23,7 @@ import { WidgetEmptyState } from "../common/empty-state";
import type { WidgetComponentProps } from "../definition";
import { AnimatedWeatherIcon } from "./animated-icon";
import { WeatherDescription } from "./icon";
import classes from "./component.module.css";
export default function WeatherWidget({
isEditMode,
@@ -56,10 +57,11 @@ export default function WeatherWidget({
<WeeklyForecast
weather={weather}
options={options}
isEditMode={isEditMode}
maxDays={getVisibleForecastDays(options.forecastDayCount, width, height)}
/>
) : (
<DailyWeather weather={weather} options={options} />
<DailyWeather weather={weather} options={options} isEditMode={isEditMode} />
)}
</Stack>
);
@@ -69,6 +71,10 @@ interface WeatherProps extends Pick<WidgetComponentProps<"weather">, "options">
weather: NonNullable<RouterOutputs["widget"]["weather"]["atLocation"]>;
}
interface CompactWeatherProps extends WeatherProps {
isEditMode: boolean;
}
const AdvancedWeather = ({ options, weather }: WeatherProps) => {
const forecastHeadingId = useId();
const t = useScopedI18n("widget.weather");
@@ -234,23 +240,23 @@ const AdvancedWeather = ({ options, weather }: WeatherProps) => {
);
};
const DailyWeather = ({ options, weather }: WeatherProps) => {
const DailyWeather = ({ options, weather, isEditMode }: CompactWeatherProps) => {
const t = useScopedI18n("widget.weather");
const tCommon = useScopedI18n("common");
return (
<>
<Group className="weather-day-group" gap="sm">
<HoverCard>
<HoverCard.Target>
<Box>
<Popover position="bottom" withArrow shadow="md">
<Popover.Target>
<UnstyledButton className={classes.detailsButton} aria-label={t("details")} disabled={isEditMode}>
<AnimatedWeatherIcon size={30} code={weather.current.weathercode} />
</Box>
</HoverCard.Target>
<HoverCard.Dropdown>
</UnstyledButton>
</Popover.Target>
<Popover.Dropdown>
<WeatherDescription weatherOnly weatherCode={weather.current.weathercode} />
</HoverCard.Dropdown>
</HoverCard>
</Popover.Dropdown>
</Popover>
<Text fz={30}>
{getPreferredUnit(
weather.current.temperature,
@@ -319,7 +325,9 @@ const DailyWeather = ({ options, weather }: WeatherProps) => {
);
};
const WeeklyForecast = ({ options, weather, maxDays }: WeatherProps & { maxDays: number }) => {
const WeeklyForecast = ({ options, weather, isEditMode, maxDays }: CompactWeatherProps & { maxDays: number }) => {
const t = useScopedI18n("widget.weather");
return (
<>
<Group className="weather-forecast-city-temp-group" wrap="nowrap" gap="md">
@@ -332,16 +340,16 @@ const WeeklyForecast = ({ options, weather, maxDays }: WeatherProps & { maxDays:
</Group>
)}
<Group gap="xs" wrap="nowrap">
<HoverCard>
<HoverCard.Target>
<Box>
<Popover position="bottom" withArrow shadow="md">
<Popover.Target>
<UnstyledButton className={classes.detailsButton} aria-label={t("details")} disabled={isEditMode}>
<AnimatedWeatherIcon size={16} code={weather.current.weathercode} />
</Box>
</HoverCard.Target>
<HoverCard.Dropdown>
</UnstyledButton>
</Popover.Target>
<Popover.Dropdown>
<WeatherDescription weatherOnly weatherCode={weather.current.weathercode} />
</HoverCard.Dropdown>
</HoverCard>
</Popover.Dropdown>
</Popover>
<Text fz={16}>
{getPreferredUnit(
weather.current.temperature,
@@ -351,35 +359,45 @@ const WeeklyForecast = ({ options, weather, maxDays }: WeatherProps & { maxDays:
</Text>
</Group>
</Group>
<Forecast weather={weather} options={options} maxDays={maxDays} />
<Forecast weather={weather} options={options} isEditMode={isEditMode} maxDays={maxDays} />
</>
);
};
function Forecast({ weather, options, maxDays }: WeatherProps & { maxDays: number }) {
function Forecast({ weather, options, isEditMode, maxDays }: CompactWeatherProps & { maxDays: number }) {
const dateFormat = options.dateFormat;
const t = useScopedI18n("widget.weather");
return (
<Group className="weather-forecast-days-group" w="100%" justify="space-evenly" wrap="nowrap" pb="sm">
{weather.daily.slice(0, maxDays).map((dayWeather, index) => (
<HoverCard key={dayWeather.time} withArrow shadow="md">
<HoverCard.Target>
<Stack
className={combineClasses(
"weather-forecast-day-stack",
`weather-forecast-day${index}`,
`weather-forecast-weekday${dayjs(dayWeather.time).day()}`,
)}
gap="0"
align="center"
<Popover key={dayWeather.time} position="bottom" withArrow shadow="md">
<Popover.Target>
<UnstyledButton
className={classes.forecastButton}
aria-label={t("detailsFor", { date: dayjs(dayWeather.time).format(dateFormat) })}
disabled={isEditMode}
>
<Text fz="xl">{dayjs(dayWeather.time).format("dd")}</Text>
<AnimatedWeatherIcon size={16} code={dayWeather.weatherCode} />
<Text fz={16}>
{getPreferredUnit(dayWeather.maxTemp, options.isFormatFahrenheit, options.disableTemperatureDecimals)}
</Text>
</Stack>
</HoverCard.Target>
<HoverCard.Dropdown>
<Stack
component="span"
className={combineClasses(
"weather-forecast-day-stack",
`weather-forecast-day${index}`,
`weather-forecast-weekday${dayjs(dayWeather.time).day()}`,
)}
gap="0"
align="center"
>
<Text component="span" fz="xl">
{dayjs(dayWeather.time).format("dd")}
</Text>
<AnimatedWeatherIcon size={16} code={dayWeather.weatherCode} />
<Text component="span" fz={16}>
{getPreferredUnit(dayWeather.maxTemp, options.isFormatFahrenheit, options.disableTemperatureDecimals)}
</Text>
</Stack>
</UnstyledButton>
</Popover.Target>
<Popover.Dropdown>
<WeatherDescription
useImperialSpeed={options.useImperialSpeed}
dateFormat={dateFormat}
@@ -401,8 +419,8 @@ function Forecast({ weather, options, maxDays }: WeatherProps & { maxDays: numbe
maxWindGusts={dayWeather.maxWindGusts}
humidity={dayWeather.humidity}
/>
</HoverCard.Dropdown>
</HoverCard>
</Popover.Dropdown>
</Popover>
))}
</Group>
);