feat: complete automatic mobile board UX

This commit is contained in:
ajnart committed 2026-08-14 11:32:09 +02:00
1 parent b217d8e026
commit e6bd1adfc2
50 files changed
+1279 -343

No files matched your search

+3 -3
View File
@@ -111,7 +111,7 @@ export default function BookmarksWidget({
return (
<Stack h="100%" mih={0} gap="sm" p={height < 120 ? "xs" : "sm"}>
{options.title.length > 0 && (
<Title order={4} px="0.25rem" lineClamp={displayMode === "advanced" ? undefined : 1}>
<Title order={4} px="0.25rem" lineClamp={1}>
{options.title}
</Title>
)}
@@ -283,14 +283,14 @@ const AdvancedBookmarksLayout = ({
style={{ width: 40, height: 40, flex: "0 0 auto" }}
/>
<Stack gap={3} miw={0}>
<Text fw={700} size="sm">
<Text fw={700} size="sm" truncate="end">
{app.name}
</Text>
<Anchor component="span" size="xs" truncate="end">
{getBookmarkHostname(app.href)}
</Anchor>
{app.description && (
<Text size="xs" c="dimmed">
<Text size="xs" c="dimmed" lineClamp={3}>
{app.description}
</Text>
)}
@@ -0,0 +1,73 @@
"use client";
import type { ReactNode } from "react";
import { Box, Center, Tooltip } from "@mantine/core";
import { IconAlertTriangle } from "@tabler/icons-react";
import { useI18n } from "@homarr/translation/client";
interface WidgetQueryProvenance {
error?: unknown;
failedIntegrationCount?: number;
staleIntegrationCount?: number;
expectedIntegrationCount?: number;
receivedIntegrationCount?: number;
}
export const hasWidgetDataWarning = ({
error,
failedIntegrationCount = 0,
staleIntegrationCount = 0,
expectedIntegrationCount,
receivedIntegrationCount,
}: WidgetQueryProvenance) =>
Boolean(error) ||
failedIntegrationCount > 0 ||
staleIntegrationCount > 0 ||
(expectedIntegrationCount !== undefined &&
receivedIntegrationCount !== undefined &&
receivedIntegrationCount < expectedIntegrationCount);
export const throwOnInitialQueryError = (error: unknown, hasData: boolean) => {
if (error && !hasData) throw error;
};
interface WidgetDataStateProps {
children: ReactNode;
hasWarning: boolean;
}
export function WidgetDataState({ children, hasWarning }: WidgetDataStateProps) {
const t = useI18n();
const label = t("board.mobile.dataWarning");
return (
<Box pos="relative" h="100%" w="100%">
{hasWarning && (
<Tooltip label={label} position="left" withArrow>
<Center
component="output"
aria-label={label}
pos="absolute"
bottom={4}
w={28}
h={28}
style={{
insetInlineStart: 4,
zIndex: 4,
borderRadius: "var(--mantine-radius-sm)",
background: "light-dark(var(--mantine-color-yellow-1), var(--mantine-color-dark-6))",
}}
>
<IconAlertTriangle
size={16}
color="light-dark(var(--mantine-color-yellow-9), var(--mantine-color-yellow-3))"
aria-hidden
/>
</Center>
</Tooltip>
)}
{children}
</Box>
);
}
+1 -1
View File
@@ -6,7 +6,7 @@ import { optionsBuilder } from "../options";
export const { definition, componentLoader } = createWidgetDefinition("customApi", {
icon: IconApi,
mobile: { supportsDetailView: true },
mobile: { supportsDetailView: true, unmountWhenOffscreen: true },
queryKey: [["widget", "customApi"]],
createOptions() {
return optionsBuilder.from((factory) => ({
@@ -10,7 +10,7 @@ export const widgetKind = "dnsHoleControls";
export const { definition, componentLoader } = createWidgetDefinition(widgetKind, {
supportsAdvancedFocus: false,
icon: IconDeviceGamepad,
mobile: { supportsDetailView: true },
mobile: { supportsDetailView: true, unmountWhenOffscreen: true },
queryKey: [["widget", "dnsHole"]],
refetchInterval: 5,
createOptions() {
@@ -10,6 +10,7 @@ export const widgetKind = "dnsHoleSummary";
export const { definition, componentLoader } = createWidgetDefinition(widgetKind, {
supportsAdvancedFocus: false,
icon: IconAd,
mobile: { unmountWhenOffscreen: true },
queryKey: [["widget", "dnsHole"]],
refetchInterval: 5,
createOptions() {