feat: expand advanced widget experiences
This commit is contained in:
1 parent
9437d211c8
commit
c099a8eef6
119 files changed
+3624
-844
No files matched your search
@@ -9,21 +9,25 @@ import { useRequiredBoard } from "@homarr/boards/context";
|
||||
import { useTimeAgo } from "@homarr/common";
|
||||
import { useI18n } from "@homarr/translation/client";
|
||||
|
||||
import type { WidgetComponentProps } from "../definition";
|
||||
import { getSafeApplicationUrl, SAFE_NEW_TAB_REL } from "../common/application-url";
|
||||
import { getUsableWidgetQueryData } from "../common/query-state";
|
||||
import { WidgetQueryErrorIndicator } from "../common/query-state-indicator";
|
||||
import type { WidgetComponentProps } from "../definition";
|
||||
import { getNotificationDisplay } from "./display";
|
||||
|
||||
export default function NotificationsWidget({
|
||||
options,
|
||||
integrationIds,
|
||||
width,
|
||||
height,
|
||||
displayMode,
|
||||
}: WidgetComponentProps<"notifications">) {
|
||||
const notificationsQuery = clientApi.widget.notifications.getNotifications.useQuery({
|
||||
...options,
|
||||
integrationIds,
|
||||
});
|
||||
const notificationIntegrations = getUsableWidgetQueryData(notificationsQuery) ?? [];
|
||||
const notificationData = getUsableWidgetQueryData(notificationsQuery);
|
||||
const notificationIntegrations = useMemo(() => notificationData ?? [], [notificationData]);
|
||||
const { isPending } = notificationsQuery;
|
||||
|
||||
const t = useI18n();
|
||||
@@ -49,12 +53,23 @@ export default function NotificationsWidget({
|
||||
const isDense = width < 280 || height < 180;
|
||||
const isRoomy = width >= 360 && height >= 220;
|
||||
const bodyLineClamp = height < 112 ? 1 : isDense ? 2 : height >= 300 ? 8 : 4;
|
||||
const notificationDisplay = getNotificationDisplay({
|
||||
displayMode,
|
||||
hideLogos: options.hideLogos,
|
||||
isRoomy,
|
||||
bodyLineClamp,
|
||||
});
|
||||
const columns = width >= 720 ? 2 : 1;
|
||||
const spacing = isRoomy ? "sm" : "xs";
|
||||
|
||||
return (
|
||||
<ScrollArea className="scroll-area-w100" w="100%" h="100%" p="xs">
|
||||
<Stack w="100%" gap="xs">
|
||||
{notificationsQuery.error && (
|
||||
<Group justify="flex-end">
|
||||
<WidgetQueryErrorIndicator error={notificationsQuery.error} label={t("widget.notifications.name")} />
|
||||
</Group>
|
||||
)}
|
||||
{failedIntegrations.length > 0 && (
|
||||
<Group gap={4} wrap="wrap">
|
||||
{failedIntegrations.map((integration) => (
|
||||
@@ -95,7 +110,7 @@ export default function NotificationsWidget({
|
||||
}}
|
||||
>
|
||||
<Flex gap={isDense ? "xs" : "sm"} align="flex-start" w="100%">
|
||||
{!options.hideLogos && notification.source?.iconUrl && (
|
||||
{notificationDisplay.showLogos && notification.source?.iconUrl && (
|
||||
<Avatar
|
||||
src={notification.source.iconUrl}
|
||||
alt={notification.source.name}
|
||||
@@ -113,7 +128,7 @@ export default function NotificationsWidget({
|
||||
<Text
|
||||
c="dimmed"
|
||||
size={isDense ? "xs" : "sm"}
|
||||
lineClamp={bodyLineClamp}
|
||||
lineClamp={notificationDisplay.bodyLineClamp}
|
||||
style={{ whiteSpace: "pre-line" }}
|
||||
>
|
||||
{notification.body}
|
||||
@@ -121,7 +136,11 @@ export default function NotificationsWidget({
|
||||
|
||||
<InfoDisplay
|
||||
date={notification.time}
|
||||
source={isRoomy ? (notification.source?.name ?? notification.integrationName) : undefined}
|
||||
source={
|
||||
notificationDisplay.showSource
|
||||
? (notification.source?.name ?? notification.integrationName)
|
||||
: undefined
|
||||
}
|
||||
dense={isDense}
|
||||
/>
|
||||
</Flex>
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
import { describe, expect, test } from "vitest";
|
||||
|
||||
import { getNotificationDisplay } from "./display";
|
||||
|
||||
describe("notification display", () => {
|
||||
test("advanced mode exposes logos, sources, and full bodies", () => {
|
||||
expect(
|
||||
getNotificationDisplay({ displayMode: "advanced", hideLogos: true, isRoomy: false, bodyLineClamp: 1 }),
|
||||
).toEqual({
|
||||
showLogos: true,
|
||||
showSource: true,
|
||||
bodyLineClamp: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
test("compact mode respects presentation constraints", () => {
|
||||
expect(
|
||||
getNotificationDisplay({ displayMode: "compact", hideLogos: true, isRoomy: false, bodyLineClamp: 2 }),
|
||||
).toEqual({
|
||||
showLogos: false,
|
||||
showSource: false,
|
||||
bodyLineClamp: 2,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,20 @@
|
||||
interface NotificationDisplayInput {
|
||||
displayMode?: "compact" | "advanced";
|
||||
hideLogos: boolean;
|
||||
isRoomy: boolean;
|
||||
bodyLineClamp: number;
|
||||
}
|
||||
|
||||
export const getNotificationDisplay = ({
|
||||
displayMode,
|
||||
hideLogos,
|
||||
isRoomy,
|
||||
bodyLineClamp,
|
||||
}: NotificationDisplayInput) => {
|
||||
const isAdvanced = displayMode === "advanced";
|
||||
return {
|
||||
showLogos: isAdvanced || !hideLogos,
|
||||
showSource: isAdvanced || isRoomy,
|
||||
bodyLineClamp: isAdvanced ? undefined : bodyLineClamp,
|
||||
};
|
||||
};
|
||||
Reference in new issue
Block a user