diff --git a/apps/nextjs/src/app/[locale]/widgets/[kind]/_content.tsx b/apps/nextjs/src/app/[locale]/widgets/[kind]/_content.tsx index 37ba09d52..836800fa7 100644 --- a/apps/nextjs/src/app/[locale]/widgets/[kind]/_content.tsx +++ b/apps/nextjs/src/app/[locale]/widgets/[kind]/_content.tsx @@ -48,6 +48,7 @@ export const WidgetPreviewPageContent = ({ kind, integrationData }: WidgetPrevie options: reduceWidgetOptionsWithDefaultValues(kind, settings, {}), integrationIds: [], advancedOptions: { + title: null, customCssClasses: [], borderColor: "", }, diff --git a/apps/nextjs/src/components/board/items/actions/create-item.ts b/apps/nextjs/src/components/board/items/actions/create-item.ts index 7011787ad..66476af41 100644 --- a/apps/nextjs/src/components/board/items/actions/create-item.ts +++ b/apps/nextjs/src/components/board/items/actions/create-item.ts @@ -29,6 +29,7 @@ export const createItemCallback = layouts: createItemLayouts(previous, firstSection), integrationIds: [], advancedOptions: { + title: null, customCssClasses: [], borderColor: "", }, diff --git a/apps/nextjs/src/components/board/items/actions/test/duplicate-item.spec.ts b/apps/nextjs/src/components/board/items/actions/test/duplicate-item.spec.ts index 88ccd781b..a6dce4384 100644 --- a/apps/nextjs/src/components/board/items/actions/test/duplicate-item.spec.ts +++ b/apps/nextjs/src/components/board/items/actions/test/duplicate-item.spec.ts @@ -20,7 +20,7 @@ describe("item actions duplicate-item", () => { kind: itemKind, integrationIds: ["1"], options: { address: "localhost" }, - advancedOptions: { customCssClasses: ["test"], borderColor: "#ff0000" }, + advancedOptions: { title: "The best one", customCssClasses: ["test"], borderColor: "#ff0000" }, }) .addLayout({ layoutId, sectionId: currentSectionId, ...currentItemSize }) .build(); diff --git a/apps/nextjs/src/components/board/items/actions/test/mocks/item-mock.ts b/apps/nextjs/src/components/board/items/actions/test/mocks/item-mock.ts index 71b84918e..649095a74 100644 --- a/apps/nextjs/src/components/board/items/actions/test/mocks/item-mock.ts +++ b/apps/nextjs/src/components/board/items/actions/test/mocks/item-mock.ts @@ -13,6 +13,7 @@ export class ItemMockBuilder { layouts: [], integrationIds: [], advancedOptions: { + title: null, customCssClasses: [], borderColor: "", }, diff --git a/apps/nextjs/src/components/board/items/item-content.module.css b/apps/nextjs/src/components/board/items/item-content.module.css new file mode 100644 index 000000000..f0f6ce4bc --- /dev/null +++ b/apps/nextjs/src/components/board/items/item-content.module.css @@ -0,0 +1,12 @@ +.badge { + @mixin dark { + --background-color: rgb(from var(--mantine-color-dark-6) r g b / var(--opacity)); + --border-color: rgb(from var(--mantine-color-dark-4) r g b / var(--opacity)); + } + @mixin light { + --background-color: rgb(from var(--mantine-color-white) r g b / var(--opacity)); + --border-color: rgb(from var(--mantine-color-gray-3) r g b / var(--opacity)); + } + background-color: var(--background-color) !important; + border-color: var(--border-color) !important; +} diff --git a/apps/nextjs/src/components/board/items/item-content.tsx b/apps/nextjs/src/components/board/items/item-content.tsx index 709d52182..9003bfacc 100644 --- a/apps/nextjs/src/components/board/items/item-content.tsx +++ b/apps/nextjs/src/components/board/items/item-content.tsx @@ -1,4 +1,4 @@ -import { Card } from "@mantine/core"; +import { Badge, Card } from "@mantine/core"; import { useElementSize } from "@mantine/hooks"; import { QueryErrorResetBoundary } from "@tanstack/react-query"; import combineClasses from "clsx"; @@ -14,6 +14,7 @@ import { WidgetError } from "@homarr/widgets/errors"; import type { SectionItem } from "~/app/[locale]/boards/_types"; import classes from "../sections/item.module.css"; import { useItemActions } from "./item-actions"; +import itemContentClasses from "./item-content.module.css"; import { BoardItemMenu } from "./item-menu"; interface BoardItemContentProps { @@ -25,28 +26,51 @@ export const BoardItemContent = ({ item }: BoardItemContentProps) => { const board = useRequiredBoard(); return ( - + + + + {item.advancedOptions.title?.trim() && ( + + {item.advancedOptions.title} + )} - radius={board.itemRadius} - withBorder - styles={{ - root: { - "--opacity": board.opacity / 100, - containerType: "size", - overflow: item.kind === "iframe" ? "hidden" : undefined, - "--border-color": item.advancedOptions.borderColor !== "" ? item.advancedOptions.borderColor : undefined, - }, - }} - p={0} - > - - + ); }; diff --git a/packages/translation/src/lang/en.json b/packages/translation/src/lang/en.json index 64cbc463f..eb85cecca 100644 --- a/packages/translation/src/lang/en.json +++ b/packages/translation/src/lang/en.json @@ -1092,6 +1092,9 @@ "integrations": { "label": "Integrations" }, + "title": { + "label": "Title" + }, "customCssClasses": { "label": "Custom css classes" }, diff --git a/packages/validation/src/shared.ts b/packages/validation/src/shared.ts index ddf7c30ea..c6da17448 100644 --- a/packages/validation/src/shared.ts +++ b/packages/validation/src/shared.ts @@ -14,6 +14,7 @@ export const integrationSchema = z.object({ export type BoardItemIntegration = z.infer; export const itemAdvancedOptionsSchema = z.object({ + title: z.string().max(64).nullable().default(null), customCssClasses: z.array(z.string()).default([]), borderColor: z.string().default(""), }); diff --git a/packages/widgets/src/modals/widget-advanced-options-modal.tsx b/packages/widgets/src/modals/widget-advanced-options-modal.tsx index 1c2996926..dc785d186 100644 --- a/packages/widgets/src/modals/widget-advanced-options-modal.tsx +++ b/packages/widgets/src/modals/widget-advanced-options-modal.tsx @@ -1,6 +1,6 @@ "use client"; -import { Button, CloseButton, ColorInput, Group, Stack, useMantineTheme } from "@mantine/core"; +import { Button, CloseButton, ColorInput, Group, Input, Stack, TextInput, useMantineTheme } from "@mantine/core"; import { useForm } from "@homarr/form"; import { createModal } from "@homarr/modals"; @@ -20,13 +20,23 @@ export const WidgetAdvancedOptionsModal = createModal(({ actions, in initialValues: innerProps.advancedOptions, }); const handleSubmit = (values: BoardItemAdvancedOptions) => { - innerProps.onSuccess(values); + innerProps.onSuccess({ + ...values, + // we want to fallback to null if the title is empty + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + title: values.title?.trim() || null, + }); actions.closeModal(); }; return (
+ form.setFieldValue("title", "")} />} + />