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 (