From e7e44375ef7ad2ac91f37225c124d63826bb6ee3 Mon Sep 17 00:00:00 2001 From: Thomas Camlong Date: Sun, 2 Aug 2026 14:35:01 +0200 Subject: [PATCH] fix: address widget review findings --- .../integration/integration-grid-shared.ts | 1 + .../widgets/src/anchor-note/component.tsx | 10 +- .../beszel-system-stats/component.module.css | 2 +- .../src/common/action-target.module.css | 11 ++ packages/widgets/src/coolify/resource-row.tsx | 3 + .../widgets/src/coolify/servers-section.tsx | 2 + .../src/dns-hole/controls/component.tsx | 6 +- .../system-health.module.css | 2 +- .../src/health-monitoring/system-health.tsx | 3 +- .../src/indexer-manager/component.module.css | 2 +- .../widgets/src/indexer-manager/component.tsx | 3 +- .../media-requests/list/component.module.css | 9 +- .../src/media-requests/list/component.tsx | 7 +- .../media-requests/search-button.module.css | 6 +- .../src/media-requests/stats/component.tsx | 3 +- .../media-transcoding/health-check-status.tsx | 4 +- packages/widgets/src/notebook/notebook.tsx | 45 ++++---- packages/widgets/src/releases/component.tsx | 18 +++- packages/widgets/src/stocks/component.tsx | 6 +- .../widgets/src/weather/component.module.css | 31 ++++++ packages/widgets/src/weather/component.tsx | 102 ++++++++++-------- 21 files changed, 190 insertions(+), 86 deletions(-) create mode 100644 packages/widgets/src/common/action-target.module.css create mode 100644 packages/widgets/src/weather/component.module.css diff --git a/apps/nextjs/src/components/integration/integration-grid-shared.ts b/apps/nextjs/src/components/integration/integration-grid-shared.ts index 03056c130..dde74820f 100644 --- a/apps/nextjs/src/components/integration/integration-grid-shared.ts +++ b/apps/nextjs/src/components/integration/integration-grid-shared.ts @@ -22,6 +22,7 @@ export const categoryTranslationKeys: Record = { 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", diff --git a/packages/widgets/src/anchor-note/component.tsx b/packages/widgets/src/anchor-note/component.tsx index 9268bf6f4..24e4de866 100644 --- a/packages/widgets/src/anchor-note/component.tsx +++ b/packages/widgets/src/anchor-note/component.tsx @@ -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 = ({ ) : ( - + diff --git a/packages/widgets/src/beszel-system-stats/component.module.css b/packages/widgets/src/beszel-system-stats/component.module.css index 9d8b2087b..9966f37be 100644 --- a/packages/widgets/src/beszel-system-stats/component.module.css +++ b/packages/widgets/src/beszel-system-stats/component.module.css @@ -14,7 +14,7 @@ pointer-events: auto; } -@media (hover: none) { +@media (any-pointer: coarse) { .beszelStatsSystemToggle { opacity: 1; pointer-events: auto; diff --git a/packages/widgets/src/common/action-target.module.css b/packages/widgets/src/common/action-target.module.css new file mode 100644 index 000000000..28f6cc672 --- /dev/null +++ b/packages/widgets/src/common/action-target.module.css @@ -0,0 +1,11 @@ +.root { + min-width: 24px; + min-height: 24px; +} + +@media (any-pointer: coarse) { + .root { + min-width: 44px; + min-height: 44px; + } +} diff --git a/packages/widgets/src/coolify/resource-row.tsx b/packages/widgets/src/coolify/resource-row.tsx index 4aaa4b625..fba5279ca 100644 --- a/packages/widgets/src/coolify/resource-row.tsx +++ b/packages/widgets/src/coolify/resource-row.tsx @@ -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 {cleanFqdn(item.fqdn) && ( = ({ {!isEnabled ? ( void toggleDns(data.integration.id).catch(() => undefined)} disabled={!controlEnabled} @@ -364,6 +366,7 @@ const ControlsCard: React.FC = ({ ) : ( void toggleDns(data.integration.id).catch(() => undefined)} disabled={!controlEnabled} @@ -375,6 +378,7 @@ const ControlsCard: React.FC = ({ )} { setSelectedIntegrationIds([data.integration.id]); @@ -440,7 +444,7 @@ const ControlsCard: React.FC = ({ {layout === "md" && ( - + diff --git a/packages/widgets/src/notebook/notebook.tsx b/packages/widgets/src/notebook/notebook.tsx index 53a334189..cede6ffe4 100644 --- a/packages/widgets/src/notebook/notebook.tsx +++ b/packages/widgets/src/notebook/notebook.tsx @@ -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({ )} {canChange && ( - <> + {isEditing && ( )} - + )} ); @@ -746,13 +735,31 @@ const ColorControl = ({ defaultColor, getCurrent, update, icon: Icon, ariaLabel - + - + - + diff --git a/packages/widgets/src/releases/component.tsx b/packages/widgets/src/releases/component.tsx index 2c7470875..3481f6172 100644 --- a/packages/widgets/src/releases/component.tsx +++ b/packages/widgets/src/releases/component.tsx @@ -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, + })} > {(options.showDetails || isAdvanced) && ( - + )} {isActive && ( 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 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, + })} > diff --git a/packages/widgets/src/stocks/component.tsx b/packages/widgets/src/stocks/component.tsx index 4fee07497..d83f409d0 100644 --- a/packages/widgets/src/stocks/component.tsx +++ b/packages/widgets/src/stocks/component.tsx @@ -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 } {stockValuesChange > 0 ? ( - ) : ( + ) : stockValuesChange < 0 ? ( + ) : ( + )} {data.symbol} diff --git a/packages/widgets/src/weather/component.module.css b/packages/widgets/src/weather/component.module.css new file mode 100644 index 000000000..4b7b623be --- /dev/null +++ b/packages/widgets/src/weather/component.module.css @@ -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; + } +} diff --git a/packages/widgets/src/weather/component.tsx b/packages/widgets/src/weather/component.tsx index f1be2ac44..43990ab62 100644 --- a/packages/widgets/src/weather/component.tsx +++ b/packages/widgets/src/weather/component.tsx @@ -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({ ) : ( - + )} ); @@ -69,6 +71,10 @@ interface WeatherProps extends Pick, "options"> weather: NonNullable; } +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 ( <> - - - + + + - - - + + + - - + + {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 ( <> @@ -332,16 +340,16 @@ const WeeklyForecast = ({ options, weather, maxDays }: WeatherProps & { maxDays: )} - - - + + + - - - + + + - - + + {getPreferredUnit( weather.current.temperature, @@ -351,35 +359,45 @@ const WeeklyForecast = ({ options, weather, maxDays }: WeatherProps & { 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 ( {weather.daily.slice(0, maxDays).map((dayWeather, index) => ( - - - + + - {dayjs(dayWeather.time).format("dd")} - - - {getPreferredUnit(dayWeather.maxTemp, options.isFormatFahrenheit, options.disableTemperatureDecimals)} - - - - + + + {dayjs(dayWeather.time).format("dd")} + + + + {getPreferredUnit(dayWeather.maxTemp, options.isFormatFahrenheit, options.disableTemperatureDecimals)} + + + + + - - + + ))} );