mirror of
https://github.com/ajnart/homarr.git
synced 2026-02-26 16:30:57 +01:00
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, {}),
|
||||
integrationIds: [],
|
||||
advancedOptions: {
|
||||
title: null,
|
||||
customCssClasses: [],
|
||||
borderColor: "",
|
||||
},
|
||||
|
||||
@@ -29,6 +29,7 @@ export const createItemCallback =
|
||||
layouts: createItemLayouts(previous, firstSection),
|
||||
integrationIds: [],
|
||||
advancedOptions: {
|
||||
title: null,
|
||||
customCssClasses: [],
|
||||
borderColor: "",
|
||||
},
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -13,6 +13,7 @@ export class ItemMockBuilder {
|
||||
layouts: [],
|
||||
integrationIds: [],
|
||||
advancedOptions: {
|
||||
title: null,
|
||||
customCssClasses: [],
|
||||
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 { 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 (
|
||||
<Card
|
||||
ref={ref}
|
||||
className={combineClasses(
|
||||
classes.itemCard,
|
||||
`${item.kind}-wrapper`,
|
||||
"grid-stack-item-content",
|
||||
item.advancedOptions.customCssClasses.join(" "),
|
||||
<>
|
||||
<Card
|
||||
ref={ref}
|
||||
className={combineClasses(
|
||||
classes.itemCard,
|
||||
`${item.kind}-wrapper`,
|
||||
"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": {
|
||||
"label": "Integrations"
|
||||
},
|
||||
"title": {
|
||||
"label": "Title"
|
||||
},
|
||||
"customCssClasses": {
|
||||
"label": "Custom css classes"
|
||||
},
|
||||
|
||||
@@ -14,6 +14,7 @@ export const integrationSchema = z.object({
|
||||
export type BoardItemIntegration = z.infer<typeof integrationSchema>;
|
||||
|
||||
export const itemAdvancedOptionsSchema = z.object({
|
||||
title: z.string().max(64).nullable().default(null),
|
||||
customCssClasses: z.array(z.string()).default([]),
|
||||
borderColor: z.string().default(""),
|
||||
});
|
||||
|
||||
@@ -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<InnerProps>(({ 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 onSubmit={form.onSubmit(handleSubmit)}>
|
||||
<Stack>
|
||||
<TextInput
|
||||
label={t("item.edit.field.title.label")}
|
||||
{...form.getInputProps("title")}
|
||||
rightSection={<Input.ClearButton onClick={() => form.setFieldValue("title", "")} />}
|
||||
/>
|
||||
<TextMultiSelect
|
||||
label={t("item.edit.field.customCssClasses.label")}
|
||||
{...form.getInputProps("customCssClasses")}
|
||||
|
||||
Reference in New Issue
Block a user