From 3d1bb2f0badfe1f19ca7ad4a63b1748302162453 Mon Sep 17 00:00:00 2001 From: Thomas Camlong Date: Thu, 28 May 2026 14:55:15 +0200 Subject: [PATCH] feat(widgets): wire up tabbed app editing in widget edit modal - Add hideButtons/formRef props to AppForm for headless embedding - Add Mantine Tabs to WidgetEditModal for app widgets (permission-gated) - Pass appId from BoardItemMenu to the modal - Add translation keys for tab labels and propagation notice --- .../src/components/board/items/item-menu.tsx | 1 + .../forms-collection/src/new-app/_form.tsx | 78 +++++--- packages/translation/src/lang/en.json | 7 + .../widgets/src/modals/widget-edit-modal.tsx | 174 +++++++++++------- 4 files changed, 167 insertions(+), 93 deletions(-) diff --git a/apps/nextjs/src/components/board/items/item-menu.tsx b/apps/nextjs/src/components/board/items/item-menu.tsx index a7d870a28..b8296c1c3 100644 --- a/apps/nextjs/src/components/board/items/item-menu.tsx +++ b/apps/nextjs/src/components/board/items/item-menu.tsx @@ -85,6 +85,7 @@ const BoardItemMenuInner = ({ offset, item, resetErrorBoundary }: BoardItemMenuP ), integrationSupport: "supportedIntegrations" in currentDefinition, settings, + appId: item.kind === "app" ? (item.options.appId as string | undefined) : undefined, }, { title(t) { diff --git a/packages/forms-collection/src/new-app/_form.tsx b/packages/forms-collection/src/new-app/_form.tsx index 05c7e3f6f..9973ccb61 100644 --- a/packages/forms-collection/src/new-app/_form.tsx +++ b/packages/forms-collection/src/new-app/_form.tsx @@ -1,7 +1,7 @@ "use client"; import type { ChangeEventHandler } from "react"; -import { useEffect, useRef } from "react"; +import { useEffect, useImperativeHandle, useRef } from "react"; import { Button, Checkbox, Collapse, Group, Stack, Textarea, TextInput } from "@mantine/core"; import { useDebouncedValue, useDisclosure } from "@mantine/hooks"; import type { z } from "zod/v4"; @@ -17,6 +17,11 @@ import { findBestIconMatch } from "./icon-matcher"; type FormType = z.infer; +export interface AppFormHandle { + submit: () => void; + isDirty: () => boolean; +} + interface AppFormProps { showBackToOverview: boolean; buttonLabels: { @@ -26,6 +31,8 @@ interface AppFormProps { initialValues?: FormType; handleSubmit: (values: FormType, redirect: boolean, afterSuccess?: () => void) => void; isPending: boolean; + hideButtons?: boolean; + formRef?: React.Ref; } export const AppForm = ({ @@ -34,6 +41,8 @@ export const AppForm = ({ handleSubmit: originalHandleSubmit, initialValues, isPending, + hideButtons, + formRef, }: AppFormProps) => { const t = useI18n(); @@ -62,6 +71,15 @@ export const AppForm = ({ originalHandleSubmit(values, redirect, afterSuccess); }; + useImperativeHandle( + formRef, + () => ({ + submit: () => form.onSubmit(handleSubmit)(), + isDirty: () => form.isDirty(), + }), + [form, handleSubmit], + ); + const [opened, { open, close }] = useDisclosure((initialValues?.pingUrl?.length ?? 0) > 0); const handleClickDifferentUrlPing: ChangeEventHandler = () => { @@ -92,32 +110,32 @@ export const AppForm = ({ } }, [debouncedName, iconsData]); - return ( -
- - - -