@cosmicdrift/kumiko-document-designer 0.0.0-stage → 0.364.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (126) hide show
  1. package/LICENSE +57 -0
  2. package/README.md +37 -2
  3. package/dist/constants.d.ts +27 -0
  4. package/dist/constants.js +37 -0
  5. package/dist/feature.d.ts +2 -0
  6. package/dist/feature.js +85 -0
  7. package/dist/i18n.d.ts +6 -0
  8. package/dist/i18n.js +764 -0
  9. package/dist/index.d.ts +2 -0
  10. package/dist/index.js +2 -0
  11. package/dist/list-definition.d.ts +6 -0
  12. package/dist/list-definition.js +109 -0
  13. package/dist/web/cells.d.ts +10 -0
  14. package/dist/web/cells.js +87 -0
  15. package/dist/web/client-plugin.d.ts +12 -0
  16. package/dist/web/client-plugin.js +43 -0
  17. package/dist/web/compose-layout.d.ts +6 -0
  18. package/dist/web/compose-layout.js +13 -0
  19. package/dist/web/editor-header.d.ts +37 -0
  20. package/dist/web/editor-header.js +43 -0
  21. package/dist/web/editor-shell.d.ts +40 -0
  22. package/dist/web/editor-shell.js +36 -0
  23. package/dist/web/errors.d.ts +6 -0
  24. package/dist/web/errors.js +24 -0
  25. package/dist/web/field-issue-text.d.ts +4 -0
  26. package/dist/web/field-issue-text.js +12 -0
  27. package/dist/web/file-name.d.ts +2 -0
  28. package/dist/web/file-name.js +17 -0
  29. package/dist/web/i18n.d.ts +2 -0
  30. package/dist/web/i18n.js +146 -0
  31. package/dist/web/index.d.ts +6 -0
  32. package/dist/web/index.js +7 -0
  33. package/dist/web/layout-editor-body.d.ts +14 -0
  34. package/dist/web/layout-editor-body.js +187 -0
  35. package/dist/web/layout-editor.d.ts +2 -0
  36. package/dist/web/layout-editor.js +43 -0
  37. package/dist/web/layout-html.d.ts +9 -0
  38. package/dist/web/layout-html.js +93 -0
  39. package/dist/web/layout-meta.d.ts +29 -0
  40. package/dist/web/layout-meta.js +32 -0
  41. package/dist/web/layout-rails.d.ts +28 -0
  42. package/dist/web/layout-rails.js +100 -0
  43. package/dist/web/layout-types.d.ts +66 -0
  44. package/dist/web/layout-types.js +1 -0
  45. package/dist/web/layout-users.d.ts +4 -0
  46. package/dist/web/layout-users.js +11 -0
  47. package/dist/web/mail-dialogs.d.ts +24 -0
  48. package/dist/web/mail-dialogs.js +15 -0
  49. package/dist/web/mail-draft.d.ts +19 -0
  50. package/dist/web/mail-draft.js +154 -0
  51. package/dist/web/mail-edit-center.d.ts +19 -0
  52. package/dist/web/mail-edit-center.js +12 -0
  53. package/dist/web/mail-editor-body.d.ts +17 -0
  54. package/dist/web/mail-editor-body.js +209 -0
  55. package/dist/web/mail-editor.d.ts +2 -0
  56. package/dist/web/mail-editor.js +68 -0
  57. package/dist/web/mail-footer.d.ts +10 -0
  58. package/dist/web/mail-footer.js +43 -0
  59. package/dist/web/mail-left-rail.d.ts +23 -0
  60. package/dist/web/mail-left-rail.js +56 -0
  61. package/dist/web/mail-missing-locale.d.ts +10 -0
  62. package/dist/web/mail-missing-locale.js +45 -0
  63. package/dist/web/mail-origin-bar.d.ts +15 -0
  64. package/dist/web/mail-origin-bar.js +60 -0
  65. package/dist/web/mail-pdf-link-modal.d.ts +11 -0
  66. package/dist/web/mail-pdf-link-modal.js +54 -0
  67. package/dist/web/mail-preview-side.d.ts +19 -0
  68. package/dist/web/mail-preview-side.js +57 -0
  69. package/dist/web/mail-preview.d.ts +24 -0
  70. package/dist/web/mail-preview.js +54 -0
  71. package/dist/web/mail-right-rail.d.ts +19 -0
  72. package/dist/web/mail-right-rail.js +57 -0
  73. package/dist/web/mail-types.d.ts +117 -0
  74. package/dist/web/mail-types.js +1 -0
  75. package/dist/web/mail-upload-modal.d.ts +16 -0
  76. package/dist/web/mail-upload-modal.js +56 -0
  77. package/dist/web/mail-versions.d.ts +6 -0
  78. package/dist/web/mail-versions.js +43 -0
  79. package/dist/web/origin-bar.d.ts +22 -0
  80. package/dist/web/origin-bar.js +104 -0
  81. package/dist/web/overrides-drawer.d.ts +10 -0
  82. package/dist/web/overrides-drawer.js +95 -0
  83. package/dist/web/overview-row.d.ts +23 -0
  84. package/dist/web/overview-row.js +48 -0
  85. package/dist/web/page-canvas.d.ts +19 -0
  86. package/dist/web/page-canvas.js +48 -0
  87. package/dist/web/pdf-footer.d.ts +13 -0
  88. package/dist/web/pdf-footer.js +34 -0
  89. package/dist/web/pdf-left-rail.d.ts +9 -0
  90. package/dist/web/pdf-left-rail.js +16 -0
  91. package/dist/web/pdf-meta.d.ts +21 -0
  92. package/dist/web/pdf-meta.js +95 -0
  93. package/dist/web/pdf-preview.d.ts +4 -0
  94. package/dist/web/pdf-preview.js +112 -0
  95. package/dist/web/pdf-template-editor.d.ts +8 -0
  96. package/dist/web/pdf-template-editor.js +340 -0
  97. package/dist/web/preview-record-picker.d.ts +12 -0
  98. package/dist/web/preview-record-picker.js +7 -0
  99. package/dist/web/rail.d.ts +30 -0
  100. package/dist/web/rail.js +22 -0
  101. package/dist/web/relative-time.d.ts +1 -0
  102. package/dist/web/relative-time.js +18 -0
  103. package/dist/web/settings-panel.d.ts +23 -0
  104. package/dist/web/settings-panel.js +36 -0
  105. package/dist/web/status-label.d.ts +14 -0
  106. package/dist/web/status-label.js +26 -0
  107. package/dist/web/template-rows.d.ts +9 -0
  108. package/dist/web/template-rows.js +34 -0
  109. package/dist/web/types.d.ts +126 -0
  110. package/dist/web/types.js +3 -0
  111. package/dist/web/use-insert-targets.d.ts +9 -0
  112. package/dist/web/use-insert-targets.js +17 -0
  113. package/dist/web/use-mail-persistence.d.ts +14 -0
  114. package/dist/web/use-mail-persistence.js +49 -0
  115. package/dist/web/use-overview-rows.d.ts +7 -0
  116. package/dist/web/use-overview-rows.js +19 -0
  117. package/dist/web/use-own-address.d.ts +5 -0
  118. package/dist/web/use-own-address.js +14 -0
  119. package/dist/web/use-settled-value.d.ts +2 -0
  120. package/dist/web/use-settled-value.js +18 -0
  121. package/dist/web/use-test-mail.d.ts +15 -0
  122. package/dist/web/use-test-mail.js +27 -0
  123. package/dist/web/used-by-label.d.ts +9 -0
  124. package/dist/web/used-by-label.js +32 -0
  125. package/package.json +63 -4
  126. package/src/changes.json +44 -0
@@ -0,0 +1,19 @@
1
+ import { type ReactNode } from "react";
2
+ import type { MailPreview, MailValidation } from "./mail-types.js";
3
+ import type { OwnAddress } from "./use-own-address.js";
4
+ export type TestMailControls = {
5
+ readonly pending: boolean;
6
+ readonly onSend: () => void;
7
+ readonly includeAttachments: boolean;
8
+ readonly onIncludeAttachments: (next: boolean) => void;
9
+ readonly prefixSubject: boolean;
10
+ readonly onPrefixSubject: (next: boolean) => void;
11
+ };
12
+ export declare function PreviewSide({ preview, validation, slug, locale, own, testMail, }: {
13
+ readonly preview: MailPreview | null;
14
+ readonly validation: MailValidation | null;
15
+ readonly slug: string;
16
+ readonly locale: string;
17
+ readonly own: OwnAddress;
18
+ readonly testMail: TestMailControls;
19
+ }): ReactNode;
@@ -0,0 +1,57 @@
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ // @runtime client
3
+ import { useNav, usePrimitives, useTranslation } from "@cosmicdrift/kumiko-renderer";
4
+ import { useState } from "react";
5
+ import { displayValue } from "./mail-draft.js";
6
+ import { usePreviewRecordPicker } from "./preview-record-picker.js";
7
+ import { RailField, RailHint, RailRow, RailSection } from "./rail.js";
8
+ const noop = () => undefined;
9
+ // Check params list names as arrays; the message templates expect one string.
10
+ function checkText(t, check) {
11
+ const params = Object.fromEntries(Object.entries(check.params ?? {}).map(([name, value]) => [
12
+ name,
13
+ Array.isArray(value) ? value.join(", ") : value,
14
+ ]));
15
+ const translated = t(check.i18nKey, params);
16
+ return translated === check.i18nKey ? check.code : translated;
17
+ }
18
+ function SampleValues({ preview, validation, picked, }) {
19
+ const t = useTranslation();
20
+ const values = { ...(preview?.sampleValues ?? {}), ...(picked ?? {}) };
21
+ const names = Object.keys(validation?.variables ?? values);
22
+ return (_jsx(_Fragment, { children: names.map((name) => {
23
+ const missing = validation?.missingRequired.includes(name) === true;
24
+ return (_jsx(RailRow, { testId: `mail-sample-${name}`, label: validation?.variables[name]?.label ?? name, bad: missing, trailing: missing
25
+ ? t("documentDesigner.mail.preview.missingInContent")
26
+ : displayValue(t, values[name]) }, name));
27
+ }) }));
28
+ }
29
+ function RecordPickerLink({ slug, locale, onPick, }) {
30
+ const picker = usePreviewRecordPicker();
31
+ const { Button, Modal } = usePrimitives();
32
+ const [open, setOpen] = useState(false);
33
+ if (picker === null)
34
+ return null;
35
+ return (_jsxs(_Fragment, { children: [_jsx(Button, { variant: "link", onClick: () => setOpen(true), testId: "mail-record-picker-open", children: picker.label }), _jsx(Modal, { open: open, onOpenChange: setOpen, title: picker.label, testId: "mail-record-picker-modal", children: picker.renderPicker({
36
+ slug,
37
+ locale,
38
+ onPick: (values) => {
39
+ onPick(values);
40
+ setOpen(false);
41
+ },
42
+ }) })] }));
43
+ }
44
+ function TestMailRecipient({ own }) {
45
+ const t = useTranslation();
46
+ const nav = useNav();
47
+ const { Field, Input, Link } = usePrimitives();
48
+ const { profileScreenId } = own;
49
+ return (_jsxs(RailField, { children: [_jsx(Field, { id: "mail-test-recipient", label: t("documentDesigner.mail.testMail.recipient"), children: _jsx(Input, { kind: "text", id: "mail-test-recipient", name: "mail-test-recipient", testId: "mail-test-recipient", value: own.email ?? t("documentDesigner.mail.testMail.ownAddress"), onChange: noop, disabled: true }) }), profileScreenId !== null && (_jsx(Link, { href: nav.hrefFor({ screenId: profileScreenId }), onPress: () => nav.navigate({ screenId: profileScreenId }), testId: "mail-test-recipient-edit", children: t("documentDesigner.mail.testMail.changeAddress") }))] }));
50
+ }
51
+ export function PreviewSide({ preview, validation, slug, locale, own, testMail, }) {
52
+ const t = useTranslation();
53
+ const { Button, Field, Input, Text } = usePrimitives();
54
+ const hasAttachments = preview?.attachments.some((a) => a.kind !== "inline") === true;
55
+ const [picked, setPicked] = useState(null);
56
+ return (_jsxs("div", { "data-testid": "mail-preview-side", children: [_jsxs(RailSection, { title: t("documentDesigner.mail.preview.samples"), action: _jsx(RecordPickerLink, { slug: slug, locale: locale, onPick: setPicked }), children: [_jsx(SampleValues, { preview: preview, validation: validation, picked: picked }), _jsx(RailHint, { children: t("documentDesigner.mail.preview.samplesHint") })] }), _jsxs(RailSection, { title: t("documentDesigner.mail.testMail.title"), children: [_jsx(TestMailRecipient, { own: own }), _jsxs(RailField, { children: [_jsx(Field, { id: "mail-test-prefix", label: t("documentDesigner.mail.testMail.prefixSubject"), layout: "inline", children: _jsx(Input, { kind: "boolean", id: "mail-test-prefix", name: "mail-test-prefix", value: testMail.prefixSubject, onChange: testMail.onPrefixSubject }) }), hasAttachments ? (_jsx(Field, { id: "mail-test-attachments", label: t("documentDesigner.mail.testMail.attachments"), layout: "inline", children: _jsx(Input, { kind: "boolean", id: "mail-test-attachments", name: "mail-test-attachments", value: testMail.includeAttachments, onChange: testMail.onIncludeAttachments }) })) : (_jsx(Text, { variant: "muted", testId: "mail-test-no-attachments", children: t("documentDesigner.mail.testMail.noAttachments") })), _jsx(Button, { variant: "secondary", width: "full", disabled: testMail.pending, loading: testMail.pending, onClick: testMail.onSend, testId: "mail-test-send", children: t("documentDesigner.mail.testMail.send") })] }), _jsx(RailHint, { children: t("documentDesigner.mail.testMail.hint") })] }), _jsxs(RailSection, { title: t("documentDesigner.mail.preview.checks"), testId: "mail-preview-checks", children: [(preview?.checks ?? []).map((check) => (_jsx(RailRow, { testId: `mail-check-${check.code}`, symbol: check.ok ? "✓" : "!", label: checkText(t, check), bad: !check.ok }, check.code))), _jsx(RailHint, { children: t("documentDesigner.mail.preview.checksHint") })] })] }));
57
+ }
@@ -0,0 +1,24 @@
1
+ import { type ReactNode } from "react";
2
+ import type { EditorOrigin } from "./editor-header.js";
3
+ import { type TestMailControls } from "./mail-preview-side.js";
4
+ import type { MailValidation } from "./mail-types.js";
5
+ import type { OwnAddress } from "./use-own-address.js";
6
+ export type PreviewPayload = {
7
+ readonly slug: string;
8
+ readonly kind: "mail";
9
+ readonly locale: string;
10
+ readonly content: string;
11
+ readonly meta: Readonly<Record<string, unknown>>;
12
+ readonly variableSchema: unknown;
13
+ };
14
+ export declare function MailPreviewMode({ payload, validation, slug, locale, fromName, own, controls, origin, testMail, }: {
15
+ readonly payload: PreviewPayload;
16
+ readonly validation: MailValidation | null;
17
+ readonly slug: string;
18
+ readonly locale: string;
19
+ readonly fromName: string;
20
+ readonly own: OwnAddress;
21
+ readonly controls: ReactNode;
22
+ readonly origin: EditorOrigin | null;
23
+ readonly testMail: TestMailControls;
24
+ }): ReactNode;
@@ -0,0 +1,54 @@
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ // @runtime client
3
+ import { TemplateResolverQueries } from "@cosmicdrift/kumiko-bundled-features/template-resolver/web";
4
+ import { dispatcherErrorText, usePrimitives, useQuery, useTranslation, } from "@cosmicdrift/kumiko-renderer";
5
+ import { LoadingState, ModeSwitch } from "@cosmicdrift/kumiko-renderer-web";
6
+ import { useState } from "react";
7
+ import { EditorBody, EditorInfoBar } from "./editor-shell.js";
8
+ import { formatKilobytes } from "./mail-draft.js";
9
+ import { PreviewSide } from "./mail-preview-side.js";
10
+ function attachmentText(t, attachment) {
11
+ switch (attachment.kind) {
12
+ case "inline":
13
+ return null;
14
+ case "file":
15
+ return `⎘ ${attachment.name} · ${formatKilobytes(attachment.sizeBytes)}`;
16
+ case "rendered-pdf":
17
+ return `⎘ ${attachment.name} · ${t("documentDesigner.mail.preview.generatedOnSend")}`;
18
+ }
19
+ }
20
+ function HeadRow({ label, children, }) {
21
+ return (_jsxs(_Fragment, { children: [_jsx("span", { className: "text-muted-foreground", children: label }), _jsx("span", { className: "min-w-0", children: children })] }));
22
+ }
23
+ function MailFrame({ preview, format, width, fromName, recipient, }) {
24
+ const t = useTranslation();
25
+ const attachments = preview.attachments.flatMap((a) => attachmentText(t, a) ?? []);
26
+ return (_jsxs("div", { className: "mx-auto flex w-full max-w-[680px] flex-col border bg-card shadow-md", "data-testid": "mail-preview-frame", children: [_jsxs("div", { className: "grid grid-cols-[80px_minmax(0,1fr)] gap-y-1 border-b px-5 py-3.5 text-sm", children: [_jsx(HeadRow, { label: t("documentDesigner.mail.preview.from"), children: _jsx("span", { "data-testid": "mail-preview-from", children: fromName === "" ? t("documentDesigner.mail.props.senderDefault") : fromName }) }), recipient !== null && (_jsx(HeadRow, { label: t("documentDesigner.mail.preview.to"), children: _jsx("span", { "data-testid": "mail-preview-to", children: recipient }) })), _jsx(HeadRow, { label: t("documentDesigner.mail.meta.subject"), children: _jsx("span", { className: "font-medium", "data-testid": "mail-preview-subject", children: preview.subject ?? "" }) }), attachments.length > 0 && (_jsx(HeadRow, { label: t("documentDesigner.mail.preview.attachments"), children: _jsx("span", { className: "flex flex-col items-start gap-1", "data-testid": "mail-preview-attachments", children: attachments.map((line) => (_jsx("span", { className: "rounded border bg-muted px-2 py-0.5 text-xs", children: line }, line))) }) }))] }), format === "html" ? (_jsx("iframe", { title: t("documentDesigner.mail.preview.frameTitle"), sandbox: "", srcDoc: preview.displayHtml ?? preview.html, className: `h-[560px] border-0 bg-background ${width === "mobile" ? "w-[375px] self-center" : "w-full"}`, "data-testid": "mail-preview-iframe" })) : (_jsx("pre", { className: "h-[560px] overflow-auto whitespace-pre-wrap p-4 font-mono text-sm", "data-testid": "mail-preview-text", children: preview.text ?? "" }))] }));
27
+ }
28
+ // The preview mail goes to the example recipient of the sample values, not to the test-mail address.
29
+ function recipientOf(preview, own) {
30
+ const { recipientName: name, recipientEmail: email } = preview.sampleValues;
31
+ if (typeof name !== "string" || name === "")
32
+ return own.email;
33
+ return typeof email === "string" && email !== "" ? `${name} <${email}>` : name;
34
+ }
35
+ function layoutText(t, preview) {
36
+ return preview?.mailLayout == null
37
+ ? t("documentDesigner.mail.preview.barPlain")
38
+ : t("documentDesigner.mail.preview.bar", { version: preview.mailLayout.version });
39
+ }
40
+ export function MailPreviewMode({ payload, validation, slug, locale, fromName, own, controls, origin, testMail, }) {
41
+ const t = useTranslation();
42
+ const { Banner } = usePrimitives();
43
+ const [format, setFormat] = useState("html");
44
+ const [width, setWidth] = useState("desktop");
45
+ const query = useQuery(TemplateResolverQueries.preview, payload);
46
+ const preview = query.data;
47
+ return (_jsxs(_Fragment, { children: [_jsx(EditorInfoBar, { tone: "muted", testId: "mail-preview-bar", origin: origin, controls: controls, links: _jsxs(_Fragment, { children: [_jsx(ModeSwitch, { value: format, onChange: setFormat, ariaLabel: t("documentDesigner.mail.preview.formatSwitch"), testId: "mail-preview-format", options: [
48
+ { value: "html", label: t("documentDesigner.mail.preview.html") },
49
+ { value: "text", label: t("documentDesigner.mail.preview.text") },
50
+ ] }), _jsx(ModeSwitch, { value: width, onChange: setWidth, ariaLabel: t("documentDesigner.mail.preview.widthSwitch"), testId: "mail-preview-width", options: [
51
+ { value: "desktop", label: t("documentDesigner.mail.preview.desktop") },
52
+ { value: "mobile", label: t("documentDesigner.mail.preview.mobile") },
53
+ ] })] }), children: layoutText(t, preview) }), _jsx(EditorBody, { testId: "mail-editor-preview", center: _jsxs("div", { className: "flex min-h-0 flex-1 flex-col gap-3 overflow-auto px-8 py-6", children: [query.error !== null && (_jsx(Banner, { variant: "error", testId: "mail-preview-error", children: dispatcherErrorText(query.error, t) })), preview === null ? (query.error === null && _jsx(LoadingState, {})) : (_jsx(MailFrame, { preview: preview, format: format, width: width, fromName: fromName, recipient: recipientOf(preview, own) }))] }), right: _jsx(PreviewSide, { preview: preview, validation: validation, slug: slug, locale: locale, own: own, testMail: testMail }) })] }));
54
+ }
@@ -0,0 +1,19 @@
1
+ import type { ReactNode } from "react";
2
+ import type { LinkedResourceView, MailDraft } from "./mail-types.js";
3
+ import type { OverviewRowView } from "./overview-row.js";
4
+ export declare function layoutOptionLabel(t: (key: string, params?: Readonly<Record<string, unknown>>) => string, layout: OverviewRowView): string;
5
+ export declare function AttachmentSection({ attachments, pdfTitles, canEdit, onAddPdf, onRemove, }: {
6
+ readonly attachments: readonly LinkedResourceView[];
7
+ readonly pdfTitles: ReadonlyMap<string, string>;
8
+ readonly canEdit: boolean;
9
+ readonly onAddPdf: () => void;
10
+ readonly onRemove: (key: string) => void;
11
+ }): ReactNode;
12
+ export declare function PropertiesSection({ draft, layouts, onChange, }: {
13
+ readonly draft: MailDraft;
14
+ readonly layouts: readonly OverviewRowView[];
15
+ readonly onChange: (patch: Partial<MailDraft>) => void;
16
+ }): ReactNode;
17
+ export declare function UsageSection({ slug }: {
18
+ readonly slug: string;
19
+ }): ReactNode;
@@ -0,0 +1,57 @@
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ // @runtime client
3
+ import { TemplateResolverQueries } from "@cosmicdrift/kumiko-bundled-features/template-resolver/web";
4
+ import { usePrimitives, useQuery, useTranslation } from "@cosmicdrift/kumiko-renderer";
5
+ import { DEFAULT_LAYOUT_SLUG } from "../constants.js";
6
+ import { RailField, RailHint, RailRow, RailSection } from "./rail.js";
7
+ import { usageParts } from "./used-by-label.js";
8
+ export function layoutOptionLabel(t, layout) {
9
+ // A layout has no registry title; the slug would read like an id, so name it by kind.
10
+ const title = layout.title === layout.slug
11
+ ? t(`documentDesigner.editor.title.${layout.kind === "mail-layout" ? "mail" : "pdf"}`)
12
+ : layout.title;
13
+ return t(layout.origin === "tenant"
14
+ ? "documentDesigner.mail.props.layoutTenant"
15
+ : "documentDesigner.mail.props.layoutSystem", { title, version: layout.version });
16
+ }
17
+ function UsageRows({ slug }) {
18
+ const t = useTranslation();
19
+ const { data } = useQuery(TemplateResolverQueries.usage, {
20
+ slug,
21
+ kind: "mail",
22
+ });
23
+ return (_jsx(_Fragment, { children: (data?.usedBy ?? []).map((usage) => {
24
+ const parts = usageParts(t, usage);
25
+ return (_jsx(RailRow, { label: t("documentDesigner.mail.usage.line", parts), testId: "mail-usage-row" }, `${usage.feature}:${usage.notification}`));
26
+ }) }));
27
+ }
28
+ export function AttachmentSection({ attachments, pdfTitles, canEdit, onAddPdf, onRemove, }) {
29
+ const t = useTranslation();
30
+ const { Button } = usePrimitives();
31
+ return (_jsxs(RailSection, { title: t("documentDesigner.mail.attachments.title"), testId: "mail-editor-attachments", action: _jsx(Button, { variant: "link", disabled: !canEdit, onClick: onAddPdf, testId: "mail-attachment-pdf-open", children: t("documentDesigner.mail.attachments.addPdf") }), children: [attachments.length === 0 && _jsx(RailRow, { label: t("documentDesigner.mail.attachments.none") }), attachments.map((attachment) => (_jsx(RailRow, { testId: "mail-attachment-row", symbol: "\u2398", label: attachment.kind === "pdf"
32
+ ? t("documentDesigner.mail.attachments.pdfRow", {
33
+ title: pdfTitles.get(attachment.templateSlug) ?? attachment.templateSlug,
34
+ })
35
+ : attachment.kind === "file"
36
+ ? attachment.fileName
37
+ : attachment.key, trailing: attachment.kind === "pdf"
38
+ ? attachment.fileName
39
+ : t("documentDesigner.mail.attachments.file"), action: _jsx(Button, { variant: "danger-ghost", size: "sm", onClick: () => onRemove(attachment.key), testId: `mail-attachment-remove-${attachment.key}`, children: t("documentDesigner.mail.resource.remove") }) }, attachment.key))), _jsx(RailHint, { children: t("documentDesigner.mail.attachments.hint") })] }));
40
+ }
41
+ export function PropertiesSection({ draft, layouts, onChange, }) {
42
+ const t = useTranslation();
43
+ const { Field, Input } = usePrimitives();
44
+ const layoutValue = draft.layoutSlug === "" ? DEFAULT_LAYOUT_SLUG : draft.layoutSlug;
45
+ const categoryValue = draft.category === "" ? "transactional" : draft.category;
46
+ return (_jsxs(RailSection, { title: t("documentDesigner.mail.props.title"), testId: "mail-editor-properties", children: [_jsx(RailField, { children: _jsx(Field, { id: "mail-layout", label: t("documentDesigner.mail.props.layout"), children: _jsx(Input, { kind: "select", id: "mail-layout", name: "mail-layout", value: layoutValue, display: "dropdown", onChange: (next) => onChange({ layoutSlug: next }), options: layouts.map((layout) => ({
47
+ value: layout.slug,
48
+ label: layoutOptionLabel(t, layout),
49
+ })) }) }) }), _jsx(RailField, { children: _jsx(Field, { id: "mail-sender", label: t("documentDesigner.mail.props.sender"), children: _jsx(Input, { kind: "text", id: "mail-sender", name: "mail-sender", testId: "mail-sender", value: draft.senderName, onChange: (next) => onChange({ senderName: next }), placeholder: t("documentDesigner.mail.props.senderPlaceholder") }) }) }), _jsx(RailHint, { children: t("documentDesigner.mail.props.senderHint") }), _jsx(RailField, { children: _jsx(Field, { id: "mail-reply-to", label: t("documentDesigner.mail.props.replyTo"), children: _jsx(Input, { kind: "email", id: "mail-reply-to", name: "mail-reply-to", testId: "mail-reply-to", value: draft.replyTo, onChange: (next) => onChange({ replyTo: next }) }) }) }), _jsx(RailField, { children: _jsx(Field, { id: "mail-category", label: t("documentDesigner.mail.props.category"), children: _jsx(Input, { kind: "select", id: "mail-category", name: "mail-category", value: categoryValue, display: "dropdown", onChange: (next) => onChange({ category: next === "marketing" ? "marketing" : "transactional" }), options: [
50
+ { value: "transactional", label: t("documentDesigner.mail.props.transactional") },
51
+ { value: "marketing", label: t("documentDesigner.mail.props.marketing") },
52
+ ] }) }) }), _jsx(RailHint, { children: t("documentDesigner.mail.props.categoryHint") })] }));
53
+ }
54
+ export function UsageSection({ slug }) {
55
+ const t = useTranslation();
56
+ return (_jsxs(RailSection, { title: t("documentDesigner.mail.usage.title"), testId: "mail-editor-usage", children: [_jsx(UsageRows, { slug: slug }), _jsx(RailHint, { children: t("documentDesigner.mail.usage.hint") })] }));
57
+ }
@@ -0,0 +1,117 @@
1
+ import type { ValidationIssue } from "./layout-types.js";
2
+ import type { ServedVersionView } from "./types.js";
3
+ export type PlaceholderType = "text" | "money" | "date" | "link" | "list";
4
+ export type MailVariable = {
5
+ readonly type: PlaceholderType;
6
+ readonly label?: string;
7
+ readonly required?: boolean;
8
+ readonly example?: unknown;
9
+ readonly item?: Readonly<Record<string, {
10
+ readonly type: PlaceholderType;
11
+ readonly label?: string;
12
+ }>>;
13
+ };
14
+ export type MailWarning = {
15
+ readonly code: string;
16
+ readonly detail: string;
17
+ readonly i18nKey: string;
18
+ };
19
+ export type MailValidation = {
20
+ readonly canSave: boolean;
21
+ readonly canPublish: boolean;
22
+ readonly issues: readonly ValidationIssue[];
23
+ readonly publishIssues: readonly ValidationIssue[];
24
+ readonly used: readonly string[];
25
+ readonly unknown: readonly string[];
26
+ readonly missingRequired: readonly string[];
27
+ readonly warnings: readonly MailWarning[];
28
+ readonly variables: Readonly<Record<string, MailVariable>>;
29
+ readonly systemPlaceholders: readonly string[];
30
+ };
31
+ export type PreviewAttachment = {
32
+ readonly kind: "inline";
33
+ readonly name: string;
34
+ readonly sizeBytes: number;
35
+ } | {
36
+ readonly kind: "file";
37
+ readonly name: string;
38
+ readonly sizeBytes: number;
39
+ } | {
40
+ readonly kind: "rendered-pdf";
41
+ readonly name: string;
42
+ readonly templateSlug: string;
43
+ };
44
+ export type PreviewCheck = {
45
+ readonly code: string;
46
+ readonly ok: boolean;
47
+ readonly i18nKey: string;
48
+ readonly params?: Readonly<Record<string, unknown>>;
49
+ };
50
+ export type MailPreview = {
51
+ readonly subject: string | null;
52
+ readonly html: string;
53
+ readonly displayHtml?: string;
54
+ readonly text: string | null;
55
+ readonly attachments: readonly PreviewAttachment[];
56
+ readonly mailLayout?: {
57
+ readonly slug: string;
58
+ readonly scope: string;
59
+ readonly version: number;
60
+ } | null;
61
+ readonly sampleValues: Readonly<Record<string, unknown>>;
62
+ readonly checks: readonly PreviewCheck[];
63
+ };
64
+ export type TemplateRowSummary = {
65
+ readonly id: string;
66
+ readonly slug: string;
67
+ readonly kind: string;
68
+ readonly locale: string;
69
+ readonly scope: "tenant" | "system";
70
+ readonly status: string;
71
+ readonly modifiedAt: string;
72
+ };
73
+ export type TemplateRowView = {
74
+ readonly id: string;
75
+ readonly version: number;
76
+ readonly slug: string;
77
+ readonly locale: string;
78
+ readonly scope: "tenant" | "system";
79
+ readonly status: string;
80
+ readonly content: string;
81
+ readonly variableSchema: unknown;
82
+ readonly linkedResources: unknown;
83
+ readonly meta: unknown;
84
+ readonly modifiedAt: string | null;
85
+ readonly active?: ServedVersionView | null;
86
+ };
87
+ export type MailCategory = "" | "transactional" | "marketing";
88
+ export type MailDraft = {
89
+ readonly subject: string;
90
+ readonly previewText: string;
91
+ readonly layoutSlug: string;
92
+ readonly category: MailCategory;
93
+ readonly senderName: string;
94
+ readonly replyTo: string;
95
+ readonly body: string;
96
+ };
97
+ export type LinkedResourceView = {
98
+ readonly kind: "image";
99
+ readonly key: string;
100
+ readonly fileRefId: string;
101
+ } | {
102
+ readonly kind: "file";
103
+ readonly key: string;
104
+ readonly fileRefId: string;
105
+ readonly fileName: string;
106
+ } | {
107
+ readonly kind: "pdf";
108
+ readonly key: string;
109
+ readonly templateSlug: string;
110
+ readonly fileName: string;
111
+ };
112
+ export type UpsertResponse = {
113
+ readonly id: string;
114
+ };
115
+ export type SendTestResponse = {
116
+ readonly subject: string;
117
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,16 @@
1
+ import { type ReactNode } from "react";
2
+ export type UploadedResource = {
3
+ readonly key: string;
4
+ readonly purpose: "image" | "attachment";
5
+ };
6
+ type UploadTarget = {
7
+ readonly templateId: string;
8
+ readonly takenKeys: readonly string[];
9
+ readonly onUploaded: (uploaded: UploadedResource) => void;
10
+ };
11
+ export declare function UploadModal({ open, onOpenChange, target, }: {
12
+ readonly open: boolean;
13
+ readonly onOpenChange: (open: boolean) => void;
14
+ readonly target: UploadTarget;
15
+ }): ReactNode;
16
+ export {};
@@ -0,0 +1,56 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ // @runtime client
3
+ import { TemplateResolverHandlers } from "@cosmicdrift/kumiko-bundled-features/template-resolver/web";
4
+ import { dispatcherErrorText, useMutation, usePrimitives, useTranslation, } from "@cosmicdrift/kumiko-renderer";
5
+ import { useState } from "react";
6
+ import { fetchStoredFileName } from "./file-name.js";
7
+ import { uniqueResourceKey } from "./mail-draft.js";
8
+ const IMAGE_ACCEPT = ["image/png", "image/jpeg", "image/gif", "image/webp"];
9
+ function useResourceUpload(target) {
10
+ const t = useTranslation();
11
+ const resourceUpload = useMutation(TemplateResolverHandlers.resourceUpload);
12
+ const [error, setError] = useState(null);
13
+ const [busy, setBusy] = useState(false);
14
+ const upload = async (fileRefId, purpose) => {
15
+ if (fileRefId === null)
16
+ return;
17
+ setError(null);
18
+ setBusy(true);
19
+ try {
20
+ const fileName = await fetchStoredFileName(fileRefId);
21
+ if (fileName === null) {
22
+ setError(t("documentDesigner.mail.upload.noFileName"));
23
+ return;
24
+ }
25
+ const key = uniqueResourceKey(fileName, target.takenKeys);
26
+ const result = await resourceUpload.mutate({
27
+ sourceFileRefId: fileRefId,
28
+ templateId: target.templateId,
29
+ key,
30
+ purpose,
31
+ ...(purpose === "attachment" && { fileName }),
32
+ });
33
+ if (!result.isSuccess) {
34
+ setError(dispatcherErrorText(result.error, t));
35
+ return;
36
+ }
37
+ target.onUploaded({ key, purpose });
38
+ }
39
+ finally {
40
+ setBusy(false);
41
+ }
42
+ };
43
+ return { upload, error, busy };
44
+ }
45
+ export function UploadModal({ open, onOpenChange, target, }) {
46
+ const t = useTranslation();
47
+ const { Banner, Field, Input, Modal, Text } = usePrimitives();
48
+ const { upload, error, busy } = useResourceUpload({
49
+ ...target,
50
+ onUploaded: (uploaded) => {
51
+ onOpenChange(false);
52
+ target.onUploaded(uploaded);
53
+ },
54
+ });
55
+ return (_jsx(Modal, { open: open, onOpenChange: onOpenChange, title: t("documentDesigner.mail.upload.title"), testId: "mail-upload-modal", children: _jsxs("div", { className: "flex flex-col gap-4", children: [error !== null && (_jsx(Banner, { variant: "error", testId: "mail-upload-error", children: error })), busy && _jsx(Text, { variant: "muted", children: t("documentDesigner.mail.upload.busy") }), _jsx(Field, { id: "mail-upload-image", label: t("documentDesigner.mail.upload.image"), description: t("documentDesigner.mail.upload.imageHint"), children: _jsx(Input, { kind: "file", id: "mail-upload-image", name: "mail-upload-image", value: null, accept: IMAGE_ACCEPT, disabled: busy, onChange: (fileId) => void upload(fileId, "image") }) }), _jsx(Field, { id: "mail-upload-attachment", label: t("documentDesigner.mail.upload.attachment"), description: t("documentDesigner.mail.upload.attachmentHint"), children: _jsx(Input, { kind: "file", id: "mail-upload-attachment", name: "mail-upload-attachment", value: null, disabled: busy, onChange: (fileId) => void upload(fileId, "attachment") }) })] }) }));
56
+ }
@@ -0,0 +1,6 @@
1
+ import type { ReactNode } from "react";
2
+ export declare function VersionsSection({ rowId, currentVersion, currentStatus, }: {
3
+ readonly rowId: string | null;
4
+ readonly currentVersion: number;
5
+ readonly currentStatus: string;
6
+ }): ReactNode;
@@ -0,0 +1,43 @@
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ // @runtime client
3
+ import { formatWhen, useAppFeatures, useQuery, useTranslation } from "@cosmicdrift/kumiko-renderer";
4
+ import { StatusBadge } from "@cosmicdrift/kumiko-renderer-web";
5
+ import { RailHint, RailRow, RailSection } from "./rail.js";
6
+ import { asRowStatus, statusLabel, statusTone } from "./status-label.js";
7
+ const AUDIT_FEATURE = "audit";
8
+ const AUDIT_LIST_QUERY = "audit:query:list";
9
+ const MEMBER_DIRECTORY_QUERY = "tenant:query:member-directory";
10
+ const VERSION_LIMIT = 20;
11
+ function VersionRows({ rowId, currentVersion, currentStatus, }) {
12
+ const t = useTranslation();
13
+ const audit = useQuery(AUDIT_LIST_QUERY, {
14
+ aggregateType: "template-resource",
15
+ aggregateId: rowId,
16
+ limit: VERSION_LIMIT,
17
+ });
18
+ const members = useQuery(MEMBER_DIRECTORY_QUERY, { limit: 200 });
19
+ const names = new Map((members.data?.rows ?? []).map((member) => [member.id, member.label]));
20
+ const status = asRowStatus(currentStatus);
21
+ // The event log has one entry per change; show each version once, newest first.
22
+ const seen = new Set();
23
+ const versions = (audit.data?.rows ?? []).filter((row) => {
24
+ if (seen.has(row.version))
25
+ return false;
26
+ seen.add(row.version);
27
+ return true;
28
+ });
29
+ return (_jsx(_Fragment, { children: versions.map((row) => (_jsx(RailRow, { testId: `mail-version-${row.version}`, label: t("documentDesigner.mail.versions.line", {
30
+ version: row.version,
31
+ when: formatWhen(row.createdAt),
32
+ name: names.get(row.createdBy) ?? "–",
33
+ }), trailing: row.version === currentVersion ? (_jsx(StatusBadge, { tone: statusTone(status), children: statusLabel(t, status, row.version) })) : undefined }, row.version))) }));
34
+ }
35
+ // Hidden when the audit feature is not mounted, so no failing query shows up as an error.
36
+ export function VersionsSection({ rowId, currentVersion, currentStatus, }) {
37
+ const t = useTranslation();
38
+ const features = useAppFeatures();
39
+ if (rowId === null || !features.some((feature) => feature.featureName === AUDIT_FEATURE)) {
40
+ return null;
41
+ }
42
+ return (_jsxs(RailSection, { title: t("documentDesigner.mail.versions.title"), testId: "mail-editor-versions", children: [_jsx(VersionRows, { rowId: rowId, currentVersion: currentVersion, currentStatus: currentStatus }), _jsx(RailHint, { children: t("documentDesigner.mail.versions.hint") })] }));
43
+ }
@@ -0,0 +1,22 @@
1
+ import { type ReactNode } from "react";
2
+ import type { LayoutKind, LayoutSourceView, TemplateDiff, TenantOverrideCounts } from "./layout-types.js";
3
+ export declare function DiffView({ data, mode, }: {
4
+ readonly data: TemplateDiff;
5
+ readonly mode?: "diff" | "full";
6
+ }): ReactNode;
7
+ export declare function DiffPanel({ tenantId }: {
8
+ readonly tenantId: string;
9
+ }): ReactNode;
10
+ export declare function DiffDrawer({ tenantId, onClose, }: {
11
+ readonly tenantId: string;
12
+ readonly onClose: () => void;
13
+ }): ReactNode;
14
+ export declare function OriginBar({ isSystemAdmin, source, kind, userCount, overrides, onReset, onResetError, }: {
15
+ readonly isSystemAdmin: boolean;
16
+ readonly source: LayoutSourceView;
17
+ readonly kind: LayoutKind;
18
+ readonly userCount: number;
19
+ readonly overrides: TenantOverrideCounts | null;
20
+ readonly onReset: () => void;
21
+ readonly onResetError: (message: string) => void;
22
+ }): ReactNode;
@@ -0,0 +1,104 @@
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ // @runtime client
3
+ import { TemplateResolverHandlers, TemplateResolverQueries, } from "@cosmicdrift/kumiko-bundled-features/template-resolver/web";
4
+ import { dispatcherErrorText, useMutation, usePrimitives, useQuery, useTranslation, } from "@cosmicdrift/kumiko-renderer";
5
+ import { EmptyState, ErrorState, LoadingState } from "@cosmicdrift/kumiko-renderer-web";
6
+ import { useState } from "react";
7
+ import { originOf } from "./editor-header.js";
8
+ import { EditorInfoBar } from "./editor-shell.js";
9
+ import { draftBesideActiveSentence } from "./status-label.js";
10
+ const DIFF_MARK = {
11
+ same: " ",
12
+ added: "+",
13
+ removed: "-",
14
+ };
15
+ const DIFF_TONE = {
16
+ same: "text-muted-foreground",
17
+ added: "bg-status-ok-surface text-status-ok",
18
+ removed: "bg-status-bad-surface text-status-bad",
19
+ };
20
+ function metaValueText(value) {
21
+ if (value === undefined || value === null)
22
+ return "–";
23
+ return typeof value === "string" ? value : JSON.stringify(value);
24
+ }
25
+ // "full" shows the tenant's version as it reads (everything but the removed system blocks).
26
+ export function DiffView({ data, mode = "diff", }) {
27
+ const t = useTranslation();
28
+ if (mode === "diff" && data.identical) {
29
+ return _jsx(EmptyState, { title: t("documentDesigner.editor.diff.identical") });
30
+ }
31
+ // Diff blocks have no identity beyond their position.
32
+ const entries = data.blocks
33
+ .map((block, position) => ({ block, key: `block-${position}` }))
34
+ .filter(({ block }) => mode === "diff" || block.change !== "removed");
35
+ return (_jsxs("div", { className: "flex flex-col gap-3", children: [_jsx("ul", { className: "flex flex-col gap-1", "data-testid": "document-designer-diff", children: entries.map(({ block, key }) => (_jsxs("li", { "data-change": block.change, className: `whitespace-pre-wrap rounded px-2 py-1 font-mono text-xs ${DIFF_TONE[block.change]}`, children: [mode === "diff" && `${DIFF_MARK[block.change]} `, block.content] }, key))) }), mode === "diff" && data.meta.length > 0 && (_jsx("ul", { className: "flex flex-col gap-1", "data-testid": "document-designer-diff-meta", children: data.meta.map((entry) => (_jsxs("li", { className: "rounded bg-muted px-2 py-1 font-mono text-xs", children: [entry.key, ": ", metaValueText(entry.system), " \u2192 ", metaValueText(entry.tenant)] }, entry.key))) }))] }));
36
+ }
37
+ export function DiffPanel({ tenantId }) {
38
+ const { data, loading, error, refetch } = useQuery(TemplateResolverQueries.diffToSystem, { id: tenantId });
39
+ if (error !== null)
40
+ return _jsx(ErrorState, { error: error, onRetry: () => void refetch() });
41
+ if (loading || data === null)
42
+ return _jsx(LoadingState, {});
43
+ return _jsx(DiffView, { data: data });
44
+ }
45
+ export function DiffDrawer({ tenantId, onClose, }) {
46
+ const t = useTranslation();
47
+ const { Drawer, Modal } = usePrimitives();
48
+ const title = t("documentDesigner.editor.diff.title");
49
+ const body = _jsx(DiffPanel, { tenantId: tenantId });
50
+ const onOpenChange = (open) => {
51
+ if (!open)
52
+ onClose();
53
+ };
54
+ return Drawer === undefined ? (_jsx(Modal, { open: true, onOpenChange: onOpenChange, title: title, children: body })) : (_jsx(Drawer, { open: true, onOpenChange: onOpenChange, title: title, children: body }));
55
+ }
56
+ function originText(t, args) {
57
+ const { isSystemAdmin, source, kind, userCount, overrides } = args;
58
+ const systemVersion = source.system?.version ?? 0;
59
+ if (isSystemAdmin) {
60
+ if (overrides === null)
61
+ return t("documentDesigner.editor.origin.platformPlain", { version: systemVersion });
62
+ return overrides.totalTenants === null
63
+ ? t("documentDesigner.editor.origin.platformCount", {
64
+ version: systemVersion,
65
+ active: overrides.activeOverrides,
66
+ })
67
+ : t("documentDesigner.editor.origin.platformOfTotal", {
68
+ version: systemVersion,
69
+ active: overrides.activeOverrides,
70
+ total: overrides.totalTenants,
71
+ });
72
+ }
73
+ const usesKey = kind === "mail-layout" ? "mails" : "pdfs";
74
+ if (source.tenant !== null) {
75
+ return t(`documentDesigner.editor.origin.tenant.${usesKey}`, {
76
+ version: systemVersion,
77
+ count: userCount,
78
+ });
79
+ }
80
+ return source.system === null
81
+ ? t("documentDesigner.editor.origin.none")
82
+ : t("documentDesigner.editor.origin.system");
83
+ }
84
+ export function OriginBar({ isSystemAdmin, source, kind, userCount, overrides, onReset, onResetError, }) {
85
+ const t = useTranslation();
86
+ const { Button, Dialog } = usePrimitives();
87
+ const [diffOpen, setDiffOpen] = useState(false);
88
+ const [resetOpen, setResetOpen] = useState(false);
89
+ const resetToSystem = useMutation(TemplateResolverHandlers.resetToSystem);
90
+ const { tenant, system } = source;
91
+ const besideActive = isSystemAdmin || tenant === null ? null : draftBesideActiveSentence(t, tenant);
92
+ const canDiff = tenant !== null && system?.contentFormat === "html";
93
+ const canReset = tenant !== null && system !== null && !isSystemAdmin;
94
+ const links = (_jsxs(_Fragment, { children: [canDiff && (_jsx(Button, { variant: "link", onClick: () => setDiffOpen(true), testId: "document-designer-diff-open", children: t("documentDesigner.editor.diff.open") })), canReset && (_jsx(Button, { variant: "link", onClick: () => setResetOpen(true), testId: "document-designer-reset-open", children: t("documentDesigner.editor.reset.open") }))] }));
95
+ return (_jsxs(EditorInfoBar, { tone: tenant !== null ? "active" : "muted", testId: "document-designer-origin-bar", origin: originOf(tenant === null, system !== null), links: links, overlays: _jsxs(_Fragment, { children: [diffOpen && tenant !== null && (_jsx(DiffDrawer, { tenantId: tenant.id, onClose: () => setDiffOpen(false) })), _jsx(Dialog, { open: resetOpen, onOpenChange: setResetOpen, title: t("documentDesigner.editor.reset.title"), description: t("documentDesigner.editor.reset.description"), confirmLabel: t("documentDesigner.editor.reset.confirm"), variant: "danger", onConfirm: async () => {
96
+ if (tenant === null)
97
+ return;
98
+ const result = await resetToSystem.mutate({ id: tenant.id });
99
+ if (result.isSuccess)
100
+ onReset();
101
+ else
102
+ onResetError(dispatcherErrorText(result.error, t));
103
+ } })] }), children: [originText(t, { isSystemAdmin, source, kind, userCount, overrides }), besideActive !== null && ` ${besideActive}`] }));
104
+ }
@@ -0,0 +1,10 @@
1
+ import { type ReactNode } from "react";
2
+ import { type TenantOverrides } from "./types.js";
3
+ export type OverridesDrawerProps = {
4
+ readonly open: boolean;
5
+ readonly onOpenChange: (open: boolean) => void;
6
+ readonly slug: string;
7
+ readonly locale: string;
8
+ readonly overrides: TenantOverrides | null;
9
+ };
10
+ export declare function OverridesDrawer({ open, onOpenChange, slug, locale, overrides, }: OverridesDrawerProps): ReactNode;