diff --git a/apps/docs/docs/widgets/weather/index.mdx b/apps/docs/docs/widgets/weather/index.mdx
index a4b84b5fa..f36dc68ea 100644
--- a/apps/docs/docs/widgets/weather/index.mdx
+++ b/apps/docs/docs/widgets/weather/index.mdx
@@ -14,6 +14,7 @@ import { weatherWidget } from ".";
The Weather Widget uses your location to display the current, highest and lowest temperature.
+In the compact view, select a condition icon or forecast day to open details with a mouse, keyboard or touch.
The advanced view always combines current conditions with the full seven-day forecast, including humidity, wind,
gusts, sunrise and sunset. Both views reuse the same cached Open Meteo response.
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)}
+
+
+
+
+
-
-
+
+
))}
);