fix: align widget accessibility and locales
This commit is contained in:
1 parent
d880c4a281
commit
6c8dbc182f
11 files changed
+46
-46
No files matched your search
@@ -14,7 +14,7 @@ import { clientApi } from "@homarr/api/client";
|
||||
import { useIntegrationsWithInteractAccess } from "@homarr/auth/client";
|
||||
import { useTimeAgo } from "@homarr/common";
|
||||
import type { AnchorNotePermission } from "@homarr/integrations";
|
||||
import { useScopedI18n } from "@homarr/translation/client";
|
||||
import { useCurrentIntlLocale, useScopedI18n } from "@homarr/translation/client";
|
||||
|
||||
import { WidgetEmptyState } from "../common/empty-state";
|
||||
import type { WidgetComponentProps } from "../definition";
|
||||
@@ -180,6 +180,7 @@ const AnchorNoteWidgetContent = ({
|
||||
isAdvanced,
|
||||
}: AnchorNoteWidgetContentProps) => {
|
||||
const t = useScopedI18n("widget.anchorNote");
|
||||
const locale = useCurrentIntlLocale();
|
||||
const { data: note, refetch } = clientApi.widget.anchorNotes.getNote.useQuery({
|
||||
integrationId,
|
||||
noteId,
|
||||
@@ -327,7 +328,7 @@ const AnchorNoteWidgetContent = ({
|
||||
</Badge>
|
||||
)}
|
||||
<Text size="xs" c="dimmed">
|
||||
{t("createdAt", { date: new Date(note.createdAt).toLocaleDateString() })}
|
||||
{t("createdAt", { date: new Date(note.createdAt).toLocaleDateString(locale) })}
|
||||
</Text>
|
||||
</Group>
|
||||
)}
|
||||
|
||||
@@ -2,19 +2,7 @@
|
||||
|
||||
import { useMemo } from "react";
|
||||
import type { BoxProps } from "@mantine/core";
|
||||
import {
|
||||
Avatar,
|
||||
AvatarGroup,
|
||||
Badge,
|
||||
Card,
|
||||
Flex,
|
||||
Group,
|
||||
SimpleGrid,
|
||||
Stack,
|
||||
Text,
|
||||
Tooltip,
|
||||
TooltipFloating,
|
||||
} from "@mantine/core";
|
||||
import { Avatar, AvatarGroup, Badge, Card, Flex, Group, SimpleGrid, Stack, Text, Tooltip } from "@mantine/core";
|
||||
import { useElementSize } from "@mantine/hooks";
|
||||
import { IconBarrierBlock, IconPercentage, IconSearch, IconWorldWww } from "@tabler/icons-react";
|
||||
|
||||
@@ -177,10 +165,11 @@ const StatCard = ({ item, data, usePiHoleColors, t }: StatCardProps) => {
|
||||
const value = item.value(data, width <= 64 ? "sm" : "md");
|
||||
|
||||
return (
|
||||
<TooltipFloating label={tooltip} disabled={!tooltip} w={250} multiline>
|
||||
<Tooltip label={tooltip} disabled={!tooltip} w={250} multiline events={{ hover: true, focus: true, touch: true }}>
|
||||
<Card
|
||||
ref={ref}
|
||||
component="section"
|
||||
tabIndex={tooltip ? 0 : undefined}
|
||||
aria-label={`${label}: ${value}`}
|
||||
className="summary-card"
|
||||
p="sm"
|
||||
@@ -223,7 +212,7 @@ const StatCard = ({ item, data, usePiHoleColors, t }: StatCardProps) => {
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Card>
|
||||
</TooltipFloating>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
} from "@tabler/icons-react";
|
||||
|
||||
import { clientApi } from "@homarr/api/client";
|
||||
import { useI18n, useScopedI18n } from "@homarr/translation/client";
|
||||
import { useCurrentIntlLocale, useI18n, useScopedI18n } from "@homarr/translation/client";
|
||||
|
||||
import { WidgetEmptyState } from "../../common/empty-state";
|
||||
import type { WidgetComponentProps } from "../../definition";
|
||||
@@ -139,6 +139,7 @@ function Carousel({
|
||||
setPaused,
|
||||
}: CarouselProps) {
|
||||
const t = useScopedI18n("widget.immich-albumCarousel");
|
||||
const locale = useCurrentIntlLocale();
|
||||
const reduceMotion = useReducedMotion();
|
||||
|
||||
useEffect(() => {
|
||||
@@ -214,7 +215,7 @@ function Carousel({
|
||||
)}
|
||||
<Group gap="xs">
|
||||
<IconCalendar size={16} />
|
||||
<Text size="xs">{new Date(currentAsset.fileModifiedAt).toLocaleDateString()}</Text>
|
||||
<Text size="xs">{new Date(currentAsset.fileModifiedAt).toLocaleDateString(locale)}</Text>
|
||||
</Group>
|
||||
<Text size="xs" c="dimmed">
|
||||
{safeCurrentIndex + 1} / {assets.length}
|
||||
|
||||
@@ -20,6 +20,7 @@ vi.mock("@homarr/api/client", () => ({
|
||||
}));
|
||||
|
||||
vi.mock("@homarr/translation/client", () => ({
|
||||
useCurrentIntlLocale: () => "en",
|
||||
useI18n: () => (key: string) => key,
|
||||
useScopedI18n: () => (key: string) => key,
|
||||
}));
|
||||
|
||||
@@ -6,7 +6,7 @@ import { IconDatabase, IconPhoto, IconUsers, IconVideo } from "@tabler/icons-rea
|
||||
|
||||
import { clientApi } from "@homarr/api/client";
|
||||
import { formatBytes } from "@homarr/common";
|
||||
import { useI18n } from "@homarr/translation/client";
|
||||
import { useCurrentIntlLocale, useI18n } from "@homarr/translation/client";
|
||||
|
||||
import { WidgetEmptyState } from "../../common/empty-state";
|
||||
import type { WidgetComponentProps } from "../../definition";
|
||||
@@ -24,6 +24,7 @@ export default function ImmichServerStatsWidget({
|
||||
widgetStateRef,
|
||||
}: WidgetComponentProps<"immich-serverStats">) {
|
||||
const t = useI18n();
|
||||
const locale = useCurrentIntlLocale();
|
||||
const input = { integrationId: integrationIds[0] ?? "" };
|
||||
const albumsInput = { ...input, limit: MAX_ADVANCED_ALBUMS };
|
||||
const isAdvanced = displayMode === "advanced";
|
||||
@@ -50,7 +51,7 @@ export default function ImmichServerStatsWidget({
|
||||
<StatItem
|
||||
icon={<IconUsers size={20} />}
|
||||
label={t("widget.immich-serverStats.users")}
|
||||
value={stats.userCount}
|
||||
value={stats.userCount.toLocaleString(locale)}
|
||||
dense={statsLayout.dense}
|
||||
/>
|
||||
)}
|
||||
@@ -58,7 +59,7 @@ export default function ImmichServerStatsWidget({
|
||||
<StatItem
|
||||
icon={<IconPhoto size={20} />}
|
||||
label={t("widget.immich-serverStats.photos")}
|
||||
value={stats.photoCount}
|
||||
value={stats.photoCount.toLocaleString(locale)}
|
||||
dense={statsLayout.dense}
|
||||
/>
|
||||
)}
|
||||
@@ -66,7 +67,7 @@ export default function ImmichServerStatsWidget({
|
||||
<StatItem
|
||||
icon={<IconVideo size={20} />}
|
||||
label={t("widget.immich-serverStats.videos")}
|
||||
value={stats.videoCount}
|
||||
value={stats.videoCount.toLocaleString(locale)}
|
||||
dense={statsLayout.dense}
|
||||
/>
|
||||
)}
|
||||
@@ -103,7 +104,7 @@ export default function ImmichServerStatsWidget({
|
||||
{album.albumName}
|
||||
</Text>
|
||||
<Text size="xs" c="dimmed">
|
||||
{album.assetCount.toLocaleString()}
|
||||
{album.assetCount.toLocaleString(locale)}
|
||||
</Text>
|
||||
</Group>
|
||||
<Progress value={(album.assetCount / Math.max(maxAssets, 1)) * 100} size="sm" />
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
SimpleGrid,
|
||||
Stack,
|
||||
Text,
|
||||
TooltipFloating,
|
||||
Tooltip,
|
||||
UnstyledButton,
|
||||
} from "@mantine/core";
|
||||
import { IconBook, IconCalendar, IconClock, IconStarFilled } from "@tabler/icons-react";
|
||||
@@ -88,10 +88,11 @@ const Item = ({ item, options, isAdvanced, width, height }: ItemProps) => {
|
||||
const showSide = isAdvanced || (!isTiny && (options.showType || options.showSource));
|
||||
|
||||
return (
|
||||
<TooltipFloating
|
||||
<Tooltip
|
||||
label={item.description}
|
||||
w={300}
|
||||
multiline
|
||||
events={{ hover: true, focus: true, touch: true }}
|
||||
disabled={
|
||||
isAdvanced ||
|
||||
item.description === undefined ||
|
||||
@@ -221,7 +222,7 @@ const Item = ({ item, options, isAdvanced, width, height }: ItemProps) => {
|
||||
)}
|
||||
</Group>
|
||||
</UnstyledButton>
|
||||
</TooltipFloating>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ import { IconAutomation, IconCheck } from "@tabler/icons-react";
|
||||
import { clientApi } from "@homarr/api/client";
|
||||
import { useIntegrationsWithInteractAccess } from "@homarr/auth/client";
|
||||
import { useRegisterSpotlightContextActions } from "@homarr/spotlight";
|
||||
import { useI18n } from "@homarr/translation/client";
|
||||
import { useCurrentIntlLocale, useI18n } from "@homarr/translation/client";
|
||||
|
||||
import type { WidgetComponentProps } from "../../definition";
|
||||
|
||||
@@ -61,6 +61,7 @@ export default function SmartHomeTriggerAutomationWidget({
|
||||
}, [canInteract, integrationId, isEditMode, isPending, mutateAsync, options.automationId]);
|
||||
|
||||
const t = useI18n();
|
||||
const locale = useCurrentIntlLocale();
|
||||
useRegisterSpotlightContextActions(
|
||||
`smartHome-automation-${options.automationId}`,
|
||||
[
|
||||
@@ -150,7 +151,7 @@ export default function SmartHomeTriggerAutomationWidget({
|
||||
<Text ta="center" size="xs" c="dimmed">
|
||||
{lastExecutedAt
|
||||
? t("widget.smartHome-executeAutomation.advanced.lastExecuted", {
|
||||
time: lastExecutedAt.toLocaleTimeString(),
|
||||
time: lastExecutedAt.toLocaleTimeString(locale),
|
||||
})
|
||||
: t("widget.smartHome-executeAutomation.advanced.automationId", {
|
||||
id: options.automationId,
|
||||
|
||||
@@ -5,7 +5,7 @@ import { Flex, Stack, Text, Title, useMantineTheme } from "@mantine/core";
|
||||
import { IconMinus, IconTrendingDown, IconTrendingUp } from "@tabler/icons-react";
|
||||
|
||||
import { clientApi } from "@homarr/api/client";
|
||||
import { useScopedI18n } from "@homarr/translation/client";
|
||||
import { useCurrentIntlLocale, useScopedI18n } from "@homarr/translation/client";
|
||||
|
||||
import { WidgetEmptyState } from "../common/empty-state";
|
||||
import type { WidgetComponentProps } from "../definition";
|
||||
@@ -73,6 +73,8 @@ export function getStockSummary(priceHistory: number[], previousClose: number):
|
||||
|
||||
export default function StockPriceWidget({ options, width, height, displayMode }: WidgetComponentProps<"stockPrice">) {
|
||||
const t = useScopedI18n("widget.stockPrice");
|
||||
const locale = useCurrentIntlLocale();
|
||||
const numberFormatter = new Intl.NumberFormat(locale);
|
||||
const theme = useMantineTheme();
|
||||
const { data: result } = clientApi.widget.stockPrice.getPriceHistory.useQuery(options);
|
||||
|
||||
@@ -120,15 +122,15 @@ export default function StockPriceWidget({ options, width, height, displayMode }
|
||||
</Stack>
|
||||
|
||||
<Title pos="absolute" bottom={10} right={10} order={layout.priceOrder} fw={700}>
|
||||
{new Intl.NumberFormat().format(round(summary.currentPrice))}
|
||||
{numberFormatter.format(round(summary.currentPrice))}
|
||||
</Title>
|
||||
|
||||
{layout.showChange && (
|
||||
<Text pos="absolute" top={10} right={10} size="xl" fw={700}>
|
||||
{new Intl.NumberFormat().format(stockValuesChange)}
|
||||
{numberFormatter.format(stockValuesChange)}
|
||||
{stockValuesChangePercentage === null
|
||||
? null
|
||||
: ` (${stockValuesChange > 0 ? "+" : ""}${new Intl.NumberFormat().format(stockValuesChangePercentage)}%)`}
|
||||
: ` (${stockValuesChange > 0 ? "+" : ""}${numberFormatter.format(stockValuesChangePercentage)}%)`}
|
||||
</Text>
|
||||
)}
|
||||
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { Avatar, Badge, Group, Paper, Stack, Text } from "@mantine/core";
|
||||
|
||||
import type { TracearrHistorySession } from "@homarr/integrations/types";
|
||||
import { useScopedI18n } from "@homarr/translation/client";
|
||||
import { useCurrentIntlLocale, useScopedI18n } from "@homarr/translation/client";
|
||||
|
||||
export function RecentActivityList({ sessions }: { sessions: TracearrHistorySession[] }) {
|
||||
const t = useScopedI18n("widget.tracearr");
|
||||
const locale = useCurrentIntlLocale();
|
||||
|
||||
return (
|
||||
<Stack gap={4}>
|
||||
@@ -42,7 +43,7 @@ export function RecentActivityList({ sessions }: { sessions: TracearrHistorySess
|
||||
{session.watched ? t("recentActivity.watched") : t("recentActivity.partial")}
|
||||
</Badge>
|
||||
<Text size="xs" c="dimmed" lineClamp={1}>
|
||||
{new Date(session.startedAt).toLocaleDateString()}
|
||||
{new Date(session.startedAt).toLocaleDateString(locale)}
|
||||
</Text>
|
||||
</Stack>
|
||||
</Group>
|
||||
|
||||
@@ -2,10 +2,11 @@ import { Avatar, Badge, Group, Paper, Stack, Text } from "@mantine/core";
|
||||
import { IconAlertTriangle } from "@tabler/icons-react";
|
||||
|
||||
import type { TracearrViolation } from "@homarr/integrations/types";
|
||||
import { useScopedI18n } from "@homarr/translation/client";
|
||||
import { useCurrentIntlLocale, useScopedI18n } from "@homarr/translation/client";
|
||||
|
||||
export function ViolationsList({ violations }: { violations: TracearrViolation[] }) {
|
||||
const t = useScopedI18n("widget.tracearr");
|
||||
const locale = useCurrentIntlLocale();
|
||||
|
||||
return (
|
||||
<Stack gap={4}>
|
||||
@@ -53,7 +54,7 @@ export function ViolationsList({ violations }: { violations: TracearrViolation[]
|
||||
{violation.severity}
|
||||
</Badge>
|
||||
<Text size="xs" c="dimmed" lineClamp={1}>
|
||||
{new Date(violation.createdAt).toLocaleDateString()}
|
||||
{new Date(violation.createdAt).toLocaleDateString(locale)}
|
||||
</Text>
|
||||
</Stack>
|
||||
</Group>
|
||||
|
||||
@@ -6,7 +6,7 @@ import { Box, Group, ScrollArea, SimpleGrid, Stack, Text, useMantineColorScheme
|
||||
import { clientApi } from "@homarr/api/client";
|
||||
import { formatDuration } from "@homarr/common";
|
||||
import type { UmamiVisitorStats } from "@homarr/integrations/types";
|
||||
import { useScopedI18n } from "@homarr/translation/client";
|
||||
import { useCurrentIntlLocale, useScopedI18n } from "@homarr/translation/client";
|
||||
|
||||
import { UmamiEventsContent } from "./umami-events-content";
|
||||
import { UmamiTopPagesContent, UmamiTopReferrersContent } from "./umami-top-list";
|
||||
@@ -44,6 +44,7 @@ export function UmamiContent({
|
||||
}: UmamiContentProps) {
|
||||
const t = useScopedI18n("widget.umami");
|
||||
const tCommon = useScopedI18n("common");
|
||||
const locale = useCurrentIntlLocale();
|
||||
const { colorScheme } = useMantineColorScheme();
|
||||
const tickColor = colorScheme === "dark" ? "#c1c2c5" : "#495057";
|
||||
|
||||
@@ -217,8 +218,8 @@ export function UmamiContent({
|
||||
<Group gap={4} align="baseline">
|
||||
<Text size="lg" fw={700} lh={1}>
|
||||
{viewMode === "events"
|
||||
? (multiEventTotal ?? 0).toLocaleString()
|
||||
: (visitorStats.eventCount ?? 0).toLocaleString()}
|
||||
? (multiEventTotal ?? 0).toLocaleString(locale)
|
||||
: (visitorStats.eventCount ?? 0).toLocaleString(locale)}
|
||||
</Text>
|
||||
<Text size="xs" c="dimmed">
|
||||
{t("event.label")}
|
||||
@@ -229,7 +230,7 @@ export function UmamiContent({
|
||||
</Text>
|
||||
<Group gap={4} align="baseline">
|
||||
<Text size="lg" fw={700} lh={1}>
|
||||
{visitorStats.totalVisitors.toLocaleString()}
|
||||
{visitorStats.totalVisitors.toLocaleString(locale)}
|
||||
</Text>
|
||||
<Text size="xs" c="dimmed">
|
||||
{t("visitors")}
|
||||
@@ -239,7 +240,7 @@ export function UmamiContent({
|
||||
) : (
|
||||
<Group gap={4} align="baseline">
|
||||
<Text size="xl" fw={700} lh={1}>
|
||||
{visitorStats.totalVisitors.toLocaleString()}
|
||||
{visitorStats.totalVisitors.toLocaleString(locale)}
|
||||
</Text>
|
||||
<Text size="xs" c="dimmed">
|
||||
{t("visitors")}
|
||||
@@ -250,18 +251,18 @@ export function UmamiContent({
|
||||
{showSecondaryStats && (
|
||||
<Group wrap="wrap" style={{ columnGap: 12, rowGap: 4 }}>
|
||||
<Text size="xs" c="green">
|
||||
● {activeVisitors.toLocaleString()} {t("active")}
|
||||
● {activeVisitors.toLocaleString(locale)} {t("active")}
|
||||
</Text>
|
||||
<Text size="xs" c="dimmed">
|
||||
{visitorStats.totalPageviews.toLocaleString()} {t("pageviews")}
|
||||
{visitorStats.totalPageviews.toLocaleString(locale)} {t("pageviews")}
|
||||
</Text>
|
||||
<Text size="xs" c="dimmed">
|
||||
{visitorStats.totalVisits.toLocaleString()} {t("visits")}
|
||||
{visitorStats.totalVisits.toLocaleString(locale)} {t("visits")}
|
||||
</Text>
|
||||
{showDetailedStats && (
|
||||
<>
|
||||
<Text size="xs" c="dimmed">
|
||||
{visitorStats.bounceRate}% {t("bounceRate")}
|
||||
{visitorStats.bounceRate.toLocaleString(locale)}% {t("bounceRate")}
|
||||
</Text>
|
||||
<Text size="xs" c="dimmed">
|
||||
{formatDuration(visitorStats.avgDuration * 1000)} {t("avgDuration")}
|
||||
|
||||
Reference in new issue
Block a user