feat(widgets): add title to advanced options (#2909)
This commit is contained in:
@@ -48,6 +48,7 @@ export const WidgetPreviewPageContent = ({ kind, integrationData }: WidgetPrevie
|
|||||||
options: reduceWidgetOptionsWithDefaultValues(kind, settings, {}),
|
options: reduceWidgetOptionsWithDefaultValues(kind, settings, {}),
|
||||||
integrationIds: [],
|
integrationIds: [],
|
||||||
advancedOptions: {
|
advancedOptions: {
|
||||||
|
title: null,
|
||||||
customCssClasses: [],
|
customCssClasses: [],
|
||||||
borderColor: "",
|
borderColor: "",
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ export const createItemCallback =
|
|||||||
layouts: createItemLayouts(previous, firstSection),
|
layouts: createItemLayouts(previous, firstSection),
|
||||||
integrationIds: [],
|
integrationIds: [],
|
||||||
advancedOptions: {
|
advancedOptions: {
|
||||||
|
title: null,
|
||||||
customCssClasses: [],
|
customCssClasses: [],
|
||||||
borderColor: "",
|
borderColor: "",
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ describe("item actions duplicate-item", () => {
|
|||||||
kind: itemKind,
|
kind: itemKind,
|
||||||
integrationIds: ["1"],
|
integrationIds: ["1"],
|
||||||
options: { address: "localhost" },
|
options: { address: "localhost" },
|
||||||
advancedOptions: { customCssClasses: ["test"], borderColor: "#ff0000" },
|
advancedOptions: { title: "The best one", customCssClasses: ["test"], borderColor: "#ff0000" },
|
||||||
})
|
})
|
||||||
.addLayout({ layoutId, sectionId: currentSectionId, ...currentItemSize })
|
.addLayout({ layoutId, sectionId: currentSectionId, ...currentItemSize })
|
||||||
.build();
|
.build();
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ export class ItemMockBuilder {
|
|||||||
layouts: [],
|
layouts: [],
|
||||||
integrationIds: [],
|
integrationIds: [],
|
||||||
advancedOptions: {
|
advancedOptions: {
|
||||||
|
title: null,
|
||||||
customCssClasses: [],
|
customCssClasses: [],
|
||||||
borderColor: "",
|
borderColor: "",
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Card } from "@mantine/core";
|
import { Badge, Card } from "@mantine/core";
|
||||||
import { useElementSize } from "@mantine/hooks";
|
import { useElementSize } from "@mantine/hooks";
|
||||||
import { QueryErrorResetBoundary } from "@tanstack/react-query";
|
import { QueryErrorResetBoundary } from "@tanstack/react-query";
|
||||||
import combineClasses from "clsx";
|
import combineClasses from "clsx";
|
||||||
@@ -14,6 +14,7 @@ import { WidgetError } from "@homarr/widgets/errors";
|
|||||||
import type { SectionItem } from "~/app/[locale]/boards/_types";
|
import type { SectionItem } from "~/app/[locale]/boards/_types";
|
||||||
import classes from "../sections/item.module.css";
|
import classes from "../sections/item.module.css";
|
||||||
import { useItemActions } from "./item-actions";
|
import { useItemActions } from "./item-actions";
|
||||||
|
import itemContentClasses from "./item-content.module.css";
|
||||||
import { BoardItemMenu } from "./item-menu";
|
import { BoardItemMenu } from "./item-menu";
|
||||||
|
|
||||||
interface BoardItemContentProps {
|
interface BoardItemContentProps {
|
||||||
@@ -25,28 +26,51 @@ export const BoardItemContent = ({ item }: BoardItemContentProps) => {
|
|||||||
const board = useRequiredBoard();
|
const board = useRequiredBoard();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card
|
<>
|
||||||
ref={ref}
|
<Card
|
||||||
className={combineClasses(
|
ref={ref}
|
||||||
classes.itemCard,
|
className={combineClasses(
|
||||||
`${item.kind}-wrapper`,
|
classes.itemCard,
|
||||||
"grid-stack-item-content",
|
`${item.kind}-wrapper`,
|
||||||
item.advancedOptions.customCssClasses.join(" "),
|
"grid-stack-item-content",
|
||||||
|
item.advancedOptions.customCssClasses.join(" "),
|
||||||
|
)}
|
||||||
|
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}
|
||||||
|
>
|
||||||
|
<InnerContent item={item} width={width} height={height} />
|
||||||
|
</Card>
|
||||||
|
{item.advancedOptions.title?.trim() && (
|
||||||
|
<Badge
|
||||||
|
pos="absolute"
|
||||||
|
// It's 4 because of the mantine-react-table that has z-index 3
|
||||||
|
style={{ zIndex: 4 }}
|
||||||
|
top={2}
|
||||||
|
left={16}
|
||||||
|
size="xs"
|
||||||
|
radius={board.itemRadius}
|
||||||
|
styles={{
|
||||||
|
root: {
|
||||||
|
"--border-color": item.advancedOptions.borderColor !== "" ? item.advancedOptions.borderColor : undefined,
|
||||||
|
"--opacity": board.opacity / 100,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
className={itemContentClasses.badge}
|
||||||
|
c="var(--mantine-color-text)"
|
||||||
|
>
|
||||||
|
{item.advancedOptions.title}
|
||||||
|
</Badge>
|
||||||
)}
|
)}
|
||||||
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}
|
|
||||||
>
|
|
||||||
<InnerContent item={item} width={width} height={height} />
|
|
||||||
</Card>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1092,6 +1092,9 @@
|
|||||||
"integrations": {
|
"integrations": {
|
||||||
"label": "Integrations"
|
"label": "Integrations"
|
||||||
},
|
},
|
||||||
|
"title": {
|
||||||
|
"label": "Title"
|
||||||
|
},
|
||||||
"customCssClasses": {
|
"customCssClasses": {
|
||||||
"label": "Custom css classes"
|
"label": "Custom css classes"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ export const integrationSchema = z.object({
|
|||||||
export type BoardItemIntegration = z.infer<typeof integrationSchema>;
|
export type BoardItemIntegration = z.infer<typeof integrationSchema>;
|
||||||
|
|
||||||
export const itemAdvancedOptionsSchema = z.object({
|
export const itemAdvancedOptionsSchema = z.object({
|
||||||
|
title: z.string().max(64).nullable().default(null),
|
||||||
customCssClasses: z.array(z.string()).default([]),
|
customCssClasses: z.array(z.string()).default([]),
|
||||||
borderColor: z.string().default(""),
|
borderColor: z.string().default(""),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"use client";
|
"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 { useForm } from "@homarr/form";
|
||||||
import { createModal } from "@homarr/modals";
|
import { createModal } from "@homarr/modals";
|
||||||
@@ -20,13 +20,23 @@ export const WidgetAdvancedOptionsModal = createModal<InnerProps>(({ actions, in
|
|||||||
initialValues: innerProps.advancedOptions,
|
initialValues: innerProps.advancedOptions,
|
||||||
});
|
});
|
||||||
const handleSubmit = (values: BoardItemAdvancedOptions) => {
|
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();
|
actions.closeModal();
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form onSubmit={form.onSubmit(handleSubmit)}>
|
<form onSubmit={form.onSubmit(handleSubmit)}>
|
||||||
<Stack>
|
<Stack>
|
||||||
|
<TextInput
|
||||||
|
label={t("item.edit.field.title.label")}
|
||||||
|
{...form.getInputProps("title")}
|
||||||
|
rightSection={<Input.ClearButton onClick={() => form.setFieldValue("title", "")} />}
|
||||||
|
/>
|
||||||
<TextMultiSelect
|
<TextMultiSelect
|
||||||
label={t("item.edit.field.customCssClasses.label")}
|
label={t("item.edit.field.customCssClasses.label")}
|
||||||
{...form.getInputProps("customCssClasses")}
|
{...form.getInputProps("customCssClasses")}
|
||||||
|
|||||||
Reference in New Issue
Block a user