(null);
+ const generationRef = useRef(0);
+
+ useEffect(() => {
+ const generation = ++generationRef.current;
+ const apply = (next: string) => {
+ if (generation === generationRef.current) {
+ setCode(next);
+ }
+ };
+
+ switch (type) {
+ case 'html':
+ html(value).then(apply);
+ return;
+ case 'json':
+ json(value).then(apply);
+ return;
+ }
+ }, [value, type]);
+
+ if (code === null) {
+ return null;
+ }
+
+ return (
+ {
+ const s = window.getSelection();
+ if (s === null) {
+ return;
+ }
+ s.selectAllChildren(ev.currentTarget);
+ }}
+ />
+ );
+}
diff --git a/packages/post-kit-editor/src/email-builder-ui/App/TemplatePanel/helper/highlighters.tsx b/packages/post-kit-editor/src/email-builder-ui/App/TemplatePanel/helper/highlighters.tsx
new file mode 100644
index 0000000..c53dae8
--- /dev/null
+++ b/packages/post-kit-editor/src/email-builder-ui/App/TemplatePanel/helper/highlighters.tsx
@@ -0,0 +1,28 @@
+import hljs from 'highlight.js';
+import jsonHighlighter from 'highlight.js/lib/languages/json';
+import xmlHighlighter from 'highlight.js/lib/languages/xml';
+import prettierPluginBabel from 'prettier/plugins/babel';
+import prettierPluginEstree from 'prettier/plugins/estree';
+import prettierPluginHtml from 'prettier/plugins/html';
+import { format } from 'prettier/standalone';
+
+hljs.registerLanguage('json', jsonHighlighter);
+hljs.registerLanguage('html', xmlHighlighter);
+
+export async function html(value: string): Promise {
+ const prettyValue = await format(value, {
+ parser: 'html',
+ plugins: [prettierPluginHtml],
+ });
+ return hljs.highlight(prettyValue, { language: 'html' }).value;
+}
+
+export async function json(value: string): Promise {
+ const prettyValue = await format(value, {
+ parser: 'json',
+ printWidth: 0,
+ trailingComma: 'all',
+ plugins: [prettierPluginBabel, prettierPluginEstree],
+ });
+ return hljs.highlight(prettyValue, { language: 'javascript' }).value;
+}
diff --git a/packages/post-kit-editor/src/email-builder-ui/App/TemplatePanel/index.tsx b/packages/post-kit-editor/src/email-builder-ui/App/TemplatePanel/index.tsx
new file mode 100644
index 0000000..426995a
--- /dev/null
+++ b/packages/post-kit-editor/src/email-builder-ui/App/TemplatePanel/index.tsx
@@ -0,0 +1,132 @@
+import React from 'react';
+
+import { MonitorOutlined, PhoneIphoneOutlined } from '@mui/icons-material';
+import { Box, Stack, SxProps, ToggleButton, ToggleButtonGroup, Tooltip } from '@mui/material';
+import { Reader } from '@usewaypoint/email-builder';
+
+import EditorBlock from '../../documents/editor/EditorBlock';
+import {
+ setSelectedScreenSize,
+ useDocument,
+ useSelectedMainTab,
+ useSelectedScreenSize,
+} from '../../documents/editor/EditorContext';
+import ToggleInspectorPanelButton from '../InspectorDrawer/ToggleInspectorPanelButton';
+import ToggleSamplesPanelButton from '../SamplesDrawer/ToggleSamplesPanelButton';
+
+import DownloadJson from './DownloadJson';
+import HtmlPanel from './HtmlPanel';
+import ImportJson from './ImportJson';
+import JsonPanel from './JsonPanel';
+import MainTabsGroup from './MainTabsGroup';
+
+export default function TemplatePanel() {
+ const document = useDocument();
+ const selectedMainTab = useSelectedMainTab();
+ const selectedScreenSize = useSelectedScreenSize();
+
+ let mainBoxSx: SxProps = {
+ height: '100%',
+ };
+ if (selectedScreenSize === 'mobile') {
+ mainBoxSx = {
+ ...mainBoxSx,
+ margin: '32px auto',
+ width: 370,
+ height: 800,
+ boxShadow:
+ 'rgba(33, 36, 67, 0.04) 0px 10px 20px, rgba(33, 36, 67, 0.04) 0px 2px 6px, rgba(33, 36, 67, 0.04) 0px 0px 1px',
+ };
+ }
+
+ const handleScreenSizeChange = (_: unknown, value: unknown) => {
+ switch (value) {
+ case 'mobile':
+ case 'desktop':
+ setSelectedScreenSize(value);
+ return;
+ default:
+ setSelectedScreenSize('desktop');
+ }
+ };
+
+ const renderMainPanel = () => {
+ switch (selectedMainTab) {
+ case 'editor':
+ return (
+
+
+
+ );
+ case 'preview':
+ return (
+
+
+
+ );
+ case 'html':
+ return ;
+ case 'json':
+ return ;
+ }
+ };
+
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {renderMainPanel()}
+
+ >
+ );
+}
diff --git a/packages/post-kit-editor/src/email-builder-ui/App/index.tsx b/packages/post-kit-editor/src/email-builder-ui/App/index.tsx
new file mode 100644
index 0000000..1e84150
--- /dev/null
+++ b/packages/post-kit-editor/src/email-builder-ui/App/index.tsx
@@ -0,0 +1,42 @@
+import React from 'react';
+
+import { Stack, useTheme } from '@mui/material';
+
+import { useInspectorDrawerOpen, useSamplesDrawerOpen } from '../documents/editor/EditorContext';
+
+import InspectorDrawer, { INSPECTOR_DRAWER_WIDTH } from './InspectorDrawer';
+import SamplesDrawer, { SAMPLES_DRAWER_WIDTH } from './SamplesDrawer';
+import TemplatePanel from './TemplatePanel';
+
+function useDrawerTransition(cssProperty: 'margin-left' | 'margin-right', open: boolean) {
+ const { transitions } = useTheme();
+ return transitions.create(cssProperty, {
+ easing: !open ? transitions.easing.sharp : transitions.easing.easeOut,
+ duration: !open ? transitions.duration.leavingScreen : transitions.duration.enteringScreen,
+ });
+}
+
+export default function App() {
+ const inspectorDrawerOpen = useInspectorDrawerOpen();
+ const samplesDrawerOpen = useSamplesDrawerOpen();
+
+ const marginLeftTransition = useDrawerTransition('margin-left', samplesDrawerOpen);
+ const marginRightTransition = useDrawerTransition('margin-right', inspectorDrawerOpen);
+
+ return (
+ <>
+
+
+
+
+
+
+ >
+ );
+}
diff --git a/packages/post-kit-editor/src/email-builder-ui/EmailBuilderMuiSurface.tsx b/packages/post-kit-editor/src/email-builder-ui/EmailBuilderMuiSurface.tsx
new file mode 100644
index 0000000..50957f6
--- /dev/null
+++ b/packages/post-kit-editor/src/email-builder-ui/EmailBuilderMuiSurface.tsx
@@ -0,0 +1,89 @@
+import React, { useEffect, useRef } from 'react';
+
+import { CssBaseline, ThemeProvider } from '@mui/material';
+import { Reader } from '@usewaypoint/email-builder';
+
+import type { EmailBuilderDocument } from '../types';
+import App from './App';
+import { resetDocument, subscribeDocument } from './documents/editor/EditorContext';
+import type { TEditorConfiguration } from './documents/editor/core';
+import theme from './theme';
+
+export interface EmailBuilderMuiSurfaceProps {
+ document: EmailBuilderDocument;
+ onChange: (document: EmailBuilderDocument) => void;
+ readOnly?: boolean;
+}
+
+function asEditorDocument(document: EmailBuilderDocument): TEditorConfiguration {
+ return document as TEditorConfiguration;
+}
+
+/**
+ * Controlled MUI EmailBuilder.js canvas surface for PostKit.
+ * Syncs the Zustand editor store with parent `document` / `onChange`.
+ *
+ * The first controlled document is applied synchronously during render so SSR /
+ * `renderToStaticMarkup` paints the correct tree; later prop updates sync in
+ * an effect to avoid updating subscribers while React is rendering.
+ */
+export function EmailBuilderMuiSurface({
+ document,
+ onChange,
+ readOnly = false,
+}: EmailBuilderMuiSurfaceProps): JSX.Element {
+ const lastEmittedJsonRef = useRef(null);
+ const hasAppliedInitialRef = useRef(false);
+ const incomingJson = JSON.stringify(document);
+
+ if (!readOnly && !hasAppliedInitialRef.current) {
+ hasAppliedInitialRef.current = true;
+ lastEmittedJsonRef.current = incomingJson;
+ resetDocument(asEditorDocument(document));
+ }
+
+ useEffect(() => {
+ if (readOnly) {
+ return;
+ }
+ if (incomingJson === lastEmittedJsonRef.current) {
+ return;
+ }
+ lastEmittedJsonRef.current = incomingJson;
+ resetDocument(asEditorDocument(document));
+ }, [document, incomingJson, readOnly]);
+
+ useEffect(() => {
+ if (readOnly) {
+ return;
+ }
+ return subscribeDocument((next) => {
+ const json = JSON.stringify(next);
+ if (json === lastEmittedJsonRef.current) {
+ return;
+ }
+ lastEmittedJsonRef.current = json;
+ onChange(next as EmailBuilderDocument);
+ });
+ }, [onChange, readOnly]);
+
+ if (readOnly) {
+ return (
+
+
+
+
+
+
+ );
+ }
+
+ return (
+
+ );
+}
diff --git a/packages/post-kit-editor/src/email-builder-ui/NOTICE.md b/packages/post-kit-editor/src/email-builder-ui/NOTICE.md
new file mode 100644
index 0000000..556f475
--- /dev/null
+++ b/packages/post-kit-editor/src/email-builder-ui/NOTICE.md
@@ -0,0 +1,14 @@
+# Notice — EmailBuilder.js MUI sample
+
+Parts of `email-builder-ui/` are adapted from the
+[usewaypoint/email-builder-js](https://github.com/usewaypoint/email-builder-js)
+Vite + MUI example (`examples/vite-emailbuilder-mui`), licensed under the MIT
+License.
+
+Copyright (c) Waypoint / usewaypoint contributors.
+
+Modifications for PostKit:
+
+- Controlled document sync via `EmailBuilderMuiSurface` (no `window.location.hash`)
+- Samples drawer without Waypoint marketing / UTM links
+- Share-via-hash removed; Import/Download JSON retained
diff --git a/packages/post-kit-editor/src/email-builder-ui/documents/blocks/ColumnsContainer/ColumnsContainerEditor.tsx b/packages/post-kit-editor/src/email-builder-ui/documents/blocks/ColumnsContainer/ColumnsContainerEditor.tsx
new file mode 100644
index 0000000..2814ea0
--- /dev/null
+++ b/packages/post-kit-editor/src/email-builder-ui/documents/blocks/ColumnsContainer/ColumnsContainerEditor.tsx
@@ -0,0 +1,61 @@
+import React from 'react';
+
+import { ColumnsContainer as BaseColumnsContainer } from '@usewaypoint/block-columns-container';
+
+import { useCurrentBlockId } from '../../editor/EditorBlock';
+import { setDocument, setSelectedBlockId } from '../../editor/EditorContext';
+import EditorChildrenIds, { EditorChildrenChange } from '../helpers/EditorChildrenIds';
+
+import ColumnsContainerPropsSchema, { ColumnsContainerProps } from './ColumnsContainerPropsSchema';
+
+const EMPTY_COLUMNS = [{ childrenIds: [] }, { childrenIds: [] }, { childrenIds: [] }];
+
+export default function ColumnsContainerEditor({ style, props }: ColumnsContainerProps) {
+ const currentBlockId = useCurrentBlockId();
+
+ const { columns, ...restProps } = props ?? {};
+ const columnsValue = columns ?? EMPTY_COLUMNS;
+
+ const updateColumn = (
+ columnIndex: 0 | 1 | 2,
+ { block, blockId, childrenIds }: EditorChildrenChange,
+ ) => {
+ const nColumns = [...columnsValue];
+ nColumns[columnIndex] = { childrenIds };
+ setDocument({
+ [blockId]: block,
+ [currentBlockId]: {
+ type: 'ColumnsContainer',
+ data: ColumnsContainerPropsSchema.parse({
+ style,
+ props: {
+ ...restProps,
+ columns: nColumns,
+ },
+ }),
+ },
+ });
+ setSelectedBlockId(blockId);
+ };
+
+ return (
+ updateColumn(0, change)}
+ />,
+ updateColumn(1, change)}
+ />,
+ updateColumn(2, change)}
+ />,
+ ]}
+ />
+ );
+}
diff --git a/packages/post-kit-editor/src/email-builder-ui/documents/blocks/ColumnsContainer/ColumnsContainerPropsSchema.ts b/packages/post-kit-editor/src/email-builder-ui/documents/blocks/ColumnsContainer/ColumnsContainerPropsSchema.ts
new file mode 100644
index 0000000..7e5d5cd
--- /dev/null
+++ b/packages/post-kit-editor/src/email-builder-ui/documents/blocks/ColumnsContainer/ColumnsContainerPropsSchema.ts
@@ -0,0 +1,23 @@
+import { z } from 'zod';
+
+import { ColumnsContainerPropsSchema as BaseColumnsContainerPropsSchema } from '@usewaypoint/block-columns-container';
+
+const BasePropsShape = BaseColumnsContainerPropsSchema.shape.props.unwrap().unwrap().shape;
+
+const ColumnsContainerPropsSchema = z.object({
+ style: BaseColumnsContainerPropsSchema.shape.style,
+ props: z
+ .object({
+ ...BasePropsShape,
+ columns: z.tuple([
+ z.object({ childrenIds: z.array(z.string()) }),
+ z.object({ childrenIds: z.array(z.string()) }),
+ z.object({ childrenIds: z.array(z.string()) }),
+ ]),
+ })
+ .optional()
+ .nullable(),
+});
+
+export type ColumnsContainerProps = z.infer;
+export default ColumnsContainerPropsSchema;
diff --git a/packages/post-kit-editor/src/email-builder-ui/documents/blocks/Container/ContainerEditor.tsx b/packages/post-kit-editor/src/email-builder-ui/documents/blocks/Container/ContainerEditor.tsx
new file mode 100644
index 0000000..6e027a9
--- /dev/null
+++ b/packages/post-kit-editor/src/email-builder-ui/documents/blocks/Container/ContainerEditor.tsx
@@ -0,0 +1,37 @@
+import React from 'react';
+
+import { Container as BaseContainer } from '@usewaypoint/block-container';
+
+import { useCurrentBlockId } from '../../editor/EditorBlock';
+import { setDocument, setSelectedBlockId, useDocument } from '../../editor/EditorContext';
+import EditorChildrenIds from '../helpers/EditorChildrenIds';
+
+import { ContainerProps } from './ContainerPropsSchema';
+
+export default function ContainerEditor({ style, props }: ContainerProps) {
+ const childrenIds = props?.childrenIds ?? [];
+
+ const document = useDocument();
+ const currentBlockId = useCurrentBlockId();
+
+ return (
+
+ {
+ setDocument({
+ [blockId]: block,
+ [currentBlockId]: {
+ type: 'Container',
+ data: {
+ ...document[currentBlockId].data,
+ props: { childrenIds: childrenIds },
+ },
+ },
+ });
+ setSelectedBlockId(blockId);
+ }}
+ />
+
+ );
+}
diff --git a/packages/post-kit-editor/src/email-builder-ui/documents/blocks/Container/ContainerPropsSchema.tsx b/packages/post-kit-editor/src/email-builder-ui/documents/blocks/Container/ContainerPropsSchema.tsx
new file mode 100644
index 0000000..a0188f6
--- /dev/null
+++ b/packages/post-kit-editor/src/email-builder-ui/documents/blocks/Container/ContainerPropsSchema.tsx
@@ -0,0 +1,17 @@
+import { z } from 'zod';
+
+import { ContainerPropsSchema as BaseContainerPropsSchema } from '@usewaypoint/block-container';
+
+const ContainerPropsSchema = z.object({
+ style: BaseContainerPropsSchema.shape.style,
+ props: z
+ .object({
+ childrenIds: z.array(z.string()).optional().nullable(),
+ })
+ .optional()
+ .nullable(),
+});
+
+export default ContainerPropsSchema;
+
+export type ContainerProps = z.infer;
diff --git a/packages/post-kit-editor/src/email-builder-ui/documents/blocks/EmailLayout/EmailLayoutEditor.tsx b/packages/post-kit-editor/src/email-builder-ui/documents/blocks/EmailLayout/EmailLayoutEditor.tsx
new file mode 100644
index 0000000..0b5002d
--- /dev/null
+++ b/packages/post-kit-editor/src/email-builder-ui/documents/blocks/EmailLayout/EmailLayoutEditor.tsx
@@ -0,0 +1,103 @@
+import React from 'react';
+
+import { useCurrentBlockId } from '../../editor/EditorBlock';
+import { setDocument, setSelectedBlockId, useDocument } from '../../editor/EditorContext';
+import EditorChildrenIds from '../helpers/EditorChildrenIds';
+
+import { EmailLayoutProps } from './EmailLayoutPropsSchema';
+
+function getFontFamily(fontFamily: EmailLayoutProps['fontFamily']) {
+ const f = fontFamily ?? 'MODERN_SANS';
+ switch (f) {
+ case 'MODERN_SANS':
+ return '"Helvetica Neue", "Arial Nova", "Nimbus Sans", Arial, sans-serif';
+ case 'BOOK_SANS':
+ return 'Optima, Candara, "Noto Sans", source-sans-pro, sans-serif';
+ case 'ORGANIC_SANS':
+ return 'Seravek, "Gill Sans Nova", Ubuntu, Calibri, "DejaVu Sans", source-sans-pro, sans-serif';
+ case 'GEOMETRIC_SANS':
+ return 'Avenir, "Avenir Next LT Pro", Montserrat, Corbel, "URW Gothic", source-sans-pro, sans-serif';
+ case 'HEAVY_SANS':
+ return 'Bahnschrift, "DIN Alternate", "Franklin Gothic Medium", "Nimbus Sans Narrow", sans-serif-condensed, sans-serif';
+ case 'ROUNDED_SANS':
+ return 'ui-rounded, "Hiragino Maru Gothic ProN", Quicksand, Comfortaa, Manjari, "Arial Rounded MT Bold", Calibri, source-sans-pro, sans-serif';
+ case 'MODERN_SERIF':
+ return 'Charter, "Bitstream Charter", "Sitka Text", Cambria, serif';
+ case 'BOOK_SERIF':
+ return '"Iowan Old Style", "Palatino Linotype", "URW Palladio L", P052, serif';
+ case 'MONOSPACE':
+ return '"Nimbus Mono PS", "Courier New", "Cutive Mono", monospace';
+ }
+}
+
+export default function EmailLayoutEditor(props: EmailLayoutProps) {
+ const childrenIds = props.childrenIds ?? [];
+ const document = useDocument();
+ const currentBlockId = useCurrentBlockId();
+
+ return (
+ {
+ setSelectedBlockId(null);
+ }}
+ style={{
+ backgroundColor: props.backdropColor ?? '#F5F5F5',
+ color: props.textColor ?? '#262626',
+ fontFamily: getFontFamily(props.fontFamily),
+ fontSize: '16px',
+ fontWeight: '400',
+ letterSpacing: '0.15008px',
+ lineHeight: '1.5',
+ margin: '0',
+ padding: '32px 0',
+ width: '100%',
+ minHeight: '100%',
+ }}
+ >
+
{
+ const v = props.borderColor;
+ if (!v) {
+ return undefined;
+ }
+ return `1px solid ${v}`;
+ })(),
+ }}
+ role="presentation"
+ cellSpacing="0"
+ cellPadding="0"
+ border={0}
+ >
+
+
+ |
+ {
+ setDocument({
+ [blockId]: block,
+ [currentBlockId]: {
+ type: 'EmailLayout',
+ data: {
+ ...document[currentBlockId].data,
+ childrenIds: childrenIds,
+ },
+ },
+ });
+ setSelectedBlockId(blockId);
+ }}
+ />
+ |
+
+
+
+
+ );
+}
diff --git a/packages/post-kit-editor/src/email-builder-ui/documents/blocks/EmailLayout/EmailLayoutPropsSchema.tsx b/packages/post-kit-editor/src/email-builder-ui/documents/blocks/EmailLayout/EmailLayoutPropsSchema.tsx
new file mode 100644
index 0000000..880f3ae
--- /dev/null
+++ b/packages/post-kit-editor/src/email-builder-ui/documents/blocks/EmailLayout/EmailLayoutPropsSchema.tsx
@@ -0,0 +1,36 @@
+import { z } from 'zod';
+
+const COLOR_SCHEMA = z
+ .string()
+ .regex(/^#[0-9a-fA-F]{6}$/)
+ .nullable()
+ .optional();
+
+const FONT_FAMILY_SCHEMA = z
+ .enum([
+ 'MODERN_SANS',
+ 'BOOK_SANS',
+ 'ORGANIC_SANS',
+ 'GEOMETRIC_SANS',
+ 'HEAVY_SANS',
+ 'ROUNDED_SANS',
+ 'MODERN_SERIF',
+ 'BOOK_SERIF',
+ 'MONOSPACE',
+ ])
+ .nullable()
+ .optional();
+
+const EmailLayoutPropsSchema = z.object({
+ backdropColor: COLOR_SCHEMA,
+ borderColor: COLOR_SCHEMA,
+ borderRadius: z.number().optional().nullable(),
+ canvasColor: COLOR_SCHEMA,
+ textColor: COLOR_SCHEMA,
+ fontFamily: FONT_FAMILY_SCHEMA,
+ childrenIds: z.array(z.string()).optional().nullable(),
+});
+
+export default EmailLayoutPropsSchema;
+
+export type EmailLayoutProps = z.infer;
diff --git a/packages/post-kit-editor/src/email-builder-ui/documents/blocks/helpers/EditorChildrenIds/AddBlockMenu/BlockButton.tsx b/packages/post-kit-editor/src/email-builder-ui/documents/blocks/helpers/EditorChildrenIds/AddBlockMenu/BlockButton.tsx
new file mode 100644
index 0000000..c045dc4
--- /dev/null
+++ b/packages/post-kit-editor/src/email-builder-ui/documents/blocks/helpers/EditorChildrenIds/AddBlockMenu/BlockButton.tsx
@@ -0,0 +1,36 @@
+import React from 'react';
+
+import { Box, Button, SxProps, Typography } from '@mui/material';
+
+type BlockMenuButtonProps = {
+ label: string;
+ icon: React.ReactNode;
+ onClick: () => void;
+};
+
+const BUTTON_SX: SxProps = { p: 1.5, display: 'flex', flexDirection: 'column' };
+const ICON_SX: SxProps = {
+ mb: 0.75,
+ width: '100%',
+ bgcolor: 'cadet.200',
+ display: 'flex',
+ justifyContent: 'center',
+ p: 1,
+ border: '1px solid',
+ borderColor: 'cadet.300',
+};
+
+export default function BlockTypeButton({ label, icon, onClick }: BlockMenuButtonProps) {
+ return (
+
+ );
+}
diff --git a/packages/post-kit-editor/src/email-builder-ui/documents/blocks/helpers/EditorChildrenIds/AddBlockMenu/BlocksMenu.tsx b/packages/post-kit-editor/src/email-builder-ui/documents/blocks/helpers/EditorChildrenIds/AddBlockMenu/BlocksMenu.tsx
new file mode 100644
index 0000000..5b64588
--- /dev/null
+++ b/packages/post-kit-editor/src/email-builder-ui/documents/blocks/helpers/EditorChildrenIds/AddBlockMenu/BlocksMenu.tsx
@@ -0,0 +1,44 @@
+import React from 'react';
+
+import { Box, Menu } from '@mui/material';
+
+import { TEditorBlock } from '../../../../editor/core';
+
+import BlockButton from './BlockButton';
+import { BUTTONS } from './buttons';
+
+type BlocksMenuProps = {
+ anchorEl: HTMLElement | null;
+ setAnchorEl: (v: HTMLElement | null) => void;
+ onSelect: (block: TEditorBlock) => void;
+};
+export default function BlocksMenu({ anchorEl, setAnchorEl, onSelect }: BlocksMenuProps) {
+ const onClose = () => {
+ setAnchorEl(null);
+ };
+
+ const onClick = (block: TEditorBlock) => {
+ onSelect(block);
+ setAnchorEl(null);
+ };
+
+ if (anchorEl === null) {
+ return null;
+ }
+
+ return (
+
+ );
+}
diff --git a/packages/post-kit-editor/src/email-builder-ui/documents/blocks/helpers/EditorChildrenIds/AddBlockMenu/DividerButton.tsx b/packages/post-kit-editor/src/email-builder-ui/documents/blocks/helpers/EditorChildrenIds/AddBlockMenu/DividerButton.tsx
new file mode 100644
index 0000000..3a0bba2
--- /dev/null
+++ b/packages/post-kit-editor/src/email-builder-ui/documents/blocks/helpers/EditorChildrenIds/AddBlockMenu/DividerButton.tsx
@@ -0,0 +1,69 @@
+import React, { useEffect, useState } from 'react';
+
+import { AddOutlined } from '@mui/icons-material';
+import { IconButton } from '@mui/material';
+
+type Props = {
+ buttonElement: HTMLElement | null;
+ onClick: () => void;
+};
+export default function DividerButton({ buttonElement, onClick }: Props) {
+ const [visible, setVisible] = useState(false);
+
+ useEffect(() => {
+ function listener({ clientX, clientY }: MouseEvent) {
+ if (!buttonElement) {
+ return;
+ }
+ const rect = buttonElement.getBoundingClientRect();
+ const rectY = rect.y;
+ const bottomX = rect.x;
+ const topX = bottomX + rect.width;
+
+ if (Math.abs(clientY - rectY) < 20) {
+ if (bottomX < clientX && clientX < topX) {
+ setVisible(true);
+ return;
+ }
+ }
+ setVisible(false);
+ }
+ window.addEventListener('mousemove', listener);
+ return () => {
+ window.removeEventListener('mousemove', listener);
+ };
+ }, [buttonElement, setVisible]);
+
+ return (
+ {
+ ev.stopPropagation();
+ onClick();
+ }}
+ >
+
+
+ );
+}
diff --git a/packages/post-kit-editor/src/email-builder-ui/documents/blocks/helpers/EditorChildrenIds/AddBlockMenu/PlaceholderButton.tsx b/packages/post-kit-editor/src/email-builder-ui/documents/blocks/helpers/EditorChildrenIds/AddBlockMenu/PlaceholderButton.tsx
new file mode 100644
index 0000000..e14519a
--- /dev/null
+++ b/packages/post-kit-editor/src/email-builder-ui/documents/blocks/helpers/EditorChildrenIds/AddBlockMenu/PlaceholderButton.tsx
@@ -0,0 +1,36 @@
+import React from 'react';
+
+import { AddOutlined } from '@mui/icons-material';
+import { ButtonBase } from '@mui/material';
+
+type Props = {
+ onClick: () => void;
+};
+export default function PlaceholderButton({ onClick }: Props) {
+ return (
+ {
+ ev.stopPropagation();
+ onClick();
+ }}
+ sx={{
+ display: 'flex',
+ alignContent: 'center',
+ justifyContent: 'center',
+ height: 48,
+ width: '100%',
+ bgcolor: 'rgba(0,0,0, 0.05)',
+ }}
+ >
+
+
+ );
+}
diff --git a/packages/post-kit-editor/src/email-builder-ui/documents/blocks/helpers/EditorChildrenIds/AddBlockMenu/buttons.tsx b/packages/post-kit-editor/src/email-builder-ui/documents/blocks/helpers/EditorChildrenIds/AddBlockMenu/buttons.tsx
new file mode 100644
index 0000000..68168d9
--- /dev/null
+++ b/packages/post-kit-editor/src/email-builder-ui/documents/blocks/helpers/EditorChildrenIds/AddBlockMenu/buttons.tsx
@@ -0,0 +1,162 @@
+import React from 'react';
+
+import {
+ AccountCircleOutlined,
+ Crop32Outlined,
+ HMobiledataOutlined,
+ HorizontalRuleOutlined,
+ HtmlOutlined,
+ ImageOutlined,
+ LibraryAddOutlined,
+ NotesOutlined,
+ SmartButtonOutlined,
+ ViewColumnOutlined,
+} from '@mui/icons-material';
+
+import { TEditorBlock } from '../../../../editor/core';
+
+type TButtonProps = {
+ label: string;
+ icon: JSX.Element;
+ block: () => TEditorBlock;
+};
+export const BUTTONS: TButtonProps[] = [
+ {
+ label: 'Heading',
+ icon: ,
+ block: () => ({
+ type: 'Heading',
+ data: {
+ props: { text: 'Hello friend' },
+ style: {
+ padding: { top: 16, bottom: 16, left: 24, right: 24 },
+ },
+ },
+ }),
+ },
+ {
+ label: 'Text',
+ icon: ,
+ block: () => ({
+ type: 'Text',
+ data: {
+ props: { text: 'My new text block' },
+ style: {
+ padding: { top: 16, bottom: 16, left: 24, right: 24 },
+ fontWeight: 'normal',
+ },
+ },
+ }),
+ },
+
+ {
+ label: 'Button',
+ icon: ,
+ block: () => ({
+ type: 'Button',
+ data: {
+ props: {
+ text: 'Button',
+ url: 'https://www.usewaypoint.com',
+ },
+ style: { padding: { top: 16, bottom: 16, left: 24, right: 24 } },
+ },
+ }),
+ },
+ {
+ label: 'Image',
+ icon: ,
+ block: () => ({
+ type: 'Image',
+ data: {
+ props: {
+ // Network-neutral empty placeholder (host supplies real URLs).
+ url: '',
+ alt: 'Sample product',
+ contentAlignment: 'middle',
+ linkHref: null,
+ },
+ style: { padding: { top: 16, bottom: 16, left: 24, right: 24 } },
+ },
+ }),
+ },
+ {
+ label: 'Avatar',
+ icon: ,
+ block: () => ({
+ type: 'Avatar',
+ data: {
+ props: {
+ imageUrl:
+ "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='128' height='128'%3E%3Crect fill='%23E5E5E5' width='128' height='128'/%3E%3C/svg%3E",
+ shape: 'circle',
+ },
+ style: { padding: { top: 16, bottom: 16, left: 24, right: 24 } },
+ },
+ }),
+ },
+ {
+ label: 'Divider',
+ icon: ,
+ block: () => ({
+ type: 'Divider',
+ data: {
+ style: { padding: { top: 16, right: 0, bottom: 16, left: 0 } },
+ props: {
+ lineColor: '#CCCCCC',
+ },
+ },
+ }),
+ },
+ {
+ label: 'Spacer',
+ icon: ,
+ block: () => ({
+ type: 'Spacer',
+ data: {},
+ }),
+ },
+ {
+ label: 'Html',
+ icon: ,
+ block: () => ({
+ type: 'Html',
+ data: {
+ props: { contents: 'Hello world' },
+ style: {
+ fontSize: 16,
+ textAlign: null,
+ padding: { top: 16, bottom: 16, left: 24, right: 24 },
+ },
+ },
+ }),
+ },
+ {
+ label: 'Columns',
+ icon: ,
+ block: () => ({
+ type: 'ColumnsContainer',
+ data: {
+ props: {
+ columnsGap: 16,
+ columnsCount: 3,
+ columns: [{ childrenIds: [] }, { childrenIds: [] }, { childrenIds: [] }],
+ },
+ style: { padding: { top: 16, bottom: 16, left: 24, right: 24 } },
+ },
+ }),
+ },
+ {
+ label: 'Container',
+ icon: ,
+ block: () => ({
+ type: 'Container',
+ data: {
+ style: { padding: { top: 16, bottom: 16, left: 24, right: 24 } },
+ },
+ }),
+ },
+
+ // { label: 'ProgressBar', icon: , block: () => ({}) },
+ // { label: 'LoopContainer', icon: , block: () => ({}) },
+];
diff --git a/packages/post-kit-editor/src/email-builder-ui/documents/blocks/helpers/EditorChildrenIds/AddBlockMenu/index.tsx b/packages/post-kit-editor/src/email-builder-ui/documents/blocks/helpers/EditorChildrenIds/AddBlockMenu/index.tsx
new file mode 100644
index 0000000..69cd8ad
--- /dev/null
+++ b/packages/post-kit-editor/src/email-builder-ui/documents/blocks/helpers/EditorChildrenIds/AddBlockMenu/index.tsx
@@ -0,0 +1,37 @@
+import React, { useState } from 'react';
+
+import { TEditorBlock } from '../../../../editor/core';
+
+import BlocksMenu from './BlocksMenu';
+import DividerButton from './DividerButton';
+import PlaceholderButton from './PlaceholderButton';
+
+type Props = {
+ placeholder?: boolean;
+ onSelect: (block: TEditorBlock) => void;
+};
+export default function AddBlockButton({ onSelect, placeholder }: Props) {
+ const [menuAnchorEl, setMenuAnchorEl] = useState(null);
+ const [buttonElement, setButtonElement] = useState(null);
+
+ const handleButtonClick = () => {
+ setMenuAnchorEl(buttonElement);
+ };
+
+ const renderButton = () => {
+ if (placeholder) {
+ return ;
+ } else {
+ return ;
+ }
+ };
+
+ return (
+ <>
+
+ {renderButton()}
+
+
+ >
+ );
+}
diff --git a/packages/post-kit-editor/src/email-builder-ui/documents/blocks/helpers/EditorChildrenIds/index.tsx b/packages/post-kit-editor/src/email-builder-ui/documents/blocks/helpers/EditorChildrenIds/index.tsx
new file mode 100644
index 0000000..eee5313
--- /dev/null
+++ b/packages/post-kit-editor/src/email-builder-ui/documents/blocks/helpers/EditorChildrenIds/index.tsx
@@ -0,0 +1,58 @@
+import React, { Fragment } from 'react';
+
+import { TEditorBlock } from '../../../editor/core';
+import EditorBlock from '../../../editor/EditorBlock';
+
+import AddBlockButton from './AddBlockMenu';
+
+export type EditorChildrenChange = {
+ blockId: string;
+ block: TEditorBlock;
+ childrenIds: string[];
+};
+
+function generateId() {
+ return `block-${Date.now()}`;
+}
+
+export type EditorChildrenIdsProps = {
+ childrenIds: string[] | null | undefined;
+ onChange: (val: EditorChildrenChange) => void;
+};
+export default function EditorChildrenIds({ childrenIds, onChange }: EditorChildrenIdsProps) {
+ const appendBlock = (block: TEditorBlock) => {
+ const blockId = generateId();
+ return onChange({
+ blockId,
+ block,
+ childrenIds: [...(childrenIds || []), blockId],
+ });
+ };
+
+ const insertBlock = (block: TEditorBlock, index: number) => {
+ const blockId = generateId();
+ const newChildrenIds = [...(childrenIds || [])];
+ newChildrenIds.splice(index, 0, blockId);
+ return onChange({
+ blockId,
+ block,
+ childrenIds: newChildrenIds,
+ });
+ };
+
+ if (!childrenIds || childrenIds.length === 0) {
+ return ;
+ }
+
+ return (
+ <>
+ {childrenIds.map((childId, i) => (
+
+ insertBlock(block, i)} />
+
+
+ ))}
+
+ >
+ );
+}
diff --git a/packages/post-kit-editor/src/email-builder-ui/documents/blocks/helpers/TStyle.ts b/packages/post-kit-editor/src/email-builder-ui/documents/blocks/helpers/TStyle.ts
new file mode 100644
index 0000000..5903d1d
--- /dev/null
+++ b/packages/post-kit-editor/src/email-builder-ui/documents/blocks/helpers/TStyle.ts
@@ -0,0 +1,13 @@
+/* eslint-disable @typescript-eslint/no-explicit-any */
+
+export type TStyle = {
+ backgroundColor?: any;
+ borderColor?: any;
+ borderRadius?: any;
+ color?: any;
+ fontFamily?: any;
+ fontSize?: any;
+ fontWeight?: any;
+ padding?: any;
+ textAlign?: any;
+};
diff --git a/packages/post-kit-editor/src/email-builder-ui/documents/blocks/helpers/block-wrappers/EditorBlockWrapper.tsx b/packages/post-kit-editor/src/email-builder-ui/documents/blocks/helpers/block-wrappers/EditorBlockWrapper.tsx
new file mode 100644
index 0000000..300c1ea
--- /dev/null
+++ b/packages/post-kit-editor/src/email-builder-ui/documents/blocks/helpers/block-wrappers/EditorBlockWrapper.tsx
@@ -0,0 +1,72 @@
+import React, { CSSProperties, useState } from 'react';
+
+import { Box } from '@mui/material';
+
+import { useCurrentBlockId } from '../../../editor/EditorBlock';
+import { setSelectedBlockId, useSelectedBlockId } from '../../../editor/EditorContext';
+
+import TuneMenu from './TuneMenu';
+
+type TEditorBlockWrapperProps = {
+ children: JSX.Element;
+};
+
+export default function EditorBlockWrapper({ children }: TEditorBlockWrapperProps) {
+ const selectedBlockId = useSelectedBlockId();
+ const [mouseInside, setMouseInside] = useState(false);
+ const blockId = useCurrentBlockId();
+
+ let outline: CSSProperties['outline'];
+ if (selectedBlockId === blockId) {
+ outline = '2px solid rgba(0,121,204, 1)';
+ } else if (mouseInside) {
+ outline = '2px solid rgba(0,121,204, 0.3)';
+ }
+
+ const selectBlock = () => {
+ setSelectedBlockId(blockId);
+ };
+
+ const renderMenu = () => {
+ if (selectedBlockId !== blockId) {
+ return null;
+ }
+ return ;
+ };
+
+ return (
+ {
+ setMouseInside(true);
+ ev.stopPropagation();
+ }}
+ onMouseLeave={() => {
+ setMouseInside(false);
+ }}
+ onClick={(ev) => {
+ selectBlock();
+ ev.stopPropagation();
+ ev.preventDefault();
+ }}
+ onKeyDown={(ev) => {
+ if (ev.key === 'Enter' || ev.key === ' ') {
+ ev.preventDefault();
+ ev.stopPropagation();
+ selectBlock();
+ }
+ }}
+ >
+ {renderMenu()}
+ {children}
+
+ );
+}
diff --git a/packages/post-kit-editor/src/email-builder-ui/documents/blocks/helpers/block-wrappers/ReaderBlockWrapper.tsx b/packages/post-kit-editor/src/email-builder-ui/documents/blocks/helpers/block-wrappers/ReaderBlockWrapper.tsx
new file mode 100644
index 0000000..91a4c72
--- /dev/null
+++ b/packages/post-kit-editor/src/email-builder-ui/documents/blocks/helpers/block-wrappers/ReaderBlockWrapper.tsx
@@ -0,0 +1,26 @@
+import React, { CSSProperties } from 'react';
+
+import { TStyle } from '../TStyle';
+
+type TReaderBlockWrapperProps = {
+ style: TStyle;
+ children: JSX.Element;
+};
+
+export default function ReaderBlockWrapper({ style, children }: TReaderBlockWrapperProps) {
+ const { padding, borderColor, ...restStyle } = style;
+ const cssStyle: CSSProperties = {
+ ...restStyle,
+ };
+
+ if (padding) {
+ const { top, bottom, left, right } = padding;
+ cssStyle.padding = `${top}px ${right}px ${bottom}px ${left}px`;
+ }
+
+ if (borderColor) {
+ cssStyle.border = `1px solid ${borderColor}`;
+ }
+
+ return {children}
;
+}
diff --git a/packages/post-kit-editor/src/email-builder-ui/documents/blocks/helpers/block-wrappers/TuneMenu.tsx b/packages/post-kit-editor/src/email-builder-ui/documents/blocks/helpers/block-wrappers/TuneMenu.tsx
new file mode 100644
index 0000000..d7f38d7
--- /dev/null
+++ b/packages/post-kit-editor/src/email-builder-ui/documents/blocks/helpers/block-wrappers/TuneMenu.tsx
@@ -0,0 +1,221 @@
+import React from 'react';
+
+import {
+ ArrowDownwardOutlined,
+ ArrowUpwardOutlined,
+ ContentCopyOutlined,
+ DeleteOutlined,
+} from '@mui/icons-material';
+import { IconButton, Paper, Stack, SxProps, Tooltip } from '@mui/material';
+
+import { TEditorBlock, TEditorConfiguration } from '../../../editor/core';
+import { resetDocument, setSelectedBlockId, useDocument } from '../../../editor/EditorContext';
+import { ColumnsContainerProps } from '../../ColumnsContainer/ColumnsContainerPropsSchema';
+import cloneDocumentBlock from '../cloneDocumentBlock';
+import { deleteBlockSubtree } from '../deleteBlockSubtree';
+
+const sx: SxProps = {
+ position: 'absolute',
+ top: 0,
+ left: -56,
+ borderRadius: 64,
+ paddingX: 0.5,
+ paddingY: 1,
+ zIndex: 'fab',
+};
+
+function findParentBlockId(blockId: string, document: TEditorConfiguration) {
+ for (const [id, b] of Object.entries(document)) {
+ if (id === blockId) {
+ continue;
+ }
+ const block = b as TEditorBlock;
+ switch (block.type) {
+ case 'EmailLayout':
+ if (block.data.childrenIds?.includes(blockId)) {
+ return id;
+ }
+ break;
+ case 'Container':
+ if (block.data.props?.childrenIds?.includes(blockId)) {
+ return id;
+ }
+ break;
+ case 'ColumnsContainer':
+ if (block.data.props?.columns?.some((col) => col?.childrenIds?.includes(blockId))) {
+ return id;
+ }
+ break;
+ }
+ }
+ return null;
+}
+
+type Props = {
+ blockId: string;
+};
+export default function TuneMenu({ blockId }: Props) {
+ const document = useDocument();
+
+ const handleDuplicateClick = () => {
+ const parentBlockId = findParentBlockId(blockId, document);
+
+ const { document: newDocument, blockId: newBlockId } = cloneDocumentBlock(document, blockId);
+
+ if (parentBlockId) {
+ const parentBlock = newDocument[parentBlockId];
+ switch (parentBlock.type) {
+ case 'EmailLayout': {
+ if (!parentBlock.data.childrenIds) {
+ parentBlock.data.childrenIds = [];
+ }
+ const index = parentBlock.data.childrenIds.indexOf(blockId);
+ parentBlock.data.childrenIds.splice(index + 1, 0, newBlockId);
+ break;
+ }
+ case 'Container': {
+ if (!parentBlock.data.props) {
+ parentBlock.data.props = {};
+ }
+ if (!parentBlock.data.props.childrenIds) {
+ parentBlock.data.props.childrenIds = [];
+ }
+ const index = parentBlock.data.props.childrenIds.indexOf(blockId);
+ parentBlock.data.props.childrenIds.splice(index + 1, 0, newBlockId);
+ break;
+ }
+ case 'ColumnsContainer':
+ if (!parentBlock.data.props) {
+ parentBlock.data.props = {
+ columns: [{ childrenIds: [] }, { childrenIds: [] }, { childrenIds: [] }],
+ };
+ }
+ if (!parentBlock.data.props.columns) {
+ parentBlock.data.props.columns = [
+ { childrenIds: [] },
+ { childrenIds: [] },
+ { childrenIds: [] },
+ ];
+ }
+
+ for (const column of parentBlock.data.props.columns) {
+ if (!column?.childrenIds) {
+ continue;
+ }
+ if (column.childrenIds.includes(blockId)) {
+ const index = column.childrenIds.indexOf(blockId);
+ column.childrenIds.splice(index + 1, 0, newBlockId);
+ }
+ }
+ break;
+ }
+
+ resetDocument(newDocument);
+ setSelectedBlockId(newBlockId);
+ }
+ };
+
+ const handleDeleteClick = () => {
+ resetDocument(deleteBlockSubtree(document, blockId));
+ setSelectedBlockId(null);
+ };
+
+ const handleMoveClick = (direction: 'up' | 'down') => {
+ const moveChildrenIds = (ids: string[] | null | undefined) => {
+ if (!ids) {
+ return ids;
+ }
+ const index = ids.indexOf(blockId);
+ if (index < 0) {
+ return ids;
+ }
+
+ const childrenIds = [...ids];
+ if (direction === 'up' && index > 0) {
+ [childrenIds[index], childrenIds[index - 1]] = [childrenIds[index - 1], childrenIds[index]];
+ } else if (direction === 'down' && index < childrenIds.length - 1) {
+ [childrenIds[index], childrenIds[index + 1]] = [childrenIds[index + 1], childrenIds[index]];
+ }
+ return childrenIds;
+ };
+
+ const nDocument: typeof document = { ...document };
+ for (const [id, b] of Object.entries(nDocument)) {
+ const block = b as TEditorBlock;
+ if (id === blockId) {
+ continue;
+ }
+
+ switch (block.type) {
+ case 'EmailLayout':
+ nDocument[id] = {
+ ...block,
+ data: {
+ ...block.data,
+ childrenIds: moveChildrenIds(block.data.childrenIds),
+ },
+ };
+ break;
+ case 'Container':
+ nDocument[id] = {
+ ...block,
+ data: {
+ ...block.data,
+ props: {
+ ...block.data.props,
+ childrenIds: moveChildrenIds(block.data.props?.childrenIds),
+ },
+ },
+ };
+ break;
+ case 'ColumnsContainer':
+ nDocument[id] = {
+ type: 'ColumnsContainer',
+ data: {
+ style: block.data.style,
+ props: {
+ ...block.data.props,
+ columns: block.data.props?.columns?.map((c) => ({
+ ...c,
+ childrenIds: moveChildrenIds(c?.childrenIds) ?? [],
+ })),
+ },
+ } as ColumnsContainerProps,
+ };
+ break;
+ default:
+ nDocument[id] = block;
+ }
+ }
+
+ resetDocument(nDocument);
+ setSelectedBlockId(blockId);
+ };
+
+ return (
+ ev.stopPropagation()}>
+
+
+ handleMoveClick('up')} sx={{ color: 'text.primary' }}>
+
+
+
+
+ handleMoveClick('down')} sx={{ color: 'text.primary' }}>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/packages/post-kit-editor/src/email-builder-ui/documents/blocks/helpers/cloneDocumentBlock.spec.ts b/packages/post-kit-editor/src/email-builder-ui/documents/blocks/helpers/cloneDocumentBlock.spec.ts
new file mode 100644
index 0000000..34f190c
--- /dev/null
+++ b/packages/post-kit-editor/src/email-builder-ui/documents/blocks/helpers/cloneDocumentBlock.spec.ts
@@ -0,0 +1,48 @@
+import assert from 'node:assert/strict';
+import { describe, it } from 'node:test';
+
+import type { TEditorConfiguration } from '../../editor/core';
+import cloneDocumentBlock, { createBlockId } from './cloneDocumentBlock';
+
+describe('cloneDocumentBlock', () => {
+ it('creates collision-safe ids that do not overwrite existing keys', () => {
+ const document = {
+ root: { type: 'EmailLayout', data: { childrenIds: ['a'] } },
+ a: { type: 'Text', data: { props: { text: 'x' } } },
+ } as unknown as TEditorConfiguration;
+
+ const ids = new Set();
+ for (let i = 0; i < 50; i++) {
+ const id = createBlockId(document);
+ assert.equal(Object.prototype.hasOwnProperty.call(document, id), false);
+ assert.equal(ids.has(id), false);
+ ids.add(id);
+ document[id] = { type: 'Spacer', data: {} } as TEditorConfiguration[string];
+ }
+ });
+
+ it('clones ColumnsContainer with missing/partial columns without throwing', () => {
+ const document = {
+ root: { type: 'EmailLayout', data: { childrenIds: ['cols'] } },
+ cols: {
+ type: 'ColumnsContainer',
+ data: {
+ props: {
+ columns: [{ childrenIds: ['child'] }, undefined],
+ },
+ },
+ },
+ child: { type: 'Text', data: { props: { text: 'c' } } },
+ } as unknown as TEditorConfiguration;
+
+ const { document: next, blockId } = cloneDocumentBlock(document, 'cols');
+ assert.ok(next[blockId]);
+ const cloned = next[blockId] as {
+ type: string;
+ data: { props: { columns: Array<{ childrenIds: string[] } | undefined> } };
+ };
+ assert.equal(cloned.type, 'ColumnsContainer');
+ assert.equal(cloned.data.props.columns[0]?.childrenIds.length, 1);
+ assert.notEqual(cloned.data.props.columns[0]?.childrenIds[0], 'child');
+ });
+});
diff --git a/packages/post-kit-editor/src/email-builder-ui/documents/blocks/helpers/cloneDocumentBlock.ts b/packages/post-kit-editor/src/email-builder-ui/documents/blocks/helpers/cloneDocumentBlock.ts
new file mode 100644
index 0000000..0f2deb0
--- /dev/null
+++ b/packages/post-kit-editor/src/email-builder-ui/documents/blocks/helpers/cloneDocumentBlock.ts
@@ -0,0 +1,82 @@
+import { TEditorBlock, TEditorConfiguration } from '../../editor/core';
+
+type TResult = {
+ document: TEditorConfiguration;
+ blockId: string;
+};
+
+/** Collision-safe block id; prefers `crypto.randomUUID` when available. */
+export function createBlockId(document: TEditorConfiguration): string {
+ const taken = (id: string) => Object.prototype.hasOwnProperty.call(document, id);
+
+ if (typeof globalThis.crypto?.randomUUID === 'function') {
+ let id: string;
+ do {
+ id = `block-${globalThis.crypto.randomUUID()}`;
+ } while (taken(id));
+ return id;
+ }
+
+ let n = 0;
+ let id: string;
+ do {
+ id = `block-${Date.now().toString(36)}-${Math.floor(Math.random() * 1e9).toString(36)}-${n++}`;
+ } while (taken(id));
+ return id;
+}
+
+function cloneChildrenIds(document: TEditorConfiguration, blockIds: string[]): string[] {
+ return blockIds.map((blockId) => {
+ const newBlock = cloneBlock(document, blockId);
+ const newBlockId = createBlockId(document);
+ document[newBlockId] = newBlock;
+ return newBlockId;
+ });
+}
+
+function cloneBlock(document: TEditorConfiguration, blockId: string): TEditorBlock {
+ const clone = structuredClone(document[blockId]);
+ switch (clone.type) {
+ case 'EmailLayout':
+ throw new Error('Cloning EmailLayout blocks is not supported.');
+ case 'Avatar':
+ case 'Button':
+ case 'Divider':
+ case 'Heading':
+ case 'Html':
+ case 'Image':
+ case 'Spacer':
+ case 'Text':
+ return clone;
+ case 'Container':
+ if (clone.data?.props?.childrenIds) {
+ clone.data.props.childrenIds = cloneChildrenIds(document, clone.data.props.childrenIds);
+ }
+ return clone;
+ case 'ColumnsContainer': {
+ const props = clone.data?.props;
+ const columns = props?.columns;
+ if (props && columns) {
+ props.columns = columns.map((col) => ({
+ ...(col ?? { childrenIds: [] }),
+ childrenIds: cloneChildrenIds(document, col?.childrenIds ?? []),
+ })) as typeof columns;
+ }
+ return clone;
+ }
+ }
+}
+
+export default function cloneDocumentBlock(
+ originalDocument: TEditorConfiguration,
+ originalBlockId: string,
+): TResult {
+ const document = { ...originalDocument };
+ const cloned = cloneBlock(document, originalBlockId);
+ const blockId = createBlockId(document);
+ document[blockId] = cloned;
+ return {
+ document,
+ blockId,
+ };
+}
diff --git a/packages/post-kit-editor/src/email-builder-ui/documents/blocks/helpers/deleteBlockSubtree.spec.ts b/packages/post-kit-editor/src/email-builder-ui/documents/blocks/helpers/deleteBlockSubtree.spec.ts
new file mode 100644
index 0000000..d50fdaa
--- /dev/null
+++ b/packages/post-kit-editor/src/email-builder-ui/documents/blocks/helpers/deleteBlockSubtree.spec.ts
@@ -0,0 +1,61 @@
+import assert from 'node:assert/strict';
+import { describe, it } from 'node:test';
+
+import type { TEditorConfiguration } from '../../editor/core';
+import { collectSubtreeBlockIds, deleteBlockSubtree } from './deleteBlockSubtree';
+
+const nestedDoc = {
+ root: {
+ type: 'EmailLayout',
+ data: { childrenIds: ['container-1'] },
+ },
+ 'container-1': {
+ type: 'Container',
+ data: {
+ props: { childrenIds: ['text-1', 'columns-1'] },
+ },
+ },
+ 'text-1': {
+ type: 'Text',
+ data: { props: { text: 'hello' } },
+ },
+ 'columns-1': {
+ type: 'ColumnsContainer',
+ data: {
+ props: {
+ columns: [{ childrenIds: ['inner-a'] }, { childrenIds: [] }, undefined],
+ },
+ },
+ },
+ 'inner-a': {
+ type: 'Heading',
+ data: { props: { text: 'col' } },
+ },
+ 'orphan-keep': {
+ type: 'Spacer',
+ data: {},
+ },
+} as unknown as TEditorConfiguration;
+
+describe('deleteBlockSubtree', () => {
+ it('collects container and columns descendants', () => {
+ const ids = collectSubtreeBlockIds(nestedDoc, 'container-1');
+ assert.deepEqual(ids.sort(), ['columns-1', 'container-1', 'inner-a', 'text-1'].sort());
+ });
+
+ it('removes the full subtree and parent references without touching siblings', () => {
+ const next = deleteBlockSubtree(nestedDoc, 'container-1');
+ assert.equal(next['container-1'], undefined);
+ assert.equal(next['text-1'], undefined);
+ assert.equal(next['columns-1'], undefined);
+ assert.equal(next['inner-a'], undefined);
+ assert.ok(next['orphan-keep']);
+ assert.deepEqual((next.root as { data: { childrenIds: string[] } }).data.childrenIds, []);
+ });
+
+ it('tolerates missing columns when deleting a sibling', () => {
+ const next = deleteBlockSubtree(nestedDoc, 'orphan-keep');
+ assert.equal(next['orphan-keep'], undefined);
+ assert.ok(next['container-1']);
+ });
+});
diff --git a/packages/post-kit-editor/src/email-builder-ui/documents/blocks/helpers/deleteBlockSubtree.ts b/packages/post-kit-editor/src/email-builder-ui/documents/blocks/helpers/deleteBlockSubtree.ts
new file mode 100644
index 0000000..7dc3389
--- /dev/null
+++ b/packages/post-kit-editor/src/email-builder-ui/documents/blocks/helpers/deleteBlockSubtree.ts
@@ -0,0 +1,103 @@
+import { TEditorBlock, TEditorConfiguration } from '../../editor/core';
+import type { ColumnsContainerProps } from '../ColumnsContainer/ColumnsContainerPropsSchema';
+
+/** Collect `blockId` and all descendant block ids (Container / ColumnsContainer). */
+export function collectSubtreeBlockIds(document: TEditorConfiguration, blockId: string): string[] {
+ const ids: string[] = [];
+ const visit = (id: string) => {
+ ids.push(id);
+ const block = document[id] as TEditorBlock | undefined;
+ if (!block) {
+ return;
+ }
+ switch (block.type) {
+ case 'Container':
+ for (const childId of block.data.props?.childrenIds ?? []) {
+ visit(childId);
+ }
+ break;
+ case 'ColumnsContainer':
+ for (const col of block.data.props?.columns ?? []) {
+ for (const childId of col?.childrenIds ?? []) {
+ visit(childId);
+ }
+ }
+ break;
+ default:
+ break;
+ }
+ };
+ visit(blockId);
+ return ids;
+}
+
+/**
+ * Remove `blockId` (and its subtree) from parent references and delete those
+ * records from the document. Returns a shallow-copied document map.
+ */
+export function deleteBlockSubtree(
+ document: TEditorConfiguration,
+ blockId: string,
+): TEditorConfiguration {
+ const toDelete = new Set(collectSubtreeBlockIds(document, blockId));
+ const filterChildrenIds = (childrenIds: string[] | null | undefined) => {
+ if (!childrenIds) {
+ return childrenIds;
+ }
+ return childrenIds.filter((f) => !toDelete.has(f));
+ };
+
+ const nDocument: TEditorConfiguration = { ...document };
+ for (const [id, b] of Object.entries(nDocument)) {
+ const block = b as TEditorBlock;
+ if (toDelete.has(id)) {
+ continue;
+ }
+
+ switch (block.type) {
+ case 'EmailLayout':
+ nDocument[id] = {
+ ...block,
+ data: {
+ ...block.data,
+ childrenIds: filterChildrenIds(block.data.childrenIds),
+ },
+ };
+ break;
+ case 'Container':
+ nDocument[id] = {
+ ...block,
+ data: {
+ ...block.data,
+ props: {
+ ...block.data.props,
+ childrenIds: filterChildrenIds(block.data.props?.childrenIds),
+ },
+ },
+ };
+ break;
+ case 'ColumnsContainer':
+ nDocument[id] = {
+ type: 'ColumnsContainer',
+ data: {
+ style: block.data.style,
+ props: {
+ ...block.data.props,
+ columns: block.data.props?.columns?.map((c) => ({
+ ...(c ?? { childrenIds: [] }),
+ childrenIds: filterChildrenIds(c?.childrenIds) ?? [],
+ })),
+ },
+ } as ColumnsContainerProps,
+ };
+ break;
+ default:
+ nDocument[id] = block;
+ }
+ }
+
+ for (const id of toDelete) {
+ delete nDocument[id];
+ }
+ return nDocument;
+}
diff --git a/packages/post-kit-editor/src/email-builder-ui/documents/blocks/helpers/fontFamily.ts b/packages/post-kit-editor/src/email-builder-ui/documents/blocks/helpers/fontFamily.ts
new file mode 100644
index 0000000..8cebb5c
--- /dev/null
+++ b/packages/post-kit-editor/src/email-builder-ui/documents/blocks/helpers/fontFamily.ts
@@ -0,0 +1,62 @@
+export const FONT_FAMILIES = [
+ {
+ key: 'MODERN_SANS',
+ label: 'Modern sans',
+ value: '"Helvetica Neue", "Arial Nova", "Nimbus Sans", Arial, sans-serif',
+ },
+ {
+ key: 'BOOK_SANS',
+ label: 'Book sans',
+ value: 'Optima, Candara, "Noto Sans", source-sans-pro, sans-serif',
+ },
+ {
+ key: 'ORGANIC_SANS',
+ label: 'Organic sans',
+ value: 'Seravek, "Gill Sans Nova", Ubuntu, Calibri, "DejaVu Sans", source-sans-pro, sans-serif',
+ },
+ {
+ key: 'GEOMETRIC_SANS',
+ label: 'Geometric sans',
+ value:
+ 'Avenir, "Avenir Next LT Pro", Montserrat, Corbel, "URW Gothic", source-sans-pro, sans-serif',
+ },
+ {
+ key: 'HEAVY_SANS',
+ label: 'Heavy sans',
+ value:
+ 'Bahnschrift, "DIN Alternate", "Franklin Gothic Medium", "Nimbus Sans Narrow", sans-serif-condensed, sans-serif',
+ },
+ {
+ key: 'ROUNDED_SANS',
+ label: 'Rounded sans',
+ value:
+ 'ui-rounded, "Hiragino Maru Gothic ProN", Quicksand, Comfortaa, Manjari, "Arial Rounded MT Bold", Calibri, source-sans-pro, sans-serif',
+ },
+ {
+ key: 'MODERN_SERIF',
+ label: 'Modern serif',
+ value: 'Charter, "Bitstream Charter", "Sitka Text", Cambria, serif',
+ },
+ {
+ key: 'BOOK_SERIF',
+ label: 'Book serif',
+ value: '"Iowan Old Style", "Palatino Linotype", "URW Palladio L", P052, serif',
+ },
+ {
+ key: 'MONOSPACE',
+ label: 'Monospace',
+ value: '"Nimbus Mono PS", "Courier New", "Cutive Mono", monospace',
+ },
+];
+
+export const FONT_FAMILY_NAMES = [
+ 'MODERN_SANS',
+ 'BOOK_SANS',
+ 'ORGANIC_SANS',
+ 'GEOMETRIC_SANS',
+ 'HEAVY_SANS',
+ 'ROUNDED_SANS',
+ 'MODERN_SERIF',
+ 'BOOK_SERIF',
+ 'MONOSPACE',
+] as const;
diff --git a/packages/post-kit-editor/src/email-builder-ui/documents/blocks/helpers/zod.ts b/packages/post-kit-editor/src/email-builder-ui/documents/blocks/helpers/zod.ts
new file mode 100644
index 0000000..7c975e2
--- /dev/null
+++ b/packages/post-kit-editor/src/email-builder-ui/documents/blocks/helpers/zod.ts
@@ -0,0 +1,28 @@
+import { z } from 'zod';
+
+import { FONT_FAMILY_NAMES } from './fontFamily';
+
+export function zColor() {
+ return z.string().regex(/^#[0-9a-fA-F]{6}$/);
+}
+
+export function zFontFamily() {
+ return z.enum(FONT_FAMILY_NAMES);
+}
+
+export function zFontWeight() {
+ return z.enum(['bold', 'normal']);
+}
+
+export function zTextAlign() {
+ return z.enum(['left', 'center', 'right']);
+}
+
+export function zPadding() {
+ return z.object({
+ top: z.number(),
+ bottom: z.number(),
+ right: z.number(),
+ left: z.number(),
+ });
+}
diff --git a/packages/post-kit-editor/src/email-builder-ui/documents/editor/EditorBlock.tsx b/packages/post-kit-editor/src/email-builder-ui/documents/editor/EditorBlock.tsx
new file mode 100644
index 0000000..a600b37
--- /dev/null
+++ b/packages/post-kit-editor/src/email-builder-ui/documents/editor/EditorBlock.tsx
@@ -0,0 +1,29 @@
+import React, { createContext, useContext } from 'react';
+
+import { EditorBlock as CoreEditorBlock } from './core';
+import { useDocument } from './EditorContext';
+
+const EditorBlockContext = createContext(null);
+export const useCurrentBlockId = () => useContext(EditorBlockContext)!;
+
+type EditorBlockProps = {
+ id: string;
+};
+
+/**
+ *
+ * @param id - Block id
+ * @returns EditorBlock component that loads data from the EditorDocumentContext
+ */
+export default function EditorBlock({ id }: EditorBlockProps) {
+ const document = useDocument();
+ const block = document[id];
+ if (!block) {
+ throw new Error('Could not find block');
+ }
+ return (
+
+
+
+ );
+}
diff --git a/packages/post-kit-editor/src/email-builder-ui/documents/editor/EditorContext.tsx b/packages/post-kit-editor/src/email-builder-ui/documents/editor/EditorContext.tsx
new file mode 100644
index 0000000..f3db181
--- /dev/null
+++ b/packages/post-kit-editor/src/email-builder-ui/documents/editor/EditorContext.tsx
@@ -0,0 +1,137 @@
+import { useStore } from 'zustand';
+import { createStore } from 'zustand/vanilla';
+
+import { EMPTY_EMAIL } from '../../getConfiguration';
+
+import { TEditorConfiguration } from './core';
+
+type TValue = {
+ document: TEditorConfiguration;
+
+ selectedBlockId: string | null;
+ selectedSidebarTab: 'block-configuration' | 'styles';
+ selectedMainTab: 'editor' | 'preview' | 'json' | 'html';
+ selectedScreenSize: 'desktop' | 'mobile';
+
+ inspectorDrawerOpen: boolean;
+ samplesDrawerOpen: boolean;
+};
+
+const editorStateStore = createStore(() => ({
+ document: EMPTY_EMAIL,
+ selectedBlockId: null,
+ selectedSidebarTab: 'styles',
+ selectedMainTab: 'editor',
+ selectedScreenSize: 'desktop',
+
+ inspectorDrawerOpen: true,
+ samplesDrawerOpen: true,
+}));
+
+/**
+ * Zustand's SSR path uses `getInitialState` unless `getServerState` is set on
+ * the vanilla store API. Without this, `resetDocument` updates `getState()` but
+ * hooks still see EMPTY under `renderToStaticMarkup` / SSR.
+ */
+(
+ editorStateStore as typeof editorStateStore & {
+ getServerState: () => TValue;
+ }
+).getServerState = () => editorStateStore.getState();
+
+export function useDocument() {
+ return useStore(editorStateStore, (s) => s.document);
+}
+
+export function useSelectedBlockId() {
+ return useStore(editorStateStore, (s) => s.selectedBlockId);
+}
+
+export function useSelectedScreenSize() {
+ return useStore(editorStateStore, (s) => s.selectedScreenSize);
+}
+
+export function useSelectedMainTab() {
+ return useStore(editorStateStore, (s) => s.selectedMainTab);
+}
+
+export function setSelectedMainTab(selectedMainTab: TValue['selectedMainTab']) {
+ return editorStateStore.setState({ selectedMainTab });
+}
+
+export function useSelectedSidebarTab() {
+ return useStore(editorStateStore, (s) => s.selectedSidebarTab);
+}
+
+export function useInspectorDrawerOpen() {
+ return useStore(editorStateStore, (s) => s.inspectorDrawerOpen);
+}
+
+export function useSamplesDrawerOpen() {
+ return useStore(editorStateStore, (s) => s.samplesDrawerOpen);
+}
+
+export function setSelectedBlockId(selectedBlockId: TValue['selectedBlockId']) {
+ const selectedSidebarTab = selectedBlockId === null ? 'styles' : 'block-configuration';
+ const options: Partial = {};
+ if (selectedBlockId !== null) {
+ options.inspectorDrawerOpen = true;
+ }
+ return editorStateStore.setState({
+ selectedBlockId,
+ selectedSidebarTab,
+ ...options,
+ });
+}
+
+export function setSidebarTab(selectedSidebarTab: TValue['selectedSidebarTab']) {
+ return editorStateStore.setState({ selectedSidebarTab });
+}
+
+export function getDocument(): TEditorConfiguration {
+ return editorStateStore.getState().document;
+}
+
+export function resetDocument(document: TValue['document']) {
+ return editorStateStore.setState({
+ document,
+ selectedSidebarTab: 'styles',
+ selectedBlockId: null,
+ });
+}
+
+export function setDocument(document: TValue['document']) {
+ const originalDocument = editorStateStore.getState().document;
+ return editorStateStore.setState({
+ document: {
+ ...originalDocument,
+ ...document,
+ },
+ });
+}
+
+export function toggleInspectorDrawerOpen() {
+ const inspectorDrawerOpen = !editorStateStore.getState().inspectorDrawerOpen;
+ return editorStateStore.setState({ inspectorDrawerOpen });
+}
+
+export function toggleSamplesDrawerOpen() {
+ const samplesDrawerOpen = !editorStateStore.getState().samplesDrawerOpen;
+ return editorStateStore.setState({ samplesDrawerOpen });
+}
+
+export function setSelectedScreenSize(selectedScreenSize: TValue['selectedScreenSize']) {
+ return editorStateStore.setState({ selectedScreenSize });
+}
+
+/**
+ * Subscribe to document changes in the editor store.
+ * Returns an unsubscribe function.
+ */
+export function subscribeDocument(listener: (document: TEditorConfiguration) => void): () => void {
+ return editorStateStore.subscribe((state, prevState) => {
+ if (state.document !== prevState.document) {
+ listener(state.document);
+ }
+ });
+}
diff --git a/packages/post-kit-editor/src/email-builder-ui/documents/editor/core.tsx b/packages/post-kit-editor/src/email-builder-ui/documents/editor/core.tsx
new file mode 100644
index 0000000..2314311
--- /dev/null
+++ b/packages/post-kit-editor/src/email-builder-ui/documents/editor/core.tsx
@@ -0,0 +1,127 @@
+import React from 'react';
+import { z } from 'zod';
+
+import { Avatar, AvatarPropsSchema } from '@usewaypoint/block-avatar';
+import { Button, ButtonPropsSchema } from '@usewaypoint/block-button';
+import { Divider, DividerPropsSchema } from '@usewaypoint/block-divider';
+import { Heading, HeadingPropsSchema } from '@usewaypoint/block-heading';
+import { Html, HtmlPropsSchema } from '@usewaypoint/block-html';
+import { Image, ImagePropsSchema } from '@usewaypoint/block-image';
+import { Spacer, SpacerPropsSchema } from '@usewaypoint/block-spacer';
+import { Text, TextPropsSchema } from '@usewaypoint/block-text';
+import {
+ buildBlockComponent,
+ buildBlockConfigurationDictionary,
+ buildBlockConfigurationSchema,
+} from '@usewaypoint/document-core';
+
+import ColumnsContainerEditor from '../blocks/ColumnsContainer/ColumnsContainerEditor';
+import ColumnsContainerPropsSchema from '../blocks/ColumnsContainer/ColumnsContainerPropsSchema';
+import ContainerEditor from '../blocks/Container/ContainerEditor';
+import ContainerPropsSchema from '../blocks/Container/ContainerPropsSchema';
+import EmailLayoutEditor from '../blocks/EmailLayout/EmailLayoutEditor';
+import EmailLayoutPropsSchema from '../blocks/EmailLayout/EmailLayoutPropsSchema';
+import EditorBlockWrapper from '../blocks/helpers/block-wrappers/EditorBlockWrapper';
+
+const EDITOR_DICTIONARY = buildBlockConfigurationDictionary({
+ Avatar: {
+ schema: AvatarPropsSchema,
+ Component: (props) => (
+
+
+
+ ),
+ },
+ Button: {
+ schema: ButtonPropsSchema,
+ Component: (props) => (
+
+
+
+ ),
+ },
+ Container: {
+ schema: ContainerPropsSchema,
+ Component: (props) => (
+
+
+
+ ),
+ },
+ ColumnsContainer: {
+ schema: ColumnsContainerPropsSchema,
+ Component: (props) => (
+
+
+
+ ),
+ },
+ Heading: {
+ schema: HeadingPropsSchema,
+ Component: (props) => (
+
+
+
+ ),
+ },
+ Html: {
+ schema: HtmlPropsSchema,
+ Component: (props) => (
+
+
+
+ ),
+ },
+ Image: {
+ schema: ImagePropsSchema,
+ Component: (data) => {
+ const props = {
+ ...data,
+ props: {
+ ...data.props,
+ url: data.props?.url ?? '',
+ },
+ };
+ return (
+
+
+
+ );
+ },
+ },
+ Text: {
+ schema: TextPropsSchema,
+ Component: (props) => (
+
+
+
+ ),
+ },
+ EmailLayout: {
+ schema: EmailLayoutPropsSchema,
+ Component: (p) => ,
+ },
+ Spacer: {
+ schema: SpacerPropsSchema,
+ Component: (props) => (
+
+
+
+ ),
+ },
+ Divider: {
+ schema: DividerPropsSchema,
+ Component: (props) => (
+
+
+
+ ),
+ },
+});
+
+export const EditorBlock = buildBlockComponent(EDITOR_DICTIONARY);
+export const EditorBlockSchema = buildBlockConfigurationSchema(EDITOR_DICTIONARY);
+export const EditorConfigurationSchema = z.record(z.string(), EditorBlockSchema);
+
+export type TEditorBlock = z.infer;
+export type TEditorConfiguration = Record;
diff --git a/packages/post-kit-editor/src/email-builder-ui/getConfiguration/index.tsx b/packages/post-kit-editor/src/email-builder-ui/getConfiguration/index.tsx
new file mode 100644
index 0000000..acade7c
--- /dev/null
+++ b/packages/post-kit-editor/src/email-builder-ui/getConfiguration/index.tsx
@@ -0,0 +1,48 @@
+import EMPTY_EMAIL_MESSAGE from './sample/empty-email-message';
+import ONE_TIME_PASSCODE from './sample/one-time-passcode';
+import ORDER_ECOMMERCE from './sample/order-ecommerce';
+import POST_METRICS_REPORT from './sample/post-metrics-report';
+import RESERVATION_REMINDER from './sample/reservation-reminder';
+import RESET_PASSWORD from './sample/reset-password';
+import RESPOND_TO_MESSAGE from './sample/respond-to-message';
+import SUBSCRIPTION_RECEIPT from './sample/subscription-receipt';
+import WELCOME from './sample/welcome';
+
+/** Default empty email document — safe for SSR / Node (no `window`). */
+export const EMPTY_EMAIL = EMPTY_EMAIL_MESSAGE;
+
+export default function getConfiguration(template: string) {
+ if (template.startsWith('#sample/')) {
+ const sampleName = template.replace('#sample/', '');
+ switch (sampleName) {
+ case 'welcome':
+ return WELCOME;
+ case 'one-time-password':
+ return ONE_TIME_PASSCODE;
+ case 'order-ecomerce':
+ return ORDER_ECOMMERCE;
+ case 'post-metrics-report':
+ return POST_METRICS_REPORT;
+ case 'reservation-reminder':
+ return RESERVATION_REMINDER;
+ case 'reset-password':
+ return RESET_PASSWORD;
+ case 'respond-to-message':
+ return RESPOND_TO_MESSAGE;
+ case 'subscription-receipt':
+ return SUBSCRIPTION_RECEIPT;
+ }
+ }
+
+ if (typeof atob === 'function' && template.startsWith('#code/')) {
+ const encodedString = template.replace('#code/', '');
+ const configurationString = decodeURIComponent(atob(encodedString));
+ try {
+ return JSON.parse(configurationString);
+ } catch {
+ console.error(`Couldn't load configuration from hash.`);
+ }
+ }
+
+ return EMPTY_EMAIL;
+}
diff --git a/packages/post-kit-editor/src/email-builder-ui/getConfiguration/sample/empty-email-message.ts b/packages/post-kit-editor/src/email-builder-ui/getConfiguration/sample/empty-email-message.ts
new file mode 100644
index 0000000..89dcec4
--- /dev/null
+++ b/packages/post-kit-editor/src/email-builder-ui/getConfiguration/sample/empty-email-message.ts
@@ -0,0 +1,16 @@
+import { TEditorConfiguration } from '../../documents/editor/core';
+
+const EMPTY_EMAIL_MESSAGE: TEditorConfiguration = {
+ root: {
+ type: 'EmailLayout',
+ data: {
+ backdropColor: '#F5F5F5',
+ canvasColor: '#FFFFFF',
+ textColor: '#262626',
+ fontFamily: 'MODERN_SANS',
+ childrenIds: [],
+ },
+ },
+};
+
+export default EMPTY_EMAIL_MESSAGE;
diff --git a/packages/post-kit-editor/src/email-builder-ui/getConfiguration/sample/one-time-passcode.ts b/packages/post-kit-editor/src/email-builder-ui/getConfiguration/sample/one-time-passcode.ts
new file mode 100644
index 0000000..190e5c0
--- /dev/null
+++ b/packages/post-kit-editor/src/email-builder-ui/getConfiguration/sample/one-time-passcode.ts
@@ -0,0 +1,130 @@
+import { TEditorConfiguration } from '../../documents/editor/core';
+
+const ONE_TIME_PASSCODE: TEditorConfiguration = {
+ root: {
+ type: 'EmailLayout',
+ data: {
+ backdropColor: '#000000',
+ canvasColor: '#000000',
+ textColor: '#FFFFFF',
+ fontFamily: 'BOOK_SERIF',
+ childrenIds: [
+ 'block_ChPX66qUhF46uynDE8AY11',
+ 'block_CkNrtQgkqPt2YWLv1hr5eJ',
+ 'block_BFLBa3q5y8kax9KngyXP65',
+ 'block_4T7sDFb4rqbSyWjLGJKmov',
+ 'block_Rvc8ZfTjfhXjpphHquJKvP',
+ ],
+ },
+ },
+ block_ChPX66qUhF46uynDE8AY11: {
+ type: 'Image',
+ data: {
+ style: {
+ backgroundColor: null,
+ padding: {
+ top: 24,
+ bottom: 24,
+ left: 24,
+ right: 24,
+ },
+ textAlign: 'center',
+ },
+ props: {
+ height: 24,
+ url: 'https://d1iiu589g39o6c.cloudfront.net/live/platforms/platform_A9wwKSL6EV6orh6f/images/wptemplateimage_jc7ZfPvdHJ6rtH1W/&.png',
+ contentAlignment: 'middle',
+ },
+ },
+ },
+ block_CkNrtQgkqPt2YWLv1hr5eJ: {
+ type: 'Text',
+ data: {
+ style: {
+ color: '#ffffff',
+ backgroundColor: null,
+ fontSize: 16,
+ fontFamily: null,
+ fontWeight: 'normal',
+ textAlign: 'center',
+ padding: {
+ top: 16,
+ bottom: 16,
+ left: 24,
+ right: 24,
+ },
+ },
+ props: {
+ text: 'Here is your one-time passcode:',
+ },
+ },
+ },
+ block_BFLBa3q5y8kax9KngyXP65: {
+ type: 'Heading',
+ data: {
+ style: {
+ color: null,
+ backgroundColor: null,
+ fontFamily: 'MONOSPACE',
+ fontWeight: 'bold',
+ textAlign: 'center',
+ padding: {
+ top: 16,
+ bottom: 16,
+ left: 24,
+ right: 24,
+ },
+ },
+ props: {
+ level: 'h1',
+ text: '0123456',
+ },
+ },
+ },
+ block_4T7sDFb4rqbSyWjLGJKmov: {
+ type: 'Text',
+ data: {
+ style: {
+ color: '#868686',
+ backgroundColor: null,
+ fontSize: 16,
+ fontFamily: null,
+ fontWeight: 'normal',
+ textAlign: 'center',
+ padding: {
+ top: 16,
+ bottom: 16,
+ left: 24,
+ right: 24,
+ },
+ },
+ props: {
+ text: 'This code will expire in 30 minutes.',
+ },
+ },
+ },
+ block_Rvc8ZfTjfhXjpphHquJKvP: {
+ type: 'Text',
+ data: {
+ style: {
+ color: '#868686',
+ backgroundColor: null,
+ fontSize: 14,
+ fontFamily: null,
+ fontWeight: 'normal',
+ textAlign: 'center',
+ padding: {
+ top: 16,
+ bottom: 16,
+ left: 24,
+ right: 24,
+ },
+ },
+ props: {
+ text: 'Problems? Just reply to this email.',
+ },
+ },
+ },
+};
+
+export default ONE_TIME_PASSCODE;
diff --git a/packages/post-kit-editor/src/email-builder-ui/getConfiguration/sample/order-ecommerce.ts b/packages/post-kit-editor/src/email-builder-ui/getConfiguration/sample/order-ecommerce.ts
new file mode 100644
index 0000000..7f2ce0f
--- /dev/null
+++ b/packages/post-kit-editor/src/email-builder-ui/getConfiguration/sample/order-ecommerce.ts
@@ -0,0 +1,1445 @@
+import { TEditorConfiguration } from '../../documents/editor/core';
+
+const ORDER_ECOMMERCE: TEditorConfiguration = {
+ root: {
+ type: 'EmailLayout',
+ data: {
+ backdropColor: '#FFFFFF',
+ canvasColor: '#FFFFFF',
+ textColor: '#333333',
+ fontFamily: 'MODERN_SANS',
+ childrenIds: [
+ 'block_Ei34o65X5XnD5dexNQgXh8',
+ 'block_SLut2hpFsy7U6SmhtLtWNU',
+ 'block_RrwLc5YMpHJGE5Xe9fAZVW',
+ 'block_FHCeHrN3XNaH7bu6HhjVNT',
+ 'block_3vynUg15EevMA6DiLsWJk2',
+ 'block_5eQPGdKJ6JQYXCD1MEaHbv',
+ 'block_WK3b19BzGE8VNKDwiSZ8s8',
+ 'block_Pe2Dm5Cbqq5CcjL5wEdMg4',
+ 'block_SizNWJDCcqw9tGH2ComdDX',
+ 'block_4Qxv32WQo8pDzPu1d3vixz',
+ 'block_LKiZYDTPeJGkRWHForFSDQ',
+ 'block_P6XtJj721vPfrhXKzS8uR5',
+ 'block_AKTwpjBmtevcfj82z52i8p',
+ ],
+ },
+ },
+ block_PHe2XSbV4RvD76p21F3VdN: {
+ type: 'Heading',
+ data: {
+ style: {
+ color: null,
+ backgroundColor: null,
+ fontFamily: null,
+ fontWeight: 'normal',
+ textAlign: 'left',
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ level: 'h2',
+ text: 'Unbranded Supply',
+ },
+ },
+ },
+ block_DeTzPQDerYjBEMkt6TuD41: {
+ type: 'Container',
+ data: {
+ style: {
+ backgroundColor: null,
+ borderColor: null,
+ borderRadius: null,
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ childrenIds: ['block_PHe2XSbV4RvD76p21F3VdN'],
+ },
+ },
+ },
+ block_TPxZn2Fjxc7MwgXAEybxkV: {
+ type: 'Text',
+ data: {
+ style: {
+ color: '#808080',
+ backgroundColor: null,
+ fontSize: 14,
+ fontFamily: null,
+ fontWeight: 'normal',
+ textAlign: 'right',
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ text: '#103571871',
+ },
+ },
+ },
+ block_5fuNN9F4uZRTGa4Hy5F4Nd: {
+ type: 'Container',
+ data: {
+ style: {
+ backgroundColor: null,
+ borderColor: null,
+ borderRadius: null,
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ childrenIds: ['block_TPxZn2Fjxc7MwgXAEybxkV'],
+ },
+ },
+ },
+ block_Ei34o65X5XnD5dexNQgXh8: {
+ type: 'ColumnsContainer',
+ data: {
+ style: {
+ backgroundColor: null,
+ padding: {
+ top: 16,
+ bottom: 24,
+ left: 24,
+ right: 24,
+ },
+ },
+ props: {
+ columnsCount: 2,
+ columns: [
+ {
+ childrenIds: ['block_DeTzPQDerYjBEMkt6TuD41'],
+ },
+ {
+ childrenIds: ['block_5fuNN9F4uZRTGa4Hy5F4Nd'],
+ },
+ {
+ childrenIds: [],
+ },
+ ],
+ },
+ },
+ },
+ block_SLut2hpFsy7U6SmhtLtWNU: {
+ type: 'Heading',
+ data: {
+ style: {
+ color: null,
+ backgroundColor: null,
+ fontFamily: null,
+ fontWeight: 'normal',
+ textAlign: 'left',
+ padding: {
+ top: 16,
+ bottom: 0,
+ left: 24,
+ right: 24,
+ },
+ },
+ props: {
+ level: 'h3',
+ text: 'Thank you for your purchase!',
+ },
+ },
+ },
+ block_RrwLc5YMpHJGE5Xe9fAZVW: {
+ type: 'Text',
+ data: {
+ style: {
+ color: '#404040',
+ backgroundColor: null,
+ fontSize: 16,
+ fontFamily: null,
+ fontWeight: 'normal',
+ textAlign: 'left',
+ padding: {
+ top: 16,
+ bottom: 16,
+ left: 24,
+ right: 24,
+ },
+ },
+ props: {
+ text: 'Hi Jordan,\n\nWe are preparing your package. Your tracking number will be generated once the package is ready to ship.',
+ },
+ },
+ },
+ block_FHCeHrN3XNaH7bu6HhjVNT: {
+ type: 'Button',
+ data: {
+ style: {
+ backgroundColor: null,
+ fontSize: 16,
+ fontFamily: null,
+ fontWeight: 'normal',
+ textAlign: 'left',
+ padding: {
+ top: 16,
+ bottom: 40,
+ left: 24,
+ right: 24,
+ },
+ },
+ props: {
+ buttonBackgroundColor: '#5696E5',
+ buttonStyle: 'rounded',
+ buttonTextColor: '#FFFFFF',
+ fullWidth: false,
+ size: 'large',
+ text: 'View your order',
+ url: 'https://example.usewaypoint.com/orders/103571871',
+ },
+ },
+ },
+ block_3vynUg15EevMA6DiLsWJk2: {
+ type: 'Heading',
+ data: {
+ style: {
+ color: null,
+ backgroundColor: null,
+ fontFamily: null,
+ fontWeight: 'normal',
+ textAlign: 'left',
+ padding: {
+ top: 16,
+ bottom: 0,
+ left: 24,
+ right: 24,
+ },
+ },
+ props: {
+ level: 'h3',
+ text: 'Order summary',
+ },
+ },
+ },
+ block_Dvs2GYcF6SzfCYYNwfv1oM: {
+ type: 'Image',
+ data: {
+ style: {
+ backgroundColor: null,
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ textAlign: 'left',
+ },
+ props: {
+ url: 'https://d1iiu589g39o6c.cloudfront.net/live/platforms/platform_A9wwKSL6EV6orh6f/images/wptemplateimage_FBfTeYhbdXtqYpCA/kiran-ck-6rXpQzfCYlw-unsplash.jpg',
+ alt: '',
+ linkHref: null,
+ contentAlignment: 'middle',
+ },
+ },
+ },
+ block_KnMb2mQSf7nz8HXx6jyDUV: {
+ type: 'Container',
+ data: {
+ style: {
+ backgroundColor: null,
+ borderColor: null,
+ borderRadius: null,
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ childrenIds: ['block_Dvs2GYcF6SzfCYYNwfv1oM'],
+ },
+ },
+ },
+ block_4sAeV4cLVKV8y4QFGV3Gf7: {
+ type: 'Container',
+ data: {
+ style: {
+ backgroundColor: null,
+ borderColor: null,
+ borderRadius: null,
+ padding: {
+ top: 4,
+ bottom: 4,
+ left: 4,
+ right: 4,
+ },
+ },
+ props: {
+ childrenIds: ['block_KnMb2mQSf7nz8HXx6jyDUV'],
+ },
+ },
+ },
+ block_TBZZDHvoKHcW3j2nwQgmhC: {
+ type: 'Text',
+ data: {
+ style: {
+ color: null,
+ backgroundColor: null,
+ fontSize: 16,
+ fontFamily: null,
+ fontWeight: 'bold',
+ textAlign: 'left',
+ padding: {
+ top: 0,
+ bottom: 4,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ text: 'Unbranded Pen x 5',
+ },
+ },
+ },
+ block_KqpmZUcZajsnFMbTMhizZs: {
+ type: 'Text',
+ data: {
+ style: {
+ color: '#808080',
+ backgroundColor: null,
+ fontSize: 14,
+ fontFamily: null,
+ fontWeight: 'normal',
+ textAlign: 'left',
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ text: 'Black / Black ink',
+ },
+ },
+ },
+ block_92JgagxxVxzyfnbvj5iUUJ: {
+ type: 'Container',
+ data: {
+ style: {
+ backgroundColor: null,
+ borderColor: null,
+ borderRadius: null,
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ childrenIds: [],
+ },
+ },
+ },
+ block_9bNBgfiJJyh65pfsy1fu7e: {
+ type: 'Text',
+ data: {
+ style: {
+ color: '#808080',
+ backgroundColor: null,
+ fontSize: 12,
+ fontFamily: null,
+ fontWeight: 'normal',
+ textAlign: 'left',
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ text: '($5.00)',
+ },
+ },
+ },
+ block_7JwR5SHM2Bfjihamh45tRL: {
+ type: 'Container',
+ data: {
+ style: {
+ backgroundColor: null,
+ borderColor: null,
+ borderRadius: null,
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ childrenIds: [],
+ },
+ },
+ },
+ block_GQ77o7MDMwj48B5nocEJNq: {
+ type: 'Container',
+ data: {
+ style: {
+ backgroundColor: null,
+ borderColor: null,
+ borderRadius: null,
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ childrenIds: ['block_TBZZDHvoKHcW3j2nwQgmhC', 'block_KqpmZUcZajsnFMbTMhizZs'],
+ },
+ },
+ },
+ block_6VvTYskm3BULZZEfYCkjwN: {
+ type: 'Text',
+ data: {
+ style: {
+ color: null,
+ backgroundColor: null,
+ fontSize: 16,
+ fontFamily: null,
+ fontWeight: 'bold',
+ textAlign: 'right',
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ text: '$25.00',
+ },
+ },
+ },
+ block_V34TMZ9yg6t4xKMYfyD2Rk: {
+ type: 'Container',
+ data: {
+ style: {
+ backgroundColor: null,
+ borderColor: null,
+ borderRadius: null,
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ childrenIds: ['block_6VvTYskm3BULZZEfYCkjwN'],
+ },
+ },
+ },
+ block_YBtUndjQaRuLFpEkgvjagk: {
+ type: 'ColumnsContainer',
+ data: {
+ style: {
+ backgroundColor: null,
+ padding: {
+ top: 0,
+ bottom: 0,
+ right: 0,
+ left: 0,
+ },
+ },
+ props: {
+ fixedWidths: [64, null, 80],
+ columnsCount: 3,
+ columnsGap: 16,
+ columns: [
+ {
+ childrenIds: ['block_4sAeV4cLVKV8y4QFGV3Gf7'],
+ },
+ {
+ childrenIds: ['block_GQ77o7MDMwj48B5nocEJNq'],
+ },
+ {
+ childrenIds: ['block_V34TMZ9yg6t4xKMYfyD2Rk'],
+ },
+ ],
+ },
+ },
+ },
+ block_XykBAUxf8awiR2CxBfNLZN: {
+ type: 'Container',
+ data: {
+ style: {
+ backgroundColor: null,
+ borderColor: null,
+ borderRadius: null,
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ childrenIds: ['block_YBtUndjQaRuLFpEkgvjagk'],
+ },
+ },
+ },
+ block_9AvGGkcg4Rq93DmxXjXwEP: {
+ type: 'Divider',
+ data: {
+ style: {
+ backgroundColor: null,
+ padding: {
+ top: 8,
+ bottom: 8,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ lineHeight: 1,
+ lineColor: '#EEEEEE',
+ },
+ },
+ },
+ block_5eQPGdKJ6JQYXCD1MEaHbv: {
+ type: 'Container',
+ data: {
+ style: {
+ backgroundColor: null,
+ borderColor: null,
+ borderRadius: null,
+ padding: {
+ top: 16,
+ bottom: 16,
+ left: 24,
+ right: 24,
+ },
+ },
+ props: {
+ childrenIds: ['block_XykBAUxf8awiR2CxBfNLZN', 'block_9AvGGkcg4Rq93DmxXjXwEP'],
+ },
+ },
+ },
+ block_Wnt477QxYNynetWGwkLg89: {
+ type: 'Container',
+ data: {
+ style: {
+ backgroundColor: null,
+ borderColor: null,
+ borderRadius: null,
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ childrenIds: [],
+ },
+ },
+ },
+ block_FnzMxssTraByh9FbbnJvRw: {
+ type: 'Text',
+ data: {
+ style: {
+ color: '#808080',
+ backgroundColor: null,
+ fontSize: 16,
+ fontFamily: null,
+ fontWeight: 'normal',
+ textAlign: 'left',
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ text: 'Discount (BLKFRI)',
+ },
+ },
+ },
+ block_NyMsczQB7L3BKUa1RpUauu: {
+ type: 'Container',
+ data: {
+ style: {
+ backgroundColor: null,
+ borderColor: null,
+ borderRadius: null,
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ childrenIds: ['block_FnzMxssTraByh9FbbnJvRw'],
+ },
+ },
+ },
+ block_AcbYK2jnr4sqG1GAbUHqNL: {
+ type: 'Text',
+ data: {
+ style: {
+ color: null,
+ backgroundColor: null,
+ fontSize: 16,
+ fontFamily: null,
+ fontWeight: 'bold',
+ textAlign: 'right',
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ text: '$5.00',
+ },
+ },
+ },
+ block_BgHgpkAxq2qJUttN8pimnE: {
+ type: 'Container',
+ data: {
+ style: {
+ backgroundColor: null,
+ borderColor: null,
+ borderRadius: null,
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ childrenIds: ['block_AcbYK2jnr4sqG1GAbUHqNL'],
+ },
+ },
+ },
+ block_2QEX1chf5uWU5ZZebf4gmu: {
+ type: 'ColumnsContainer',
+ data: {
+ style: {
+ backgroundColor: null,
+ padding: {
+ top: 4,
+ bottom: 4,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ columnsCount: 2,
+ columns: [
+ {
+ childrenIds: ['block_NyMsczQB7L3BKUa1RpUauu'],
+ },
+ {
+ childrenIds: ['block_BgHgpkAxq2qJUttN8pimnE'],
+ },
+ {
+ childrenIds: [],
+ },
+ ],
+ },
+ },
+ },
+ block_Q87SYRrWCwfRa56q3qLBDG: {
+ type: 'Text',
+ data: {
+ style: {
+ color: '#808080',
+ backgroundColor: null,
+ fontSize: 16,
+ fontFamily: null,
+ fontWeight: 'normal',
+ textAlign: 'left',
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ text: 'Subtotal',
+ },
+ },
+ },
+ block_QUNbV1p7f9qaSEvyLmnxmD: {
+ type: 'Container',
+ data: {
+ style: {
+ backgroundColor: null,
+ borderColor: null,
+ borderRadius: null,
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ childrenIds: ['block_Q87SYRrWCwfRa56q3qLBDG'],
+ },
+ },
+ },
+ block_cF6MdfmbKisXytmfPU4QY: {
+ type: 'Text',
+ data: {
+ style: {
+ color: null,
+ backgroundColor: null,
+ fontSize: 16,
+ fontFamily: null,
+ fontWeight: 'bold',
+ textAlign: 'right',
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ text: '$25.00',
+ },
+ },
+ },
+ block_4WkcuRqzqvTFvHGSBLGAAG: {
+ type: 'Container',
+ data: {
+ style: {
+ backgroundColor: null,
+ borderColor: null,
+ borderRadius: null,
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ childrenIds: ['block_cF6MdfmbKisXytmfPU4QY'],
+ },
+ },
+ },
+ block_Lu666sTP6hqLkHiBThmm4G: {
+ type: 'ColumnsContainer',
+ data: {
+ style: {
+ backgroundColor: null,
+ padding: {
+ top: 4,
+ bottom: 4,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ columnsCount: 2,
+ columns: [
+ {
+ childrenIds: ['block_QUNbV1p7f9qaSEvyLmnxmD'],
+ },
+ {
+ childrenIds: ['block_4WkcuRqzqvTFvHGSBLGAAG'],
+ },
+ {
+ childrenIds: [],
+ },
+ ],
+ },
+ },
+ },
+ block_GgUZQtnTQbiBFhMdJE5YYB: {
+ type: 'Text',
+ data: {
+ style: {
+ color: '#808080',
+ backgroundColor: null,
+ fontSize: 16,
+ fontFamily: null,
+ fontWeight: 'normal',
+ textAlign: 'left',
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ text: 'Shipping',
+ },
+ },
+ },
+ block_XzjZ3cnqJrKrwubFwD4VSr: {
+ type: 'Container',
+ data: {
+ style: {
+ backgroundColor: null,
+ borderColor: null,
+ borderRadius: null,
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ childrenIds: ['block_GgUZQtnTQbiBFhMdJE5YYB'],
+ },
+ },
+ },
+ block_ViKMzYtjFGRksFuzRn5rny: {
+ type: 'Text',
+ data: {
+ style: {
+ color: null,
+ backgroundColor: null,
+ fontSize: 16,
+ fontFamily: null,
+ fontWeight: 'bold',
+ textAlign: 'right',
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ text: '$5.00',
+ },
+ },
+ },
+ block_AcDrP2ZMVByFU269wvMgtw: {
+ type: 'Container',
+ data: {
+ style: {
+ backgroundColor: null,
+ borderColor: null,
+ borderRadius: null,
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ childrenIds: ['block_ViKMzYtjFGRksFuzRn5rny'],
+ },
+ },
+ },
+ block_Gn2h8bajFuW8zDBsiVVitV: {
+ type: 'ColumnsContainer',
+ data: {
+ style: {
+ backgroundColor: null,
+ padding: {
+ top: 4,
+ bottom: 4,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ columnsCount: 2,
+ columns: [
+ {
+ childrenIds: ['block_XzjZ3cnqJrKrwubFwD4VSr'],
+ },
+ {
+ childrenIds: ['block_AcDrP2ZMVByFU269wvMgtw'],
+ },
+ {
+ childrenIds: [],
+ },
+ ],
+ },
+ },
+ },
+ block_64aQbVaGVuFmvjPfr9hend: {
+ type: 'Text',
+ data: {
+ style: {
+ color: '#808080',
+ backgroundColor: null,
+ fontSize: 16,
+ fontFamily: null,
+ fontWeight: 'normal',
+ textAlign: 'left',
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ text: 'Taxes',
+ },
+ },
+ },
+ block_QTKa4AdGxtSbJmRY7ytjD8: {
+ type: 'Container',
+ data: {
+ style: {
+ backgroundColor: null,
+ borderColor: null,
+ borderRadius: null,
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ childrenIds: ['block_64aQbVaGVuFmvjPfr9hend'],
+ },
+ },
+ },
+ block_H1MgkbcNH4sz8pMC2h4dVh: {
+ type: 'Text',
+ data: {
+ style: {
+ color: null,
+ backgroundColor: null,
+ fontSize: 16,
+ fontFamily: null,
+ fontWeight: 'bold',
+ textAlign: 'right',
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ text: '$0.00',
+ },
+ },
+ },
+ block_Mi7A4sXfSXaEcjFV17WgSg: {
+ type: 'Container',
+ data: {
+ style: {
+ backgroundColor: null,
+ borderColor: null,
+ borderRadius: null,
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ childrenIds: ['block_H1MgkbcNH4sz8pMC2h4dVh'],
+ },
+ },
+ },
+ block_2xkeWLDtTXj5jFg57KhfYR: {
+ type: 'ColumnsContainer',
+ data: {
+ style: {
+ backgroundColor: null,
+ padding: {
+ top: 4,
+ bottom: 4,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ columnsCount: 2,
+ columns: [
+ {
+ childrenIds: ['block_QTKa4AdGxtSbJmRY7ytjD8'],
+ },
+ {
+ childrenIds: ['block_Mi7A4sXfSXaEcjFV17WgSg'],
+ },
+ {
+ childrenIds: [],
+ },
+ ],
+ },
+ },
+ },
+ block_QgSNshqbsUkk4FrRywjoRd: {
+ type: 'Divider',
+ data: {
+ style: {
+ backgroundColor: null,
+ padding: {
+ top: 16,
+ bottom: 16,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ lineHeight: 1,
+ lineColor: '#EEEEEE',
+ },
+ },
+ },
+ block_B5teSFiXFfpLmiB29c1WYF: {
+ type: 'Text',
+ data: {
+ style: {
+ color: '#808080',
+ backgroundColor: null,
+ fontSize: 16,
+ fontFamily: null,
+ fontWeight: 'normal',
+ textAlign: 'left',
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ text: 'Total',
+ },
+ },
+ },
+ block_DPodDUHaaLcYuAG3CQi1F7: {
+ type: 'Container',
+ data: {
+ style: {
+ backgroundColor: null,
+ borderColor: null,
+ borderRadius: null,
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ childrenIds: ['block_B5teSFiXFfpLmiB29c1WYF'],
+ },
+ },
+ },
+ block_Wo7gkfj5QDqEiXywi6e2dq: {
+ type: 'Text',
+ data: {
+ style: {
+ color: null,
+ backgroundColor: null,
+ fontSize: 21,
+ fontFamily: null,
+ fontWeight: 'bold',
+ textAlign: 'right',
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ text: '$30.00',
+ },
+ },
+ },
+ block_YABuGb9jY34Rywk4H2wmhu: {
+ type: 'Container',
+ data: {
+ style: {
+ backgroundColor: null,
+ borderColor: null,
+ borderRadius: null,
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ childrenIds: ['block_Wo7gkfj5QDqEiXywi6e2dq'],
+ },
+ },
+ },
+ block_VvK99ZhLLgmycHQXKVFV7E: {
+ type: 'ColumnsContainer',
+ data: {
+ style: {
+ backgroundColor: null,
+ padding: {
+ top: 4,
+ bottom: 4,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ columnsCount: 2,
+ columns: [
+ {
+ childrenIds: ['block_DPodDUHaaLcYuAG3CQi1F7'],
+ },
+ {
+ childrenIds: ['block_YABuGb9jY34Rywk4H2wmhu'],
+ },
+ {
+ childrenIds: [],
+ },
+ ],
+ },
+ },
+ },
+ block_VhteQbJa7bCSmykN4AcHkp: {
+ type: 'Container',
+ data: {
+ style: {
+ backgroundColor: null,
+ borderColor: null,
+ borderRadius: null,
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ childrenIds: [
+ 'block_2QEX1chf5uWU5ZZebf4gmu',
+ 'block_Lu666sTP6hqLkHiBThmm4G',
+ 'block_Gn2h8bajFuW8zDBsiVVitV',
+ 'block_2xkeWLDtTXj5jFg57KhfYR',
+ 'block_QgSNshqbsUkk4FrRywjoRd',
+ 'block_VvK99ZhLLgmycHQXKVFV7E',
+ ],
+ },
+ },
+ },
+
+ block_WK3b19BzGE8VNKDwiSZ8s8: {
+ type: 'ColumnsContainer',
+ data: {
+ style: {
+ backgroundColor: null,
+ padding: {
+ top: 16,
+ bottom: 16,
+ left: 24,
+ right: 24,
+ },
+ },
+ props: {
+ columnsCount: 2,
+ columnsGap: 16,
+ columns: [
+ {
+ childrenIds: ['block_Wnt477QxYNynetWGwkLg89'],
+ },
+ {
+ childrenIds: ['block_VhteQbJa7bCSmykN4AcHkp'],
+ },
+ {
+ childrenIds: [],
+ },
+ ],
+ },
+ },
+ },
+ block_Pe2Dm5Cbqq5CcjL5wEdMg4: {
+ type: 'Heading',
+ data: {
+ style: {
+ color: null,
+ backgroundColor: null,
+ fontFamily: null,
+ fontWeight: 'normal',
+ textAlign: 'left',
+ padding: {
+ top: 40,
+ bottom: 24,
+ left: 24,
+ right: 24,
+ },
+ },
+ props: {
+ level: 'h3',
+ text: 'Customer information',
+ },
+ },
+ },
+ block_2c68kHKvfEzD1DRVKqh4Pg: {
+ type: 'Text',
+ data: {
+ style: {
+ color: null,
+ backgroundColor: null,
+ fontSize: 16,
+ fontFamily: null,
+ fontWeight: 'bold',
+ textAlign: 'left',
+ padding: {
+ top: 0,
+ bottom: 8,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ text: 'Shipping address',
+ },
+ },
+ },
+ block_SY5nL8mzSPgeEyeDgkDUEa: {
+ type: 'Text',
+ data: {
+ style: {
+ color: null,
+ backgroundColor: null,
+ fontSize: 14,
+ fontFamily: null,
+ fontWeight: 'normal',
+ textAlign: 'left',
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ text: '123 Main St New York, NY 10002',
+ },
+ },
+ },
+ block_ThQEcRPtSMqKiThzU9EGwV: {
+ type: 'Container',
+ data: {
+ style: {
+ backgroundColor: null,
+ borderColor: null,
+ borderRadius: null,
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ childrenIds: ['block_2c68kHKvfEzD1DRVKqh4Pg', 'block_SY5nL8mzSPgeEyeDgkDUEa'],
+ },
+ },
+ },
+ block_NkMSrWFvqewuenMYqLNcRa: {
+ type: 'Text',
+ data: {
+ style: {
+ color: null,
+ backgroundColor: null,
+ fontSize: 16,
+ fontFamily: null,
+ fontWeight: 'bold',
+ textAlign: 'left',
+ padding: {
+ top: 0,
+ bottom: 8,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ text: 'Billing address',
+ },
+ },
+ },
+ block_7RTvDRNCQpM5xV6oXsCx6s: {
+ type: 'Text',
+ data: {
+ style: {
+ color: null,
+ backgroundColor: null,
+ fontSize: 14,
+ fontFamily: null,
+ fontWeight: 'normal',
+ textAlign: 'left',
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ text: '123 Main St New York, NY 10002',
+ },
+ },
+ },
+ block_KaHi1FBV64emMA3kb7x4vE: {
+ type: 'Container',
+ data: {
+ style: {
+ backgroundColor: null,
+ borderColor: null,
+ borderRadius: null,
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ childrenIds: ['block_NkMSrWFvqewuenMYqLNcRa', 'block_7RTvDRNCQpM5xV6oXsCx6s'],
+ },
+ },
+ },
+ block_SizNWJDCcqw9tGH2ComdDX: {
+ type: 'ColumnsContainer',
+ data: {
+ style: {
+ backgroundColor: null,
+ padding: {
+ top: 16,
+ bottom: 16,
+ left: 24,
+ right: 24,
+ },
+ },
+ props: {
+ columnsCount: 2,
+ columns: [
+ {
+ childrenIds: ['block_ThQEcRPtSMqKiThzU9EGwV'],
+ },
+ {
+ childrenIds: ['block_KaHi1FBV64emMA3kb7x4vE'],
+ },
+ {
+ childrenIds: [],
+ },
+ ],
+ },
+ },
+ },
+ block_4Qxv32WQo8pDzPu1d3vixz: {
+ type: 'Text',
+ data: {
+ style: {
+ color: null,
+ backgroundColor: null,
+ fontSize: 16,
+ fontFamily: null,
+ fontWeight: 'bold',
+ textAlign: 'left',
+ padding: {
+ top: 24,
+ bottom: 8,
+ left: 24,
+ right: 24,
+ },
+ },
+ props: {
+ text: 'Shipping method',
+ },
+ },
+ },
+ block_LKiZYDTPeJGkRWHForFSDQ: {
+ type: 'Text',
+ data: {
+ style: {
+ color: null,
+ backgroundColor: null,
+ fontSize: 14,
+ fontFamily: null,
+ fontWeight: 'normal',
+ textAlign: 'left',
+ padding: {
+ top: 0,
+ bottom: 48,
+ left: 24,
+ right: 24,
+ },
+ },
+ props: {
+ text: 'Ground – Standard',
+ },
+ },
+ },
+ block_P6XtJj721vPfrhXKzS8uR5: {
+ type: 'Divider',
+ data: {
+ style: {
+ backgroundColor: null,
+ padding: {
+ top: 16,
+ bottom: 16,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ lineHeight: 1,
+ lineColor: '#EEEEEE',
+ },
+ },
+ },
+ block_AKTwpjBmtevcfj82z52i8p: {
+ type: 'Text',
+ data: {
+ style: {
+ color: null,
+ backgroundColor: null,
+ fontSize: 14,
+ fontFamily: null,
+ fontWeight: 'normal',
+ textAlign: 'left',
+ padding: {
+ top: 16,
+ bottom: 16,
+ left: 24,
+ right: 24,
+ },
+ },
+ props: {
+ text: 'If you have any questions, just reply to this email.',
+ },
+ },
+ },
+};
+
+export default ORDER_ECOMMERCE;
diff --git a/packages/post-kit-editor/src/email-builder-ui/getConfiguration/sample/post-metrics-report.ts b/packages/post-kit-editor/src/email-builder-ui/getConfiguration/sample/post-metrics-report.ts
new file mode 100644
index 0000000..7094db9
--- /dev/null
+++ b/packages/post-kit-editor/src/email-builder-ui/getConfiguration/sample/post-metrics-report.ts
@@ -0,0 +1,394 @@
+import { TEditorConfiguration } from '../../documents/editor/core';
+
+const POST_METRICS_REPORT: TEditorConfiguration = {
+ root: {
+ type: 'EmailLayout',
+ data: {
+ backdropColor: '#EEEEEE',
+ canvasColor: '#FFFFFF',
+ textColor: '#242424',
+ fontFamily: 'MODERN_SANS',
+ childrenIds: [
+ 'block_6B5Ke1N2KdM4STQjw7eEHT',
+ 'block_VE1bKDbSqiYD9VtmmaYX7w',
+ 'block_QQqjnauXAixe2LnJXVmHwQ',
+ 'block_9yEYNZmmmFauyuSKi9iJA9',
+ 'block_AC6eRbFVSbXHVCg2zutkLu',
+ 'block_CYXkzjxrj6e4Sb74Kt8quM',
+ 'block_AUAxG2BgwA6XC8rF5xAaaP',
+ 'block_C1YvcFvMvzB1Fhxn3uV8zV',
+ 'block_FsiiokCgr9bZitHn7sx7TB',
+ 'block_DomD4MLJ58VcMo49vmeTH8',
+ ],
+ },
+ },
+ block_6sP1Pi9AimAoti2ZPdNXUf: {
+ type: 'Image',
+ data: {
+ style: {
+ backgroundColor: null,
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ textAlign: 'left',
+ },
+ props: {
+ height: 16,
+ url: 'https://d1iiu589g39o6c.cloudfront.net/live/platforms/platform_A9wwKSL6EV6orh6f/images/wptemplateimage_n3eLjsf37dcjFaj5/Narrative.png',
+ contentAlignment: 'middle',
+ },
+ },
+ },
+ block_9G37m6eNPw2MpUj6SYGoq1: {
+ type: 'Container',
+ data: {
+ style: {
+ backgroundColor: null,
+ borderColor: null,
+ borderRadius: null,
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ childrenIds: ['block_6sP1Pi9AimAoti2ZPdNXUf'],
+ },
+ },
+ },
+ block_A8GU16mV1RdP85jaszN7oj: {
+ type: 'Avatar',
+ data: {
+ style: {
+ textAlign: 'right',
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ size: 32,
+ shape: 'circle',
+ imageUrl: 'https://ui-avatars.com/api/?name=John+Doe',
+ alt: 'Jordan',
+ },
+ },
+ },
+ block_4WmdbYU15yfdpYcYjsVDBA: {
+ type: 'Container',
+ data: {
+ style: {
+ backgroundColor: null,
+ borderColor: null,
+ borderRadius: null,
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ childrenIds: ['block_A8GU16mV1RdP85jaszN7oj'],
+ },
+ },
+ },
+ block_JQAdLSAtvmfsRih13srJ8m: {
+ type: 'Container',
+ data: {
+ style: {
+ backgroundColor: null,
+ borderColor: null,
+ borderRadius: null,
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ childrenIds: [],
+ },
+ },
+ },
+ block_6B5Ke1N2KdM4STQjw7eEHT: {
+ type: 'ColumnsContainer',
+ data: {
+ style: {
+ backgroundColor: null,
+ padding: {
+ top: 24,
+ bottom: 24,
+ left: 24,
+ right: 24,
+ },
+ },
+ props: {
+ columnsCount: 2,
+ columns: [
+ {
+ childrenIds: ['block_9G37m6eNPw2MpUj6SYGoq1'],
+ },
+ {
+ childrenIds: ['block_4WmdbYU15yfdpYcYjsVDBA'],
+ },
+ {
+ childrenIds: ['block_JQAdLSAtvmfsRih13srJ8m'],
+ },
+ ],
+ },
+ },
+ },
+ block_VE1bKDbSqiYD9VtmmaYX7w: {
+ type: 'Heading',
+ data: {
+ style: {
+ color: null,
+ backgroundColor: null,
+ fontFamily: null,
+ fontWeight: 'bold',
+ textAlign: 'center',
+ padding: {
+ top: 24,
+ bottom: 0,
+ left: 24,
+ right: 24,
+ },
+ },
+ props: {
+ level: 'h3',
+ text: 'Last week, your posts received',
+ },
+ },
+ },
+ block_QQqjnauXAixe2LnJXVmHwQ: {
+ type: 'Text',
+ data: {
+ style: {
+ color: null,
+ backgroundColor: null,
+ fontSize: 48,
+ fontFamily: null,
+ fontWeight: 'bold',
+ textAlign: 'center',
+ padding: {
+ top: 16,
+ bottom: 0,
+ left: 24,
+ right: 24,
+ },
+ },
+ props: {
+ text: '1,511',
+ },
+ },
+ },
+ block_9yEYNZmmmFauyuSKi9iJA9: {
+ type: 'Text',
+ data: {
+ style: {
+ color: null,
+ backgroundColor: null,
+ fontSize: 14,
+ fontFamily: null,
+ fontWeight: 'bold',
+ textAlign: 'center',
+ padding: {
+ top: 0,
+ bottom: 16,
+ left: 24,
+ right: 24,
+ },
+ },
+ props: {
+ text: 'Post impressions',
+ },
+ },
+ },
+ block_AC6eRbFVSbXHVCg2zutkLu: {
+ type: 'Button',
+ data: {
+ style: {
+ backgroundColor: null,
+ fontSize: 16,
+ fontFamily: null,
+ fontWeight: 'bold',
+ textAlign: 'center',
+ padding: {
+ top: 16,
+ bottom: 24,
+ left: 24,
+ right: 24,
+ },
+ },
+ props: {
+ buttonBackgroundColor: '#24AF7F',
+ buttonStyle: 'rounded',
+ buttonTextColor: '#FFFFFF',
+ fullWidth: false,
+ size: 'medium',
+ text: 'View your analytics →',
+ url: 'https://example.usewaypoint.com/post/1234/analytics',
+ },
+ },
+ },
+ block_CYXkzjxrj6e4Sb74Kt8quM: {
+ type: 'Heading',
+ data: {
+ style: {
+ color: null,
+ backgroundColor: null,
+ fontFamily: null,
+ fontWeight: 'bold',
+ textAlign: 'center',
+ padding: {
+ top: 24,
+ bottom: 8,
+ left: 24,
+ right: 24,
+ },
+ },
+ props: {
+ level: 'h3',
+ text: 'Top performing post last week',
+ },
+ },
+ },
+ block_FpDmSnPwiVzBXUvTc4yWFh: {
+ type: 'Text',
+ data: {
+ style: {
+ color: null,
+ backgroundColor: null,
+ fontSize: 16,
+ fontFamily: null,
+ fontWeight: 'normal',
+ textAlign: 'left',
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ text: 'So excited to now have drag and drop on Waypoint. This builds on top of our new Navigator feature that we shipped earlier this week 🚢.',
+ },
+ },
+ },
+ block_LjuDF6uu4qWL3Ju3ng63ky: {
+ type: 'Container',
+ data: {
+ style: {
+ backgroundColor: '#FAFAFA',
+ borderColor: null,
+ borderRadius: 8,
+ padding: {
+ top: 24,
+ bottom: 24,
+ left: 24,
+ right: 24,
+ },
+ },
+ props: {
+ childrenIds: ['block_FpDmSnPwiVzBXUvTc4yWFh'],
+ },
+ },
+ },
+ block_AUAxG2BgwA6XC8rF5xAaaP: {
+ type: 'Container',
+ data: {
+ style: {
+ backgroundColor: null,
+ borderColor: null,
+ borderRadius: null,
+ padding: {
+ top: 16,
+ bottom: 16,
+ left: 24,
+ right: 24,
+ },
+ },
+ props: {
+ childrenIds: ['block_LjuDF6uu4qWL3Ju3ng63ky'],
+ },
+ },
+ },
+ block_C1YvcFvMvzB1Fhxn3uV8zV: {
+ type: 'Button',
+ data: {
+ style: {
+ backgroundColor: null,
+ fontSize: 16,
+ fontFamily: null,
+ fontWeight: 'bold',
+ textAlign: 'center',
+ padding: {
+ top: 16,
+ bottom: 16,
+ left: 24,
+ right: 24,
+ },
+ },
+ props: {
+ buttonBackgroundColor: '#EEEEEE',
+ buttonStyle: 'rounded',
+ buttonTextColor: '#474849',
+ fullWidth: false,
+ size: 'medium',
+ text: 'Show more',
+ url: 'https://example.usewaypoint.com/jordanisip/posts',
+ },
+ },
+ },
+ block_FsiiokCgr9bZitHn7sx7TB: {
+ type: 'Divider',
+ data: {
+ style: {
+ backgroundColor: null,
+ padding: {
+ top: 40,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ lineHeight: 1,
+ lineColor: '#EEEEEE',
+ },
+ },
+ },
+ block_DomD4MLJ58VcMo49vmeTH8: {
+ type: 'Text',
+ data: {
+ style: {
+ color: '#474849',
+ backgroundColor: null,
+ fontSize: 12,
+ fontFamily: null,
+ fontWeight: 'normal',
+ textAlign: 'center',
+ padding: {
+ top: 24,
+ bottom: 24,
+ left: 24,
+ right: 24,
+ },
+ },
+ props: {
+ text: 'Questions? Just reply to this email.',
+ },
+ },
+ },
+};
+
+export default POST_METRICS_REPORT;
diff --git a/packages/post-kit-editor/src/email-builder-ui/getConfiguration/sample/reservation-reminder.ts b/packages/post-kit-editor/src/email-builder-ui/getConfiguration/sample/reservation-reminder.ts
new file mode 100644
index 0000000..368acfa
--- /dev/null
+++ b/packages/post-kit-editor/src/email-builder-ui/getConfiguration/sample/reservation-reminder.ts
@@ -0,0 +1,2209 @@
+import { TEditorConfiguration } from '../../documents/editor/core';
+
+const RESERVATION_REMINDER: TEditorConfiguration = {
+ root: {
+ type: 'EmailLayout',
+ data: {
+ backdropColor: '#FFFFFF',
+ canvasColor: '#FFFFFF',
+ textColor: '#03124A',
+ fontFamily: 'GEOMETRIC_SANS',
+ childrenIds: [
+ 'block_DDiR1Jz1iXY5aR1PMCzRAC',
+ 'block_J98nQiyYeX1vJTu44ZSuua',
+ 'block_M7imqmjHsmhd61LcdHJz5m',
+ 'block_Fec2JmwhjNYJS7xWDyUqWu',
+ 'block_E8jr6ieqZ4fwaHxj9C1uBF',
+ 'block_H1hMtvKJ9b1eQ2aiEDjbTL',
+ 'block_JJtd5TsPSjEMNhtKeMPh82',
+ 'block_HRswcB2Ww7m3H4SijapqPq',
+ 'block_kq85AQYHuSL8fXqqcZP7D',
+ 'block_Sa3vmMr4AUT4W55Mx5EFxr',
+ 'block_YF6R8BeMFE3XuiSP4cUex2',
+ 'block_Qh1QiwAbDq1yxuzqBMAxbJ',
+ 'block_FUvBg3iUzvmUcTjmjvo5Wd',
+ 'block_UncM7Be9AkE197F3KYjpa5',
+ 'block_8hNnkxhS84scJ18FCwRr4q',
+ 'block_JihqAfW5u5wR8oQUJFP5kE',
+ ],
+ },
+ },
+ block_DDiR1Jz1iXY5aR1PMCzRAC: {
+ type: 'Image',
+ data: {
+ style: {
+ backgroundColor: null,
+ padding: {
+ top: 24,
+ bottom: 24,
+ left: 24,
+ right: 24,
+ },
+ textAlign: 'left',
+ },
+ props: {
+ height: 32,
+ url: 'https://d1iiu589g39o6c.cloudfront.net/live/platforms/platform_A9wwKSL6EV6orh6f/images/wptemplateimage_RAFATLGe3CN1wDsb/birdhouse.png',
+ contentAlignment: 'middle',
+ },
+ },
+ },
+ block_J98nQiyYeX1vJTu44ZSuua: {
+ type: 'Heading',
+ data: {
+ style: {
+ color: null,
+ backgroundColor: null,
+ fontFamily: null,
+ fontWeight: 'bold',
+ textAlign: 'left',
+ padding: {
+ top: 16,
+ bottom: 0,
+ left: 24,
+ right: 24,
+ },
+ },
+ props: {
+ level: 'h2',
+ text: 'Elaina arrives Sunday, May 21.',
+ },
+ },
+ },
+ block_M7imqmjHsmhd61LcdHJz5m: {
+ type: 'Text',
+ data: {
+ style: {
+ color: null,
+ backgroundColor: null,
+ fontSize: 16,
+ fontFamily: null,
+ fontWeight: 'normal',
+ textAlign: 'left',
+ padding: {
+ top: 16,
+ bottom: 16,
+ left: 24,
+ right: 24,
+ },
+ },
+ props: {
+ text: 'If you haven’t already, reach out to Elaina to send directions and coordinate check-in time.',
+ },
+ },
+ },
+ block_MEPSuGAUvE8WAg1AJWJvNU: {
+ type: 'Avatar',
+ data: {
+ style: {
+ textAlign: 'left',
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ size: 64,
+ shape: 'circle',
+ imageUrl: 'https://ui-avatars.com/api/?size=128&name=Elaina',
+ alt: 'Elaina',
+ },
+ },
+ },
+ block_7MDe4niz9sWkSR7Un5BvRo: {
+ type: 'Container',
+ data: {
+ style: {
+ backgroundColor: null,
+ borderColor: null,
+ borderRadius: null,
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ childrenIds: ['block_MEPSuGAUvE8WAg1AJWJvNU'],
+ },
+ },
+ },
+ block_5jMPEu49PGBxyGcuBYxXK2: {
+ type: 'Heading',
+ data: {
+ style: {
+ color: null,
+ backgroundColor: null,
+ fontFamily: null,
+ fontWeight: 'bold',
+ textAlign: 'left',
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ level: 'h3',
+ text: 'Elaina',
+ },
+ },
+ },
+ block_NUv5obp6NnExg8DgonEyzm: {
+ type: 'Text',
+ data: {
+ style: {
+ color: '#2e3c68',
+ backgroundColor: null,
+ fontSize: 14,
+ fontFamily: null,
+ fontWeight: 'normal',
+ textAlign: 'left',
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ text: 'United States\nMember since 2013',
+ },
+ },
+ },
+ block_NetzRCyQeJbPp2VWQwmxBP: {
+ type: 'Container',
+ data: {
+ style: {
+ backgroundColor: null,
+ borderColor: null,
+ borderRadius: null,
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ childrenIds: ['block_5jMPEu49PGBxyGcuBYxXK2', 'block_NUv5obp6NnExg8DgonEyzm'],
+ },
+ },
+ },
+ block_GH41bF6mW4wxDywjsxKrAv: {
+ type: 'Container',
+ data: {
+ style: {
+ backgroundColor: null,
+ borderColor: null,
+ borderRadius: null,
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ childrenIds: [],
+ },
+ },
+ },
+ block_UWushKnW34XryX4BSzpotP: {
+ type: 'ColumnsContainer',
+ data: {
+ style: {
+ backgroundColor: '#f8f8f8',
+ padding: {
+ top: 24,
+ bottom: 24,
+ left: 16,
+ right: 16,
+ },
+ },
+ props: {
+ fixedWidths: [64, null, null],
+ columnsCount: 2,
+ columnsGap: 16,
+ columns: [
+ {
+ childrenIds: ['block_7MDe4niz9sWkSR7Un5BvRo'],
+ },
+ {
+ childrenIds: ['block_NetzRCyQeJbPp2VWQwmxBP'],
+ },
+ {
+ childrenIds: ['block_GH41bF6mW4wxDywjsxKrAv'],
+ },
+ ],
+ },
+ },
+ },
+ block_Fec2JmwhjNYJS7xWDyUqWu: {
+ type: 'Container',
+ data: {
+ style: {
+ backgroundColor: null,
+ borderColor: null,
+ borderRadius: null,
+ padding: {
+ top: 8,
+ bottom: 8,
+ left: 24,
+ right: 24,
+ },
+ },
+ props: {
+ childrenIds: ['block_UWushKnW34XryX4BSzpotP'],
+ },
+ },
+ },
+ block_E8jr6ieqZ4fwaHxj9C1uBF: {
+ type: 'Button',
+ data: {
+ style: {
+ backgroundColor: null,
+ fontSize: 16,
+ fontFamily: null,
+ fontWeight: 'bold',
+ textAlign: 'left',
+ padding: {
+ top: 16,
+ bottom: 16,
+ left: 24,
+ right: 24,
+ },
+ },
+ props: {
+ buttonBackgroundColor: '#03124A',
+ buttonStyle: 'rounded',
+ buttonTextColor: '#FFFFFF',
+ fullWidth: false,
+ size: 'large',
+ text: 'Send Elaina a message',
+ url: 'https://www.usewaypoint.com',
+ },
+ },
+ },
+ block_H1hMtvKJ9b1eQ2aiEDjbTL: {
+ type: 'Image',
+ data: {
+ style: {
+ backgroundColor: null,
+ padding: {
+ top: 24,
+ bottom: 24,
+ left: 24,
+ right: 24,
+ },
+ textAlign: 'left',
+ },
+ props: {
+ url: 'https://d1iiu589g39o6c.cloudfront.net/live/platforms/platform_A9wwKSL6EV6orh6f/images/wptemplateimage_QWeWcucRbjPqoewY/L1030293.jpg',
+ alt: 'Photo',
+ linkHref: 'https://example.usewaypoint.com/listings/r029348209842',
+ contentAlignment: 'middle',
+ },
+ },
+ },
+ block_JJtd5TsPSjEMNhtKeMPh82: {
+ type: 'Heading',
+ data: {
+ style: {
+ color: null,
+ backgroundColor: null,
+ fontFamily: null,
+ fontWeight: 'bold',
+ textAlign: 'left',
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 24,
+ right: 24,
+ },
+ },
+ props: {
+ level: 'h2',
+ text: 'Private Hideaway in the North Shore',
+ },
+ },
+ },
+ block_HRswcB2Ww7m3H4SijapqPq: {
+ type: 'Text',
+ data: {
+ style: {
+ color: null,
+ backgroundColor: null,
+ fontSize: 16,
+ fontFamily: null,
+ fontWeight: 'normal',
+ textAlign: 'left',
+ padding: {
+ top: 0,
+ bottom: 16,
+ left: 24,
+ right: 24,
+ },
+ },
+ props: {
+ text: 'Entire home/apartment',
+ },
+ },
+ },
+ block_kq85AQYHuSL8fXqqcZP7D: {
+ type: 'Divider',
+ data: {
+ style: {
+ backgroundColor: null,
+ padding: {
+ top: 16,
+ bottom: 16,
+ left: 24,
+ right: 24,
+ },
+ },
+ props: {
+ lineHeight: 1,
+ lineColor: '#EEEEEE',
+ },
+ },
+ },
+ block_AvnqsSCo9CTXYXHfMCQvUN: {
+ type: 'Heading',
+ data: {
+ style: {
+ color: null,
+ backgroundColor: null,
+ fontFamily: null,
+ fontWeight: 'bold',
+ textAlign: 'left',
+ padding: {
+ top: 0,
+ bottom: 8,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ level: 'h3',
+ text: 'Check in',
+ },
+ },
+ },
+ block_BsbDbmYTuyisEMXQY8dwW6: {
+ type: 'Text',
+ data: {
+ style: {
+ color: null,
+ backgroundColor: null,
+ fontSize: 16,
+ fontFamily: null,
+ fontWeight: 'normal',
+ textAlign: 'left',
+ padding: {
+ top: 0,
+ bottom: 16,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ text: 'Sunday, May 21 2023.\nCheck-in anytime after 3:00 PM',
+ },
+ },
+ },
+ block_6G89B9zYn7ELqnioW62cLA: {
+ type: 'Heading',
+ data: {
+ style: {
+ color: null,
+ backgroundColor: null,
+ fontFamily: null,
+ fontWeight: 'bold',
+ textAlign: 'left',
+ padding: {
+ top: 16,
+ bottom: 8,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ level: 'h3',
+ text: 'Check out',
+ },
+ },
+ },
+ block_DhZSQJBrQCXG17HSSMAfiq: {
+ type: 'Text',
+ data: {
+ style: {
+ color: null,
+ backgroundColor: null,
+ fontSize: 16,
+ fontFamily: null,
+ fontWeight: 'normal',
+ textAlign: 'left',
+ padding: {
+ top: 0,
+ bottom: 16,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ text: 'Tuesday, May 23 2023.\nCheckout by 11:00 AM',
+ },
+ },
+ },
+ block_NAzWgDcqGdpeHWrspZ8fz7: {
+ type: 'Divider',
+ data: {
+ style: {
+ backgroundColor: null,
+ padding: {
+ top: 16,
+ bottom: 16,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ lineHeight: 1,
+ lineColor: '#EEEEEE',
+ },
+ },
+ },
+ block_Sa3vmMr4AUT4W55Mx5EFxr: {
+ type: 'Container',
+ data: {
+ style: {
+ backgroundColor: null,
+ borderColor: null,
+ borderRadius: null,
+ padding: {
+ top: 8,
+ bottom: 8,
+ left: 24,
+ right: 24,
+ },
+ },
+ props: {
+ childrenIds: [
+ 'block_AvnqsSCo9CTXYXHfMCQvUN',
+ 'block_BsbDbmYTuyisEMXQY8dwW6',
+ 'block_6G89B9zYn7ELqnioW62cLA',
+ 'block_DhZSQJBrQCXG17HSSMAfiq',
+ 'block_NAzWgDcqGdpeHWrspZ8fz7',
+ ],
+ },
+ },
+ },
+ block_MxedV7vMjjU4ZsikW1Fadv: {
+ type: 'Heading',
+ data: {
+ style: {
+ color: null,
+ backgroundColor: null,
+ fontFamily: null,
+ fontWeight: 'bold',
+ textAlign: 'left',
+ padding: {
+ top: 0,
+ bottom: 8,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ level: 'h3',
+ text: 'Guests',
+ },
+ },
+ },
+ block_8htLTdnN2CcAX67kwJFo2V: {
+ type: 'Text',
+ data: {
+ style: {
+ color: null,
+ backgroundColor: null,
+ fontSize: 16,
+ fontFamily: null,
+ fontWeight: 'normal',
+ textAlign: 'left',
+ padding: {
+ top: 0,
+ bottom: 16,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ text: 'Let them know upfront if your listing is suitable for children by updating your House Rules.',
+ },
+ },
+ },
+ block_UR2m2tSekeEBy6s76D9Vju: {
+ type: 'Divider',
+ data: {
+ style: {
+ backgroundColor: null,
+ padding: {
+ top: 16,
+ bottom: 16,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ lineHeight: 1,
+ lineColor: '#EEEEEE',
+ },
+ },
+ },
+ block_YF6R8BeMFE3XuiSP4cUex2: {
+ type: 'Container',
+ data: {
+ style: {
+ backgroundColor: null,
+ borderColor: null,
+ borderRadius: null,
+ padding: {
+ top: 8,
+ bottom: 8,
+ left: 24,
+ right: 24,
+ },
+ },
+ props: {
+ childrenIds: [
+ 'block_MxedV7vMjjU4ZsikW1Fadv',
+ 'block_8htLTdnN2CcAX67kwJFo2V',
+ 'block_UR2m2tSekeEBy6s76D9Vju',
+ ],
+ },
+ },
+ },
+ block_BouZQPSLfEfWvVp9qrHaW1: {
+ type: 'Heading',
+ data: {
+ style: {
+ color: null,
+ backgroundColor: null,
+ fontFamily: null,
+ fontWeight: 'bold',
+ textAlign: 'left',
+ padding: {
+ top: 0,
+ bottom: 8,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ level: 'h3',
+ text: 'Confirmation code',
+ },
+ },
+ },
+ block_Si5vw3ga7PZppiYv6g7GV7: {
+ type: 'Text',
+ data: {
+ style: {
+ color: null,
+ backgroundColor: null,
+ fontSize: 16,
+ fontFamily: null,
+ fontWeight: 'normal',
+ textAlign: 'left',
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ text: 'HMFN35C4MH',
+ },
+ },
+ },
+ block_S6ARLFYsniJdJdkkie4UNa: {
+ type: 'Container',
+ data: {
+ style: {
+ backgroundColor: null,
+ borderColor: null,
+ borderRadius: null,
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ childrenIds: ['block_Si5vw3ga7PZppiYv6g7GV7'],
+ },
+ },
+ },
+ block_MawqUvY8CdWpQ84dy4eRsa: {
+ type: 'Text',
+ data: {
+ style: {
+ color: null,
+ backgroundColor: null,
+ fontSize: 16,
+ fontFamily: null,
+ fontWeight: 'bold',
+ textAlign: 'right',
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ text: 'View itinerary',
+ },
+ },
+ },
+ block_KjrdgHLJcyAcsjKStPktPU: {
+ type: 'Container',
+ data: {
+ style: {
+ backgroundColor: null,
+ borderColor: null,
+ borderRadius: null,
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ childrenIds: ['block_MawqUvY8CdWpQ84dy4eRsa'],
+ },
+ },
+ },
+ block_DK7wXQDdPd8rzFxMeyxDzs: {
+ type: 'Container',
+ data: {
+ style: {
+ backgroundColor: null,
+ borderColor: null,
+ borderRadius: null,
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ childrenIds: [],
+ },
+ },
+ },
+ block_LHBuzMB27wJAjyi6bwLKeN: {
+ type: 'ColumnsContainer',
+ data: {
+ style: {
+ backgroundColor: null,
+ padding: {
+ top: 8,
+ bottom: 16,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ columnsCount: 2,
+ columns: [
+ {
+ childrenIds: ['block_S6ARLFYsniJdJdkkie4UNa'],
+ },
+ {
+ childrenIds: ['block_KjrdgHLJcyAcsjKStPktPU'],
+ },
+ {
+ childrenIds: ['block_DK7wXQDdPd8rzFxMeyxDzs'],
+ },
+ ],
+ },
+ },
+ },
+ block_CWgMBgMBgpLMjqd1biz1mz: {
+ type: 'Divider',
+ data: {
+ style: {
+ backgroundColor: null,
+ padding: {
+ top: 16,
+ bottom: 16,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ lineHeight: 1,
+ lineColor: '#EEEEEE',
+ },
+ },
+ },
+ block_Qh1QiwAbDq1yxuzqBMAxbJ: {
+ type: 'Container',
+ data: {
+ style: {
+ backgroundColor: null,
+ borderColor: null,
+ borderRadius: null,
+ padding: {
+ top: 8,
+ bottom: 8,
+ left: 24,
+ right: 24,
+ },
+ },
+ props: {
+ childrenIds: [
+ 'block_BouZQPSLfEfWvVp9qrHaW1',
+ 'block_LHBuzMB27wJAjyi6bwLKeN',
+ 'block_CWgMBgMBgpLMjqd1biz1mz',
+ ],
+ },
+ },
+ },
+ block_SnYxmj5ia2K4dqVEaZDLst: {
+ type: 'Heading',
+ data: {
+ style: {
+ color: null,
+ backgroundColor: null,
+ fontFamily: null,
+ fontWeight: 'bold',
+ textAlign: 'left',
+ padding: {
+ top: 0,
+ bottom: 16,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ level: 'h3',
+ text: 'Guest paid',
+ },
+ },
+ },
+ block_DBsnXnwmLBhETFknwe3qJt: {
+ type: 'Text',
+ data: {
+ style: {
+ color: null,
+ backgroundColor: null,
+ fontSize: 16,
+ fontFamily: null,
+ fontWeight: 'normal',
+ textAlign: 'left',
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ text: '$99.00 x 2 nights',
+ },
+ },
+ },
+ block_4DvvyfwDXkgXXsjswWE9DE: {
+ type: 'Container',
+ data: {
+ style: {
+ backgroundColor: null,
+ borderColor: null,
+ borderRadius: null,
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ childrenIds: ['block_DBsnXnwmLBhETFknwe3qJt'],
+ },
+ },
+ },
+ block_L6NUphHkWkpcXZgzBbW9dM: {
+ type: 'Text',
+ data: {
+ style: {
+ color: null,
+ backgroundColor: null,
+ fontSize: 16,
+ fontFamily: null,
+ fontWeight: 'normal',
+ textAlign: 'right',
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ text: '$198.00',
+ },
+ },
+ },
+ block_DE363EYUo29c6qTiQLFzHv: {
+ type: 'Container',
+ data: {
+ style: {
+ backgroundColor: null,
+ borderColor: null,
+ borderRadius: null,
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ childrenIds: ['block_L6NUphHkWkpcXZgzBbW9dM'],
+ },
+ },
+ },
+ block_Hs2DNRHwYGFvzMGVrZWUat: {
+ type: 'Container',
+ data: {
+ style: {
+ backgroundColor: null,
+ borderColor: null,
+ borderRadius: null,
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ childrenIds: [],
+ },
+ },
+ },
+ block_8rxqcogAeoRMFJJwHDLBvY: {
+ type: 'ColumnsContainer',
+ data: {
+ style: {
+ backgroundColor: null,
+ padding: {
+ top: 8,
+ bottom: 8,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ columnsCount: 2,
+ columns: [
+ {
+ childrenIds: ['block_4DvvyfwDXkgXXsjswWE9DE'],
+ },
+ {
+ childrenIds: ['block_DE363EYUo29c6qTiQLFzHv'],
+ },
+ {
+ childrenIds: ['block_Hs2DNRHwYGFvzMGVrZWUat'],
+ },
+ ],
+ },
+ },
+ },
+ block_KbFcTU27iLRQK6eVKkJ5As: {
+ type: 'Text',
+ data: {
+ style: {
+ color: null,
+ backgroundColor: null,
+ fontSize: 16,
+ fontFamily: null,
+ fontWeight: 'normal',
+ textAlign: 'left',
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ text: 'Cleaning fee',
+ },
+ },
+ },
+ block_McUGzTsrZTZGi8rXAs85KR: {
+ type: 'Container',
+ data: {
+ style: {
+ backgroundColor: null,
+ borderColor: null,
+ borderRadius: null,
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ childrenIds: ['block_KbFcTU27iLRQK6eVKkJ5As'],
+ },
+ },
+ },
+ block_VhFNCUhgsbekxdnzvddmSH: {
+ type: 'Text',
+ data: {
+ style: {
+ color: null,
+ backgroundColor: null,
+ fontSize: 16,
+ fontFamily: null,
+ fontWeight: 'normal',
+ textAlign: 'right',
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ text: '$50.00',
+ },
+ },
+ },
+ block_N5gv3mrQEm485SpKfBFHzi: {
+ type: 'Container',
+ data: {
+ style: {
+ backgroundColor: null,
+ borderColor: null,
+ borderRadius: null,
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ childrenIds: ['block_VhFNCUhgsbekxdnzvddmSH'],
+ },
+ },
+ },
+ block_T3dLYTNNPBosywgZPEvuyn: {
+ type: 'Container',
+ data: {
+ style: {
+ backgroundColor: null,
+ borderColor: null,
+ borderRadius: null,
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ childrenIds: [],
+ },
+ },
+ },
+ block_RUef6VdysRRfcaDSdPPPRL: {
+ type: 'ColumnsContainer',
+ data: {
+ style: {
+ backgroundColor: null,
+ padding: {
+ top: 8,
+ bottom: 8,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ columnsCount: 2,
+ columns: [
+ {
+ childrenIds: ['block_McUGzTsrZTZGi8rXAs85KR'],
+ },
+ {
+ childrenIds: ['block_N5gv3mrQEm485SpKfBFHzi'],
+ },
+ {
+ childrenIds: ['block_T3dLYTNNPBosywgZPEvuyn'],
+ },
+ ],
+ },
+ },
+ },
+ block_4HKuXRyhnMPYfpEre1Y4EP: {
+ type: 'Text',
+ data: {
+ style: {
+ color: null,
+ backgroundColor: null,
+ fontSize: 16,
+ fontFamily: null,
+ fontWeight: 'normal',
+ textAlign: 'left',
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ text: 'Guest service fee',
+ },
+ },
+ },
+ block_C9rqNoPW6sR7FJ94dCW48c: {
+ type: 'Container',
+ data: {
+ style: {
+ backgroundColor: null,
+ borderColor: null,
+ borderRadius: null,
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ childrenIds: ['block_4HKuXRyhnMPYfpEre1Y4EP'],
+ },
+ },
+ },
+ block_BGnqpBpBPAJZMqiSB2tFhs: {
+ type: 'Text',
+ data: {
+ style: {
+ color: null,
+ backgroundColor: null,
+ fontSize: 16,
+ fontFamily: null,
+ fontWeight: 'normal',
+ textAlign: 'right',
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ text: '$35.01',
+ },
+ },
+ },
+ block_8EUayHcU4ze8PPYG4uVyqQ: {
+ type: 'Container',
+ data: {
+ style: {
+ backgroundColor: null,
+ borderColor: null,
+ borderRadius: null,
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ childrenIds: ['block_BGnqpBpBPAJZMqiSB2tFhs'],
+ },
+ },
+ },
+ block_NAdj8o21Qtuz5U1Ptsp8QS: {
+ type: 'Container',
+ data: {
+ style: {
+ backgroundColor: null,
+ borderColor: null,
+ borderRadius: null,
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ childrenIds: [],
+ },
+ },
+ },
+ block_FDZhF8wpD8Am5TBAxqXYRn: {
+ type: 'ColumnsContainer',
+ data: {
+ style: {
+ backgroundColor: null,
+ padding: {
+ top: 8,
+ bottom: 8,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ columnsCount: 2,
+ columns: [
+ {
+ childrenIds: ['block_C9rqNoPW6sR7FJ94dCW48c'],
+ },
+ {
+ childrenIds: ['block_8EUayHcU4ze8PPYG4uVyqQ'],
+ },
+ {
+ childrenIds: ['block_NAdj8o21Qtuz5U1Ptsp8QS'],
+ },
+ ],
+ },
+ },
+ },
+ block_Fnft7v54tmFaNiTJ3Qn5sg: {
+ type: 'Text',
+ data: {
+ style: {
+ color: null,
+ backgroundColor: null,
+ fontSize: 16,
+ fontFamily: null,
+ fontWeight: 'normal',
+ textAlign: 'left',
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ text: 'Occupancy taxes',
+ },
+ },
+ },
+ block_Qvmy5aUBDfQXJyGjUyTZa3: {
+ type: 'Container',
+ data: {
+ style: {
+ backgroundColor: null,
+ borderColor: null,
+ borderRadius: null,
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ childrenIds: ['block_Fnft7v54tmFaNiTJ3Qn5sg'],
+ },
+ },
+ },
+ block_Jnd8bMVCEZPckFeotT1H6F: {
+ type: 'Text',
+ data: {
+ style: {
+ color: null,
+ backgroundColor: null,
+ fontSize: 16,
+ fontFamily: null,
+ fontWeight: 'normal',
+ textAlign: 'right',
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ text: '$46.02',
+ },
+ },
+ },
+ block_9LZ7J716qiGogPFAufpYdi: {
+ type: 'Container',
+ data: {
+ style: {
+ backgroundColor: null,
+ borderColor: null,
+ borderRadius: null,
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ childrenIds: ['block_Jnd8bMVCEZPckFeotT1H6F'],
+ },
+ },
+ },
+ block_RU3D4pVFkQDvAyNS7P5rRR: {
+ type: 'Container',
+ data: {
+ style: {
+ backgroundColor: null,
+ borderColor: null,
+ borderRadius: null,
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ childrenIds: [],
+ },
+ },
+ },
+ block_9Fj1tXV6otFnGdgJevbFPG: {
+ type: 'ColumnsContainer',
+ data: {
+ style: {
+ backgroundColor: null,
+ padding: {
+ top: 8,
+ bottom: 8,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ columnsCount: 2,
+ columns: [
+ {
+ childrenIds: ['block_Qvmy5aUBDfQXJyGjUyTZa3'],
+ },
+ {
+ childrenIds: ['block_9LZ7J716qiGogPFAufpYdi'],
+ },
+ {
+ childrenIds: ['block_RU3D4pVFkQDvAyNS7P5rRR'],
+ },
+ ],
+ },
+ },
+ },
+ block_JgZwrNfGi5BfW8KWeZT4Gq: {
+ type: 'Text',
+ data: {
+ style: {
+ color: null,
+ backgroundColor: null,
+ fontSize: 16,
+ fontFamily: null,
+ fontWeight: 'bold',
+ textAlign: 'left',
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ text: 'Total (USD)',
+ },
+ },
+ },
+ block_7eXpDRpkNETHdMN8Bkg9m1: {
+ type: 'Container',
+ data: {
+ style: {
+ backgroundColor: null,
+ borderColor: null,
+ borderRadius: null,
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ childrenIds: ['block_JgZwrNfGi5BfW8KWeZT4Gq'],
+ },
+ },
+ },
+ block_8Xioqh7pENRgRhzFpVqh8w: {
+ type: 'Text',
+ data: {
+ style: {
+ color: null,
+ backgroundColor: null,
+ fontSize: 16,
+ fontFamily: null,
+ fontWeight: 'bold',
+ textAlign: 'right',
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ text: '$329.03',
+ },
+ },
+ },
+ block_DCRQd2eFKSbUGvmQkxDdHs: {
+ type: 'Container',
+ data: {
+ style: {
+ backgroundColor: null,
+ borderColor: null,
+ borderRadius: null,
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ childrenIds: ['block_8Xioqh7pENRgRhzFpVqh8w'],
+ },
+ },
+ },
+ block_65qW362ex9LdhQXx1dLSuq: {
+ type: 'Container',
+ data: {
+ style: {
+ backgroundColor: null,
+ borderColor: null,
+ borderRadius: null,
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ childrenIds: [],
+ },
+ },
+ },
+ block_7FUnUJ6umTXZz3Mn54KLCY: {
+ type: 'ColumnsContainer',
+ data: {
+ style: {
+ backgroundColor: null,
+ padding: {
+ top: 24,
+ bottom: 16,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ columnsCount: 2,
+ columns: [
+ {
+ childrenIds: ['block_7eXpDRpkNETHdMN8Bkg9m1'],
+ },
+ {
+ childrenIds: ['block_DCRQd2eFKSbUGvmQkxDdHs'],
+ },
+ {
+ childrenIds: ['block_65qW362ex9LdhQXx1dLSuq'],
+ },
+ ],
+ },
+ },
+ },
+ block_7RKLxAoSQFEyUSMx9fdkw3: {
+ type: 'Divider',
+ data: {
+ style: {
+ backgroundColor: null,
+ padding: {
+ top: 16,
+ bottom: 16,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ lineHeight: 1,
+ lineColor: '#EEEEEE',
+ },
+ },
+ },
+ block_FUvBg3iUzvmUcTjmjvo5Wd: {
+ type: 'Container',
+ data: {
+ style: {
+ backgroundColor: null,
+ borderColor: null,
+ borderRadius: null,
+ padding: {
+ top: 8,
+ bottom: 8,
+ left: 24,
+ right: 24,
+ },
+ },
+ props: {
+ childrenIds: [
+ 'block_SnYxmj5ia2K4dqVEaZDLst',
+ 'block_8rxqcogAeoRMFJJwHDLBvY',
+ 'block_RUef6VdysRRfcaDSdPPPRL',
+ 'block_FDZhF8wpD8Am5TBAxqXYRn',
+ 'block_9Fj1tXV6otFnGdgJevbFPG',
+ 'block_7FUnUJ6umTXZz3Mn54KLCY',
+ 'block_7RKLxAoSQFEyUSMx9fdkw3',
+ ],
+ },
+ },
+ },
+ block_WQ5J7hXLdDXn8Vq4bVkAie: {
+ type: 'Heading',
+ data: {
+ style: {
+ color: null,
+ backgroundColor: null,
+ fontFamily: null,
+ fontWeight: 'bold',
+ textAlign: 'left',
+ padding: {
+ top: 0,
+ bottom: 16,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ level: 'h3',
+ text: 'Host payout',
+ },
+ },
+ },
+ block_9ztgHz93w9GvUBGTrDUCHE: {
+ type: 'Text',
+ data: {
+ style: {
+ color: null,
+ backgroundColor: null,
+ fontSize: 16,
+ fontFamily: null,
+ fontWeight: 'normal',
+ textAlign: 'left',
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ text: '$99.00 x 2 nights',
+ },
+ },
+ },
+ block_CbV491KJbEv4crKswx5HHD: {
+ type: 'Container',
+ data: {
+ style: {
+ backgroundColor: null,
+ borderColor: null,
+ borderRadius: null,
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ childrenIds: ['block_9ztgHz93w9GvUBGTrDUCHE'],
+ },
+ },
+ },
+ block_GJqkQ13ELZmPRexPJirGiF: {
+ type: 'Text',
+ data: {
+ style: {
+ color: null,
+ backgroundColor: null,
+ fontSize: 16,
+ fontFamily: null,
+ fontWeight: 'normal',
+ textAlign: 'right',
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ text: '$198.00',
+ },
+ },
+ },
+ block_BPtTenyy93Su76mzF1cpqd: {
+ type: 'Container',
+ data: {
+ style: {
+ backgroundColor: null,
+ borderColor: null,
+ borderRadius: null,
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ childrenIds: ['block_GJqkQ13ELZmPRexPJirGiF'],
+ },
+ },
+ },
+ block_94kXBLAgQPLF79YgFvAefs: {
+ type: 'Container',
+ data: {
+ style: {
+ backgroundColor: null,
+ borderColor: null,
+ borderRadius: null,
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ childrenIds: [],
+ },
+ },
+ },
+ block_StE5sGCYCoZTGAgs5KDKVX: {
+ type: 'ColumnsContainer',
+ data: {
+ style: {
+ backgroundColor: null,
+ padding: {
+ top: 8,
+ bottom: 8,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ columnsCount: 2,
+ columns: [
+ {
+ childrenIds: ['block_CbV491KJbEv4crKswx5HHD'],
+ },
+ {
+ childrenIds: ['block_BPtTenyy93Su76mzF1cpqd'],
+ },
+ {
+ childrenIds: ['block_94kXBLAgQPLF79YgFvAefs'],
+ },
+ ],
+ },
+ },
+ },
+ block_NAtKDFR77MRHULTzQJy3M4: {
+ type: 'Text',
+ data: {
+ style: {
+ color: null,
+ backgroundColor: null,
+ fontSize: 16,
+ fontFamily: null,
+ fontWeight: 'normal',
+ textAlign: 'left',
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ text: 'Cleaning fee',
+ },
+ },
+ },
+ block_GG9WkMFMLq5ySDKBScvVnF: {
+ type: 'Container',
+ data: {
+ style: {
+ backgroundColor: null,
+ borderColor: null,
+ borderRadius: null,
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ childrenIds: ['block_NAtKDFR77MRHULTzQJy3M4'],
+ },
+ },
+ },
+ block_ScETZ7SFzGKLaxS5NqDh96: {
+ type: 'Text',
+ data: {
+ style: {
+ color: null,
+ backgroundColor: null,
+ fontSize: 16,
+ fontFamily: null,
+ fontWeight: 'normal',
+ textAlign: 'right',
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ text: '$50.00',
+ },
+ },
+ },
+ block_NAzfAuBqmG9wA2j2qykgF7: {
+ type: 'Container',
+ data: {
+ style: {
+ backgroundColor: null,
+ borderColor: null,
+ borderRadius: null,
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ childrenIds: ['block_ScETZ7SFzGKLaxS5NqDh96'],
+ },
+ },
+ },
+ block_QAXFjyCSg9Mwog7ukkRzcX: {
+ type: 'Container',
+ data: {
+ style: {
+ backgroundColor: null,
+ borderColor: null,
+ borderRadius: null,
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ childrenIds: [],
+ },
+ },
+ },
+ block_RYUv7gd391axaL7itsyKqD: {
+ type: 'ColumnsContainer',
+ data: {
+ style: {
+ backgroundColor: null,
+ padding: {
+ top: 8,
+ bottom: 8,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ columnsCount: 2,
+ columns: [
+ {
+ childrenIds: ['block_GG9WkMFMLq5ySDKBScvVnF'],
+ },
+ {
+ childrenIds: ['block_NAzfAuBqmG9wA2j2qykgF7'],
+ },
+ {
+ childrenIds: ['block_QAXFjyCSg9Mwog7ukkRzcX'],
+ },
+ ],
+ },
+ },
+ },
+ block_LLCNdKMpQ7YzhfmVU5GhCR: {
+ type: 'Text',
+ data: {
+ style: {
+ color: null,
+ backgroundColor: null,
+ fontSize: 16,
+ fontFamily: null,
+ fontWeight: 'normal',
+ textAlign: 'left',
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ text: 'Host service fee (3%)',
+ },
+ },
+ },
+ block_Y5UCxoKxDgggVgnrgPakvt: {
+ type: 'Container',
+ data: {
+ style: {
+ backgroundColor: null,
+ borderColor: null,
+ borderRadius: null,
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ childrenIds: ['block_LLCNdKMpQ7YzhfmVU5GhCR'],
+ },
+ },
+ },
+ block_G9S6kAvgE1VkeoytcpG7dg: {
+ type: 'Text',
+ data: {
+ style: {
+ color: null,
+ backgroundColor: null,
+ fontSize: 16,
+ fontFamily: null,
+ fontWeight: 'normal',
+ textAlign: 'right',
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ text: '$7.44',
+ },
+ },
+ },
+ block_QrX6x2NPgGGScZPn4mSe1a: {
+ type: 'Container',
+ data: {
+ style: {
+ backgroundColor: null,
+ borderColor: null,
+ borderRadius: null,
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ childrenIds: ['block_G9S6kAvgE1VkeoytcpG7dg'],
+ },
+ },
+ },
+ block_QYx54o6C4Tvkw58N3nMt9t: {
+ type: 'Container',
+ data: {
+ style: {
+ backgroundColor: null,
+ borderColor: null,
+ borderRadius: null,
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ childrenIds: [],
+ },
+ },
+ },
+ block_LSBTeahYDd47Fxt3ymwBZj: {
+ type: 'ColumnsContainer',
+ data: {
+ style: {
+ backgroundColor: null,
+ padding: {
+ top: 8,
+ bottom: 8,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ columnsCount: 2,
+ columns: [
+ {
+ childrenIds: ['block_Y5UCxoKxDgggVgnrgPakvt'],
+ },
+ {
+ childrenIds: ['block_QrX6x2NPgGGScZPn4mSe1a'],
+ },
+ {
+ childrenIds: ['block_QYx54o6C4Tvkw58N3nMt9t'],
+ },
+ ],
+ },
+ },
+ },
+ block_JRj1iySUsD8LjG1r9R1qCP: {
+ type: 'Text',
+ data: {
+ style: {
+ color: null,
+ backgroundColor: null,
+ fontSize: 16,
+ fontFamily: null,
+ fontWeight: 'bold',
+ textAlign: 'left',
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ text: 'Total (USD)',
+ },
+ },
+ },
+ block_FANLGemZZEXcrKPfe1CNw: {
+ type: 'Container',
+ data: {
+ style: {
+ backgroundColor: null,
+ borderColor: null,
+ borderRadius: null,
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ childrenIds: ['block_JRj1iySUsD8LjG1r9R1qCP'],
+ },
+ },
+ },
+ block_LFBMTsnpzjENRjTbjecCr9: {
+ type: 'Text',
+ data: {
+ style: {
+ color: null,
+ backgroundColor: null,
+ fontSize: 16,
+ fontFamily: null,
+ fontWeight: 'bold',
+ textAlign: 'right',
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ text: '$240.56',
+ },
+ },
+ },
+ block_Eb9iDCjkJQBET2qNsaL74L: {
+ type: 'Container',
+ data: {
+ style: {
+ backgroundColor: null,
+ borderColor: null,
+ borderRadius: null,
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ childrenIds: ['block_LFBMTsnpzjENRjTbjecCr9'],
+ },
+ },
+ },
+ block_UPrvRbjeyXXT57HiVpHxuH: {
+ type: 'Container',
+ data: {
+ style: {
+ backgroundColor: null,
+ borderColor: null,
+ borderRadius: null,
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ childrenIds: [],
+ },
+ },
+ },
+ block_V7XtD9k81GjEmSt2HHCrQH: {
+ type: 'ColumnsContainer',
+ data: {
+ style: {
+ backgroundColor: null,
+ padding: {
+ top: 24,
+ bottom: 16,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ columnsCount: 2,
+ columns: [
+ {
+ childrenIds: ['block_FANLGemZZEXcrKPfe1CNw'],
+ },
+ {
+ childrenIds: ['block_Eb9iDCjkJQBET2qNsaL74L'],
+ },
+ {
+ childrenIds: ['block_UPrvRbjeyXXT57HiVpHxuH'],
+ },
+ ],
+ },
+ },
+ },
+ block_9tD2RA16EdVQfPXKNSXeru: {
+ type: 'Text',
+ data: {
+ style: {
+ color: null,
+ backgroundColor: null,
+ fontSize: 16,
+ fontFamily: null,
+ fontWeight: 'normal',
+ textAlign: 'left',
+ padding: {
+ top: 16,
+ bottom: 16,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ text: 'The money you earn hosting will be sent to you 24 hours after your guest arrives. You can check your upcoming payments in your transaction history.',
+ },
+ },
+ },
+ block_A9ub75sjdu8XvuCnwSA9AC: {
+ type: 'Divider',
+ data: {
+ style: {
+ backgroundColor: null,
+ padding: {
+ top: 16,
+ bottom: 16,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ lineHeight: 1,
+ lineColor: '#EEEEEE',
+ },
+ },
+ },
+ block_UncM7Be9AkE197F3KYjpa5: {
+ type: 'Container',
+ data: {
+ style: {
+ backgroundColor: null,
+ borderColor: null,
+ borderRadius: null,
+ padding: {
+ top: 8,
+ bottom: 8,
+ left: 24,
+ right: 24,
+ },
+ },
+ props: {
+ childrenIds: [
+ 'block_WQ5J7hXLdDXn8Vq4bVkAie',
+ 'block_StE5sGCYCoZTGAgs5KDKVX',
+ 'block_RYUv7gd391axaL7itsyKqD',
+ 'block_LSBTeahYDd47Fxt3ymwBZj',
+ 'block_V7XtD9k81GjEmSt2HHCrQH',
+ 'block_9tD2RA16EdVQfPXKNSXeru',
+ 'block_A9ub75sjdu8XvuCnwSA9AC',
+ ],
+ },
+ },
+ },
+ block_3vaa8gDUB8D7gyYzTpKGzN: {
+ type: 'Heading',
+ data: {
+ style: {
+ color: null,
+ backgroundColor: null,
+ fontFamily: null,
+ fontWeight: 'bold',
+ textAlign: 'left',
+ padding: {
+ top: 0,
+ bottom: 8,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ level: 'h3',
+ text: 'Cancellations',
+ },
+ },
+ },
+ block_AqF4QDsGrVadYYB9yJywmL: {
+ type: 'Text',
+ data: {
+ style: {
+ color: null,
+ backgroundColor: null,
+ fontSize: 16,
+ fontFamily: null,
+ fontWeight: 'normal',
+ textAlign: 'left',
+ padding: {
+ top: 0,
+ bottom: 24,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ text: 'Your cancellation policy for guests is Moderate.\n\nThe penalties for canceling this reservation include getting a public review that shows you canceled, paying a cancellation fee, and having the canceled nights blocked on your calendar.',
+ },
+ },
+ },
+ block_F9xbhy5omsUCjE9MuDfxJ5: {
+ type: 'Divider',
+ data: {
+ style: {
+ backgroundColor: null,
+ padding: {
+ top: 16,
+ bottom: 16,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ lineHeight: 1,
+ lineColor: '#EEEEEE',
+ },
+ },
+ },
+ block_8hNnkxhS84scJ18FCwRr4q: {
+ type: 'Container',
+ data: {
+ style: {
+ backgroundColor: null,
+ borderColor: null,
+ borderRadius: null,
+ padding: {
+ top: 8,
+ bottom: 0,
+ left: 24,
+ right: 24,
+ },
+ },
+ props: {
+ childrenIds: [
+ 'block_3vaa8gDUB8D7gyYzTpKGzN',
+ 'block_AqF4QDsGrVadYYB9yJywmL',
+ 'block_F9xbhy5omsUCjE9MuDfxJ5',
+ ],
+ },
+ },
+ },
+ block_JihqAfW5u5wR8oQUJFP5kE: {
+ type: 'Text',
+ data: {
+ style: {
+ color: null,
+ backgroundColor: null,
+ fontSize: 14,
+ fontFamily: null,
+ fontWeight: 'normal',
+ textAlign: 'left',
+ padding: {
+ top: 16,
+ bottom: 16,
+ left: 24,
+ right: 24,
+ },
+ },
+ props: {
+ text: 'Need help? Just reply to this email.',
+ },
+ },
+ },
+};
+
+export default RESERVATION_REMINDER;
diff --git a/packages/post-kit-editor/src/email-builder-ui/getConfiguration/sample/reset-password.ts b/packages/post-kit-editor/src/email-builder-ui/getConfiguration/sample/reset-password.ts
new file mode 100644
index 0000000..353df35
--- /dev/null
+++ b/packages/post-kit-editor/src/email-builder-ui/getConfiguration/sample/reset-password.ts
@@ -0,0 +1,156 @@
+import { TEditorConfiguration } from '../../documents/editor/core';
+
+const RESET_PASSWORD: TEditorConfiguration = {
+ root: {
+ type: 'EmailLayout',
+ data: {
+ backdropColor: '#F2F5F7',
+ canvasColor: '#FFFFFF',
+ textColor: '#242424',
+ fontFamily: 'MODERN_SANS',
+ childrenIds: [
+ 'block_3gpSGmkgL4nWSBQjWCjK2z',
+ 'block_BjpQ7DGTtvaEuYRMd7VE7w',
+ 'block_xyg4GWmgGbJJEDRQc76bC',
+ 'block_76VptLCZ47t3EkAarUufEJ',
+ 'block_Gtk3kDYwsJqEmQf2XGWPRc',
+ 'block_LACDCzUS2bsvEbmnq1KHuW',
+ ],
+ },
+ },
+ block_3gpSGmkgL4nWSBQjWCjK2z: {
+ type: 'Image',
+ data: {
+ style: {
+ padding: {
+ top: 24,
+ bottom: 8,
+ right: 24,
+ left: 24,
+ },
+ backgroundColor: null,
+ textAlign: 'left',
+ },
+ props: {
+ height: 24,
+ url: 'https://d1iiu589g39o6c.cloudfront.net/live/platforms/platform_A9wwKSL6EV6orh6f/images/wptemplateimage_Xh1R23U9ziyct9nd/codoc.png',
+ alt: '',
+ linkHref: null,
+ contentAlignment: 'middle',
+ },
+ },
+ },
+ block_BjpQ7DGTtvaEuYRMd7VE7w: {
+ type: 'Heading',
+ data: {
+ style: {
+ color: null,
+ backgroundColor: null,
+ fontFamily: null,
+ fontWeight: 'bold',
+ textAlign: 'left',
+ padding: {
+ top: 32,
+ bottom: 0,
+ left: 24,
+ right: 24,
+ },
+ },
+ props: {
+ level: 'h3',
+ text: 'Reset your password?',
+ },
+ },
+ },
+ block_xyg4GWmgGbJJEDRQc76bC: {
+ type: 'Text',
+ data: {
+ style: {
+ color: '#474849',
+ backgroundColor: null,
+ fontSize: 14,
+ fontFamily: null,
+ fontWeight: 'normal',
+ textAlign: 'left',
+ padding: {
+ top: 8,
+ bottom: 16,
+ left: 24,
+ right: 24,
+ },
+ },
+ props: {
+ text: `If you didn't request a reset, don't worry. You can safely ignore this email.`,
+ },
+ },
+ },
+ block_76VptLCZ47t3EkAarUufEJ: {
+ type: 'Button',
+ data: {
+ style: {
+ backgroundColor: null,
+ fontSize: 14,
+ fontFamily: null,
+ fontWeight: 'bold',
+ textAlign: 'left',
+ padding: {
+ top: 12,
+ bottom: 32,
+ right: 24,
+ left: 24,
+ },
+ },
+ props: {
+ buttonBackgroundColor: '#0068FF',
+ buttonStyle: 'rectangle',
+ buttonTextColor: '#FFFFFF',
+ fullWidth: false,
+ size: 'medium',
+ text: 'Change my password',
+ url: 'https://example.usewaypoint.com/reset_password?token=02938409809w8r09a83wr098aw0',
+ },
+ },
+ },
+ block_Gtk3kDYwsJqEmQf2XGWPRc: {
+ type: 'Divider',
+ data: {
+ style: {
+ backgroundColor: null,
+ padding: {
+ top: 16,
+ bottom: 16,
+ left: 24,
+ right: 24,
+ },
+ },
+ props: {
+ lineHeight: 1,
+ lineColor: '#EEEEEE',
+ },
+ },
+ },
+ block_LACDCzUS2bsvEbmnq1KHuW: {
+ type: 'Text',
+ data: {
+ style: {
+ color: '#474849',
+ backgroundColor: null,
+ fontSize: 12,
+ fontFamily: null,
+ fontWeight: 'normal',
+ textAlign: 'left',
+ padding: {
+ top: 4,
+ bottom: 24,
+ left: 24,
+ right: 24,
+ },
+ },
+ props: {
+ text: 'Need help? Just reply to this email to contact support.',
+ },
+ },
+ },
+};
+
+export default RESET_PASSWORD;
diff --git a/packages/post-kit-editor/src/email-builder-ui/getConfiguration/sample/respond-to-message.ts b/packages/post-kit-editor/src/email-builder-ui/getConfiguration/sample/respond-to-message.ts
new file mode 100644
index 0000000..7b554af
--- /dev/null
+++ b/packages/post-kit-editor/src/email-builder-ui/getConfiguration/sample/respond-to-message.ts
@@ -0,0 +1,175 @@
+import { TEditorConfiguration } from '../../documents/editor/core';
+
+const RESPOND_TO_MESSAGE: TEditorConfiguration = {
+ root: {
+ type: 'EmailLayout',
+ data: {
+ backdropColor: '#F0ECE5',
+ canvasColor: '#F0ECE5',
+ textColor: '#030303',
+ fontFamily: 'MODERN_SERIF',
+ childrenIds: [
+ 'block_HjX7RN8eDEz7BLBHSQCNgU',
+ 'block_Jf65r5cUAnEzBfxnHKGa5S',
+ 'block_WmPDNHDpyHkygqjHuqF7St',
+ 'block_4VCKUvRMo7EbuMdN1VsdRw',
+ 'block_4siwziT4BkewmN55PpXvEu',
+ 'block_S9Rg9F3bGcviRyfMpoU5e4',
+ ],
+ },
+ },
+ block_HjX7RN8eDEz7BLBHSQCNgU: {
+ type: 'Image',
+ data: {
+ style: {
+ padding: {
+ top: 8,
+ bottom: 24,
+ left: 24,
+ right: 24,
+ },
+ },
+ props: {
+ height: 32,
+ url: 'https://d1iiu589g39o6c.cloudfront.net/live/platforms/platform_A9wwKSL6EV6orh6f/images/wptemplateimage_hW6RusynHUNTKoLm/boop.png',
+ contentAlignment: 'middle',
+ },
+ },
+ },
+ block_Jf65r5cUAnEzBfxnHKGa5S: {
+ type: 'Heading',
+ data: {
+ style: {
+ color: null,
+ backgroundColor: null,
+ fontFamily: null,
+ fontWeight: null,
+ textAlign: null,
+ padding: {
+ top: 16,
+ bottom: 0,
+ left: 24,
+ right: 24,
+ },
+ },
+ props: {
+ level: 'h2',
+ text: `Respond to Anna's Inquiry`,
+ },
+ },
+ },
+ block_WmPDNHDpyHkygqjHuqF7St: {
+ type: 'Text',
+ data: {
+ style: {
+ color: null,
+ backgroundColor: null,
+ fontSize: 16,
+ fontFamily: null,
+ fontWeight: null,
+ textAlign: null,
+ padding: {
+ top: 8,
+ bottom: 16,
+ left: 24,
+ right: 24,
+ },
+ },
+ props: {
+ text: 'Dog boarding for Aug 1 - Aug 29.',
+ },
+ },
+ },
+ block_95nkowWyi4p2VBiA46Eizs: {
+ type: 'Text',
+ data: {
+ style: {
+ color: null,
+ backgroundColor: '#faf9f9',
+ fontSize: 21,
+ fontFamily: null,
+ fontWeight: null,
+ textAlign: null,
+ padding: {
+ top: 24,
+ bottom: 24,
+ left: 24,
+ right: 24,
+ },
+ },
+ props: {
+ text: 'Any chance you can watch Emma again next month?',
+ },
+ },
+ },
+ block_4VCKUvRMo7EbuMdN1VsdRw: {
+ type: 'Container',
+ data: {
+ style: {
+ backgroundColor: null,
+ borderColor: null,
+ borderRadius: null,
+ padding: {
+ top: 16,
+ bottom: 16,
+ left: 24,
+ right: 24,
+ },
+ },
+ props: {
+ childrenIds: ['block_95nkowWyi4p2VBiA46Eizs'],
+ },
+ },
+ },
+ block_4siwziT4BkewmN55PpXvEu: {
+ type: 'Button',
+ data: {
+ style: {
+ backgroundColor: null,
+ fontSize: 16,
+ fontFamily: null,
+ fontWeight: null,
+ textAlign: 'left',
+ padding: {
+ top: 24,
+ bottom: 24,
+ left: 24,
+ right: 24,
+ },
+ },
+ props: {
+ buttonBackgroundColor: '#BE4F46',
+ buttonTextColor: '#FFFFFF',
+ size: 'large',
+ buttonStyle: 'pill',
+ text: 'Respond',
+ url: 'http://example.usewaypoint.com/request/2334234',
+ fullWidth: false,
+ },
+ },
+ },
+ block_S9Rg9F3bGcviRyfMpoU5e4: {
+ type: 'Text',
+ data: {
+ style: {
+ color: null,
+ backgroundColor: null,
+ fontSize: 16,
+ fontFamily: null,
+ fontWeight: 'normal',
+ textAlign: 'left',
+ padding: {
+ top: 0,
+ bottom: 16,
+ left: 24,
+ right: 24,
+ },
+ },
+ props: {
+ text: 'You need 2 more walks to become a Super Walker on Boop!',
+ },
+ },
+ },
+};
+
+export default RESPOND_TO_MESSAGE;
diff --git a/packages/post-kit-editor/src/email-builder-ui/getConfiguration/sample/subscription-receipt.ts b/packages/post-kit-editor/src/email-builder-ui/getConfiguration/sample/subscription-receipt.ts
new file mode 100644
index 0000000..9755c77
--- /dev/null
+++ b/packages/post-kit-editor/src/email-builder-ui/getConfiguration/sample/subscription-receipt.ts
@@ -0,0 +1,1349 @@
+import { TEditorConfiguration } from '../../documents/editor/core';
+
+const SUBSCRIPTION_RECEIPT: TEditorConfiguration = {
+ root: {
+ type: 'EmailLayout',
+ data: {
+ backdropColor: '#F5F5F5',
+ canvasColor: '#F5F5F5',
+ textColor: '#242424',
+ fontFamily: 'MODERN_SANS',
+ childrenIds: [
+ 'block_URcE7RiYB227zNraU1Nujd',
+ 'block_TisRUSez8uPYr6bgHLKQeg',
+ 'block_UQHMPb5NFLrY9PkWUckmHb',
+ 'block_FLTQdJVBNsmRxurTZTSC2V',
+ 'block_Qq64GeHw7K24Fgz5oX81kt',
+ ],
+ },
+ },
+ block_DNGbxXXMnwkRrecLDTP6VR: {
+ type: 'Image',
+ data: {
+ style: {
+ backgroundColor: null,
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ textAlign: 'left',
+ },
+ props: {
+ height: 18,
+ url: 'https://d1iiu589g39o6c.cloudfront.net/live/platforms/platform_A9wwKSL6EV6orh6f/images/wptemplateimage_9TcdHLq5SpEkRADB/REMIX.png',
+ alt: 'Remix',
+ linkHref: 'https://remix.example.com',
+ contentAlignment: 'middle',
+ },
+ },
+ },
+ block_ULKdXApwJ1dsJCa5fntAXV: {
+ type: 'Container',
+ data: {
+ style: {
+ backgroundColor: null,
+ borderColor: null,
+ borderRadius: null,
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ childrenIds: ['block_DNGbxXXMnwkRrecLDTP6VR'],
+ },
+ },
+ },
+ block_FBxe99baQhH1dptybLYVrF: {
+ type: 'Button',
+ data: {
+ style: {
+ backgroundColor: null,
+ fontSize: 14,
+ fontFamily: null,
+ fontWeight: 'normal',
+ textAlign: 'right',
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ buttonBackgroundColor: '#FFFFFF',
+ buttonStyle: 'pill',
+ buttonTextColor: '#242424',
+ fullWidth: false,
+ size: 'medium',
+ text: 'Sign in',
+ url: 'https://remix.example.com/dashboard',
+ },
+ },
+ },
+ block_WDjxjPcraFGwiBHj5vjhdV: {
+ type: 'Container',
+ data: {
+ style: {
+ backgroundColor: null,
+ borderColor: null,
+ borderRadius: null,
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ childrenIds: ['block_FBxe99baQhH1dptybLYVrF'],
+ },
+ },
+ },
+ block_JUxxjtK5TSUk9MV6Rtra4Q: {
+ type: 'Container',
+ data: {
+ style: {
+ backgroundColor: null,
+ borderColor: null,
+ borderRadius: null,
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ childrenIds: [],
+ },
+ },
+ },
+ block_URcE7RiYB227zNraU1Nujd: {
+ type: 'ColumnsContainer',
+ data: {
+ style: {
+ backgroundColor: '#f5f5f5',
+ padding: {
+ top: 16,
+ bottom: 16,
+ left: 24,
+ right: 24,
+ },
+ },
+ props: {
+ columnsCount: 2,
+ columns: [
+ {
+ childrenIds: ['block_ULKdXApwJ1dsJCa5fntAXV'],
+ },
+ {
+ childrenIds: ['block_WDjxjPcraFGwiBHj5vjhdV'],
+ },
+ {
+ childrenIds: ['block_JUxxjtK5TSUk9MV6Rtra4Q'],
+ },
+ ],
+ },
+ },
+ },
+ block_9mYZ55v7d29WRjNgPf5cuw: {
+ type: 'Text',
+ data: {
+ style: {
+ color: null,
+ backgroundColor: null,
+ fontSize: 14,
+ fontFamily: null,
+ fontWeight: 'normal',
+ textAlign: 'left',
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ text: 'Receipt from Remix Studios, Inc.',
+ },
+ },
+ },
+ block_R5vyrdxj7v4FqyFbQ7iDBM: {
+ type: 'Heading',
+ data: {
+ style: {
+ color: null,
+ backgroundColor: null,
+ fontFamily: null,
+ fontWeight: 'bold',
+ textAlign: 'left',
+ padding: {
+ top: 16,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ level: 'h1',
+ text: '$99.75',
+ },
+ },
+ },
+ block_7vHVGWiRQYr8sigcW9nJvD: {
+ type: 'Text',
+ data: {
+ style: {
+ color: '#474849',
+ backgroundColor: null,
+ fontSize: 14,
+ fontFamily: null,
+ fontWeight: 'normal',
+ textAlign: 'left',
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ text: 'Paid August 1, 2023',
+ },
+ },
+ },
+ block_AL2uK2hkvCnaT6JtDuvE5n: {
+ type: 'Divider',
+ data: {
+ style: {
+ backgroundColor: null,
+ padding: {
+ top: 16,
+ bottom: 16,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ lineHeight: 1,
+ lineColor: '#EEEEEE',
+ },
+ },
+ },
+ block_ADvJa3qqbBH1TB84VxbFwC: {
+ type: 'Text',
+ data: {
+ style: {
+ color: null,
+ backgroundColor: null,
+ fontSize: 14,
+ fontFamily: null,
+ fontWeight: 'normal',
+ textAlign: 'left',
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ text: 'Download receipt',
+ },
+ },
+ },
+ block_9NpGWp5DnLHA2gbTUzsWHX: {
+ type: 'Container',
+ data: {
+ style: {
+ backgroundColor: null,
+ borderColor: null,
+ borderRadius: null,
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ childrenIds: [
+ 'block_9mYZ55v7d29WRjNgPf5cuw',
+ 'block_R5vyrdxj7v4FqyFbQ7iDBM',
+ 'block_7vHVGWiRQYr8sigcW9nJvD',
+ 'block_AL2uK2hkvCnaT6JtDuvE5n',
+ 'block_ADvJa3qqbBH1TB84VxbFwC',
+ ],
+ },
+ },
+ },
+ block_FMRV8DJAAWqpoJta6ivKEe: {
+ type: 'Image',
+ data: {
+ style: {
+ backgroundColor: null,
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ textAlign: 'right',
+ },
+ props: {
+ url: 'https://d1iiu589g39o6c.cloudfront.net/live/platforms/platform_A9wwKSL6EV6orh6f/images/wptemplateimage_8yUGBZcXaAtTEofB/invoice-skeleton.png',
+ alt: 'Your invoice has been paid.',
+ linkHref: 'http://remix.example.com/receipt/1923-2093',
+ contentAlignment: 'middle',
+ },
+ },
+ },
+ block_SGQxVVB8bXVmVg9NyDY3Fu: {
+ type: 'Container',
+ data: {
+ style: {
+ backgroundColor: null,
+ borderColor: null,
+ borderRadius: null,
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ childrenIds: ['block_FMRV8DJAAWqpoJta6ivKEe'],
+ },
+ },
+ },
+ block_KY8rgT1mqUBiCm2uranpvx: {
+ type: 'Container',
+ data: {
+ style: {
+ backgroundColor: null,
+ borderColor: null,
+ borderRadius: null,
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ childrenIds: [],
+ },
+ },
+ },
+ block_4TVeBRBPut2oZoQpG9FV4J: {
+ type: 'ColumnsContainer',
+ data: {
+ style: {
+ backgroundColor: null,
+ padding: {
+ top: 16,
+ bottom: 16,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ columnsCount: 2,
+ columns: [
+ {
+ childrenIds: ['block_9NpGWp5DnLHA2gbTUzsWHX'],
+ },
+ {
+ childrenIds: ['block_SGQxVVB8bXVmVg9NyDY3Fu'],
+ },
+ {
+ childrenIds: ['block_KY8rgT1mqUBiCm2uranpvx'],
+ },
+ ],
+ },
+ },
+ },
+ block_67EyoqnbtLAHWXiobN39NX: {
+ type: 'Text',
+ data: {
+ style: {
+ color: '#474849',
+ backgroundColor: null,
+ fontSize: 14,
+ fontFamily: null,
+ fontWeight: 'normal',
+ textAlign: 'left',
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ text: 'Receipt number',
+ },
+ },
+ },
+ block_UCrU3np77mWqQgTfYHG1NL: {
+ type: 'Container',
+ data: {
+ style: {
+ backgroundColor: null,
+ borderColor: null,
+ borderRadius: null,
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ childrenIds: ['block_67EyoqnbtLAHWXiobN39NX'],
+ },
+ },
+ },
+ block_LQ93SUN8XxG6uaLbGpsAx1: {
+ type: 'Text',
+ data: {
+ style: {
+ color: null,
+ backgroundColor: null,
+ fontSize: 14,
+ fontFamily: null,
+ fontWeight: 'normal',
+ textAlign: 'right',
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ text: '1923-2093',
+ },
+ },
+ },
+ block_6KX5ggTcFs5ckbt9wZppac: {
+ type: 'Container',
+ data: {
+ style: {
+ backgroundColor: null,
+ borderColor: null,
+ borderRadius: null,
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ childrenIds: ['block_LQ93SUN8XxG6uaLbGpsAx1'],
+ },
+ },
+ },
+ block_AsFj6GNHWFUhG6GgM7Ww9r: {
+ type: 'Container',
+ data: {
+ style: {
+ backgroundColor: null,
+ borderColor: null,
+ borderRadius: null,
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ childrenIds: [],
+ },
+ },
+ },
+ block_SfGDZ1NhRmtjFJS6qs4Zpc: {
+ type: 'ColumnsContainer',
+ data: {
+ style: {
+ backgroundColor: null,
+ padding: {
+ top: 8,
+ bottom: 8,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ columnsCount: 2,
+ columns: [
+ {
+ childrenIds: ['block_UCrU3np77mWqQgTfYHG1NL'],
+ },
+ {
+ childrenIds: ['block_6KX5ggTcFs5ckbt9wZppac'],
+ },
+ {
+ childrenIds: ['block_AsFj6GNHWFUhG6GgM7Ww9r'],
+ },
+ ],
+ },
+ },
+ },
+ block_6UhQGq7NwSAhRog8yRYjJo: {
+ type: 'Text',
+ data: {
+ style: {
+ color: '#474849',
+ backgroundColor: null,
+ fontSize: 14,
+ fontFamily: null,
+ fontWeight: 'normal',
+ textAlign: 'left',
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ text: 'Invoice number',
+ },
+ },
+ },
+ block_F3FdxCgPYxnH8SYgFgBsuY: {
+ type: 'Container',
+ data: {
+ style: {
+ backgroundColor: null,
+ borderColor: null,
+ borderRadius: null,
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ childrenIds: ['block_6UhQGq7NwSAhRog8yRYjJo'],
+ },
+ },
+ },
+ block_L5on9HCEU6BRvx25RBRgS2: {
+ type: 'Text',
+ data: {
+ style: {
+ color: null,
+ backgroundColor: null,
+ fontSize: 14,
+ fontFamily: null,
+ fontWeight: 'normal',
+ textAlign: 'right',
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ text: '2ABC1234-20923',
+ },
+ },
+ },
+ block_WrzeLHiJs9VnXSkLXXD5ED: {
+ type: 'Container',
+ data: {
+ style: {
+ backgroundColor: null,
+ borderColor: null,
+ borderRadius: null,
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ childrenIds: ['block_L5on9HCEU6BRvx25RBRgS2'],
+ },
+ },
+ },
+ block_Wb8pxrh2frxYZWM6hmfc5L: {
+ type: 'Container',
+ data: {
+ style: {
+ backgroundColor: null,
+ borderColor: null,
+ borderRadius: null,
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ childrenIds: [],
+ },
+ },
+ },
+ block_XiNoAViyVVEWafdMW4x4TL: {
+ type: 'ColumnsContainer',
+ data: {
+ style: {
+ backgroundColor: null,
+ padding: {
+ top: 8,
+ bottom: 8,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ columnsCount: 2,
+ columns: [
+ {
+ childrenIds: ['block_F3FdxCgPYxnH8SYgFgBsuY'],
+ },
+ {
+ childrenIds: ['block_WrzeLHiJs9VnXSkLXXD5ED'],
+ },
+ {
+ childrenIds: ['block_Wb8pxrh2frxYZWM6hmfc5L'],
+ },
+ ],
+ },
+ },
+ },
+ block_LbwZcoeGigXpK4PTVTyZ8E: {
+ type: 'Text',
+ data: {
+ style: {
+ color: '#474849',
+ backgroundColor: null,
+ fontSize: 14,
+ fontFamily: null,
+ fontWeight: 'normal',
+ textAlign: 'left',
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ text: 'Payment method',
+ },
+ },
+ },
+ block_ETbxLryzrgoKbVBnw1ieyi: {
+ type: 'Container',
+ data: {
+ style: {
+ backgroundColor: null,
+ borderColor: null,
+ borderRadius: null,
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ childrenIds: ['block_LbwZcoeGigXpK4PTVTyZ8E'],
+ },
+ },
+ },
+ block_P8fn3PavFZVTrEwufKHKEF: {
+ type: 'Text',
+ data: {
+ style: {
+ color: null,
+ backgroundColor: null,
+ fontSize: 14,
+ fontFamily: null,
+ fontWeight: 'normal',
+ textAlign: 'right',
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ text: 'VISA – 4252',
+ },
+ },
+ },
+ block_2Bx6KDcv1nT4hPiQ8Eabfv: {
+ type: 'Container',
+ data: {
+ style: {
+ backgroundColor: null,
+ borderColor: null,
+ borderRadius: null,
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ childrenIds: ['block_P8fn3PavFZVTrEwufKHKEF'],
+ },
+ },
+ },
+ block_WSzbB3PKqRUxCuUqp9wuK6: {
+ type: 'Container',
+ data: {
+ style: {
+ backgroundColor: null,
+ borderColor: null,
+ borderRadius: null,
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ childrenIds: [],
+ },
+ },
+ },
+ block_NYNuDnrs6ZnZ6kj927yv7W: {
+ type: 'ColumnsContainer',
+ data: {
+ style: {
+ backgroundColor: null,
+ padding: {
+ top: 8,
+ bottom: 8,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ columnsCount: 2,
+ columns: [
+ {
+ childrenIds: ['block_ETbxLryzrgoKbVBnw1ieyi'],
+ },
+ {
+ childrenIds: ['block_2Bx6KDcv1nT4hPiQ8Eabfv'],
+ },
+ {
+ childrenIds: ['block_WSzbB3PKqRUxCuUqp9wuK6'],
+ },
+ ],
+ },
+ },
+ },
+ block_TisRUSez8uPYr6bgHLKQeg: {
+ type: 'Container',
+ data: {
+ style: {
+ backgroundColor: '#ffffff',
+ borderColor: null,
+ borderRadius: null,
+ padding: {
+ top: 16,
+ bottom: 24,
+ left: 24,
+ right: 24,
+ },
+ },
+ props: {
+ childrenIds: [
+ 'block_4TVeBRBPut2oZoQpG9FV4J',
+ 'block_SfGDZ1NhRmtjFJS6qs4Zpc',
+ 'block_XiNoAViyVVEWafdMW4x4TL',
+ 'block_NYNuDnrs6ZnZ6kj927yv7W',
+ ],
+ },
+ },
+ },
+ block_UQHMPb5NFLrY9PkWUckmHb: {
+ type: 'Spacer',
+ data: {
+ props: {
+ height: 24,
+ },
+ },
+ },
+ block_RNsVmDsY33ipzGLtRUsYys: {
+ type: 'Heading',
+ data: {
+ style: {
+ color: null,
+ backgroundColor: null,
+ fontFamily: null,
+ fontWeight: 'normal',
+ textAlign: 'left',
+ padding: {
+ top: 16,
+ bottom: 16,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ level: 'h3',
+ text: 'Receipt #1923-2093',
+ },
+ },
+ },
+ block_Y7W2h9xDuNreQdgMrv82KZ: {
+ type: 'Text',
+ data: {
+ style: {
+ color: '#474849',
+ backgroundColor: null,
+ fontSize: 14,
+ fontFamily: null,
+ fontWeight: 'normal',
+ textAlign: 'left',
+ padding: {
+ top: 16,
+ bottom: 16,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ text: `JULY 10, 2023 – AUGUST 1, 2023`,
+ },
+ },
+ },
+ block_4m1h6Xzhvi9oDrFMgWajjH: {
+ type: 'Text',
+ data: {
+ style: {
+ color: null,
+ backgroundColor: null,
+ fontSize: 14,
+ fontFamily: null,
+ fontWeight: 'bold',
+ textAlign: 'left',
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ text: 'Remix Pro',
+ },
+ },
+ },
+ block_7egXzHguX1zPm5CwYiKuPh: {
+ type: 'Text',
+ data: {
+ style: {
+ color: '#474849',
+ backgroundColor: null,
+ fontSize: 12,
+ fontFamily: null,
+ fontWeight: 'normal',
+ textAlign: 'left',
+ padding: {
+ top: 8,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ text: '5 seats',
+ },
+ },
+ },
+ block_WbxCFjDKk1Ev11prfgLhvX: {
+ type: 'Container',
+ data: {
+ style: {
+ backgroundColor: null,
+ borderColor: null,
+ borderRadius: null,
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ childrenIds: ['block_4m1h6Xzhvi9oDrFMgWajjH', 'block_7egXzHguX1zPm5CwYiKuPh'],
+ },
+ },
+ },
+ block_FUaNAn8h7DzU7kVzeFqsug: {
+ type: 'Text',
+ data: {
+ style: {
+ color: null,
+ backgroundColor: null,
+ fontSize: 14,
+ fontFamily: null,
+ fontWeight: 'bold',
+ textAlign: 'right',
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ text: '$99.75',
+ },
+ },
+ },
+ block_9gDnB1o17vBrS4usmyAVUr: {
+ type: 'Container',
+ data: {
+ style: {
+ backgroundColor: null,
+ borderColor: null,
+ borderRadius: null,
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ childrenIds: ['block_FUaNAn8h7DzU7kVzeFqsug'],
+ },
+ },
+ },
+ block_5wDvatGEixESNpVrjWbAGi: {
+ type: 'Container',
+ data: {
+ style: {
+ backgroundColor: null,
+ borderColor: null,
+ borderRadius: null,
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ childrenIds: [],
+ },
+ },
+ },
+ block_KFadGhRvS4kHVRr41XMiRs: {
+ type: 'ColumnsContainer',
+ data: {
+ style: {
+ backgroundColor: null,
+ padding: {
+ top: 8,
+ bottom: 8,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ columnsCount: 2,
+ columns: [
+ {
+ childrenIds: ['block_WbxCFjDKk1Ev11prfgLhvX'],
+ },
+ {
+ childrenIds: ['block_9gDnB1o17vBrS4usmyAVUr'],
+ },
+ {
+ childrenIds: ['block_5wDvatGEixESNpVrjWbAGi'],
+ },
+ ],
+ },
+ },
+ },
+ block_3rTWcrZLSt1nnCYW5BdiU9: {
+ type: 'Divider',
+ data: {
+ style: {
+ backgroundColor: null,
+ padding: {
+ top: 8,
+ bottom: 8,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ lineHeight: 1,
+ lineColor: '#EEEEEE',
+ },
+ },
+ },
+ block_CSV8NQS6gBMQcFXvFAFo3Q: {
+ type: 'Container',
+ data: {
+ style: {
+ backgroundColor: null,
+ borderColor: null,
+ borderRadius: null,
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ childrenIds: ['block_KFadGhRvS4kHVRr41XMiRs', 'block_3rTWcrZLSt1nnCYW5BdiU9'],
+ },
+ },
+ },
+ block_XZZUC61s2jBfaMfjcFYSo2: {
+ type: 'Text',
+ data: {
+ style: {
+ color: null,
+ backgroundColor: null,
+ fontSize: 14,
+ fontFamily: null,
+ fontWeight: 'bold',
+ textAlign: 'left',
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ text: 'Total',
+ },
+ },
+ },
+ block_LfhgxAyqkNPTGubxqYJNZ5: {
+ type: 'Container',
+ data: {
+ style: {
+ backgroundColor: null,
+ borderColor: null,
+ borderRadius: null,
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ childrenIds: ['block_XZZUC61s2jBfaMfjcFYSo2'],
+ },
+ },
+ },
+ block_MsXkdwkAWAbvJ5EcwvqSqv: {
+ type: 'Text',
+ data: {
+ style: {
+ color: null,
+ backgroundColor: null,
+ fontSize: 14,
+ fontFamily: null,
+ fontWeight: 'bold',
+ textAlign: 'right',
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ text: '$99.75',
+ },
+ },
+ },
+ block_PiKRrj91HQKM1qUW4gDJt1: {
+ type: 'Container',
+ data: {
+ style: {
+ backgroundColor: null,
+ borderColor: null,
+ borderRadius: null,
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ childrenIds: ['block_MsXkdwkAWAbvJ5EcwvqSqv'],
+ },
+ },
+ },
+ block_WhzFAeoTPhhgNcG9rdcd4K: {
+ type: 'Container',
+ data: {
+ style: {
+ backgroundColor: null,
+ borderColor: null,
+ borderRadius: null,
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ childrenIds: [],
+ },
+ },
+ },
+ block_LJcwZCh6z1EyFeeKH8Egts: {
+ type: 'ColumnsContainer',
+ data: {
+ style: {
+ backgroundColor: null,
+ padding: {
+ top: 8,
+ bottom: 8,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ columnsCount: 2,
+ columns: [
+ {
+ childrenIds: ['block_LfhgxAyqkNPTGubxqYJNZ5'],
+ },
+ {
+ childrenIds: ['block_PiKRrj91HQKM1qUW4gDJt1'],
+ },
+ {
+ childrenIds: ['block_WhzFAeoTPhhgNcG9rdcd4K'],
+ },
+ ],
+ },
+ },
+ },
+ block_HSbX9aqHvSXw9GAB361vpM: {
+ type: 'Divider',
+ data: {
+ style: {
+ backgroundColor: null,
+ padding: {
+ top: 8,
+ bottom: 8,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ lineHeight: 1,
+ lineColor: '#EEEEEE',
+ },
+ },
+ },
+ block_3of5ne4hc1PFiSKHz59FP8: {
+ type: 'Text',
+ data: {
+ style: {
+ color: null,
+ backgroundColor: null,
+ fontSize: 14,
+ fontFamily: null,
+ fontWeight: 'bold',
+ textAlign: 'left',
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ text: 'Amount paid',
+ },
+ },
+ },
+ block_GTPnMRuHqf1bCLk8VLAr4z: {
+ type: 'Container',
+ data: {
+ style: {
+ backgroundColor: null,
+ borderColor: null,
+ borderRadius: null,
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ childrenIds: ['block_3of5ne4hc1PFiSKHz59FP8'],
+ },
+ },
+ },
+ block_XvojX8xDiQRr4VNHDwtyXZ: {
+ type: 'Text',
+ data: {
+ style: {
+ color: null,
+ backgroundColor: null,
+ fontSize: 14,
+ fontFamily: null,
+ fontWeight: 'bold',
+ textAlign: 'right',
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ text: '$99.75',
+ },
+ },
+ },
+ block_LbEheyx16SX5S8GK2zY3sJ: {
+ type: 'Container',
+ data: {
+ style: {
+ backgroundColor: null,
+ borderColor: null,
+ borderRadius: null,
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ childrenIds: ['block_XvojX8xDiQRr4VNHDwtyXZ'],
+ },
+ },
+ },
+ block_M6BwLBjomRNsdee4rdoA4y: {
+ type: 'Container',
+ data: {
+ style: {
+ backgroundColor: null,
+ borderColor: null,
+ borderRadius: null,
+ padding: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ childrenIds: [],
+ },
+ },
+ },
+ block_DUQnBfK11aHN7ZypDPfeug: {
+ type: 'ColumnsContainer',
+ data: {
+ style: {
+ backgroundColor: null,
+ padding: {
+ top: 8,
+ bottom: 8,
+ left: 0,
+ right: 0,
+ },
+ },
+ props: {
+ columnsCount: 2,
+ columns: [
+ {
+ childrenIds: ['block_GTPnMRuHqf1bCLk8VLAr4z'],
+ },
+ {
+ childrenIds: ['block_LbEheyx16SX5S8GK2zY3sJ'],
+ },
+ {
+ childrenIds: ['block_M6BwLBjomRNsdee4rdoA4y'],
+ },
+ ],
+ },
+ },
+ },
+ block_FLTQdJVBNsmRxurTZTSC2V: {
+ type: 'Container',
+ data: {
+ style: {
+ backgroundColor: '#ffffff',
+ borderColor: null,
+ borderRadius: null,
+ padding: {
+ top: 16,
+ bottom: 24,
+ left: 24,
+ right: 24,
+ },
+ },
+ props: {
+ childrenIds: [
+ 'block_RNsVmDsY33ipzGLtRUsYys',
+ 'block_Y7W2h9xDuNreQdgMrv82KZ',
+ 'block_CSV8NQS6gBMQcFXvFAFo3Q',
+ 'block_LJcwZCh6z1EyFeeKH8Egts',
+ 'block_HSbX9aqHvSXw9GAB361vpM',
+ 'block_DUQnBfK11aHN7ZypDPfeug',
+ ],
+ },
+ },
+ },
+ block_Qq64GeHw7K24Fgz5oX81kt: {
+ type: 'Text',
+ data: {
+ style: {
+ color: '#474849',
+ backgroundColor: null,
+ fontSize: 12,
+ fontFamily: null,
+ fontWeight: 'normal',
+ textAlign: 'left',
+ padding: {
+ top: 24,
+ bottom: 16,
+ left: 24,
+ right: 24,
+ },
+ },
+ props: {
+ text: 'Can we help? Just reply to this email.',
+ },
+ },
+ },
+};
+
+export default SUBSCRIPTION_RECEIPT;
diff --git a/packages/post-kit-editor/src/email-builder-ui/getConfiguration/sample/welcome.ts b/packages/post-kit-editor/src/email-builder-ui/getConfiguration/sample/welcome.ts
new file mode 100644
index 0000000..54cf5e6
--- /dev/null
+++ b/packages/post-kit-editor/src/email-builder-ui/getConfiguration/sample/welcome.ts
@@ -0,0 +1,170 @@
+import { TEditorConfiguration } from '../../documents/editor/core';
+
+const WELCOME: TEditorConfiguration = {
+ root: {
+ type: 'EmailLayout',
+ data: {
+ backdropColor: '#F2F5F7',
+ canvasColor: '#FFFFFF',
+ textColor: '#242424',
+ fontFamily: 'MODERN_SANS',
+ childrenIds: [
+ 'block-1709571212684',
+ 'block-1709571228545',
+ 'block-1709571234315',
+ 'block-1709571247550',
+ 'block-1709571258507',
+ 'block-1709571281151',
+ 'block-1709571302968',
+ 'block-1709571282795',
+ ],
+ },
+ },
+ 'block-1709571212684': {
+ type: 'Image',
+ data: {
+ style: {
+ padding: {
+ top: 24,
+ bottom: 24,
+ right: 24,
+ left: 24,
+ },
+ },
+ props: {
+ url: 'https://d1iiu589g39o6c.cloudfront.net/live/platforms/platform_A9wwKSL6EV6orh6f/images/wptemplateimage_JTNBBPGrNs2Ph4JL/marketbase.png',
+ alt: 'Marketbase',
+ linkHref: 'https://marketbase.app',
+ contentAlignment: 'middle',
+ },
+ },
+ },
+ 'block-1709571228545': {
+ type: 'Text',
+ data: {
+ style: {
+ fontWeight: 'normal',
+ padding: {
+ top: 0,
+ bottom: 16,
+ right: 24,
+ left: 24,
+ },
+ },
+ props: {
+ text: 'Hi Anna 👋,',
+ },
+ },
+ },
+ 'block-1709571234315': {
+ type: 'Text',
+ data: {
+ style: {
+ fontWeight: 'normal',
+ padding: {
+ top: 0,
+ bottom: 16,
+ right: 24,
+ left: 24,
+ },
+ },
+ props: {
+ text: 'Welcome to Marketbase! Marketbase is how teams within fast growing marketplaces effortlessly monitor conversations to prevent disintermediation, identify problematic users, and increase trust & safety within their community.',
+ },
+ },
+ },
+ 'block-1709571247550': {
+ type: 'Text',
+ data: {
+ style: {
+ fontWeight: 'normal',
+ padding: {
+ top: 0,
+ bottom: 16,
+ right: 24,
+ left: 24,
+ },
+ },
+ props: {
+ text: 'Best of all, you can connect your existing messaging services in minutes:',
+ },
+ },
+ },
+ 'block-1709571258507': {
+ type: 'Image',
+ data: {
+ style: {
+ padding: {
+ top: 16,
+ bottom: 16,
+ right: 24,
+ left: 24,
+ },
+ },
+ props: {
+ url: 'https://d1iiu589g39o6c.cloudfront.net/live/platforms/platform_A9wwKSL6EV6orh6f/images/wptemplateimage_oWB821TUkDXvr2f4/Screenshot%202023-11-22%20at%2011.51.30%20AM.png',
+ alt: 'Video thumbnail',
+ linkHref: 'https://capture.dropbox.com/NBQEmoCKKP9RGBWr',
+ contentAlignment: 'middle',
+ },
+ },
+ },
+ 'block-1709571281151': {
+ type: 'Text',
+ data: {
+ style: {
+ fontWeight: 'normal',
+ padding: {
+ top: 16,
+ bottom: 16,
+ right: 24,
+ left: 24,
+ },
+ },
+ props: {
+ text: 'If you ever need help, just reply to this email and one of us will get back to you shortly. We’re here to help.',
+ },
+ },
+ },
+ 'block-1709571282795': {
+ type: 'Image',
+ data: {
+ style: {
+ padding: {
+ top: 16,
+ bottom: 40,
+ right: 24,
+ left: 24,
+ },
+ },
+ props: {
+ url: 'https://d1iiu589g39o6c.cloudfront.net/live/platforms/platform_A9wwKSL6EV6orh6f/images/wptemplateimage_cAK8FpmBEGoSRNi3/Screenshot%202023-11-22%20at%2011.48.53%20AM.png',
+ alt: 'Illustration',
+ linkHref: null,
+ contentAlignment: 'middle',
+ },
+ },
+ },
+ 'block-1709571302968': {
+ type: 'Button',
+ data: {
+ style: {
+ fontSize: 14,
+ padding: {
+ top: 16,
+ bottom: 24,
+ right: 24,
+ left: 24,
+ },
+ },
+ props: {
+ buttonBackgroundColor: '#0079cc',
+ buttonStyle: 'rectangle',
+ text: 'Open dashboard',
+ url: 'https://www.usewaypoint.com',
+ },
+ },
+ },
+};
+
+export default WELCOME;
diff --git a/packages/post-kit-editor/src/email-builder-ui/theme.spec.ts b/packages/post-kit-editor/src/email-builder-ui/theme.spec.ts
new file mode 100644
index 0000000..e9fa66e
--- /dev/null
+++ b/packages/post-kit-editor/src/email-builder-ui/theme.spec.ts
@@ -0,0 +1,14 @@
+import assert from 'node:assert/strict';
+import { describe, it } from 'node:test';
+import { readFileSync } from 'node:fs';
+import path from 'node:path';
+import { fileURLToPath } from 'node:url';
+
+describe('theme monospace pre styles', () => {
+ it('terminates the font-family declaration before white-space', () => {
+ const themePath = path.join(path.dirname(fileURLToPath(import.meta.url)), 'theme.ts');
+ const source = readFileSync(themePath, 'utf8');
+ assert.match(source, /font-family:\s*\$\{MONOSPACE_FONT_FAMILY\};/);
+ assert.match(source, /font-family:\s*\$\{MONOSPACE_FONT_FAMILY\};\s*white-space:/s);
+ });
+});
diff --git a/packages/post-kit-editor/src/email-builder-ui/theme.ts b/packages/post-kit-editor/src/email-builder-ui/theme.ts
new file mode 100644
index 0000000..bd62ab3
--- /dev/null
+++ b/packages/post-kit-editor/src/email-builder-ui/theme.ts
@@ -0,0 +1,520 @@
+import { alpha, createTheme, darken, lighten } from '@mui/material/styles';
+
+const BRAND_NAVY = '#212443';
+const BRAND_BLUE = '#0079CC';
+const BRAND_GREEN = '#1F8466';
+const BRAND_RED = '#E81212';
+const BRAND_YELLOW = '#F6DC9F';
+const BRAND_PURPLE = '#6C0E7C';
+const BRAND_BROWN = '#CC996C';
+const STANDARD_FONT_FAMILY =
+ '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"';
+const MONOSPACE_FONT_FAMILY =
+ 'ui-monospace, Menlo, Monaco, "Cascadia Mono", "Segoe UI Mono", "Roboto Mono", "Oxygen Mono", "Ubuntu Monospace", "Source Code Pro", "Fira Mono", "Droid Sans Mono", "Courier New", monospace';
+
+const BASE_THEME = createTheme({
+ palette: {
+ background: {
+ default: '#f2f5f7',
+ },
+ text: {
+ primary: '#1F1F21',
+ secondary: '#4F4F4F',
+ },
+ },
+ typography: {
+ fontFamily: STANDARD_FONT_FAMILY,
+ },
+});
+
+const THEME = createTheme(BASE_THEME, {
+ palette: {
+ brand: {
+ navy: BRAND_NAVY,
+ blue: BRAND_BLUE,
+ red: BRAND_RED,
+ green: BRAND_GREEN,
+ yellow: BRAND_YELLOW,
+ purple: BRAND_PURPLE,
+ brown: BRAND_BROWN,
+ },
+ success: {
+ main: BRAND_GREEN,
+ light: lighten(BRAND_GREEN, 0.15),
+ dark: darken(BRAND_GREEN, 0.15),
+ },
+ error: {
+ main: BRAND_RED,
+ light: lighten(BRAND_RED, 0.15),
+ dark: darken(BRAND_RED, 0.15),
+ },
+ cadet: {
+ 100: '#F9FAFB',
+ 200: '#F2F5F7',
+ 300: '#DCE4EA',
+ 400: '#A8BBCA',
+ 500: '#6A8BA4',
+ },
+ highlight: {
+ 100: lighten(BRAND_YELLOW, 0.8),
+ 200: lighten(BRAND_YELLOW, 0.6),
+ 300: lighten(BRAND_YELLOW, 0.4),
+ 400: lighten(BRAND_YELLOW, 0.2),
+ 500: BRAND_YELLOW,
+ },
+ info: {
+ main: BRAND_BLUE,
+ },
+ primary: {
+ main: BRAND_BLUE,
+ },
+ },
+ components: {
+ MuiCssBaseline: {
+ styleOverrides: `
+ address {
+ font-style: normal;
+ }
+ fieldset {
+ border: none;
+ padding: 0;
+ }
+ pre {
+ font-family: ${MONOSPACE_FONT_FAMILY};
+ white-space: pre-wrap;
+ font-size: 12px;
+ }
+ `,
+ },
+ MuiAlert: {
+ styleOverrides: {
+ root: {
+ fontSize: BASE_THEME.typography.pxToRem(14),
+ },
+ action: {
+ paddingTop: 0,
+ marginRight: 0,
+ },
+ filledSuccess: {
+ backgroundColor: BRAND_GREEN,
+ },
+ },
+ },
+ MuiStepLabel: {
+ styleOverrides: {
+ label: {
+ fontWeight: BASE_THEME.typography.fontWeightMedium,
+ },
+ },
+ },
+ MuiDialog: {
+ defaultProps: {
+ fullWidth: true,
+ },
+ },
+ MuiDialogContent: {
+ styleOverrides: {
+ root: {
+ paddingTop: BASE_THEME.spacing(1),
+ paddingBottom: BASE_THEME.spacing(2),
+ },
+ },
+ },
+ MuiDialogTitle: {
+ defaultProps: {
+ variant: 'h4',
+ },
+ styleOverrides: {
+ root: {
+ paddingTop: BASE_THEME.spacing(3),
+ paddingBottom: BASE_THEME.spacing(1),
+ },
+ },
+ },
+ MuiDialogActions: {
+ styleOverrides: {
+ root: {
+ borderTop: '1px solid',
+ borderTopColor: BASE_THEME.palette.divider,
+ marginTop: BASE_THEME.spacing(2.5),
+ padding: `${BASE_THEME.spacing(1.5)} ${BASE_THEME.spacing(3)}`,
+ },
+ },
+ },
+ MuiTableCell: {
+ styleOverrides: {
+ root: {
+ ...BASE_THEME.typography.body2,
+ borderColor: BASE_THEME.palette.grey[200],
+ },
+ head: {
+ ...BASE_THEME.typography.overline,
+ fontWeight: BASE_THEME.typography.fontWeightMedium,
+ letterSpacing: '0.075em',
+ color: BASE_THEME.palette.text.secondary,
+ },
+ },
+ },
+ MuiTableRow: {
+ styleOverrides: {
+ root: {
+ '&:last-child td': {
+ borderBottom: 0,
+ },
+ },
+ },
+ },
+ MuiAvatar: {
+ styleOverrides: {
+ root: {
+ textTransform: 'uppercase',
+ fontSize: BASE_THEME.typography.pxToRem(14),
+ },
+ },
+ },
+ MuiChip: {
+ styleOverrides: {
+ root: {
+ '&.MuiChip-filledError, &.MuiChip-filledSuccess': {
+ fill: BASE_THEME.palette.primary.contrastText,
+ },
+ },
+ sizeSmall: {
+ borderRadius: BASE_THEME.spacing(0.5),
+ fontSize: 12,
+ },
+ iconSmall: {
+ fontSize: 14,
+ marginLeft: BASE_THEME.spacing(1),
+ },
+ colorSecondary: {
+ borderColor: BASE_THEME.palette.grey[400],
+ color: BASE_THEME.palette.text.secondary,
+ },
+ label: {
+ fontWeight: BASE_THEME.typography.fontWeightMedium,
+ },
+ },
+ },
+ MuiDrawer: {
+ defaultProps: {
+ PaperProps: {
+ elevation: 2,
+ },
+ },
+ },
+ MuiTooltip: {
+ styleOverrides: {
+ tooltip: {
+ fontSize: BASE_THEME.typography.pxToRem(12),
+ backgroundColor: alpha(BASE_THEME.palette.text.primary, 0.9),
+ },
+ },
+ },
+ MuiSlider: {
+ styleOverrides: {
+ root: {
+ height: 1,
+ },
+ track: {
+ height: 1,
+ border: 'none',
+ },
+ rail: {
+ height: 1,
+ backgroundColor: BASE_THEME.palette.grey[500],
+ },
+ mark: {
+ backgroundColor: BASE_THEME.palette.grey[500],
+ },
+ markActive: {
+ height: 0,
+ },
+ thumb: {
+ height: 16,
+ width: 16,
+ cursor: 'col-resize',
+ '&:hover, &.Mui-active, &.Mui-focusVisible': {
+ boxShadow: `0 0 0 4px ${alpha(BRAND_BLUE, 0.2)}`,
+ },
+ '&:before': {
+ display: 'none',
+ },
+ },
+ },
+ },
+ MuiPaper: {
+ defaultProps: {
+ elevation: 2,
+ square: true,
+ },
+ },
+ MuiButtonBase: {
+ defaultProps: {
+ disableTouchRipple: true,
+ focusRipple: true,
+ },
+ styleOverrides: {
+ root: {
+ '&.MuiButton-containedSecondary.Mui-disabled': {
+ backgroundColor: BASE_THEME.palette.grey[100],
+ },
+ },
+ },
+ },
+ MuiButtonGroup: {
+ defaultProps: {
+ disableElevation: true,
+ },
+ },
+ MuiIconButton: {
+ styleOverrides: {
+ edgeStart: {
+ marginLeft: BASE_THEME.spacing(-1),
+ },
+ colorSecondary: {
+ color: BASE_THEME.palette.grey[500],
+ },
+ },
+ },
+ MuiButton: {
+ defaultProps: {
+ disableElevation: true,
+ },
+ styleOverrides: {
+ textPrimary: {
+ color: BASE_THEME.palette.text.primary,
+ },
+ textSecondary: {
+ color: BASE_THEME.palette.text.secondary,
+ },
+ outlinedPrimary: {
+ borderColor: BASE_THEME.palette.grey[300],
+ color: BASE_THEME.palette.text.primary,
+ '&:hover, &:active, &:focus': {
+ borderColor: BASE_THEME.palette.grey[500],
+ color: BASE_THEME.palette.text.primary,
+ },
+ },
+ containedSecondary: {
+ backgroundColor: BASE_THEME.palette.common.white,
+ border: `1px solid ${BASE_THEME.palette.grey[300]}`,
+ color: BASE_THEME.palette.text.primary,
+ '&:hover, &:active, &:focus': {
+ backgroundColor: BASE_THEME.palette.common.white,
+ borderColor: BASE_THEME.palette.grey[500],
+ color: BASE_THEME.palette.text.primary,
+ },
+ },
+ },
+ },
+ MuiToggleButton: {
+ styleOverrides: {
+ root: {
+ paddingLeft: BASE_THEME.spacing(1.5),
+ paddingRight: BASE_THEME.spacing(1.5),
+ },
+ },
+ },
+ MuiInputBase: {
+ styleOverrides: {
+ root: {
+ '&:not(.Mui-disabled, .Mui-error):before': {
+ borderBottom: `1px solid ${BASE_THEME.palette.grey[400]}`,
+ },
+ '&:hover:not(.Mui-disabled, .Mui-error):before': {
+ borderBottom: `1px solid ${BASE_THEME.palette.grey[500]} !important`,
+ },
+ '&:after': {
+ borderBottom: `1px solid ${BASE_THEME.palette.text.primary} !important`,
+ },
+ '&.MuiOutlinedInput-root:not(.Mui-error)': {
+ '& fieldset': {
+ borderColor: BASE_THEME.palette.grey[300],
+ transition: 'border-color 0.2s',
+ },
+ },
+ '&.MuiOutlinedInput-root:not(.Mui-disabled, .Mui-error)': {
+ '&:hover fieldset': {
+ borderColor: BASE_THEME.palette.grey[400],
+ },
+ '&.Mui-focused fieldset': {
+ borderColor: BASE_THEME.palette.text.secondary,
+ borderWidth: 1,
+ },
+ },
+ },
+ input: {
+ fontSize: BASE_THEME.typography.pxToRem(14),
+ '&.Mui-disabled': {
+ WebkitTextFillColor: 'inherit',
+ color: BASE_THEME.palette.text.secondary,
+ },
+ },
+ inputSizeSmall: {},
+ },
+ },
+ MuiOutlinedInput: {
+ styleOverrides: {
+ notchedOutline: {
+ '& legend': {
+ fontSize: '0.85em',
+ maxWidth: '100%',
+ },
+ },
+ },
+ },
+ MuiInputAdornment: {
+ styleOverrides: {
+ root: {
+ '& .MuiTypography-root': {
+ fontSize: BASE_THEME.typography.pxToRem(14),
+ color: BASE_THEME.palette.text.secondary,
+ },
+ },
+ },
+ },
+ MuiInputLabel: {
+ defaultProps: {
+ shrink: true,
+ },
+ styleOverrides: {
+ shrink: {
+ transform: 'scale(0.85)',
+ fontWeight: BASE_THEME.typography.fontWeightMedium,
+ '&.Mui-focused': {
+ color: BASE_THEME.palette.text.primary,
+ },
+ '&.MuiInputLabel-standard': {
+ transform: 'translate(0, -4px) scale(0.85)',
+ color: '#4F4F4F',
+ },
+ '&.MuiInputLabel-outlined': {
+ transform: 'translate(15px, -8px) scale(0.85)',
+ },
+ },
+ },
+ },
+ MuiTabs: {
+ defaultProps: {
+ variant: 'scrollable',
+ },
+ styleOverrides: {
+ indicator: {
+ height: 1,
+ backgroundColor: BASE_THEME.palette.text.primary,
+ },
+ },
+ },
+ MuiTab: {
+ styleOverrides: {
+ root: {
+ textTransform: 'none',
+ minWidth: BASE_THEME.spacing(2),
+ paddingLeft: BASE_THEME.spacing(1.5),
+ paddingRight: BASE_THEME.spacing(1.5),
+ fontSize: BASE_THEME.typography.pxToRem(14),
+ fontFamily: BASE_THEME.typography.fontFamily,
+ lineHeight: 1.5,
+ fontWeight: BASE_THEME.typography.fontWeightMedium,
+ transition: 'color 0.2s',
+ '&.Mui-selected': {
+ color: BASE_THEME.palette.text.primary,
+ },
+ '&:hover': {
+ color: BASE_THEME.palette.text.primary,
+ },
+ },
+ },
+ },
+ MuiCard: {
+ styleOverrides: {
+ root: {
+ borderRadius: 0,
+ },
+ },
+ },
+ MuiCardHeader: {
+ styleOverrides: {
+ title: {
+ fontSize: BASE_THEME.typography.pxToRem(18),
+ fontWeight: BASE_THEME.typography.fontWeightMedium,
+ },
+ },
+ },
+ },
+ typography: {
+ fontFamily: BASE_THEME.typography.fontFamily,
+ h1: {
+ fontFamily: BASE_THEME.typography.fontFamily,
+ fontSize: BASE_THEME.typography.pxToRem(40),
+ lineHeight: 1.2,
+ letterSpacing: '-0.02em',
+ fontWeight: BASE_THEME.typography.fontWeightMedium,
+ },
+ h2: {
+ fontFamily: BASE_THEME.typography.fontFamily,
+ fontSize: BASE_THEME.typography.pxToRem(32),
+ lineHeight: 1.2,
+ letterSpacing: '-0.02em',
+ fontWeight: BASE_THEME.typography.fontWeightMedium,
+ },
+ h3: {
+ fontFamily: BASE_THEME.typography.fontFamily,
+ fontSize: BASE_THEME.typography.pxToRem(24),
+ lineHeight: 1.5,
+ letterSpacing: '-0.01em',
+ fontWeight: BASE_THEME.typography.fontWeightMedium,
+ },
+ h4: {
+ fontFamily: BASE_THEME.typography.fontFamily,
+ fontSize: BASE_THEME.typography.pxToRem(20),
+ lineHeight: 1.5,
+ letterSpacing: '-0.01em',
+ fontWeight: BASE_THEME.typography.fontWeightMedium,
+ },
+ h5: {
+ fontFamily: BASE_THEME.typography.fontFamily,
+ fontSize: BASE_THEME.typography.pxToRem(18),
+ lineHeight: 1.5,
+ letterSpacing: '-0.01em',
+ fontWeight: BASE_THEME.typography.fontWeightMedium,
+ },
+ h6: {
+ fontFamily: BASE_THEME.typography.fontFamily,
+ fontSize: BASE_THEME.typography.pxToRem(16),
+ lineHeight: 1.5,
+ letterSpacing: '-0.005em',
+ fontWeight: BASE_THEME.typography.fontWeightMedium,
+ },
+ body1: {
+ fontSize: BASE_THEME.typography.pxToRem(14),
+ },
+ body2: {
+ fontSize: BASE_THEME.typography.pxToRem(12),
+ },
+ overline: {
+ fontWeight: BASE_THEME.typography.fontWeightMedium,
+ letterSpacing: '0.05em',
+ },
+ button: {
+ textTransform: 'none',
+ fontWeight: BASE_THEME.typography.fontWeightMedium,
+ lineHeight: 1.5,
+ },
+ caption: {
+ letterSpacing: 0,
+ lineHeight: 1.5,
+ },
+ },
+ shadows: [
+ 'none',
+ '0px 4px 15px rgba(33, 36, 67, 0.04), 0px 0px 2px rgba(33, 36, 67, 0.04), 0px 0px 1px rgba(33, 36, 67, 0.04)',
+ '0px 10px 20px rgba(33, 36, 67, 0.04), 0px 2px 6px rgba(33, 36, 67, 0.04), 0px 0px 1px rgba(33, 36, 67, 0.04)',
+ '0px 16px 24px rgba(33, 36, 67, 0.05), 0px 2px 6px rgba(33, 36, 67, 0.05), 0px 0px 1px rgba(33, 36, 67, 0.05)',
+ '0px 24px 32px rgba(33, 36, 67, 0.06), 0px 16px 24px rgba(33, 36, 67, 0.06), 0px 4px 8px rgba(33, 36, 67, 0.06)',
+ ...Array(20).fill('none'),
+ ],
+});
+
+export default THEME;
diff --git a/packages/post-kit-editor/src/index.spec.tsx b/packages/post-kit-editor/src/index.spec.tsx
index c0bdc1e..24694d0 100644
--- a/packages/post-kit-editor/src/index.spec.tsx
+++ b/packages/post-kit-editor/src/index.spec.tsx
@@ -69,6 +69,7 @@ describe('EmailTemplateEditor', () => {
it('renders the EmailBuilder canvas inside the root container', () => {
const html = renderToStaticMarkup();
assert.match(html, new RegExp(`class="[^"]*${EDITOR_CLASS_PREFIX}canvas`));
+ assert.match(html, /data-testid="pk-editor-eb-mui-surface"/);
assert.match(html, /Hello \{\{name\}\}/);
});
@@ -155,5 +156,15 @@ describe('EmailBuilderCanvas', () => {
{}} readOnly />,
);
assert.match(html, /Hello \{\{name\}\}/);
+ assert.match(html, /data-testid="pk-editor-eb-mui-surface"/);
+ });
+
+ it('renders the MUI EmailBuilder surface with Samples drawer when editable', () => {
+ const document: EmailBuilderDocument = template.templateJson;
+ const html = renderToStaticMarkup(
+ {}} />,
+ );
+ assert.match(html, /data-testid="pk-editor-eb-mui-surface"/);
+ assert.match(html, />Samples);
});
});
diff --git a/packages/post-kit-editor/src/index.ts b/packages/post-kit-editor/src/index.ts
index 8aca667..750f7c2 100644
--- a/packages/post-kit-editor/src/index.ts
+++ b/packages/post-kit-editor/src/index.ts
@@ -8,6 +8,11 @@ export {
EmailTemplateEditor,
type EmailTemplateEditorProps,
} from './email-template-editor';
+export {
+ ADMIN_CLASS_PREFIX,
+ EmailTemplateAdmin,
+ type EmailTemplateAdminProps,
+} from './admin/EmailTemplateAdmin';
export { EmailBuilderCanvas, type EmailBuilderCanvasProps } from './canvas/EmailBuilderCanvas';
export { loadTemplateSource, serializeTemplateSource, TemplateSourceError } from './serialization';
export type { SerializedTemplateSource, SaveResult, SendTestResult } from './save-send/types';
diff --git a/packages/post-kit-editor/stories/Admin/EmailBuilder/Blocks/Avatar.stories.tsx b/packages/post-kit-editor/stories/Admin/EmailBuilder/Blocks/Avatar.stories.tsx
new file mode 100644
index 0000000..65465bc
--- /dev/null
+++ b/packages/post-kit-editor/stories/Admin/EmailBuilder/Blocks/Avatar.stories.tsx
@@ -0,0 +1,18 @@
+import type { Meta, StoryObj } from '@storybook/react';
+
+import { EmailBuilderCanvas } from '../../../../src';
+import { createAvatarBlockDocument } from '../../../fixtures';
+import { CanvasStory } from './_CanvasStory';
+
+const meta = {
+ title: 'Admin/Email Builder/Blocks/Avatar',
+ component: EmailBuilderCanvas,
+ parameters: { layout: 'fullscreen' },
+} satisfies Meta;
+
+export default meta;
+type Story = StoryObj;
+
+export const Default: Story = {
+ render: () => ,
+};
diff --git a/packages/post-kit-editor/stories/Admin/EmailBuilder/Blocks/Button.stories.tsx b/packages/post-kit-editor/stories/Admin/EmailBuilder/Blocks/Button.stories.tsx
new file mode 100644
index 0000000..7548262
--- /dev/null
+++ b/packages/post-kit-editor/stories/Admin/EmailBuilder/Blocks/Button.stories.tsx
@@ -0,0 +1,22 @@
+import type { Meta, StoryObj } from '@storybook/react';
+
+import { EmailBuilderCanvas } from '../../../../src';
+import { createButtonBlockDocument, createConfiguredButtonDocument } from '../../../fixtures';
+import { CanvasStory } from './_CanvasStory';
+
+const meta = {
+ title: 'Admin/Email Builder/Blocks/Button',
+ component: EmailBuilderCanvas,
+ parameters: { layout: 'fullscreen' },
+} satisfies Meta;
+
+export default meta;
+type Story = StoryObj;
+
+export const Default: Story = {
+ render: () => ,
+};
+
+export const Configured: Story = {
+ render: () => ,
+};
diff --git a/packages/post-kit-editor/stories/Admin/EmailBuilder/Blocks/Columns.stories.tsx b/packages/post-kit-editor/stories/Admin/EmailBuilder/Blocks/Columns.stories.tsx
new file mode 100644
index 0000000..07973ee
--- /dev/null
+++ b/packages/post-kit-editor/stories/Admin/EmailBuilder/Blocks/Columns.stories.tsx
@@ -0,0 +1,18 @@
+import type { Meta, StoryObj } from '@storybook/react';
+
+import { EmailBuilderCanvas } from '../../../../src';
+import { createColumnsBlockDocument } from '../../../fixtures';
+import { CanvasStory } from './_CanvasStory';
+
+const meta = {
+ title: 'Admin/Email Builder/Blocks/Columns',
+ component: EmailBuilderCanvas,
+ parameters: { layout: 'fullscreen' },
+} satisfies Meta;
+
+export default meta;
+type Story = StoryObj;
+
+export const Default: Story = {
+ render: () => ,
+};
diff --git a/packages/post-kit-editor/stories/Admin/EmailBuilder/Blocks/Container.stories.tsx b/packages/post-kit-editor/stories/Admin/EmailBuilder/Blocks/Container.stories.tsx
new file mode 100644
index 0000000..d188746
--- /dev/null
+++ b/packages/post-kit-editor/stories/Admin/EmailBuilder/Blocks/Container.stories.tsx
@@ -0,0 +1,18 @@
+import type { Meta, StoryObj } from '@storybook/react';
+
+import { EmailBuilderCanvas } from '../../../../src';
+import { createContainerBlockDocument } from '../../../fixtures';
+import { CanvasStory } from './_CanvasStory';
+
+const meta = {
+ title: 'Admin/Email Builder/Blocks/Container',
+ component: EmailBuilderCanvas,
+ parameters: { layout: 'fullscreen' },
+} satisfies Meta;
+
+export default meta;
+type Story = StoryObj;
+
+export const Default: Story = {
+ render: () => ,
+};
diff --git a/packages/post-kit-editor/stories/Admin/EmailBuilder/Blocks/Divider.stories.tsx b/packages/post-kit-editor/stories/Admin/EmailBuilder/Blocks/Divider.stories.tsx
new file mode 100644
index 0000000..c641a7b
--- /dev/null
+++ b/packages/post-kit-editor/stories/Admin/EmailBuilder/Blocks/Divider.stories.tsx
@@ -0,0 +1,18 @@
+import type { Meta, StoryObj } from '@storybook/react';
+
+import { EmailBuilderCanvas } from '../../../../src';
+import { createDividerBlockDocument } from '../../../fixtures';
+import { CanvasStory } from './_CanvasStory';
+
+const meta = {
+ title: 'Admin/Email Builder/Blocks/Divider',
+ component: EmailBuilderCanvas,
+ parameters: { layout: 'fullscreen' },
+} satisfies Meta;
+
+export default meta;
+type Story = StoryObj;
+
+export const Default: Story = {
+ render: () => ,
+};
diff --git a/packages/post-kit-editor/stories/Admin/EmailBuilder/Blocks/Heading.stories.tsx b/packages/post-kit-editor/stories/Admin/EmailBuilder/Blocks/Heading.stories.tsx
new file mode 100644
index 0000000..6cdb5d9
--- /dev/null
+++ b/packages/post-kit-editor/stories/Admin/EmailBuilder/Blocks/Heading.stories.tsx
@@ -0,0 +1,22 @@
+import type { Meta, StoryObj } from '@storybook/react';
+
+import { EmailBuilderCanvas } from '../../../../src';
+import { createConfiguredHeadingDocument, createHeadingBlockDocument } from '../../../fixtures';
+import { CanvasStory } from './_CanvasStory';
+
+const meta = {
+ title: 'Admin/Email Builder/Blocks/Heading',
+ component: EmailBuilderCanvas,
+ parameters: { layout: 'fullscreen' },
+} satisfies Meta;
+
+export default meta;
+type Story = StoryObj;
+
+export const Default: Story = {
+ render: () => ,
+};
+
+export const Configured: Story = {
+ render: () => ,
+};
diff --git a/packages/post-kit-editor/stories/Admin/EmailBuilder/Blocks/Html.stories.tsx b/packages/post-kit-editor/stories/Admin/EmailBuilder/Blocks/Html.stories.tsx
new file mode 100644
index 0000000..37f3a3d
--- /dev/null
+++ b/packages/post-kit-editor/stories/Admin/EmailBuilder/Blocks/Html.stories.tsx
@@ -0,0 +1,18 @@
+import type { Meta, StoryObj } from '@storybook/react';
+
+import { EmailBuilderCanvas } from '../../../../src';
+import { createHtmlBlockDocument } from '../../../fixtures';
+import { CanvasStory } from './_CanvasStory';
+
+const meta = {
+ title: 'Admin/Email Builder/Blocks/Html',
+ component: EmailBuilderCanvas,
+ parameters: { layout: 'fullscreen' },
+} satisfies Meta;
+
+export default meta;
+type Story = StoryObj;
+
+export const Default: Story = {
+ render: () => ,
+};
diff --git a/packages/post-kit-editor/stories/Admin/EmailBuilder/Blocks/Image.stories.tsx b/packages/post-kit-editor/stories/Admin/EmailBuilder/Blocks/Image.stories.tsx
new file mode 100644
index 0000000..0d57f22
--- /dev/null
+++ b/packages/post-kit-editor/stories/Admin/EmailBuilder/Blocks/Image.stories.tsx
@@ -0,0 +1,18 @@
+import type { Meta, StoryObj } from '@storybook/react';
+
+import { EmailBuilderCanvas } from '../../../../src';
+import { createImageBlockDocument } from '../../../fixtures';
+import { CanvasStory } from './_CanvasStory';
+
+const meta = {
+ title: 'Admin/Email Builder/Blocks/Image',
+ component: EmailBuilderCanvas,
+ parameters: { layout: 'fullscreen' },
+} satisfies Meta;
+
+export default meta;
+type Story = StoryObj;
+
+export const Default: Story = {
+ render: () => ,
+};
diff --git a/packages/post-kit-editor/stories/Admin/EmailBuilder/Blocks/Spacer.stories.tsx b/packages/post-kit-editor/stories/Admin/EmailBuilder/Blocks/Spacer.stories.tsx
new file mode 100644
index 0000000..3903ac0
--- /dev/null
+++ b/packages/post-kit-editor/stories/Admin/EmailBuilder/Blocks/Spacer.stories.tsx
@@ -0,0 +1,18 @@
+import type { Meta, StoryObj } from '@storybook/react';
+
+import { EmailBuilderCanvas } from '../../../../src';
+import { createSpacerBlockDocument } from '../../../fixtures';
+import { CanvasStory } from './_CanvasStory';
+
+const meta = {
+ title: 'Admin/Email Builder/Blocks/Spacer',
+ component: EmailBuilderCanvas,
+ parameters: { layout: 'fullscreen' },
+} satisfies Meta;
+
+export default meta;
+type Story = StoryObj;
+
+export const Default: Story = {
+ render: () => ,
+};
diff --git a/packages/post-kit-editor/stories/Admin/EmailBuilder/Blocks/Text.stories.tsx b/packages/post-kit-editor/stories/Admin/EmailBuilder/Blocks/Text.stories.tsx
new file mode 100644
index 0000000..207112e
--- /dev/null
+++ b/packages/post-kit-editor/stories/Admin/EmailBuilder/Blocks/Text.stories.tsx
@@ -0,0 +1,22 @@
+import type { Meta, StoryObj } from '@storybook/react';
+
+import { EmailBuilderCanvas } from '../../../../src';
+import { createConfiguredTextDocument, createTextBlockDocument } from '../../../fixtures';
+import { CanvasStory } from './_CanvasStory';
+
+const meta = {
+ title: 'Admin/Email Builder/Blocks/Text',
+ component: EmailBuilderCanvas,
+ parameters: { layout: 'fullscreen' },
+} satisfies Meta;
+
+export default meta;
+type Story = StoryObj;
+
+export const Default: Story = {
+ render: () => ,
+};
+
+export const Configured: Story = {
+ render: () => ,
+};
diff --git a/packages/post-kit-editor/stories/Admin/EmailBuilder/Blocks/_CanvasStory.tsx b/packages/post-kit-editor/stories/Admin/EmailBuilder/Blocks/_CanvasStory.tsx
new file mode 100644
index 0000000..a3d753c
--- /dev/null
+++ b/packages/post-kit-editor/stories/Admin/EmailBuilder/Blocks/_CanvasStory.tsx
@@ -0,0 +1,9 @@
+import React, { useState } from 'react';
+
+import { EmailBuilderCanvas, type EmailBuilderDocument } from '../../../../src';
+
+/** Stateful canvas wrapper for block-focused stories. */
+export function CanvasStory({ initial }: { initial: EmailBuilderDocument }): JSX.Element {
+ const [document, setDocument] = useState(initial);
+ return ;
+}
diff --git a/packages/post-kit-editor/stories/Admin/EmailBuilder/Interactions.stories.tsx b/packages/post-kit-editor/stories/Admin/EmailBuilder/Interactions.stories.tsx
new file mode 100644
index 0000000..2772487
--- /dev/null
+++ b/packages/post-kit-editor/stories/Admin/EmailBuilder/Interactions.stories.tsx
@@ -0,0 +1,86 @@
+import type { Meta, StoryObj } from '@storybook/react';
+import { expect, userEvent, within } from '@storybook/test';
+
+import { EmailTemplateEditor } from '../../../src';
+import {
+ createEmptyEmailDocument,
+ delaySave,
+ delaySaveFailure,
+ emptyTemplate,
+ sampleAvailableVariables,
+} from '../../fixtures';
+import { CanvasStory } from './Blocks/_CanvasStory';
+
+const meta = {
+ title: 'Admin/Email Builder/Interactions',
+ parameters: { layout: 'fullscreen' },
+} satisfies Meta;
+
+export default meta;
+type Story = StoryObj;
+
+/**
+ * Samples drawer opens by default. Play loads “Welcome email” from the Samples
+ * list (deterministic). Hover-gated add-block menus are intentionally skipped.
+ */
+export const AddBlock: Story = {
+ parameters: {
+ docs: {
+ description: {
+ story:
+ 'Empty canvas → Samples drawer “Welcome email”. Direct add-block controls are hover-only and not exercised here.',
+ },
+ },
+ },
+ render: () => ,
+ play: async ({ canvasElement, step }) => {
+ const canvas = within(canvasElement);
+ const welcome = await canvas.findByRole('button', { name: /welcome email/i });
+ await step('Load Welcome email sample', async () => {
+ await userEvent.click(welcome);
+ });
+ await expect(welcome).toBeInTheDocument();
+ },
+};
+
+export const SaveSuccess: Story = {
+ render: () => (
+
+ ),
+ play: async ({ canvasElement, step }) => {
+ const canvas = within(canvasElement);
+ const save = await canvas.findByTestId('pk-editor-save');
+ await step('Click Save', async () => {
+ await userEvent.click(save);
+ });
+ await step('Assert success feedback', async () => {
+ const status = await canvas.findByTestId('pk-editor-save-status');
+ await expect(status).toHaveTextContent(/mock save/i);
+ });
+ },
+};
+
+export const SaveFailure: Story = {
+ render: () => (
+
+ ),
+ play: async ({ canvasElement, step }) => {
+ const canvas = within(canvasElement);
+ const save = await canvas.findByTestId('pk-editor-save');
+ await step('Click Save', async () => {
+ await userEvent.click(save);
+ });
+ await step('Assert failure feedback', async () => {
+ const status = await canvas.findByTestId('pk-editor-save-status');
+ await expect(status).toHaveTextContent(/synthetic save failure/i);
+ });
+ },
+};
diff --git a/packages/post-kit-editor/stories/Admin/EmailBuilder/Layout.stories.tsx b/packages/post-kit-editor/stories/Admin/EmailBuilder/Layout.stories.tsx
new file mode 100644
index 0000000..10dca5d
--- /dev/null
+++ b/packages/post-kit-editor/stories/Admin/EmailBuilder/Layout.stories.tsx
@@ -0,0 +1,41 @@
+import type { Meta, StoryObj } from '@storybook/react';
+
+import { EmailBuilderCanvas } from '../../../src';
+import {
+ createColoursLayoutDocument,
+ createColumnsBlockDocument,
+ createLongContentDocument,
+ createNestedContainersDocument,
+ createTypographyLayoutDocument,
+} from '../../fixtures';
+import { CanvasStory } from './Blocks/_CanvasStory';
+
+const meta = {
+ title: 'Admin/Email Builder/Layout',
+ component: EmailBuilderCanvas,
+ parameters: { layout: 'fullscreen' },
+} satisfies Meta;
+
+export default meta;
+type Story = StoryObj;
+
+export const Typography: Story = {
+ render: () => ,
+};
+
+export const Colours: Story = {
+ name: 'Colours',
+ render: () => ,
+};
+
+export const NestedContainers: Story = {
+ render: () => ,
+};
+
+export const ColumnsLayout: Story = {
+ render: () => ,
+};
+
+export const LongContent: Story = {
+ render: () => ,
+};
diff --git a/packages/post-kit-editor/stories/Admin/EmailBuilder/Overview.stories.tsx b/packages/post-kit-editor/stories/Admin/EmailBuilder/Overview.stories.tsx
new file mode 100644
index 0000000..4a05059
--- /dev/null
+++ b/packages/post-kit-editor/stories/Admin/EmailBuilder/Overview.stories.tsx
@@ -0,0 +1,53 @@
+import type { Meta, StoryObj } from '@storybook/react';
+
+import { EmailTemplateAdmin } from '../../../src';
+import {
+ demoCatalog,
+ emptyTemplate,
+ mockSave,
+ mockSendTest,
+ sampleAvailableVariables,
+ welcomeTemplate,
+} from '../../fixtures';
+
+const meta = {
+ title: 'Admin/Email Builder/Overview',
+ component: EmailTemplateAdmin,
+ parameters: {
+ layout: 'fullscreen',
+ },
+} satisfies Meta;
+
+export default meta;
+type Story = StoryObj;
+
+export const Empty: Story = {
+ args: {
+ templates: [emptyTemplate],
+ availableVariables: sampleAvailableVariables,
+ onSave: mockSave,
+ onSendTest: mockSendTest,
+ },
+};
+
+export const ExistingWelcome: Story = {
+ name: 'Existing Welcome',
+ args: {
+ templates: demoCatalog,
+ availableVariables: sampleAvailableVariables,
+ onSave: mockSave,
+ onSendTest: mockSendTest,
+ },
+};
+
+/** Alias for visual-baseline story id stability (`--existing-welcome` preferred). */
+export const FullAdmin: Story = {
+ ...ExistingWelcome,
+ name: 'Full Admin (visual baseline)',
+ args: {
+ templates: [welcomeTemplate, emptyTemplate],
+ availableVariables: sampleAvailableVariables,
+ onSave: mockSave,
+ onSendTest: mockSendTest,
+ },
+};
diff --git a/packages/post-kit-editor/stories/Admin/EmailBuilder/Responsive.stories.tsx b/packages/post-kit-editor/stories/Admin/EmailBuilder/Responsive.stories.tsx
new file mode 100644
index 0000000..2793afa
--- /dev/null
+++ b/packages/post-kit-editor/stories/Admin/EmailBuilder/Responsive.stories.tsx
@@ -0,0 +1,43 @@
+import type { Meta, StoryObj } from '@storybook/react';
+
+import { EmailTemplateAdmin } from '../../../src';
+import {
+ mockSave,
+ mockSendTest,
+ receiptTemplate,
+ sampleAvailableVariables,
+ welcomeTemplate,
+} from '../../fixtures';
+
+const meta = {
+ title: 'Admin/Email Builder/Responsive',
+ component: EmailTemplateAdmin,
+ parameters: { layout: 'fullscreen' },
+} satisfies Meta;
+
+export default meta;
+type Story = StoryObj;
+
+export const Desktop: Story = {
+ parameters: {
+ viewport: { defaultViewport: 'desktop' },
+ },
+ args: {
+ templates: [welcomeTemplate],
+ availableVariables: sampleAvailableVariables,
+ onSave: mockSave,
+ onSendTest: mockSendTest,
+ },
+};
+
+export const Mobile: Story = {
+ parameters: {
+ viewport: { defaultViewport: 'mobile' },
+ },
+ args: {
+ templates: [receiptTemplate],
+ availableVariables: sampleAvailableVariables,
+ onSave: mockSave,
+ onSendTest: mockSendTest,
+ },
+};
diff --git a/packages/post-kit-editor/stories/Admin/EmailBuilder/States.stories.tsx b/packages/post-kit-editor/stories/Admin/EmailBuilder/States.stories.tsx
new file mode 100644
index 0000000..7b4201b
--- /dev/null
+++ b/packages/post-kit-editor/stories/Admin/EmailBuilder/States.stories.tsx
@@ -0,0 +1,141 @@
+import type { Meta, StoryObj } from '@storybook/react';
+import React, { useState } from 'react';
+
+import { EmailTemplateAdmin, EmailTemplateEditor } from '../../../src';
+import {
+ delaySave,
+ emptyTemplate,
+ mockSave,
+ mockSaveFailure,
+ sampleAvailableVariables,
+ welcomeTemplate,
+} from '../../fixtures';
+
+const meta = {
+ title: 'Admin/Email Builder/States',
+ parameters: { layout: 'fullscreen' },
+} satisfies Meta;
+
+export default meta;
+type Story = StoryObj;
+
+export const Loading: Story = {
+ render: () => ,
+};
+
+export const LoadError: Story = {
+ render: () => (
+
+ ),
+};
+
+export const Saving: Story = {
+ parameters: {
+ docs: {
+ description: {
+ story: 'Click Save to see the pending “Saving…” state (1.2s delayed mock).',
+ },
+ },
+ },
+ render: () => (
+
+ ),
+};
+
+export const Saved: Story = {
+ parameters: {
+ docs: {
+ description: {
+ story: 'Immediate successful save feedback via mockSave — click Save.',
+ },
+ },
+ },
+ render: () => (
+
+ ),
+};
+
+export const SaveError: Story = {
+ parameters: {
+ docs: {
+ description: {
+ story: 'Click Save to surface synthetic failure feedback.',
+ },
+ },
+ },
+ render: () => (
+
+ ),
+};
+
+export const InvalidDocument: Story = {
+ parameters: {
+ docs: {
+ description: {
+ story:
+ 'Admin loadError shell — used when the host cannot parse template source (loadTemplateSource would throw before mount).',
+ },
+ },
+ },
+ render: () => (
+
+ ),
+};
+
+export const ExistingTemplate: Story = {
+ render: () => (
+
+ ),
+};
+
+function UnsavedChangesDemo(): JSX.Element {
+ const [dirty, setDirty] = useState(false);
+ return (
+
+
+ Dirty: {dirty ? 'yes' : 'no'} — edit metadata or canvas to mark unsaved.
+
+
+
+ );
+}
+
+export const UnsavedChanges: Story = {
+ parameters: {
+ docs: {
+ description: {
+ story: 'Edit any field; the host `onDirtyChange` flag above flips to yes until Save.',
+ },
+ },
+ },
+ render: () => ,
+};
diff --git a/packages/post-kit-editor/stories/Admin/EmailBuilder/Templates.stories.tsx b/packages/post-kit-editor/stories/Admin/EmailBuilder/Templates.stories.tsx
new file mode 100644
index 0000000..9e9cfc1
--- /dev/null
+++ b/packages/post-kit-editor/stories/Admin/EmailBuilder/Templates.stories.tsx
@@ -0,0 +1,85 @@
+import type { Meta, StoryObj } from '@storybook/react';
+
+import { EmailTemplateAdmin, EmailTemplateEditor } from '../../../src';
+import {
+ mockSave,
+ mockSendTest,
+ otpTemplate,
+ passwordResetTemplate,
+ receiptTemplate,
+ reportTemplate,
+ sampleAvailableVariables,
+ transactionalTemplate,
+ welcomeTemplate,
+} from '../../fixtures';
+
+const meta = {
+ title: 'Admin/Email Builder/Templates',
+ component: EmailTemplateAdmin,
+ parameters: { layout: 'fullscreen' },
+} satisfies Meta;
+
+export default meta;
+type Story = StoryObj;
+
+const adminArgs = {
+ availableVariables: sampleAvailableVariables,
+ onSave: mockSave,
+ onSendTest: mockSendTest,
+};
+
+export const Welcome: Story = {
+ args: {
+ ...adminArgs,
+ templates: [welcomeTemplate],
+ },
+};
+
+export const Otp: Story = {
+ name: 'OTP',
+ args: {
+ ...adminArgs,
+ templates: [otpTemplate],
+ },
+};
+
+export const PasswordReset: Story = {
+ args: {
+ ...adminArgs,
+ templates: [passwordResetTemplate],
+ },
+};
+
+export const Transactional: Story = {
+ args: {
+ ...adminArgs,
+ templates: [transactionalTemplate],
+ },
+};
+
+export const Receipt: Story = {
+ args: {
+ ...adminArgs,
+ templates: [receiptTemplate],
+ },
+};
+
+export const Report: Story = {
+ args: {
+ ...adminArgs,
+ templates: [reportTemplate],
+ },
+};
+
+/** Editor-only mount of Welcome (advanced / chrome without catalog). */
+export const WelcomeEditor: Story = {
+ name: 'Welcome (Editor)',
+ render: () => (
+
+ ),
+};
diff --git a/packages/post-kit-editor/stories/EmailBuilderCanvas.stories.tsx b/packages/post-kit-editor/stories/EmailBuilderCanvas.stories.tsx
deleted file mode 100644
index cc6d066..0000000
--- a/packages/post-kit-editor/stories/EmailBuilderCanvas.stories.tsx
+++ /dev/null
@@ -1,40 +0,0 @@
-import type { Meta, StoryObj } from '@storybook/react';
-import React, { useState } from 'react';
-
-import { EmailBuilderCanvas, type EmailBuilderDocument } from '../src';
-import { nestedBlocksTemplate } from './fixtures';
-
-const meta = {
- title: 'Editor/EmailBuilderCanvas',
- component: EmailBuilderCanvas,
- parameters: {
- docs: {
- description: {
- story:
- 'Public `EmailBuilderCanvas` only — editable and read-only. No metadata, preview, or save chrome.',
- },
- },
- },
-} satisfies Meta;
-
-export default meta;
-type Story = StoryObj;
-
-function EditableCanvas(): JSX.Element {
- const [document, setDocument] = useState(nestedBlocksTemplate.templateJson);
- return ;
-}
-
-export const Editable: Story = {
- render: () => ,
-};
-
-export const ReadOnly: Story = {
- args: {
- document: nestedBlocksTemplate.templateJson,
- onChange: () => {
- /* read-only */
- },
- readOnly: true,
- },
-};
diff --git a/packages/post-kit-editor/stories/EmailTemplateEditor.stories.tsx b/packages/post-kit-editor/stories/EmailTemplateEditor.stories.tsx
deleted file mode 100644
index 01c84ad..0000000
--- a/packages/post-kit-editor/stories/EmailTemplateEditor.stories.tsx
+++ /dev/null
@@ -1,68 +0,0 @@
-import type { Meta, StoryObj } from '@storybook/react';
-
-import {
- EmailTemplateEditor,
- type SerializedTemplateSource,
- type TemplateSourceFiles,
-} from '../src';
-import { nestedBlocksTemplate, sampleAvailableVariables } from './fixtures';
-
-const meta = {
- title: 'Editor/EmailTemplateEditor',
- component: EmailTemplateEditor,
- parameters: {
- docs: {
- description: {
- story:
- 'Full consumer mount via the public `EmailTemplateEditor` export. Save and send-test are mocked in-memory — no network, Git, or filesystem I/O.',
- },
- },
- },
-} satisfies Meta;
-
-export default meta;
-type Story = StoryObj;
-
-function mockSave(
- _serialized: SerializedTemplateSource,
- _files: TemplateSourceFiles,
-): { ok: true; message: string } {
- void _serialized;
- void _files;
- return { ok: true, message: 'Mock save (no persistence).' };
-}
-
-function mockSendTest(
- _serialized: SerializedTemplateSource,
- _files: TemplateSourceFiles,
- recipient: string,
-): { ok: true; message: string } {
- void _serialized;
- void _files;
- return { ok: true, message: `Mock send-test to ${recipient} (no network).` };
-}
-
-export const FullEditor: Story = {
- args: {
- template: nestedBlocksTemplate,
- availableVariables: sampleAvailableVariables,
- onSave: mockSave,
- onSendTest: mockSendTest,
- },
-};
-
-export const Loading: Story = {
- args: {
- template: nestedBlocksTemplate,
- onSave: mockSave,
- loading: true,
- },
-};
-
-export const LoadError: Story = {
- args: {
- template: nestedBlocksTemplate,
- onSave: mockSave,
- loadError: 'Synthetic load failure for Storybook (host could not fetch template files).',
- },
-};
diff --git a/packages/post-kit-editor/stories/MetadataAndVariables.stories.tsx b/packages/post-kit-editor/stories/MetadataAndVariables.stories.tsx
deleted file mode 100644
index b42eeb0..0000000
--- a/packages/post-kit-editor/stories/MetadataAndVariables.stories.tsx
+++ /dev/null
@@ -1,52 +0,0 @@
-/**
- * Private/dev-only: `MetadataPanel`, `VariableCatalogue`, and
- * `InsertionTargetProvider` are not public package exports. Stories import
- * them from `src/` for isolated visual exploration only.
- */
-import type { Meta, StoryObj } from '@storybook/react';
-import React, { useState } from 'react';
-import type { TemplatePreviewData, TemplateSourceMetadata } from '@singleton-sd/post-kit-types';
-
-import { InsertionTargetProvider } from '../src/insertion-target';
-import { MetadataPanel } from '../src/metadata/MetadataPanel';
-import { VariableCatalogue } from '../src/variables/VariableCatalogue';
-import { nestedBlocksTemplate, sampleAvailableVariables } from './fixtures';
-
-function MetadataAndVariablesDemo(): JSX.Element {
- const [metadata, setMetadata] = useState(nestedBlocksTemplate.metadata);
- const [previewData] = useState(nestedBlocksTemplate.previewData);
-
- return (
-
-
-
-
- setMetadata((current) => ({ ...current, variables }))
- }
- />
-
-
- );
-}
-
-const meta = {
- title: 'Editor/MetadataAndVariables',
- parameters: {
- docs: {
- description: {
- component:
- 'Metadata panel + variable catalogue (private modules; not part of the published public API). Focus the subject field to enable catalogue insert.',
- },
- },
- },
-} satisfies Meta;
-
-export default meta;
-type Story = StoryObj;
-
-export const Default: Story = {
- render: () => ,
-};
diff --git a/packages/post-kit-editor/stories/Preview.stories.tsx b/packages/post-kit-editor/stories/Preview.stories.tsx
deleted file mode 100644
index 6f8e250..0000000
--- a/packages/post-kit-editor/stories/Preview.stories.tsx
+++ /dev/null
@@ -1,57 +0,0 @@
-/**
- * Private/dev-only: `PreviewDataEditor` and `PreviewPane` are not public
- * package exports. Stories import them from `src/` for isolated exploration.
- */
-import type { Meta, StoryObj } from '@storybook/react';
-import React, { useState } from 'react';
-import type { TemplatePreviewData } from '@singleton-sd/post-kit-types';
-
-import { PreviewDataEditor } from '../src/preview/PreviewDataEditor';
-import { PreviewPane } from '../src/preview/PreviewPane';
-import type { TemplateSourceFiles } from '../src';
-import { nestedBlocksTemplate } from './fixtures';
-
-function PreviewDemo(): JSX.Element {
- const [files, setFiles] = useState(nestedBlocksTemplate);
-
- const handlePreviewDataChange = (previewData: TemplatePreviewData) => {
- setFiles((current) => ({ ...current, previewData }));
- };
-
- return (
-
- );
-}
-
-const meta = {
- title: 'Editor/Preview',
- parameters: {
- docs: {
- description: {
- component:
- 'Preview-data editor + sandboxed rendered preview pane (private modules). Uses `@singleton-sd/post-kit-compiler/preview` with synthetic fixtures only.',
- },
- },
- },
-} satisfies Meta;
-
-export default meta;
-type Story = StoryObj;
-
-export const DataAndPane: Story = {
- render: () => ,
-};
diff --git a/packages/post-kit-editor/stories/SaveSendBar.stories.tsx b/packages/post-kit-editor/stories/SaveSendBar.stories.tsx
deleted file mode 100644
index 310322c..0000000
--- a/packages/post-kit-editor/stories/SaveSendBar.stories.tsx
+++ /dev/null
@@ -1,97 +0,0 @@
-/**
- * Private/dev-only: `SaveSendBar` is not a public package export. Stories
- * import it from `src/` with mocked callbacks — no network I/O.
- */
-import type { Meta, StoryObj } from '@storybook/react';
-import React, { useState } from 'react';
-
-import { SaveSendBar } from '../src/save-send/SaveSendBar';
-import type { ActionFeedback } from '../src/save-send/types';
-
-function SaveSendDemo({
- initialSave = { status: 'idle' } as ActionFeedback,
- initialSend = { status: 'idle' } as ActionFeedback,
- validationBlocked = false,
- showSendTest = true,
-}: {
- initialSave?: ActionFeedback;
- initialSend?: ActionFeedback;
- validationBlocked?: boolean;
- showSendTest?: boolean;
-}): JSX.Element {
- const [saveFeedback, setSaveFeedback] = useState(initialSave);
- const [sendFeedback, setSendFeedback] = useState(initialSend);
- const [busy, setBusy] = useState(false);
-
- const onSave = () => {
- setBusy(true);
- setSaveFeedback({ status: 'pending' });
- window.setTimeout(() => {
- setSaveFeedback({ status: 'success', message: 'Mock save (no persistence).' });
- setBusy(false);
- }, 400);
- };
-
- const onSendTest = (recipient: string) => {
- setBusy(true);
- setSendFeedback({ status: 'pending' });
- window.setTimeout(() => {
- setSendFeedback({
- status: 'success',
- message: `Mock send-test to ${recipient} (no network).`,
- });
- setBusy(false);
- }, 400);
- };
-
- return (
-
-
-
- );
-}
-
-const meta = {
- title: 'Editor/SaveSendBar',
- parameters: {
- docs: {
- description: {
- component:
- 'Save / send-test controls with mocked in-memory callbacks (private module). Never calls PostKit or the network.',
- },
- },
- },
-} satisfies Meta;
-
-export default meta;
-type Story = StoryObj;
-
-export const IdleWithSendTest: Story = {
- render: () => ,
-};
-
-export const SaveOnly: Story = {
- render: () => ,
-};
-
-export const ValidationBlocked: Story = {
- render: () => ,
-};
-
-export const FailureFeedback: Story = {
- render: () => (
-
- ),
-};
diff --git a/packages/post-kit-editor/stories/fixtures.ts b/packages/post-kit-editor/stories/fixtures.ts
deleted file mode 100644
index cecef52..0000000
--- a/packages/post-kit-editor/stories/fixtures.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-/**
- * Synthetic Storybook fixtures only — mirrors `src/__fixtures__/nested-blocks/`
- * and `examples/minimal/sample/`. No secrets or customer-identifying values.
- */
-import { loadTemplateSource, type TemplateSourceFiles, type TemplateVariable } from '../src';
-
-import templateJson from '../src/__fixtures__/nested-blocks/template.json';
-import metadata from '../src/__fixtures__/nested-blocks/metadata.json';
-import previewData from '../src/__fixtures__/nested-blocks/preview.json';
-
-export const nestedBlocksTemplate: TemplateSourceFiles = loadTemplateSource({
- templateJson,
- metadata,
- previewData,
-});
-
-export const sampleAvailableVariables: TemplateVariable[] = [
- { name: 'name', label: 'Recipient name', description: 'Synthetic display name' },
-];
diff --git a/packages/post-kit-editor/stories/fixtures/documents.ts b/packages/post-kit-editor/stories/fixtures/documents.ts
new file mode 100644
index 0000000..9dcfd44
--- /dev/null
+++ b/packages/post-kit-editor/stories/fixtures/documents.ts
@@ -0,0 +1,416 @@
+/**
+ * Synthetic EmailBuilder documents for Storybook — no network image URLs.
+ */
+import type { EmailBuilderDocument } from '../../src';
+
+import emptySample from '../../src/email-builder-ui/getConfiguration/sample/empty-email-message';
+import welcomeSample from '../../src/email-builder-ui/getConfiguration/sample/welcome';
+import otpSample from '../../src/email-builder-ui/getConfiguration/sample/one-time-passcode';
+import resetPasswordSample from '../../src/email-builder-ui/getConfiguration/sample/reset-password';
+import respondSample from '../../src/email-builder-ui/getConfiguration/sample/respond-to-message';
+import receiptSample from '../../src/email-builder-ui/getConfiguration/sample/order-ecommerce';
+import reportSample from '../../src/email-builder-ui/getConfiguration/sample/post-metrics-report';
+
+/** Deterministic 1×1 SVG used wherever samples previously pointed at CDN images. */
+export const FIXTURE_IMAGE_DATA_URI =
+ 'data:image/svg+xml,' +
+ encodeURIComponent(
+ ``,
+ );
+
+/** Smaller avatar-shaped SVG. */
+export const FIXTURE_AVATAR_DATA_URI =
+ 'data:image/svg+xml,' +
+ encodeURIComponent(
+ ``,
+ );
+
+const IMAGE_URL_KEYS = new Set(['url', 'imageUrl']);
+
+function isHttpUrl(value: unknown): value is string {
+ return typeof value === 'string' && /^https?:\/\//i.test(value);
+}
+
+function looksLikeImageUrl(key: string, value: string): boolean {
+ if (!IMAGE_URL_KEYS.has(key)) return false;
+ // Button `url` props are hrefs — only replace when the value looks like an image asset
+ // or is under a known CDN / avatar host used by samples.
+ if (key === 'imageUrl') return true;
+ if (/\.(png|jpe?g|gif|webp|svg)(\?|$)/i.test(value)) return true;
+ if (/cloudfront\.net|ui-avatars\.com|assets\.usewaypoint\.com/i.test(value)) return true;
+ return false;
+}
+
+function replaceExternalImageUrls(value: unknown): unknown {
+ if (Array.isArray(value)) {
+ return value.map(replaceExternalImageUrls);
+ }
+ if (value && typeof value === 'object') {
+ const out: Record = {};
+ for (const [key, entry] of Object.entries(value as Record)) {
+ if (isHttpUrl(entry) && looksLikeImageUrl(key, entry)) {
+ out[key] = key === 'imageUrl' ? FIXTURE_AVATAR_DATA_URI : FIXTURE_IMAGE_DATA_URI;
+ } else {
+ out[key] = replaceExternalImageUrls(entry);
+ }
+ }
+ return out;
+ }
+ return value;
+}
+
+/** Deep-clone a sample document and swap external image URLs for data URIs. */
+export function sanitizeDocumentImages(document: EmailBuilderDocument): EmailBuilderDocument {
+ return replaceExternalImageUrls(JSON.parse(JSON.stringify(document))) as EmailBuilderDocument;
+}
+
+type BlockData = Record;
+
+function layoutRoot(
+ childrenIds: string[],
+ overrides: Partial<{
+ backdropColor: string;
+ canvasColor: string;
+ textColor: string;
+ fontFamily: string;
+ }> = {},
+): EmailBuilderDocument['root'] {
+ return {
+ type: 'EmailLayout',
+ data: {
+ backdropColor: overrides.backdropColor ?? '#F5F5F5',
+ canvasColor: overrides.canvasColor ?? '#FFFFFF',
+ textColor: overrides.textColor ?? '#262626',
+ fontFamily: overrides.fontFamily ?? 'MODERN_SANS',
+ childrenIds,
+ },
+ };
+}
+
+export function createEmptyEmailDocument(): EmailBuilderDocument {
+ return sanitizeDocumentImages(emptySample as EmailBuilderDocument);
+}
+
+export function createSingleBlockDocument(
+ blockId: string,
+ block: { type: string; data: BlockData },
+ layoutOverrides?: Parameters[1],
+): EmailBuilderDocument {
+ return {
+ root: layoutRoot([blockId], layoutOverrides),
+ [blockId]: block,
+ } as EmailBuilderDocument;
+}
+
+const pad = { top: 16, bottom: 16, left: 24, right: 24 };
+
+export function createTextBlockDocument(): EmailBuilderDocument {
+ return createSingleBlockDocument('block-text', {
+ type: 'Text',
+ data: {
+ props: { text: 'Hello from a Text block. Use {{name}} in templates.' },
+ style: { padding: pad, fontWeight: 'normal', fontSize: 16 },
+ },
+ });
+}
+
+export function createHeadingBlockDocument(): EmailBuilderDocument {
+ return createSingleBlockDocument('block-heading', {
+ type: 'Heading',
+ data: {
+ props: { text: 'Heading block', level: 'h2' },
+ style: { padding: pad },
+ },
+ });
+}
+
+export function createButtonBlockDocument(): EmailBuilderDocument {
+ return createSingleBlockDocument('block-button', {
+ type: 'Button',
+ data: {
+ props: {
+ text: 'Continue',
+ url: 'https://example.com/continue',
+ buttonBackgroundColor: '#1f4b99',
+ buttonTextColor: '#FFFFFF',
+ },
+ style: { padding: pad, textAlign: 'center' },
+ },
+ });
+}
+
+export function createImageBlockDocument(): EmailBuilderDocument {
+ return createSingleBlockDocument('block-image', {
+ type: 'Image',
+ data: {
+ props: {
+ url: FIXTURE_IMAGE_DATA_URI,
+ alt: 'Fixture image',
+ contentAlignment: 'middle',
+ linkHref: null,
+ },
+ style: { padding: pad },
+ },
+ });
+}
+
+export function createAvatarBlockDocument(): EmailBuilderDocument {
+ return createSingleBlockDocument('block-avatar', {
+ type: 'Avatar',
+ data: {
+ props: {
+ imageUrl: FIXTURE_AVATAR_DATA_URI,
+ shape: 'circle',
+ size: 64,
+ },
+ style: { padding: pad, textAlign: 'center' },
+ },
+ });
+}
+
+export function createDividerBlockDocument(): EmailBuilderDocument {
+ return createSingleBlockDocument('block-divider', {
+ type: 'Divider',
+ data: {
+ props: { lineColor: '#CCCCCC' },
+ style: { padding: { top: 16, bottom: 16, left: 0, right: 0 } },
+ },
+ });
+}
+
+export function createSpacerBlockDocument(): EmailBuilderDocument {
+ return createSingleBlockDocument('block-spacer', {
+ type: 'Spacer',
+ data: {
+ props: { height: 48 },
+ },
+ });
+}
+
+export function createHtmlBlockDocument(): EmailBuilderDocument {
+ return createSingleBlockDocument('block-html', {
+ type: 'Html',
+ data: {
+ props: { contents: 'Custom HTML — fixture only' },
+ style: { padding: pad, fontSize: 16 },
+ },
+ });
+}
+
+export function createContainerBlockDocument(): EmailBuilderDocument {
+ return {
+ root: layoutRoot(['block-container']),
+ 'block-container': {
+ type: 'Container',
+ data: {
+ style: { padding: pad, backgroundColor: '#F0F4F8' },
+ props: { childrenIds: ['block-inner-text'] },
+ },
+ },
+ 'block-inner-text': {
+ type: 'Text',
+ data: {
+ props: { text: 'Inside a Container' },
+ style: { padding: { top: 8, bottom: 8, left: 8, right: 8 }, fontWeight: 'normal' },
+ },
+ },
+ } as EmailBuilderDocument;
+}
+
+export function createColumnsBlockDocument(): EmailBuilderDocument {
+ return {
+ root: layoutRoot(['block-columns']),
+ 'block-columns': {
+ type: 'ColumnsContainer',
+ data: {
+ props: {
+ columnsGap: 16,
+ columnsCount: 2,
+ columns: [{ childrenIds: ['col-a'] }, { childrenIds: ['col-b'] }, { childrenIds: [] }],
+ },
+ style: { padding: pad },
+ },
+ },
+ 'col-a': {
+ type: 'Text',
+ data: {
+ props: { text: 'Column A' },
+ style: { padding: { top: 8, bottom: 8, left: 8, right: 8 }, fontWeight: 'normal' },
+ },
+ },
+ 'col-b': {
+ type: 'Text',
+ data: {
+ props: { text: 'Column B' },
+ style: { padding: { top: 8, bottom: 8, left: 8, right: 8 }, fontWeight: 'normal' },
+ },
+ },
+ } as EmailBuilderDocument;
+}
+
+/** Configured variants with richer styling. */
+export function createConfiguredTextDocument(): EmailBuilderDocument {
+ return createSingleBlockDocument('block-text-configured', {
+ type: 'Text',
+ data: {
+ props: { text: 'Configured text with color and weight.' },
+ style: {
+ padding: pad,
+ fontWeight: 'bold',
+ fontSize: 18,
+ color: '#1f4b99',
+ textAlign: 'center',
+ },
+ },
+ });
+}
+
+export function createConfiguredButtonDocument(): EmailBuilderDocument {
+ return createSingleBlockDocument('block-button-configured', {
+ type: 'Button',
+ data: {
+ props: {
+ text: 'Primary action',
+ url: 'https://example.com',
+ buttonBackgroundColor: '#0f766e',
+ buttonTextColor: '#ecfdf5',
+ size: 'large',
+ },
+ style: { padding: pad, textAlign: 'center' },
+ },
+ });
+}
+
+export function createConfiguredHeadingDocument(): EmailBuilderDocument {
+ return createSingleBlockDocument('block-heading-configured', {
+ type: 'Heading',
+ data: {
+ props: { text: 'Large configured heading', level: 'h1' },
+ style: {
+ padding: pad,
+ color: '#102a43',
+ textAlign: 'center',
+ fontWeight: 'bold',
+ },
+ },
+ });
+}
+
+export function createTypographyLayoutDocument(): EmailBuilderDocument {
+ const fonts = [
+ 'MODERN_SANS',
+ 'BOOK_SANS',
+ 'GEOMETRIC_SANS',
+ 'MODERN_SERIF',
+ 'BOOK_SERIF',
+ 'MONOSPACE',
+ ] as const;
+ const childrenIds = fonts.map((_, i) => `font-${i}`);
+ const doc: EmailBuilderDocument = {
+ root: layoutRoot([...childrenIds]),
+ } as EmailBuilderDocument;
+ fonts.forEach((fontFamily, i) => {
+ const id = `font-${i}`;
+ doc[id] = {
+ type: 'Text',
+ data: {
+ props: { text: `${fontFamily} — The quick brown fox` },
+ style: {
+ padding: { top: 8, bottom: 8, left: 24, right: 24 },
+ fontFamily,
+ fontWeight: 'normal',
+ fontSize: 16,
+ },
+ },
+ };
+ });
+ return doc;
+}
+
+export function createColoursLayoutDocument(): EmailBuilderDocument {
+ return createSingleBlockDocument(
+ 'block-colour-text',
+ {
+ type: 'Text',
+ data: {
+ props: { text: 'Coloured layout: dark backdrop, light canvas, brand text.' },
+ style: { padding: pad, fontWeight: 'normal', color: '#F0F4F8' },
+ },
+ },
+ {
+ backdropColor: '#102a43',
+ canvasColor: '#243b53',
+ textColor: '#F0F4F8',
+ fontFamily: 'MODERN_SANS',
+ },
+ );
+}
+
+export function createNestedContainersDocument(): EmailBuilderDocument {
+ return {
+ root: layoutRoot(['outer']),
+ outer: {
+ type: 'Container',
+ data: {
+ style: { padding: pad, backgroundColor: '#E3E8EE' },
+ props: { childrenIds: ['inner'] },
+ },
+ },
+ inner: {
+ type: 'Container',
+ data: {
+ style: {
+ padding: { top: 12, bottom: 12, left: 12, right: 12 },
+ backgroundColor: '#F0F4F8',
+ },
+ props: { childrenIds: ['nested-text'] },
+ },
+ },
+ 'nested-text': {
+ type: 'Text',
+ data: {
+ props: { text: 'Nested containers' },
+ style: { padding: { top: 8, bottom: 8, left: 8, right: 8 }, fontWeight: 'normal' },
+ },
+ },
+ } as EmailBuilderDocument;
+}
+
+export function createLongContentDocument(): EmailBuilderDocument {
+ const paragraphs = Array.from({ length: 12 }, (_, i) => `block-long-${i}`);
+ const doc: EmailBuilderDocument = {
+ root: layoutRoot(paragraphs),
+ } as EmailBuilderDocument;
+ paragraphs.forEach((id, i) => {
+ doc[id] = {
+ type: 'Text',
+ data: {
+ props: {
+ text: `Paragraph ${i + 1}. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.`,
+ },
+ style: {
+ padding: { top: 8, bottom: 8, left: 24, right: 24 },
+ fontWeight: 'normal',
+ fontSize: 15,
+ },
+ },
+ };
+ });
+ return doc;
+}
+
+export const welcomeDocument = sanitizeDocumentImages(welcomeSample as EmailBuilderDocument);
+export const otpDocument = sanitizeDocumentImages(otpSample as EmailBuilderDocument);
+export const passwordResetDocument = sanitizeDocumentImages(
+ resetPasswordSample as EmailBuilderDocument,
+);
+export const transactionalDocument = sanitizeDocumentImages(respondSample as EmailBuilderDocument);
+export const receiptDocument = sanitizeDocumentImages(receiptSample as EmailBuilderDocument);
+export const reportDocument = sanitizeDocumentImages(reportSample as EmailBuilderDocument);
diff --git a/packages/post-kit-editor/stories/fixtures/index.ts b/packages/post-kit-editor/stories/fixtures/index.ts
new file mode 100644
index 0000000..19bbdd7
--- /dev/null
+++ b/packages/post-kit-editor/stories/fixtures/index.ts
@@ -0,0 +1,15 @@
+export * from './documents';
+export * from './template-sources';
+export * from './mocks';
+
+/** Re-export nested-blocks fixture for any leftover consumers / visual parity. */
+import { loadTemplateSource, type TemplateSourceFiles } from '../../src';
+import templateJson from '../../src/__fixtures__/nested-blocks/template.json';
+import metadata from '../../src/__fixtures__/nested-blocks/metadata.json';
+import previewData from '../../src/__fixtures__/nested-blocks/preview.json';
+
+export const nestedBlocksTemplate: TemplateSourceFiles = loadTemplateSource({
+ templateJson,
+ metadata,
+ previewData,
+});
diff --git a/packages/post-kit-editor/stories/fixtures/mocks.ts b/packages/post-kit-editor/stories/fixtures/mocks.ts
new file mode 100644
index 0000000..dd5080b
--- /dev/null
+++ b/packages/post-kit-editor/stories/fixtures/mocks.ts
@@ -0,0 +1,61 @@
+/**
+ * Mock persistence / send-test helpers for Storybook (no network).
+ */
+import type {
+ SaveResult,
+ SendTestResult,
+ SerializedTemplateSource,
+ TemplateSourceFiles,
+} from '../../src';
+
+export function mockSave(
+ _serialized: SerializedTemplateSource,
+ _files: TemplateSourceFiles,
+): SaveResult {
+ void _serialized;
+ void _files;
+ return { ok: true, message: 'Mock save (no persistence).' };
+}
+
+export function mockSaveFailure(
+ _serialized: SerializedTemplateSource,
+ _files: TemplateSourceFiles,
+): SaveResult {
+ void _serialized;
+ void _files;
+ return { ok: false, message: 'Synthetic save failure.' };
+}
+
+export function mockSendTest(
+ _serialized: SerializedTemplateSource,
+ _files: TemplateSourceFiles,
+ recipient: string,
+): SendTestResult {
+ void _serialized;
+ void _files;
+ return { ok: true, message: `Mock send-test to ${recipient} (no network).` };
+}
+
+/** Delayed success — useful for Saving state / interaction plays. */
+export function delaySave(ms = 800): typeof mockSave {
+ return async (_serialized, _files) => {
+ void _serialized;
+ void _files;
+ await new Promise((resolve) => {
+ window.setTimeout(resolve, ms);
+ });
+ return { ok: true, message: 'Mock save (delayed).' };
+ };
+}
+
+/** Delayed failure. */
+export function delaySaveFailure(ms = 800): typeof mockSaveFailure {
+ return async (_serialized, _files) => {
+ void _serialized;
+ void _files;
+ await new Promise((resolve) => {
+ window.setTimeout(resolve, ms);
+ });
+ return { ok: false, message: 'Synthetic save failure (delayed).' };
+ };
+}
diff --git a/packages/post-kit-editor/stories/fixtures/template-sources.ts b/packages/post-kit-editor/stories/fixtures/template-sources.ts
new file mode 100644
index 0000000..19b83ac
--- /dev/null
+++ b/packages/post-kit-editor/stories/fixtures/template-sources.ts
@@ -0,0 +1,115 @@
+/**
+ * Wrap documents as `TemplateSourceFiles` for Admin / Editor stories.
+ */
+import { loadTemplateSource, type TemplateSourceFiles, type TemplateVariable } from '../../src';
+import type { EmailBuilderDocument } from '../../src';
+
+import {
+ createEmptyEmailDocument,
+ otpDocument,
+ passwordResetDocument,
+ receiptDocument,
+ reportDocument,
+ transactionalDocument,
+ welcomeDocument,
+} from './documents';
+
+export function createTemplateSource(options: {
+ key: string;
+ name: string;
+ subject: string;
+ document: EmailBuilderDocument;
+ variables?: string[];
+ description?: string;
+ previewData?: Record;
+}): TemplateSourceFiles {
+ const variables = options.variables ?? [];
+ const previewData = options.previewData ?? Object.fromEntries(variables.map((v) => [v, v]));
+ return loadTemplateSource({
+ templateJson: options.document,
+ metadata: {
+ key: options.key,
+ name: options.name,
+ subject: options.subject,
+ description: options.description,
+ variables,
+ schemaVersion: '1',
+ },
+ previewData,
+ });
+}
+
+export const emptyTemplate = createTemplateSource({
+ key: 'demo.empty',
+ name: 'Empty',
+ subject: 'Empty draft',
+ document: createEmptyEmailDocument(),
+ description: 'Blank EmailLayout for Storybook',
+});
+
+export const welcomeTemplate = createTemplateSource({
+ key: 'demo.welcome',
+ name: 'Welcome',
+ subject: 'Welcome, {{name}}',
+ document: welcomeDocument,
+ variables: ['name'],
+ previewData: { name: 'Alex' },
+ description: 'Welcome email sample (images as data URIs)',
+});
+
+export const otpTemplate = createTemplateSource({
+ key: 'demo.otp',
+ name: 'One-time passcode',
+ subject: 'Your code is {{code}}',
+ document: otpDocument,
+ variables: ['code'],
+ previewData: { code: '482910' },
+});
+
+export const passwordResetTemplate = createTemplateSource({
+ key: 'demo.password-reset',
+ name: 'Password reset',
+ subject: 'Reset your password',
+ document: passwordResetDocument,
+ variables: [],
+});
+
+export const transactionalTemplate = createTemplateSource({
+ key: 'demo.transactional',
+ name: 'Respond to inquiry',
+ subject: 'Re: your message',
+ document: transactionalDocument,
+ variables: [],
+});
+
+export const receiptTemplate = createTemplateSource({
+ key: 'demo.receipt',
+ name: 'Order receipt',
+ subject: 'Your order receipt',
+ document: receiptDocument,
+ variables: [],
+});
+
+export const reportTemplate = createTemplateSource({
+ key: 'demo.report',
+ name: 'Post metrics report',
+ subject: 'Your weekly report',
+ document: reportDocument,
+ variables: [],
+});
+
+/** Catalog used by Overview / ExistingWelcome. */
+export const demoCatalog: TemplateSourceFiles[] = [
+ welcomeTemplate,
+ otpTemplate,
+ passwordResetTemplate,
+ transactionalTemplate,
+ receiptTemplate,
+ reportTemplate,
+ emptyTemplate,
+];
+
+export const sampleAvailableVariables: TemplateVariable[] = [
+ { name: 'name', label: 'Recipient name', description: 'Synthetic display name' },
+ { name: 'code', label: 'OTP code', description: 'One-time passcode' },
+];
diff --git a/packages/post-kit-editor/stories/storybook-shell.css b/packages/post-kit-editor/stories/storybook-shell.css
new file mode 100644
index 0000000..1edbdce
--- /dev/null
+++ b/packages/post-kit-editor/stories/storybook-shell.css
@@ -0,0 +1,144 @@
+/**
+ * Storybook-only layout/chrome for PostKit editor surfaces.
+ * The published package still ships class names without a required stylesheet;
+ * these rules make stories reviewable (sidebar vs canvas, panel cards).
+ */
+html,
+body,
+#storybook-root {
+ height: 100%;
+}
+
+body {
+ margin: 0;
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
+ color: #1f1f21;
+ background: #f2f5f7;
+}
+
+.pk-story-shell {
+ box-sizing: border-box;
+ padding: 1.25rem;
+ max-width: 480px;
+ background: #fff;
+ border: 1px solid #e3e8ee;
+ border-radius: 8px;
+ box-shadow: 0 1px 2px rgba(33, 36, 67, 0.06);
+}
+
+.pk-story-shell--wide {
+ max-width: none;
+ width: 100%;
+}
+
+.pk-story-shell h2,
+.pk-story-shell label,
+.pk-story-shell button,
+.pk-story-shell input,
+.pk-story-shell textarea,
+.pk-story-shell select {
+ font: inherit;
+}
+
+.pk-story-shell label {
+ display: flex;
+ flex-direction: column;
+ gap: 0.35rem;
+ margin-bottom: 0.75rem;
+ font-size: 0.875rem;
+ font-weight: 600;
+}
+
+.pk-story-shell input,
+.pk-story-shell textarea,
+.pk-story-shell select {
+ font-weight: 400;
+ padding: 0.5rem 0.65rem;
+ border: 1px solid #cfd8e3;
+ border-radius: 6px;
+ background: #fff;
+}
+
+.pk-story-shell button {
+ cursor: pointer;
+ padding: 0.45rem 0.75rem;
+ border: 1px solid #cfd8e3;
+ border-radius: 6px;
+ background: #fff;
+}
+
+.pk-story-shell button:disabled {
+ opacity: 0.55;
+ cursor: not-allowed;
+}
+
+/* EmailTemplateEditor chrome — keep PostKit sidebar beside the canvas */
+.pk-editor-root {
+ min-height: 100%;
+}
+
+.pk-editor-layout {
+ display: grid;
+ grid-template-columns: minmax(280px, 340px) minmax(0, 1fr);
+ gap: 0;
+ min-height: calc(100vh - 2rem);
+ align-items: stretch;
+ background: #f2f5f7;
+}
+
+.pk-editor-sidebar {
+ box-sizing: border-box;
+ padding: 1rem;
+ border-right: 1px solid #e3e8ee;
+ background: #fff;
+ overflow: auto;
+ max-height: calc(100vh - 2rem);
+}
+
+.pk-editor-main {
+ min-width: 0;
+ display: flex;
+ flex-direction: column;
+ gap: 1rem;
+ padding: 0.75rem;
+ overflow: auto;
+}
+
+.pk-editor-canvas {
+ min-height: 520px;
+ background: #fff;
+ border: 1px solid #e3e8ee;
+ border-radius: 8px;
+ overflow: hidden;
+}
+
+.pk-editor-preview-pane,
+.pk-editor-preview-data,
+.pk-editor-metadata,
+.pk-editor-variables,
+.pk-editor-validation,
+.pk-editor-save-send {
+ margin-bottom: 1rem;
+}
+
+.pk-editor-save-send button,
+.pk-editor-variables button {
+ margin: 0.25rem 0.25rem 0.25rem 0;
+}
+
+.pk-admin-root {
+ min-height: 100vh;
+}
+
+@media (max-width: 900px) {
+ .pk-editor-layout {
+ grid-template-columns: 1fr;
+ min-height: auto;
+ }
+
+ .pk-editor-sidebar {
+ max-height: none;
+ border-right: none;
+ border-bottom: 1px solid #e3e8ee;
+ }
+}
diff --git a/packages/post-kit-editor/tests/is-path-inside-root.mjs b/packages/post-kit-editor/tests/is-path-inside-root.mjs
new file mode 100644
index 0000000..1e50171
--- /dev/null
+++ b/packages/post-kit-editor/tests/is-path-inside-root.mjs
@@ -0,0 +1,9 @@
+import path from 'node:path';
+
+/** True when `fsPath` resolves inside `root` (rejects sibling prefix escapes). */
+export function isPathInsideRoot(root, fsPath) {
+ const resolvedRoot = path.resolve(root);
+ const resolvedPath = path.resolve(fsPath);
+ const rel = path.relative(resolvedRoot, resolvedPath);
+ return rel === '' || (!rel.startsWith('..') && !path.isAbsolute(rel));
+}
diff --git a/packages/post-kit-editor/tests/is-path-inside-root.spec.mjs b/packages/post-kit-editor/tests/is-path-inside-root.spec.mjs
new file mode 100644
index 0000000..8b5767a
--- /dev/null
+++ b/packages/post-kit-editor/tests/is-path-inside-root.spec.mjs
@@ -0,0 +1,22 @@
+import assert from 'node:assert/strict';
+import path from 'node:path';
+import { describe, it } from 'node:test';
+
+import { isPathInsideRoot } from './is-path-inside-root.mjs';
+
+describe('isPathInsideRoot', () => {
+ const root = path.resolve('/tmp/storybook-static');
+
+ it('allows files under the static root', () => {
+ assert.equal(isPathInsideRoot(root, path.join(root, 'index.html')), true);
+ assert.equal(isPathInsideRoot(root, path.join(root, 'assets', 'app.js')), true);
+ });
+
+ it('rejects sibling directories that share a prefix', () => {
+ assert.equal(isPathInsideRoot(root, path.resolve('/tmp/storybook-static-backup/x')), false);
+ });
+
+ it('rejects parent traversal', () => {
+ assert.equal(isPathInsideRoot(root, path.join(root, '..', 'secret.txt')), false);
+ });
+});
diff --git a/packages/post-kit-editor/tests/visual-gate.mjs b/packages/post-kit-editor/tests/visual-gate.mjs
new file mode 100644
index 0000000..bb2d374
--- /dev/null
+++ b/packages/post-kit-editor/tests/visual-gate.mjs
@@ -0,0 +1,90 @@
+/**
+ * Fail CI when visual capture found changed or new stories vs committed baselines.
+ *
+ * Reads `test-results/visual/manifest.json` from `pnpm test:visual`.
+ * Set VISUAL_ACCEPTED=1 (or label `visual-accepted` in CI) to pass anyway.
+ * This backs the `visual-review` check — a human review gate, not a
+ * Playwright infrastructure failure.
+ */
+import assert from 'node:assert/strict';
+import { existsSync, readFileSync } from 'node:fs';
+import path from 'node:path';
+import { fileURLToPath } from 'node:url';
+
+const rootDir = fileURLToPath(new URL('..', import.meta.url));
+const manifestEnv = process.env.VISUAL_MANIFEST;
+const manifestPath = manifestEnv
+ ? path.isAbsolute(manifestEnv)
+ ? manifestEnv
+ : path.join(rootDir, manifestEnv)
+ : path.join(rootDir, 'test-results/visual/manifest.json');
+const accepted = process.env.VISUAL_ACCEPTED === '1' || process.env.VISUAL_ACCEPTED === 'true';
+
+assert.ok(
+ existsSync(manifestPath),
+ `Missing visual manifest at ${manifestPath}. Run pnpm test:visual first.`,
+);
+
+const manifest = JSON.parse(readFileSync(manifestPath, 'utf8'));
+const summary = manifest.summary ?? {
+ total: manifest.stories?.length ?? 0,
+ unchanged: 0,
+ changed: 0,
+ new: 0,
+ changedStories: [],
+ newStories: [],
+ hasDiffs: false,
+};
+
+if (!manifest.summary && Array.isArray(manifest.stories)) {
+ const changedStories = [
+ ...new Set(manifest.stories.filter((m) => m.status === 'changed').map((m) => m.story)),
+ ].sort();
+ const newStories = [
+ ...new Set(manifest.stories.filter((m) => m.status === 'new').map((m) => m.story)),
+ ].sort();
+ summary.changed = changedStories.length;
+ summary.new = newStories.length;
+ summary.changedStories = changedStories;
+ summary.newStories = newStories;
+ summary.unchanged = manifest.stories.filter((m) => m.status === 'unchanged').length;
+ summary.hasDiffs = changedStories.length > 0 || newStories.length > 0;
+}
+
+const changedList = summary.changedStories ?? summary.changedRoutes ?? [];
+const newList = summary.newStories ?? summary.newRoutes ?? [];
+
+const lines = [
+ `Visual review: ${summary.total} comparisons`,
+ `- unchanged viewports: ${summary.unchanged}`,
+ `- changed stories (${summary.changed}): ${changedList.join(', ') || '—'}`,
+ `- new stories (${summary.new}): ${newList.join(', ') || '—'}`,
+];
+
+for (const line of lines) {
+ console.log(line);
+}
+
+if (!summary.hasDiffs) {
+ console.log('Visual review passed: no new or changed stories vs baselines.');
+ process.exit(0);
+}
+
+if (accepted) {
+ console.log(
+ 'Visual review passed: changes present but VISUAL_ACCEPTED is set (label visual-accepted).',
+ );
+ process.exit(0);
+}
+
+const reason =
+ summary.changed > 0 && summary.new > 0
+ ? `pixel changes (${changedList.join(', ') || '—'}) and new stories (${newList.join(', ') || '—'})`
+ : summary.new > 0
+ ? `new story(ies): ${newList.join(', ') || '—'}`
+ : `pixel changes: ${changedList.join(', ') || '—'}`;
+
+console.error(
+ `Visual review required (${reason}). Download the editor-visual artifact, review diffs, then either update visual-baselines/ or add the visual-accepted label.`,
+);
+process.exit(1);
diff --git a/packages/post-kit-editor/tests/visual-report-html.mjs b/packages/post-kit-editor/tests/visual-report-html.mjs
new file mode 100644
index 0000000..1408b1f
--- /dev/null
+++ b/packages/post-kit-editor/tests/visual-report-html.mjs
@@ -0,0 +1,161 @@
+/**
+ * HTML report for `pnpm test:visual` — Storybook screenshots vs committed baselines.
+ */
+
+/** @typedef {{
+ * story: string;
+ * viewport: string;
+ * status: string;
+ * prFile: string;
+ * baseFile: string | null;
+ * diffFile: string | null;
+ * mismatched: number;
+ * }} VisualManifestEntry */
+
+function storyNeedsReview(entries) {
+ return entries.some(
+ (entry) => entry.status === 'new' || entry.status === 'changed' || entry.mismatched > 0,
+ );
+}
+
+function formatStorySummary(story, entries, needsReview) {
+ const viewports = entries.map((entry) => entry.viewport).join(' + ');
+ if (!needsReview) {
+ return `${story} · unchanged (${viewports})`;
+ }
+ if (entries.some((entry) => entry.status === 'new')) {
+ return `${story} · new (${viewports})`;
+ }
+ const maxPx = Math.max(...entries.map((entry) => entry.mismatched || 0));
+ return `${story} · ${maxPx} px changed (${viewports})`;
+}
+
+function buildViewportCaseHtml(entry) {
+ const baseCell = entry.baseFile
+ ? `
`
+ : `No baseline (new screenshot)
`;
+ const diffCell = entry.diffFile
+ ? `
`
+ : entry.baseFile
+ ? `No pixel diff
`
+ : `—
`;
+ const statusLabel =
+ entry.status === 'new'
+ ? ` · new`
+ : entry.mismatched
+ ? ` · ${entry.mismatched} px changed`
+ : '';
+
+ return `
+ ${entry.viewport}${statusLabel}
+
+
Baseline${baseCell}
+
PR
+
Diff${diffCell}
+
+`;
+}
+
+function groupEntriesByStory(entries) {
+ const byStory = new Map();
+ for (const entry of entries) {
+ const list = byStory.get(entry.story) ?? [];
+ list.push(entry);
+ byStory.set(entry.story, list);
+ }
+ return [...byStory.entries()].sort(([a], [b]) => a.localeCompare(b));
+}
+
+/**
+ * @param {VisualManifestEntry[]} entries
+ * @param {{ baseLabel: string }} options
+ */
+export function buildReportHtml(entries, { baseLabel }) {
+ const storyBlocks = groupEntriesByStory(entries)
+ .map(([story, storyEntries]) => {
+ const needsReview = storyNeedsReview(storyEntries);
+ const openAttr = needsReview ? ' open' : '';
+ const modifier = needsReview ? 'route-case--review' : 'route-case--unchanged';
+ const summary = formatStorySummary(story, storyEntries, needsReview);
+ const viewportBlocks = storyEntries.map((entry) => buildViewportCaseHtml(entry)).join('\n');
+ return `
+ ${summary}
+
+${viewportBlocks}
+
+ `;
+ })
+ .join('\n');
+
+ return `
+
+
+
+ post-kit-editor visual review
+
+
+
+
+ post-kit-editor visual review
+
+ Baseline = ${baseLabel}.
+ PR = Storybook static build for this branch.
+ Diff highlights changed pixels in red.
+
+
+
+
+
+
+ ${storyBlocks}
+
+
+
+`;
+}
diff --git a/packages/post-kit-editor/tests/visual.mjs b/packages/post-kit-editor/tests/visual.mjs
new file mode 100644
index 0000000..5cf2a58
--- /dev/null
+++ b/packages/post-kit-editor/tests/visual.mjs
@@ -0,0 +1,348 @@
+/**
+ * Storybook visual screenshots for PR review, with committed baseline comparison.
+ *
+ * Requires `storybook-static/` from `pnpm build-storybook` in this package.
+ * Serves that directory, screenshots a small set of iframe stories, then
+ * pixel-diffs against `visual-baselines/`.
+ *
+ * Output under `test-results/visual/`:
+ * - `pr/-.png` — this PR
+ * - `base/-.png` — committed baseline (when present)
+ * - `diff/-.png` — red pixel diff (when both exist and differ)
+ * - `index.html` — side-by-side review page
+ * - `manifest.json` — includes `summary` for CI gating
+ *
+ * Capture always exits 0 on successful screenshots. Use `pnpm test:visual:gate`
+ * (or CI job `visual-review`) to fail when stories are `changed` or `new`.
+ *
+ * Env:
+ * - VISUAL_PORT — fixed local port (default: ephemeral)
+ * - VISUAL_SKIP_BASE — set to `1` to only capture PR shots (all status=new)
+ */
+import assert from 'node:assert/strict';
+import http from 'node:http';
+import {
+ copyFileSync,
+ createReadStream,
+ existsSync,
+ mkdirSync,
+ readFileSync,
+ writeFileSync,
+} from 'node:fs';
+import { readFile, rm, writeFile } from 'node:fs/promises';
+import path from 'node:path';
+import { fileURLToPath } from 'node:url';
+import { PNG } from 'pngjs';
+import pixelmatch from 'pixelmatch';
+import { chromium } from 'playwright';
+import { buildReportHtml } from './visual-report-html.mjs';
+import { isPathInsideRoot } from './is-path-inside-root.mjs';
+
+const rootDir = fileURLToPath(new URL('..', import.meta.url));
+const storybookDir = path.join(rootDir, 'storybook-static');
+const baselinesDir = path.join(rootDir, 'visual-baselines');
+const outDir = path.join(rootDir, 'test-results', 'visual');
+const port = Number(process.env.VISUAL_PORT ?? 0);
+const skipBase = process.env.VISUAL_SKIP_BASE === '1';
+const baseLabel = `committed:${path.relative(rootDir, baselinesDir) || 'visual-baselines'}`;
+
+/** @type {{ id: string; waitFor: string[] }[]} */
+const STORIES = [
+ {
+ id: 'admin-email-builder-overview--full-admin',
+ waitFor: ['pk-admin-root', 'pk-editor-eb-mui-surface'],
+ },
+ {
+ id: 'admin-email-builder-blocks-text--default',
+ waitFor: ['pk-editor-eb-mui-surface'],
+ },
+ {
+ id: 'admin-email-builder-templates--welcome-editor',
+ waitFor: ['pk-editor-eb-mui-surface'],
+ },
+];
+
+const VIEWPORTS = [{ name: 'desktop', width: 1440, height: 900 }];
+
+if (!existsSync(storybookDir)) {
+ throw new Error(
+ `Missing storybook-static at ${storybookDir}. Run: pnpm --filter @singleton-sd/post-kit-editor build-storybook`,
+ );
+}
+
+function contentTypeForPath(pathname) {
+ if (pathname.endsWith('.html')) return 'text/html; charset=utf-8';
+ if (pathname.endsWith('.css')) return 'text/css; charset=utf-8';
+ if (pathname.endsWith('.js') || pathname.endsWith('.mjs')) {
+ return 'text/javascript; charset=utf-8';
+ }
+ if (pathname.endsWith('.json')) return 'application/json; charset=utf-8';
+ if (pathname.endsWith('.svg')) return 'image/svg+xml';
+ if (pathname.endsWith('.ico')) return 'image/x-icon';
+ if (pathname.endsWith('.woff2')) return 'font/woff2';
+ if (pathname.endsWith('.woff')) return 'font/woff';
+ if (pathname.endsWith('.ttf')) return 'font/ttf';
+ if (pathname.endsWith('.png')) return 'image/png';
+ if (pathname.endsWith('.jpg') || pathname.endsWith('.jpeg')) {
+ return 'image/jpeg';
+ }
+ if (pathname.endsWith('.webp')) return 'image/webp';
+ if (pathname.endsWith('.map')) return 'application/json';
+ return 'application/octet-stream';
+}
+
+function normalizePathname(urlPath) {
+ const cleaned = urlPath.replace(/\?.*$/, '').replace(/\/+$/, '') || '/';
+ if (cleaned === '/') return '/index.html';
+ if (path.extname(cleaned)) return cleaned;
+ const asDir = `${cleaned}/index.html`;
+ if (existsSync(path.join(storybookDir, asDir))) return asDir;
+ return cleaned;
+}
+
+function createStaticServer(root) {
+ return http.createServer(async (req, res) => {
+ try {
+ if (!req.url) {
+ res.statusCode = 400;
+ res.end('Bad request');
+ return;
+ }
+
+ const pathname = normalizePathname(req.url);
+ // Drop leading slash so join stays under root on POSIX.
+ const relativePath = pathname.replace(/^[/\\]+/, '');
+ const fsPath = path.resolve(root, relativePath);
+
+ if (!isPathInsideRoot(root, fsPath) || !existsSync(fsPath)) {
+ res.statusCode = 404;
+ res.end('Not found');
+ return;
+ }
+
+ res.setHeader('Content-Type', contentTypeForPath(pathname));
+ if (
+ pathname.endsWith('.html') ||
+ pathname.endsWith('.css') ||
+ pathname.endsWith('.js') ||
+ pathname.endsWith('.mjs') ||
+ pathname.endsWith('.json') ||
+ pathname.endsWith('.svg') ||
+ pathname.endsWith('.map')
+ ) {
+ res.end(await readFile(fsPath));
+ return;
+ }
+
+ createReadStream(fsPath).pipe(res);
+ } catch (err) {
+ res.statusCode = 500;
+ res.end(String(err));
+ }
+ });
+}
+
+async function listen(server, preferredPort = 0) {
+ await new Promise((resolve) => server.listen(preferredPort, '127.0.0.1', resolve));
+ const actual = server.address()?.port;
+ assert.ok(typeof actual === 'number' && actual > 0, 'Failed to pick a port');
+ return actual;
+}
+
+function storySlug(storyId) {
+ return storyId.replaceAll('--', '-');
+}
+
+function padPng(img, width, height) {
+ if (img.width === width && img.height === height) return img;
+ const out = new PNG({ width, height });
+ out.data.fill(0);
+ for (let y = 0; y < img.height; y++) {
+ for (let x = 0; x < img.width; x++) {
+ const src = (img.width * y + x) << 2;
+ const dst = (width * y + x) << 2;
+ out.data[dst] = img.data[src];
+ out.data[dst + 1] = img.data[src + 1];
+ out.data[dst + 2] = img.data[src + 2];
+ out.data[dst + 3] = img.data[src + 3];
+ }
+ }
+ return out;
+}
+
+function diffPngs(baseFilePath, prFilePath, diffFilePath) {
+ const baseImg = PNG.sync.read(readFileSync(baseFilePath));
+ const prImg = PNG.sync.read(readFileSync(prFilePath));
+ const width = Math.max(baseImg.width, prImg.width);
+ const height = Math.max(baseImg.height, prImg.height);
+ const baseNorm = padPng(baseImg, width, height);
+ const prNorm = padPng(prImg, width, height);
+ const diff = new PNG({ width, height });
+ const mismatched = pixelmatch(baseNorm.data, prNorm.data, diff.data, width, height, {
+ threshold: 0.1,
+ });
+ mkdirSync(path.dirname(diffFilePath), { recursive: true });
+ writeFileSync(diffFilePath, PNG.sync.write(diff));
+ return mismatched;
+}
+
+/**
+ * @param {import('playwright').Browser} browser
+ * @param {string} url
+ * @param {{ width: number; height: number }} viewport
+ * @param {string} filePath
+ * @param {string[]} waitForTestIds
+ */
+async function screenshotStory(browser, url, viewport, filePath, waitForTestIds) {
+ const context = await browser.newContext({
+ viewport: { width: viewport.width, height: viewport.height },
+ deviceScaleFactor: 1,
+ reducedMotion: 'reduce',
+ });
+ const page = await context.newPage();
+ try {
+ const response = await page.goto(url, {
+ waitUntil: 'networkidle',
+ timeout: 90_000,
+ });
+ const status = response?.status() ?? 0;
+ if (!response || status >= 400) {
+ return { ok: false, status };
+ }
+
+ await page.addStyleTag({
+ content: `
+ *, *::before, *::after {
+ animation-duration: 0s !important;
+ animation-delay: 0s !important;
+ transition-duration: 0s !important;
+ transition-delay: 0s !important;
+ caret-color: transparent !important;
+ }
+ `,
+ });
+
+ for (const testId of waitForTestIds) {
+ await page.waitForSelector(`[data-testid="${testId}"]`, {
+ timeout: 60_000,
+ state: 'visible',
+ });
+ }
+
+ await page.evaluate(async () => {
+ /* eslint-disable no-undef -- browser page context */
+ if (document.fonts?.ready) {
+ await document.fonts.ready;
+ }
+ /* eslint-enable no-undef */
+ });
+ await page.waitForTimeout(250);
+
+ await page.screenshot({
+ path: filePath,
+ fullPage: true,
+ animations: 'disabled',
+ });
+ return { ok: true, status };
+ } finally {
+ await context.close();
+ }
+}
+
+const server = createStaticServer(storybookDir);
+const actualPort = await listen(server, port);
+const origin = `http://127.0.0.1:${actualPort}`;
+
+await rm(outDir, { recursive: true, force: true });
+for (const dir of ['pr', 'base', 'diff']) {
+ mkdirSync(path.join(outDir, dir), { recursive: true });
+}
+
+const browser = await chromium.launch();
+const manifest = [];
+
+try {
+ for (const story of STORIES) {
+ for (const viewport of VIEWPORTS) {
+ const slug = `${storySlug(story.id)}-${viewport.name}`;
+ const prRel = `pr/${slug}.png`;
+ const baseRel = `base/${slug}.png`;
+ const diffRel = `diff/${slug}.png`;
+ const prFile = path.join(outDir, prRel);
+ const baseOutFile = path.join(outDir, baseRel);
+ const diffFile = path.join(outDir, diffRel);
+ const baselineFile = path.join(baselinesDir, `${slug}.png`);
+
+ const storyUrl = `${origin}/iframe.html?id=${encodeURIComponent(story.id)}&viewMode=story`;
+ const prShot = await screenshotStory(browser, storyUrl, viewport, prFile, story.waitFor);
+ assert.ok(
+ prShot.ok && prShot.status < 400,
+ `Failed to load story ${story.id}: HTTP ${prShot.status}`,
+ );
+
+ let baseOk = false;
+ let mismatched = 0;
+ if (!skipBase && existsSync(baselineFile)) {
+ copyFileSync(baselineFile, baseOutFile);
+ baseOk = true;
+ mismatched = diffPngs(baseOutFile, prFile, diffFile);
+ if (mismatched === 0) {
+ await rm(diffFile, { force: true });
+ }
+ }
+
+ const status = !baseOk ? 'new' : mismatched > 0 ? 'changed' : 'unchanged';
+
+ manifest.push({
+ story: story.id,
+ viewport: viewport.name,
+ prFile: prRel,
+ baseFile: baseOk ? baseRel : null,
+ diffFile: mismatched > 0 ? diffRel : null,
+ mismatched: mismatched > 0 ? mismatched : 0,
+ storyUrl: `/iframe.html?id=${story.id}&viewMode=story`,
+ status,
+ });
+ }
+ }
+} finally {
+ await browser.close();
+ server.close();
+}
+
+const changedStories = [
+ ...new Set(manifest.filter((m) => m.status === 'changed').map((m) => m.story)),
+].sort();
+const newStories = [
+ ...new Set(manifest.filter((m) => m.status === 'new').map((m) => m.story)),
+].sort();
+const unchanged = manifest.filter((m) => m.status === 'unchanged').length;
+const summary = {
+ total: manifest.length,
+ unchanged,
+ changed: changedStories.length,
+ new: newStories.length,
+ changedStories,
+ newStories,
+ hasDiffs: changedStories.length > 0 || newStories.length > 0,
+};
+
+await writeFile(
+ path.join(outDir, 'manifest.json'),
+ `${JSON.stringify(
+ {
+ baseLabel: skipBase ? null : baseLabel,
+ baselinesDir: skipBase ? null : baselinesDir,
+ summary,
+ stories: manifest,
+ },
+ null,
+ 2,
+ )}\n`,
+ 'utf8',
+);
+await writeFile(path.join(outDir, 'index.html'), buildReportHtml(manifest, { baseLabel }));
+
+console.log(
+ `Visual screenshots: ${summary.total} comparisons → ${outDir} (${summary.changed} changed, ${summary.new} new). Open index.html`,
+);
diff --git a/packages/post-kit-editor/visual-baselines/admin-email-builder-blocks-text-default-desktop.png b/packages/post-kit-editor/visual-baselines/admin-email-builder-blocks-text-default-desktop.png
new file mode 100644
index 0000000..e06b638
Binary files /dev/null and b/packages/post-kit-editor/visual-baselines/admin-email-builder-blocks-text-default-desktop.png differ
diff --git a/packages/post-kit-editor/visual-baselines/admin-email-builder-overview-full-admin-desktop.png b/packages/post-kit-editor/visual-baselines/admin-email-builder-overview-full-admin-desktop.png
new file mode 100644
index 0000000..dd61b4a
Binary files /dev/null and b/packages/post-kit-editor/visual-baselines/admin-email-builder-overview-full-admin-desktop.png differ
diff --git a/packages/post-kit-editor/visual-baselines/admin-email-builder-templates-welcome-editor-desktop.png b/packages/post-kit-editor/visual-baselines/admin-email-builder-templates-welcome-editor-desktop.png
new file mode 100644
index 0000000..97ecc03
Binary files /dev/null and b/packages/post-kit-editor/visual-baselines/admin-email-builder-templates-welcome-editor-desktop.png differ
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 887c58f..567be9c 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -216,6 +216,18 @@ importers:
packages/post-kit-editor:
dependencies:
+ '@emotion/react':
+ specifier: 11.11.3
+ version: 11.11.3(@types/react@18.3.31)(react@18.3.1)
+ '@emotion/styled':
+ specifier: 11.11.0
+ version: 11.11.0(@emotion/react@11.11.3(@types/react@18.3.31)(react@18.3.1))(@types/react@18.3.31)(react@18.3.1)
+ '@mui/icons-material':
+ specifier: 5.15.10
+ version: 5.15.10(@mui/material@5.15.10(@emotion/react@11.11.3(@types/react@18.3.31)(react@18.3.1))(@emotion/styled@11.11.0(@emotion/react@11.11.3(@types/react@18.3.31)(react@18.3.1))(@types/react@18.3.31)(react@18.3.1))(@types/react@18.3.31)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/react@18.3.31)(react@18.3.1)
+ '@mui/material':
+ specifier: 5.15.10
+ version: 5.15.10(@emotion/react@11.11.3(@types/react@18.3.31)(react@18.3.1))(@emotion/styled@11.11.0(@emotion/react@11.11.3(@types/react@18.3.31)(react@18.3.1))(@types/react@18.3.31)(react@18.3.1))(@types/react@18.3.31)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@singleton-sd/post-kit-compiler':
specifier: workspace:*
version: link:../post-kit-compiler
@@ -258,19 +270,40 @@ importers:
'@usewaypoint/email-builder':
specifier: 0.0.9
version: 0.0.9(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(zod@3.25.76)
+ highlight.js:
+ specifier: 11.9.0
+ version: 11.9.0
+ prettier:
+ specifier: 3.2.5
+ version: 3.2.5
+ react-colorful:
+ specifier: 5.6.1
+ version: 5.6.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
zod:
specifier: ^3.25.76
version: 3.25.76
+ zustand:
+ specifier: 4.5.1
+ version: 4.5.1(@types/react@18.3.31)(react@18.3.1)
devDependencies:
+ '@storybook/addon-essentials':
+ specifier: 8.6.14
+ version: 8.6.14(@types/react@18.3.31)(storybook@8.6.14(prettier@3.2.5))
+ '@storybook/addon-interactions':
+ specifier: 8.6.14
+ version: 8.6.14(storybook@8.6.14(prettier@3.2.5))
'@storybook/blocks':
specifier: 8.6.14
- version: 8.6.14(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.14(prettier@3.9.6))
+ version: 8.6.14(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.14(prettier@3.2.5))
'@storybook/react':
specifier: 8.6.14
- version: 8.6.14(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.14(prettier@3.9.6))(typescript@5.9.3)
+ version: 8.6.14(@storybook/test@8.6.14(storybook@8.6.14(prettier@3.2.5)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.14(prettier@3.2.5))(typescript@5.9.3)
'@storybook/react-vite':
specifier: 8.6.14
- version: 8.6.14(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rollup@4.63.1)(storybook@8.6.14(prettier@3.9.6))(typescript@5.9.3)(vite@6.3.5(@types/node@20.19.43)(tsx@4.23.12)(yaml@2.9.0))
+ version: 8.6.14(@storybook/test@8.6.14(storybook@8.6.14(prettier@3.2.5)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rollup@4.63.1)(storybook@8.6.14(prettier@3.2.5))(typescript@5.9.3)(vite@6.3.5(@types/node@20.19.43)(tsx@4.23.12)(yaml@2.9.0))
+ '@storybook/test':
+ specifier: 8.6.14
+ version: 8.6.14(storybook@8.6.14(prettier@3.2.5))
'@types/node':
specifier: ^20.17.9
version: 20.19.43
@@ -280,6 +313,15 @@ importers:
'@types/react-dom':
specifier: ^18.3.7
version: 18.3.7(@types/react@18.3.31)
+ pixelmatch:
+ specifier: ^7.1.0
+ version: 7.2.0
+ playwright:
+ specifier: ^1.55.0
+ version: 1.63.0
+ pngjs:
+ specifier: ^7.0.0
+ version: 7.0.0
react:
specifier: ^18.3.1
version: 18.3.1
@@ -288,7 +330,7 @@ importers:
version: 18.3.1(react@18.3.1)
storybook:
specifier: 8.6.14
- version: 8.6.14(prettier@3.9.6)
+ version: 8.6.14(prettier@3.2.5)
tsx:
specifier: ^4.19.2
version: 4.23.12
@@ -354,6 +396,9 @@ importers:
packages:
+ '@adobe/css-tools@4.5.0':
+ resolution: {integrity: sha512-6OzddxPio9UiWTCemp4N8cYLV2ZN1ncRnV1cVGtve7dhPOtRkleRyx32GQCYSwDYgaHU3USMm84tNsvKzRCa1Q==}
+
'@azure-rest/core-client@2.8.0':
resolution: {integrity: sha512-F1ybHeN+++QhyFCF/ehLUEvrOB6fehPdFBFtGdj0C3B2lpQ9zkPiO5JDgsqc6IfjuUe6b3dAbXK0a7+VgSGfhw==}
engines: {node: '>=22.0.0'}
@@ -512,6 +557,10 @@ packages:
engines: {node: '>=6.0.0'}
hasBin: true
+ '@babel/runtime@7.29.7':
+ resolution: {integrity: sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==}
+ engines: {node: '>=6.9.0'}
+
'@babel/template@7.29.7':
resolution: {integrity: sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==}
engines: {node: '>=6.9.0'}
@@ -536,6 +585,63 @@ packages:
conventional-commits-parser:
optional: true
+ '@emotion/babel-plugin@11.13.5':
+ resolution: {integrity: sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ==}
+
+ '@emotion/cache@11.14.0':
+ resolution: {integrity: sha512-L/B1lc/TViYk4DcpGxtAVbx0ZyiKM5ktoIyafGkH6zg/tj+mA+NE//aPYKG0k8kCHSHVJrpLpcAlOBEXQ3SavA==}
+
+ '@emotion/hash@0.9.2':
+ resolution: {integrity: sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==}
+
+ '@emotion/is-prop-valid@1.4.0':
+ resolution: {integrity: sha512-QgD4fyscGcbbKwJmqNvUMSE02OsHUa+lAWKdEUIJKgqe5IwRSKd7+KhibEWdaKwgjLj0DRSHA9biAIqGBk05lw==}
+
+ '@emotion/memoize@0.9.0':
+ resolution: {integrity: sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==}
+
+ '@emotion/react@11.11.3':
+ resolution: {integrity: sha512-Cnn0kuq4DoONOMcnoVsTOR8E+AdnKFf//6kUWc4LCdnxj31pZWn7rIULd6Y7/Js1PiPHzn7SKCM9vB/jBni8eA==}
+ peerDependencies:
+ '@types/react': '*'
+ react: '>=16.8.0'
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@emotion/serialize@1.3.3':
+ resolution: {integrity: sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA==}
+
+ '@emotion/sheet@1.4.0':
+ resolution: {integrity: sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==}
+
+ '@emotion/styled@11.11.0':
+ resolution: {integrity: sha512-hM5Nnvu9P3midq5aaXj4I+lnSfNi7Pmd4EWk1fOZ3pxookaQTNew6bp4JaCBYM4HVFZF9g7UjJmsUmC2JlxOng==}
+ peerDependencies:
+ '@emotion/react': ^11.0.0-rc.0
+ '@types/react': '*'
+ react: '>=16.8.0'
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@emotion/unitless@0.10.0':
+ resolution: {integrity: sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==}
+
+ '@emotion/use-insertion-effect-with-fallbacks@1.2.0':
+ resolution: {integrity: sha512-yJMtVdH59sxi/aVJBpk9FQq+OR8ll5GT8oWd57UpeaKEVGab41JWaCFA7FRLoMLloOZF/c/wsPoe+bfGmRKgDg==}
+ peerDependencies:
+ react: '>=16.8.0'
+
+ '@emotion/utils@1.4.2':
+ resolution: {integrity: sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA==}
+
+ '@emotion/weak-memoize@0.3.1':
+ resolution: {integrity: sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww==}
+
+ '@emotion/weak-memoize@0.4.0':
+ resolution: {integrity: sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==}
+
'@esbuild/aix-ppc64@0.25.12':
resolution: {integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==}
engines: {node: '>=18'}
@@ -886,6 +992,21 @@ packages:
resolution: {integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ '@floating-ui/core@1.8.0':
+ resolution: {integrity: sha512-0CIZ5itps/8x7BG8dEIhs53BvCUH2PCoogtakwRTut+Arm58sJooJ0AuZhLw2HJYIR5cMLNPBSS728sPho2khQ==}
+
+ '@floating-ui/dom@1.8.0':
+ resolution: {integrity: sha512-yXSrzeHZBTZadLOlfyhCkJHNeLJnHRnRInwdZ40L7ZiaAtrBwoYlsDrX3v5zB1Utk7CLfzcOVnVVWoXEky7Ceg==}
+
+ '@floating-ui/react-dom@2.1.9':
+ resolution: {integrity: sha512-JDjEFGCpImxDCA7JJKviA0M9+RtmJdj0m/NVU5IMgBK+AmZouAQQ7/+2GLH0GXXY0YMw9oXPB8hKdbPYg5QLYg==}
+ peerDependencies:
+ react: '>=16.8.0'
+ react-dom: '>=16.8.0'
+
+ '@floating-ui/utils@0.2.12':
+ resolution: {integrity: sha512-HpCo8tmWzLVad5s2d19EhAz5zqrrQ6s69qd6moPMQvkOuSwDT1YgRfWSVuc4ennqrgv3OHppiOGMQ7oC13yIww==}
+
'@hono/node-server@2.1.1':
resolution: {integrity: sha512-ELuehkj5VCBdgEw9zs+ivkKwyzzUCSQuE96YmiPvn1ECBoZCczbFXJLeEGMTYjphP6gydh4pHMqEYPVMYUVgQg==}
engines: {node: '>=20'}
@@ -1078,6 +1199,12 @@ packages:
'@jridgewell/trace-mapping@0.3.31':
resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==}
+ '@mdx-js/react@3.1.1':
+ resolution: {integrity: sha512-f++rKLQgUVYDAtECQ6fn/is15GkEH9+nZPM3MS0RcxVqoTfawHvDlSCH7JbMhAM6uJ32v3eXLvLmLvjGu7PTQw==}
+ peerDependencies:
+ '@types/react': '>=16'
+ react: '>=16'
+
'@modelcontextprotocol/sdk@1.30.0':
resolution: {integrity: sha512-xKd8OIzlqNzcqcNumGAa6g+PW2kjD5vrpcKOnfldAUPP3j7lnqMPwlTXQm8gF+UwH72z0lqaRbjr9hqGz0eITA==}
engines: {node: '>=18'}
@@ -1088,6 +1215,114 @@ packages:
'@cfworker/json-schema':
optional: true
+ '@mui/base@5.0.0-beta.36':
+ resolution: {integrity: sha512-6A8fYiXgjqTO6pgj31Hc8wm1M3rFYCxDRh09dBVk0L0W4cb2lnurRJa3cAyic6hHY+we1S58OdGYRbKmOsDpGQ==}
+ engines: {node: '>=12.0.0'}
+ deprecated: This package has been replaced by @base-ui/react
+ peerDependencies:
+ '@types/react': ^17.0.0 || ^18.0.0
+ react: ^17.0.0 || ^18.0.0
+ react-dom: ^17.0.0 || ^18.0.0
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@mui/core-downloads-tracker@5.18.0':
+ resolution: {integrity: sha512-jbhwoQ1AY200PSSOrNXmrFCaSDSJWP7qk6urkTmIirvRXDROkqe+QwcLlUiw/PrREwsIF/vm3/dAXvjlMHF0RA==}
+
+ '@mui/icons-material@5.15.10':
+ resolution: {integrity: sha512-9cF8oUHZKo9oQ7EQ3pxPELaZuZVmphskU4OI6NiJNDVN7zcuvrEsuWjYo1Zh4fLiC39Nrvm30h/B51rcUjvSGA==}
+ engines: {node: '>=12.0.0'}
+ peerDependencies:
+ '@mui/material': ^5.0.0
+ '@types/react': ^17.0.0 || ^18.0.0
+ react: ^17.0.0 || ^18.0.0
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@mui/material@5.15.10':
+ resolution: {integrity: sha512-YJJGHjwDOucecjDEV5l9ISTCo+l9YeWrho623UajzoHRYxuKUmwrGVYOW4PKwGvCx9SU9oklZnbbi2Clc5XZHw==}
+ engines: {node: '>=12.0.0'}
+ peerDependencies:
+ '@emotion/react': ^11.5.0
+ '@emotion/styled': ^11.3.0
+ '@types/react': ^17.0.0 || ^18.0.0
+ react: ^17.0.0 || ^18.0.0
+ react-dom: ^17.0.0 || ^18.0.0
+ peerDependenciesMeta:
+ '@emotion/react':
+ optional: true
+ '@emotion/styled':
+ optional: true
+ '@types/react':
+ optional: true
+
+ '@mui/private-theming@5.17.1':
+ resolution: {integrity: sha512-XMxU0NTYcKqdsG8LRmSoxERPXwMbp16sIXPcLVgLGII/bVNagX0xaheWAwFv8+zDK7tI3ajllkuD3GZZE++ICQ==}
+ engines: {node: '>=12.0.0'}
+ peerDependencies:
+ '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0
+ react: ^17.0.0 || ^18.0.0 || ^19.0.0
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@mui/styled-engine@5.18.0':
+ resolution: {integrity: sha512-BN/vKV/O6uaQh2z5rXV+MBlVrEkwoS/TK75rFQ2mjxA7+NBo8qtTAOA4UaM0XeJfn7kh2wZ+xQw2HAx0u+TiBg==}
+ engines: {node: '>=12.0.0'}
+ peerDependencies:
+ '@emotion/react': ^11.4.1
+ '@emotion/styled': ^11.3.0
+ react: ^17.0.0 || ^18.0.0 || ^19.0.0
+ peerDependenciesMeta:
+ '@emotion/react':
+ optional: true
+ '@emotion/styled':
+ optional: true
+
+ '@mui/system@5.18.0':
+ resolution: {integrity: sha512-ojZGVcRWqWhu557cdO3pWHloIGJdzVtxs3rk0F9L+x55LsUjcMUVkEhiF7E4TMxZoF9MmIHGGs0ZX3FDLAf0Xw==}
+ engines: {node: '>=12.0.0'}
+ peerDependencies:
+ '@emotion/react': ^11.5.0
+ '@emotion/styled': ^11.3.0
+ '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0
+ react: ^17.0.0 || ^18.0.0 || ^19.0.0
+ peerDependenciesMeta:
+ '@emotion/react':
+ optional: true
+ '@emotion/styled':
+ optional: true
+ '@types/react':
+ optional: true
+
+ '@mui/types@7.2.24':
+ resolution: {integrity: sha512-3c8tRt/CbWZ+pEg7QpSwbdxOk36EfmhbKf6AGZsD1EcLDLTSZoxxJ86FVtcjxvjuhdyBiWKSTGZFaXCnidO2kw==}
+ peerDependencies:
+ '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@mui/types@7.4.12':
+ resolution: {integrity: sha512-iKNAF2u9PzSIj40CjvKJWxFXJo122jXVdrmdh0hMYd+FR+NuJMkr/L88XwWLCRiJ5P1j+uyac25+Kp6YC4hu6w==}
+ peerDependencies:
+ '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@mui/utils@5.17.1':
+ resolution: {integrity: sha512-jEZ8FTqInt2WzxDV8bhImWBqeQRD99c/id/fq83H0ER9tFl+sfZlaAoCdznGvbSQQ9ividMxqSV2c7cC1vBcQg==}
+ engines: {node: '>=12.0.0'}
+ peerDependencies:
+ '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0
+ react: ^17.0.0 || ^18.0.0 || ^19.0.0
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
'@napi-rs/lzma-linux-x64-gnu@1.5.1':
resolution: {integrity: sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ==}
engines: {node: ^22.20 || ^24.12 || >=25}
@@ -1183,6 +1418,9 @@ packages:
resolution: {integrity: sha512-//0sR/cow/s4ICQaYoAobOl4aU8cjU6x/V24V7XkKotb9+O+3zySIYp146vpaobYHnxa4pZX8NkV54Z5AwbDKA==}
engines: {node: '>=12'}
+ '@popperjs/core@2.11.8':
+ resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==}
+
'@release-it/conventional-changelog@10.0.6':
resolution: {integrity: sha512-aUb0IkcsBTMcOH5PPQ9Jv9lEOOVu2+rSgkE1ny+dzsTziQm2BhDRAtaFK/dw/HflthuXMWrqhhyfJhAV1AOEPQ==}
engines: {node: ^20.12.0 || >=22.0.0}
@@ -1346,6 +1584,61 @@ packages:
resolution: {integrity: sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==}
engines: {node: '>=18'}
+ '@storybook/addon-actions@8.6.14':
+ resolution: {integrity: sha512-mDQxylxGGCQSK7tJPkD144J8jWh9IU9ziJMHfB84PKpI/V5ZgqMDnpr2bssTrUaGDqU5e1/z8KcRF+Melhs9pQ==}
+ peerDependencies:
+ storybook: ^8.6.14
+
+ '@storybook/addon-backgrounds@8.6.14':
+ resolution: {integrity: sha512-l9xS8qWe5n4tvMwth09QxH2PmJbCctEvBAc1tjjRasAfrd69f7/uFK4WhwJAstzBTNgTc8VXI4w8ZR97i1sFbg==}
+ peerDependencies:
+ storybook: ^8.6.14
+
+ '@storybook/addon-controls@8.6.14':
+ resolution: {integrity: sha512-IiQpkNJdiRyA4Mq9mzjZlvQugL/aE7hNgVxBBGPiIZG6wb6Ht9hNnBYpap5ZXXFKV9p2qVI0FZK445ONmAa+Cw==}
+ peerDependencies:
+ storybook: ^8.6.14
+
+ '@storybook/addon-docs@8.6.14':
+ resolution: {integrity: sha512-Obpd0OhAF99JyU5pp5ci17YmpcQtMNgqW2pTXV8jAiiipWpwO++hNDeQmLmlSXB399XjtRDOcDVkoc7rc6JzdQ==}
+ peerDependencies:
+ storybook: ^8.6.14
+
+ '@storybook/addon-essentials@8.6.14':
+ resolution: {integrity: sha512-5ZZSHNaW9mXMOFkoPyc3QkoNGdJHETZydI62/OASR0lmPlJ1065TNigEo5dJddmZNn0/3bkE8eKMAzLnO5eIdA==}
+ peerDependencies:
+ storybook: ^8.6.14
+
+ '@storybook/addon-highlight@8.6.14':
+ resolution: {integrity: sha512-4H19OJlapkofiE9tM6K/vsepf4ir9jMm9T+zw5L85blJZxhKZIbJ6FO0TCG9PDc4iPt3L6+aq5B0X29s9zicNQ==}
+ peerDependencies:
+ storybook: ^8.6.14
+
+ '@storybook/addon-interactions@8.6.14':
+ resolution: {integrity: sha512-8VmElhm2XOjh22l/dO4UmXxNOolGhNiSpBcls2pqWSraVh4a670EyYBZsHpkXqfNHo2YgKyZN3C91+9zfH79qQ==}
+ peerDependencies:
+ storybook: ^8.6.14
+
+ '@storybook/addon-measure@8.6.14':
+ resolution: {integrity: sha512-1Tlyb72NX8aAqm6I6OICsUuGOP6hgnXcuFlXucyhKomPa6j3Eu2vKu561t/f0oGtAK2nO93Z70kVaEh5X+vaGw==}
+ peerDependencies:
+ storybook: ^8.6.14
+
+ '@storybook/addon-outline@8.6.14':
+ resolution: {integrity: sha512-CW857JvN6OxGWElqjlzJO2S69DHf+xO3WsEfT5mT3ZtIjmsvRDukdWfDU9bIYUFyA2lFvYjncBGjbK+I91XR7w==}
+ peerDependencies:
+ storybook: ^8.6.14
+
+ '@storybook/addon-toolbars@8.6.14':
+ resolution: {integrity: sha512-W/wEXT8h3VyZTVfWK/84BAcjAxTdtRiAkT2KAN0nbSHxxB5KEM1MjKpKu2upyzzMa3EywITqbfy4dP6lpkVTwQ==}
+ peerDependencies:
+ storybook: ^8.6.14
+
+ '@storybook/addon-viewport@8.6.14':
+ resolution: {integrity: sha512-gNzVQbMqRC+/4uQTPI2ZrWuRHGquTMZpdgB9DrD88VTEjNudP+J6r8myLfr2VvGksBbUMHkGHMXHuIhrBEnXYA==}
+ peerDependencies:
+ storybook: ^8.6.14
+
'@storybook/blocks@8.6.14':
resolution: {integrity: sha512-rBMHAfA39AGHgkrDze4RmsnQTMw1ND5fGWobr9pDcJdnDKWQWNRD7Nrlxj0gFlN3n4D9lEZhWGdFrCbku7FVAQ==}
peerDependencies:
@@ -1392,6 +1685,11 @@ packages:
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta
+ '@storybook/instrumenter@8.6.14':
+ resolution: {integrity: sha512-iG4MlWCcz1L7Yu8AwgsnfVAmMbvyRSk700Mfy2g4c8y5O+Cv1ejshE1LBBsCwHgkuqU0H4R0qu4g23+6UnUemQ==}
+ peerDependencies:
+ storybook: ^8.6.14
+
'@storybook/manager-api@8.6.14':
resolution: {integrity: sha512-ez0Zihuy17udLbfHZQXkGqwtep0mSGgHcNzGN7iZrMP1m+VmNo+7aGCJJdvXi7+iU3yq8weXSQFWg5DqWgLS7g==}
peerDependencies:
@@ -1437,14 +1735,36 @@ packages:
typescript:
optional: true
+ '@storybook/test@8.6.14':
+ resolution: {integrity: sha512-GkPNBbbZmz+XRdrhMtkxPotCLOQ1BaGNp/gFZYdGDk2KmUWBKmvc5JxxOhtoXM2703IzNFlQHSSNnhrDZYuLlw==}
+ peerDependencies:
+ storybook: ^8.6.14
+
'@storybook/theming@8.6.14':
resolution: {integrity: sha512-r4y+LsiB37V5hzpQo+BM10PaCsp7YlZ0YcZzQP1OCkPlYXmUAFy2VvDKaFRpD8IeNPKug2u4iFm/laDEbs03dg==}
peerDependencies:
storybook: ^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0
+ '@testing-library/dom@10.4.0':
+ resolution: {integrity: sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==}
+ engines: {node: '>=18'}
+
+ '@testing-library/jest-dom@6.5.0':
+ resolution: {integrity: sha512-xGGHpBXYSHUUr6XsKBfs85TWlYKpTc37cSBBVrXcib2MkHLboWlkClhWF37JKlDb9KEq3dHs+f2xR7XJEWGBxA==}
+ engines: {node: '>=14', npm: '>=6', yarn: '>=1'}
+
+ '@testing-library/user-event@14.5.2':
+ resolution: {integrity: sha512-YAh82Wh4TIrxYLmfGcixwD18oIjyC1pFQC2Y01F2lzV2HTMiYrI0nze0FD0ocB//CKS/7jIUgae+adPqxK5yCQ==}
+ engines: {node: '>=12', npm: '>=6'}
+ peerDependencies:
+ '@testing-library/dom': '>=7.21.4'
+
'@tootallnate/quickjs-emscripten@0.23.0':
resolution: {integrity: sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==}
+ '@types/aria-query@5.0.4':
+ resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==}
+
'@types/babel__core@7.20.5':
resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==}
@@ -1466,6 +1786,9 @@ packages:
'@types/json-schema@7.0.15':
resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
+ '@types/mdx@2.0.14':
+ resolution: {integrity: sha512-T48PeuJtvLosNTPVhfnIp3i/n3a4g4Bad7YCq5k64D4u7NwDrAotikQ+5+sjtUvBmxCMlbo3dVL+C2dP0rWHzg==}
+
'@types/node@20.19.43':
resolution: {integrity: sha512-6oYBAi5ikg4Pl+kGsoYtawUMBT2zZMCvPNF7pVLnHZfd1zf38DRiWn/gT01RYCdUqkv7Fhr+C9ot4/tb+2sVvA==}
@@ -1478,6 +1801,9 @@ packages:
'@types/normalize-package-data@2.4.4':
resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==}
+ '@types/parse-json@4.0.2':
+ resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==}
+
'@types/parse-path@7.1.0':
resolution: {integrity: sha512-EULJ8LApcVEPbrfND0cRQqutIOdiIgJ1Mgrhpy755r14xMohPTEpkV/k28SJvuOs9bHRFW8x+KeDAEPiGQPB9Q==}
deprecated: This is a stub types definition. parse-path provides its own type definitions, so you do not need this installed.
@@ -1490,12 +1816,20 @@ packages:
peerDependencies:
'@types/react': ^18.0.0
+ '@types/react-transition-group@4.4.12':
+ resolution: {integrity: sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w==}
+ peerDependencies:
+ '@types/react': '*'
+
'@types/react@18.3.31':
resolution: {integrity: sha512-vfEqpXTvwT91yhmwdfouStN2hSKwTvyRs8qpLfADyrq/kxDw0hZM7Wk9Ug1FELj8hIby+S/+kQCSRFF32nv2Qw==}
'@types/resolve@1.20.6':
resolution: {integrity: sha512-A4STmOXPhMUtHH+S6ymgE2GiBSMqf4oTvcQZMcHzokuTLVYzXTB8ttjcgxOVaAp2lGwEdzZ0J+cRbbeevQj1UQ==}
+ '@types/uuid@9.0.8':
+ resolution: {integrity: sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA==}
+
'@typespec/ts-http-runtime@0.3.8':
resolution: {integrity: sha512-bLMpVcWZNzq6lYOybwFwOAR1IXKcHnhUNqYeHjl1bET/qE3jFPFH+p8Wrh3rU4xwdnifPxmKNESBYnvnmc75aA==}
engines: {node: '>=22.0.0'}
@@ -1573,6 +1907,24 @@ packages:
react-dom: ^16 || ^17 || ^18
zod: ^1 || ^2 || ^3
+ '@vitest/expect@2.0.5':
+ resolution: {integrity: sha512-yHZtwuP7JZivj65Gxoi8upUN2OzHTi3zVfjwdpu2WrvCZPLwsJ2Ey5ILIPccoW23dd/zQBlJ4/dhi7DWNyXCpA==}
+
+ '@vitest/pretty-format@2.0.5':
+ resolution: {integrity: sha512-h8k+1oWHfwTkyTkb9egzwNMfJAEx4veaPSnMeKbVSjp4euqGSbQlm5+6VHwTr7u4FJslVVsUG5nopCaAYdOmSQ==}
+
+ '@vitest/pretty-format@2.1.9':
+ resolution: {integrity: sha512-KhRIdGV2U9HOUzxfiHmY8IFHTdqtOhIzCpd8WRdJiE7D/HUcZVD0EgQCVjm+Q9gkUXWgBvMmTtZgIG48wq7sOQ==}
+
+ '@vitest/spy@2.0.5':
+ resolution: {integrity: sha512-c/jdthAhvJdpfVuaexSrnawxZz6pywlTPe84LUB2m/4t3rl2fTo9NFGBG4oWgaD+FTgDDV8hJ/nibT7IfH3JfA==}
+
+ '@vitest/utils@2.0.5':
+ resolution: {integrity: sha512-d8HKbqIcya+GR67mkZbrzhS5kKhtp8dQLcmRZLGTscGVg7yImT82cIrhtn2L8+VujWcy6KZweApgNmPsTAO/UQ==}
+
+ '@vitest/utils@2.1.9':
+ resolution: {integrity: sha512-v0psaMSkNJ3A2NMrUEHFRzJtDPFn+/VWZ5WxImB21T9fjucJRmS7xCS3ppEnARb9y11OAzaD+P2Ps+b+BGX5iQ==}
+
accepts@2.0.0:
resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==}
engines: {node: '>= 0.6'}
@@ -1624,6 +1976,10 @@ packages:
resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
engines: {node: '>=8'}
+ ansi-styles@5.2.0:
+ resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==}
+ engines: {node: '>=10'}
+
ansi-styles@6.2.3:
resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==}
engines: {node: '>=12'}
@@ -1634,9 +1990,20 @@ packages:
argparse@2.0.1:
resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
+ aria-query@5.3.0:
+ resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==}
+
+ aria-query@5.3.2:
+ resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==}
+ engines: {node: '>= 0.4'}
+
array-ify@1.0.0:
resolution: {integrity: sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng==}
+ assertion-error@2.0.1:
+ resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==}
+ engines: {node: '>=12'}
+
assignment@2.0.0:
resolution: {integrity: sha512-naMULXjtgCs9SVUEtyvJNt68aF18em7/W+dhbR59kbz9cXWPEvUkCun2tqlgqRPSqZaKPpqLc5ZnwL8jVmJRvw==}
@@ -1658,6 +2025,10 @@ packages:
resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==}
engines: {node: '>= 0.4'}
+ babel-plugin-macros@3.1.0:
+ resolution: {integrity: sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==}
+ engines: {node: '>=10', npm: '>=6'}
+
balanced-match@1.0.2:
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
@@ -1740,6 +2111,14 @@ packages:
caniuse-lite@1.0.30001810:
resolution: {integrity: sha512-TITQPUkaz+aVk5GL6NhOdwk1aEaNTSDPsGFWrTuhKGtjTF70jL/Oht2W4c6rXUe5fu7Ie19VIahAXHIIiWWNeg==}
+ chai@5.3.3:
+ resolution: {integrity: sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==}
+ engines: {node: '>=18'}
+
+ chalk@3.0.0:
+ resolution: {integrity: sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==}
+ engines: {node: '>=8'}
+
chalk@4.1.2:
resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
engines: {node: '>=10'}
@@ -1751,6 +2130,10 @@ packages:
chardet@2.2.0:
resolution: {integrity: sha512-rddelWYNPRrXq6PtNEN2S3f6t9ILzvqaN5pVgi4kqt9jHQaXIial9PznB5iSPVlQSLNaaH22ItWz3EJtQ10+OA==}
+ check-error@2.1.3:
+ resolution: {integrity: sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==}
+ engines: {node: '>= 16'}
+
ci-info@4.4.0:
resolution: {integrity: sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==}
engines: {node: '>=8'}
@@ -1771,6 +2154,10 @@ packages:
resolution: {integrity: sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==}
engines: {node: '>= 12'}
+ clsx@2.1.1:
+ resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==}
+ engines: {node: '>=6'}
+
color-convert@2.0.1:
resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
engines: {node: '>=7.0.0'}
@@ -1843,6 +2230,9 @@ packages:
engines: {node: '>=18'}
hasBin: true
+ convert-source-map@1.9.0:
+ resolution: {integrity: sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==}
+
convert-source-map@2.0.0:
resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
@@ -1858,6 +2248,10 @@ packages:
resolution: {integrity: sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==}
engines: {node: '>= 0.10'}
+ cosmiconfig@7.1.0:
+ resolution: {integrity: sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==}
+ engines: {node: '>=10'}
+
cosmiconfig@9.0.0:
resolution: {integrity: sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==}
engines: {node: '>=14'}
@@ -1871,6 +2265,9 @@ packages:
resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
engines: {node: '>= 8'}
+ css.escape@1.5.1:
+ resolution: {integrity: sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==}
+
csstype@3.2.3:
resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==}
@@ -1887,6 +2284,10 @@ packages:
supports-color:
optional: true
+ deep-eql@5.0.2:
+ resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==}
+ engines: {node: '>=6'}
+
deep-extend@0.6.0:
resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==}
engines: {node: '>=4.0.0'}
@@ -1922,10 +2323,23 @@ packages:
resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==}
engines: {node: '>= 0.8'}
+ dequal@2.0.3:
+ resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==}
+ engines: {node: '>=6'}
+
doctrine@3.0.0:
resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==}
engines: {node: '>=6.0.0'}
+ dom-accessibility-api@0.5.16:
+ resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==}
+
+ dom-accessibility-api@0.6.3:
+ resolution: {integrity: sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==}
+
+ dom-helpers@5.2.1:
+ resolution: {integrity: sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==}
+
dot-prop@5.3.0:
resolution: {integrity: sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==}
engines: {node: '>=8'}
@@ -2064,6 +2478,9 @@ packages:
estree-walker@2.0.2:
resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==}
+ estree-walker@3.0.3:
+ resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==}
+
esutils@2.0.3:
resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==}
engines: {node: '>=0.10.0'}
@@ -2159,6 +2576,9 @@ packages:
resolution: {integrity: sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==}
engines: {node: '>= 18.0.0'}
+ find-root@1.1.0:
+ resolution: {integrity: sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==}
+
find-up@5.0.0:
resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==}
engines: {node: '>=10'}
@@ -2306,6 +2726,13 @@ packages:
resolution: {integrity: sha512-DoufbNNOFzwRPy8uecq+j+VCPQ+JyDelHTmSgygrA5TsR8Cbw4Qcir5sGtWiusB4BdT89nmlaVDhSJOqC/33vw==}
hasBin: true
+ highlight.js@11.9.0:
+ resolution: {integrity: sha512-fJ7cW7fQGCYAkgv4CPfwFHrfd/cLS4Hau96JuJ+ZTOWhjnhoeN1ub1tFmALm/+lW5z4WCAuAV9bm05AP0mS6Gw==}
+ engines: {node: '>=12.0.0'}
+
+ hoist-non-react-statics@3.3.2:
+ resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==}
+
hono@4.13.7:
resolution: {integrity: sha512-c8/gF9ac8Y78/agExVocyLevgR+JlpNB444Py0FSX8pJoPdYUfUzRcXtYEYGwt6l19qIlVZPN5Mfsw9jFShmQQ==}
engines: {node: '>=16.9.0'}
@@ -2355,6 +2782,10 @@ packages:
resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==}
engines: {node: '>=0.8.19'}
+ indent-string@4.0.0:
+ resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==}
+ engines: {node: '>=8'}
+
inflight@1.0.6:
resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==}
deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.
@@ -2643,6 +3074,9 @@ packages:
resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==}
hasBin: true
+ loupe@3.2.1:
+ resolution: {integrity: sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==}
+
lru-cache@10.4.3:
resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==}
@@ -2653,6 +3087,10 @@ packages:
resolution: {integrity: sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==}
engines: {node: '>=12'}
+ lz-string@1.5.0:
+ resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==}
+ hasBin: true
+
macos-release@3.5.1:
resolution: {integrity: sha512-Lci/1in+elqZ589PXnfP/iwZXpwQifTM94WJRQwG2tZSdfY7NfB/aUaTARHrohWCgHlXoabeaeXRDOnF5X9JQw==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
@@ -2664,6 +3102,9 @@ packages:
magic-string@0.30.21:
resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
+ map-or-similar@1.5.0:
+ resolution: {integrity: sha512-0aF7ZmVon1igznGI4VS30yugpduQW3y3GkcgGJOp7d8x8QrizhigUxjI/m2UojsXXto+jLAH3KSz+xOJTiORjg==}
+
marked@12.0.2:
resolution: {integrity: sha512-qXUm7e/YKFoqFPYPa3Ukg9xlI5cyAtGmyEIzMfW//m6kXwCy2Ps9DYf5ioijFKQ8qyuscrHoY04iJGctu2Kg0Q==}
engines: {node: '>= 18'}
@@ -2677,6 +3118,9 @@ packages:
resolution: {integrity: sha512-yz3xRaG20c6/BOzvYoDaGtPmGscs7YivItZEEqe6GbwNfHuxu9YNmvnEkMzKldAGY4/80pRcQRZSEnhquk9XuQ==}
engines: {node: '>= 0.8'}
+ memoizerific@1.11.3:
+ resolution: {integrity: sha512-/EuHYwAPdLtXwAwSZkh/Gutery6pD2KYd44oQLhAvQp/50mpyduZh8Q7PYHXTCJ+wuXxt7oij2LXyIJOOYFPog==}
+
meow@13.2.0:
resolution: {integrity: sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA==}
engines: {node: '>=18'}
@@ -2720,6 +3164,10 @@ packages:
resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==}
engines: {node: '>=18'}
+ min-indent@1.0.1:
+ resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==}
+ engines: {node: '>=4'}
+
minimatch@3.1.5:
resolution: {integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==}
@@ -2899,10 +3347,18 @@ packages:
path-to-regexp@8.4.2:
resolution: {integrity: sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==}
+ path-type@4.0.0:
+ resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==}
+ engines: {node: '>=8'}
+
path-type@5.0.0:
resolution: {integrity: sha512-5HviZNaZcfqP95rwpv+1HDgUamezbqdSYTyzjTvwtJSnIH+3vnbmWsItli8OFEndS984VT55M3jduxZbX351gg==}
engines: {node: '>=12'}
+ pathval@2.0.1:
+ resolution: {integrity: sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==}
+ engines: {node: '>= 14.16'}
+
picocolors@1.1.1:
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
@@ -2914,10 +3370,32 @@ packages:
resolution: {integrity: sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==}
engines: {node: '>=12'}
+ pixelmatch@7.2.0:
+ resolution: {integrity: sha512-xhcb4yHu9sM/G7foGzoLtXYcC0zHEaOXXjRKhGup0fw78Nf2Tkiapv4EQyMzrbcmQPsllAI7DbFY2UT7PlI9Pg==}
+ hasBin: true
+
pkce-challenge@5.0.1:
resolution: {integrity: sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==}
engines: {node: '>=16.20.0'}
+ playwright-core@1.63.0:
+ resolution: {integrity: sha512-rYCsBF/M5HjUch52bbtVONEFjv6Xu8sm8h72dNlR5bzIE1fvC/bxgspzkjSfU+MweEMmPM8KJebG6nnyxo5mCg==}
+ engines: {node: '>=20'}
+ hasBin: true
+
+ playwright@1.63.0:
+ resolution: {integrity: sha512-+7ziBLidS4NaNCdt57SUDT+wYmmd5fmiQejUic/kb+YsYSCPyOOE9sebzMjNmQrsnNpDJqd4WHvV/8lfKfUDUg==}
+ engines: {node: '>=20'}
+ hasBin: true
+
+ pngjs@7.0.0:
+ resolution: {integrity: sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow==}
+ engines: {node: '>=14.19.0'}
+
+ polished@4.3.1:
+ resolution: {integrity: sha512-OBatVyC/N7SCW/FaDHrSd+vn0o5cS855TOmYi4OkdWUMSJCET/xip//ch8xGUvtr3i44X9LVyWwQlRMTN3pwSA==}
+ engines: {node: '>=10'}
+
possible-typed-array-names@1.1.0:
resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==}
engines: {node: '>= 0.4'}
@@ -2930,11 +3408,20 @@ packages:
resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}
engines: {node: '>= 0.8.0'}
+ prettier@3.2.5:
+ resolution: {integrity: sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==}
+ engines: {node: '>=14'}
+ hasBin: true
+
prettier@3.9.6:
resolution: {integrity: sha512-OpN0zzVdiaiAhxpuuj5efpIS4sY9j7bY6uR5mnj5yPzGkdkjNKSJeUThPb60Jw29QuAZgA4o+/iB49kFiaBX6g==}
engines: {node: '>=14'}
hasBin: true
+ pretty-format@27.5.1:
+ resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==}
+ engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
+
pretty-ms@9.3.0:
resolution: {integrity: sha512-gjVS5hOP+M3wMm5nmNOucbIrqudzs9v/57bWRHQWLYklXqoXKrVfYW2W9+glfGsqtPgpiz5WwyEEB+ksXIx3gQ==}
engines: {node: '>=18'}
@@ -2943,6 +3430,9 @@ packages:
resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==}
engines: {node: '>= 0.6.0'}
+ prop-types@15.8.1:
+ resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==}
+
proto-list@1.2.4:
resolution: {integrity: sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==}
@@ -2987,6 +3477,12 @@ packages:
resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==}
hasBin: true
+ react-colorful@5.6.1:
+ resolution: {integrity: sha512-1exovf0uGTGyq5mXQT0zgQ80uvj2PCwvF8zY1RN9/vbJVSjSo3fsB/4L3ObbF7u70NduSiK4xu4Y6q1MHoUGEw==}
+ peerDependencies:
+ react: '>=16.8.0'
+ react-dom: '>=16.8.0'
+
react-docgen-typescript@2.4.0:
resolution: {integrity: sha512-ZtAp5XTO5HRzQctjPU0ybY0RRCQO19X/8fxn3w7y2VVTUbGHDKULPTL4ky3vB05euSgG5NpALhEhDPvQ56wvXg==}
peerDependencies:
@@ -3001,6 +3497,24 @@ packages:
peerDependencies:
react: ^18.3.1
+ react-is@16.13.1:
+ resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
+
+ react-is@17.0.2:
+ resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==}
+
+ react-is@18.3.1:
+ resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==}
+
+ react-is@19.3.0:
+ resolution: {integrity: sha512-UpMYezM4v5/18F28aC66AEsjXIgE02kyEMH6yLdgLXu/UTfa1Ntwck/nNLrbqJsEXW7gPb0coNO9FQse9WTovA==}
+
+ react-transition-group@4.4.5:
+ resolution: {integrity: sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==}
+ peerDependencies:
+ react: '>=16.6.0'
+ react-dom: '>=16.6.0'
+
react@18.3.1:
resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==}
engines: {node: '>=0.10.0'}
@@ -3017,6 +3531,10 @@ packages:
resolution: {integrity: sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==}
engines: {node: '>= 0.10'}
+ redent@3.0.0:
+ resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==}
+ engines: {node: '>=8'}
+
registry-auth-token@5.1.1:
resolution: {integrity: sha512-P7B4+jq8DeD2nMsAcdfaqHbssgHtZ7Z5+++a5ask90fvmJ8p5je4mOa+wzu+DB4vQ5tdJV/xywY+UnVFeQLV5Q==}
engines: {node: '>=14'}
@@ -3173,6 +3691,10 @@ packages:
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
engines: {node: '>=0.10.0'}
+ source-map@0.5.7:
+ resolution: {integrity: sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==}
+ engines: {node: '>=0.10.0'}
+
source-map@0.6.1:
resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==}
engines: {node: '>=0.10.0'}
@@ -3245,6 +3767,10 @@ packages:
resolution: {integrity: sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==}
engines: {node: '>=18'}
+ strip-indent@3.0.0:
+ resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==}
+ engines: {node: '>=8'}
+
strip-indent@4.1.1:
resolution: {integrity: sha512-SlyRoSkdh1dYP0PzclLE7r0M9sgbFKKMFXpFRUMNuKhQSbC6VQIGzq3E0qsfvGJaUFJPGv6Ws1NZ/haTAjfbMA==}
engines: {node: '>=12'}
@@ -3266,6 +3792,9 @@ packages:
stubborn-utils@1.0.2:
resolution: {integrity: sha512-zOh9jPYI+xrNOyisSelgym4tolKTJCQd5GBhK0+0xJvcYDcwlOoxF/rnFKQ2KRZknXSG9jWAp66fwP6AxN9STg==}
+ stylis@4.2.0:
+ resolution: {integrity: sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==}
+
supports-color@7.2.0:
resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
engines: {node: '>=8'}
@@ -3285,6 +3814,14 @@ packages:
resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==}
engines: {node: '>=12.0.0'}
+ tinyrainbow@1.2.0:
+ resolution: {integrity: sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==}
+ engines: {node: '>=14.0.0'}
+
+ tinyspy@3.0.2:
+ resolution: {integrity: sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==}
+ engines: {node: '>=14.0.0'}
+
to-regex-range@5.0.1:
resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
engines: {node: '>=8.0'}
@@ -3392,12 +3929,22 @@ packages:
resolution: {integrity: sha512-n2huDr9h9yzd6exQVnH/jU5mr+Pfx08LRXXZhkLLetAMESRj+anQsTAh940iMrIetKAmry9coFuZQ2jY8/p3WA==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
+ use-sync-external-store@1.2.0:
+ resolution: {integrity: sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==}
+ peerDependencies:
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0
+
util-deprecate@1.0.2:
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
util@0.12.5:
resolution: {integrity: sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==}
+ uuid@9.0.1:
+ resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==}
+ deprecated: uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028).
+ hasBin: true
+
validate-npm-package-license@3.0.4:
resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==}
@@ -3524,6 +4071,10 @@ packages:
yallist@3.1.1:
resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==}
+ yaml@1.10.3:
+ resolution: {integrity: sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA==}
+ engines: {node: '>= 6'}
+
yaml@2.9.0:
resolution: {integrity: sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==}
engines: {node: '>= 14.6'}
@@ -3553,8 +4104,25 @@ packages:
zod@3.25.76:
resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==}
+ zustand@4.5.1:
+ resolution: {integrity: sha512-XlauQmH64xXSC1qGYNv00ODaQ3B+tNPoy22jv2diYiP4eoDKr9LA+Bh5Bc3gplTrFdb6JVI+N4kc1DZ/tbtfPg==}
+ engines: {node: '>=12.7.0'}
+ peerDependencies:
+ '@types/react': '>=16.8'
+ immer: '>=9.0.6'
+ react: '>=16.8'
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ immer:
+ optional: true
+ react:
+ optional: true
+
snapshots:
+ '@adobe/css-tools@4.5.0': {}
+
'@azure-rest/core-client@2.8.0':
dependencies:
'@azure/abort-controller': 2.2.0
@@ -3848,6 +4416,8 @@ snapshots:
dependencies:
'@babel/types': 7.29.8
+ '@babel/runtime@7.29.7': {}
+
'@babel/template@7.29.7':
dependencies:
'@babel/code-frame': 7.29.7
@@ -3880,6 +4450,91 @@ snapshots:
conventional-commits-filter: 5.0.0
conventional-commits-parser: 6.4.0
+ '@emotion/babel-plugin@11.13.5':
+ dependencies:
+ '@babel/helper-module-imports': 7.29.7
+ '@babel/runtime': 7.29.7
+ '@emotion/hash': 0.9.2
+ '@emotion/memoize': 0.9.0
+ '@emotion/serialize': 1.3.3
+ babel-plugin-macros: 3.1.0
+ convert-source-map: 1.9.0
+ escape-string-regexp: 4.0.0
+ find-root: 1.1.0
+ source-map: 0.5.7
+ stylis: 4.2.0
+ transitivePeerDependencies:
+ - supports-color
+
+ '@emotion/cache@11.14.0':
+ dependencies:
+ '@emotion/memoize': 0.9.0
+ '@emotion/sheet': 1.4.0
+ '@emotion/utils': 1.4.2
+ '@emotion/weak-memoize': 0.4.0
+ stylis: 4.2.0
+
+ '@emotion/hash@0.9.2': {}
+
+ '@emotion/is-prop-valid@1.4.0':
+ dependencies:
+ '@emotion/memoize': 0.9.0
+
+ '@emotion/memoize@0.9.0': {}
+
+ '@emotion/react@11.11.3(@types/react@18.3.31)(react@18.3.1)':
+ dependencies:
+ '@babel/runtime': 7.29.7
+ '@emotion/babel-plugin': 11.13.5
+ '@emotion/cache': 11.14.0
+ '@emotion/serialize': 1.3.3
+ '@emotion/use-insertion-effect-with-fallbacks': 1.2.0(react@18.3.1)
+ '@emotion/utils': 1.4.2
+ '@emotion/weak-memoize': 0.3.1
+ hoist-non-react-statics: 3.3.2
+ react: 18.3.1
+ optionalDependencies:
+ '@types/react': 18.3.31
+ transitivePeerDependencies:
+ - supports-color
+
+ '@emotion/serialize@1.3.3':
+ dependencies:
+ '@emotion/hash': 0.9.2
+ '@emotion/memoize': 0.9.0
+ '@emotion/unitless': 0.10.0
+ '@emotion/utils': 1.4.2
+ csstype: 3.2.3
+
+ '@emotion/sheet@1.4.0': {}
+
+ '@emotion/styled@11.11.0(@emotion/react@11.11.3(@types/react@18.3.31)(react@18.3.1))(@types/react@18.3.31)(react@18.3.1)':
+ dependencies:
+ '@babel/runtime': 7.29.7
+ '@emotion/babel-plugin': 11.13.5
+ '@emotion/is-prop-valid': 1.4.0
+ '@emotion/react': 11.11.3(@types/react@18.3.31)(react@18.3.1)
+ '@emotion/serialize': 1.3.3
+ '@emotion/use-insertion-effect-with-fallbacks': 1.2.0(react@18.3.1)
+ '@emotion/utils': 1.4.2
+ react: 18.3.1
+ optionalDependencies:
+ '@types/react': 18.3.31
+ transitivePeerDependencies:
+ - supports-color
+
+ '@emotion/unitless@0.10.0': {}
+
+ '@emotion/use-insertion-effect-with-fallbacks@1.2.0(react@18.3.1)':
+ dependencies:
+ react: 18.3.1
+
+ '@emotion/utils@1.4.2': {}
+
+ '@emotion/weak-memoize@0.3.1': {}
+
+ '@emotion/weak-memoize@0.4.0': {}
+
'@esbuild/aix-ppc64@0.25.12':
optional: true
@@ -4082,6 +4737,23 @@ snapshots:
'@eslint/core': 0.17.0
levn: 0.4.1
+ '@floating-ui/core@1.8.0':
+ dependencies:
+ '@floating-ui/utils': 0.2.12
+
+ '@floating-ui/dom@1.8.0':
+ dependencies:
+ '@floating-ui/core': 1.8.0
+ '@floating-ui/utils': 0.2.12
+
+ '@floating-ui/react-dom@2.1.9(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+ dependencies:
+ '@floating-ui/dom': 1.8.0
+ react: 18.3.1
+ react-dom: 18.3.1(react@18.3.1)
+
+ '@floating-ui/utils@0.2.12': {}
+
'@hono/node-server@2.1.1(hono@4.13.7)':
dependencies:
hono: 4.13.7
@@ -4266,6 +4938,12 @@ snapshots:
'@jridgewell/resolve-uri': 3.1.2
'@jridgewell/sourcemap-codec': 1.6.0
+ '@mdx-js/react@3.1.1(@types/react@18.3.31)(react@18.3.1)':
+ dependencies:
+ '@types/mdx': 2.0.14
+ '@types/react': 18.3.31
+ react: 18.3.1
+
'@modelcontextprotocol/sdk@1.30.0(zod@3.25.76)':
dependencies:
'@hono/node-server': 2.1.1(hono@4.13.7)
@@ -4288,6 +4966,110 @@ snapshots:
transitivePeerDependencies:
- supports-color
+ '@mui/base@5.0.0-beta.36(@types/react@18.3.31)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+ dependencies:
+ '@babel/runtime': 7.29.7
+ '@floating-ui/react-dom': 2.1.9(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@mui/types': 7.4.12(@types/react@18.3.31)
+ '@mui/utils': 5.17.1(@types/react@18.3.31)(react@18.3.1)
+ '@popperjs/core': 2.11.8
+ clsx: 2.1.1
+ prop-types: 15.8.1
+ react: 18.3.1
+ react-dom: 18.3.1(react@18.3.1)
+ optionalDependencies:
+ '@types/react': 18.3.31
+
+ '@mui/core-downloads-tracker@5.18.0': {}
+
+ '@mui/icons-material@5.15.10(@mui/material@5.15.10(@emotion/react@11.11.3(@types/react@18.3.31)(react@18.3.1))(@emotion/styled@11.11.0(@emotion/react@11.11.3(@types/react@18.3.31)(react@18.3.1))(@types/react@18.3.31)(react@18.3.1))(@types/react@18.3.31)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/react@18.3.31)(react@18.3.1)':
+ dependencies:
+ '@babel/runtime': 7.29.7
+ '@mui/material': 5.15.10(@emotion/react@11.11.3(@types/react@18.3.31)(react@18.3.1))(@emotion/styled@11.11.0(@emotion/react@11.11.3(@types/react@18.3.31)(react@18.3.1))(@types/react@18.3.31)(react@18.3.1))(@types/react@18.3.31)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ react: 18.3.1
+ optionalDependencies:
+ '@types/react': 18.3.31
+
+ '@mui/material@5.15.10(@emotion/react@11.11.3(@types/react@18.3.31)(react@18.3.1))(@emotion/styled@11.11.0(@emotion/react@11.11.3(@types/react@18.3.31)(react@18.3.1))(@types/react@18.3.31)(react@18.3.1))(@types/react@18.3.31)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+ dependencies:
+ '@babel/runtime': 7.29.7
+ '@mui/base': 5.0.0-beta.36(@types/react@18.3.31)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@mui/core-downloads-tracker': 5.18.0
+ '@mui/system': 5.18.0(@emotion/react@11.11.3(@types/react@18.3.31)(react@18.3.1))(@emotion/styled@11.11.0(@emotion/react@11.11.3(@types/react@18.3.31)(react@18.3.1))(@types/react@18.3.31)(react@18.3.1))(@types/react@18.3.31)(react@18.3.1)
+ '@mui/types': 7.4.12(@types/react@18.3.31)
+ '@mui/utils': 5.17.1(@types/react@18.3.31)(react@18.3.1)
+ '@types/react-transition-group': 4.4.12(@types/react@18.3.31)
+ clsx: 2.1.1
+ csstype: 3.2.3
+ prop-types: 15.8.1
+ react: 18.3.1
+ react-dom: 18.3.1(react@18.3.1)
+ react-is: 18.3.1
+ react-transition-group: 4.4.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ optionalDependencies:
+ '@emotion/react': 11.11.3(@types/react@18.3.31)(react@18.3.1)
+ '@emotion/styled': 11.11.0(@emotion/react@11.11.3(@types/react@18.3.31)(react@18.3.1))(@types/react@18.3.31)(react@18.3.1)
+ '@types/react': 18.3.31
+
+ '@mui/private-theming@5.17.1(@types/react@18.3.31)(react@18.3.1)':
+ dependencies:
+ '@babel/runtime': 7.29.7
+ '@mui/utils': 5.17.1(@types/react@18.3.31)(react@18.3.1)
+ prop-types: 15.8.1
+ react: 18.3.1
+ optionalDependencies:
+ '@types/react': 18.3.31
+
+ '@mui/styled-engine@5.18.0(@emotion/react@11.11.3(@types/react@18.3.31)(react@18.3.1))(@emotion/styled@11.11.0(@emotion/react@11.11.3(@types/react@18.3.31)(react@18.3.1))(@types/react@18.3.31)(react@18.3.1))(react@18.3.1)':
+ dependencies:
+ '@babel/runtime': 7.29.7
+ '@emotion/cache': 11.14.0
+ '@emotion/serialize': 1.3.3
+ csstype: 3.2.3
+ prop-types: 15.8.1
+ react: 18.3.1
+ optionalDependencies:
+ '@emotion/react': 11.11.3(@types/react@18.3.31)(react@18.3.1)
+ '@emotion/styled': 11.11.0(@emotion/react@11.11.3(@types/react@18.3.31)(react@18.3.1))(@types/react@18.3.31)(react@18.3.1)
+
+ '@mui/system@5.18.0(@emotion/react@11.11.3(@types/react@18.3.31)(react@18.3.1))(@emotion/styled@11.11.0(@emotion/react@11.11.3(@types/react@18.3.31)(react@18.3.1))(@types/react@18.3.31)(react@18.3.1))(@types/react@18.3.31)(react@18.3.1)':
+ dependencies:
+ '@babel/runtime': 7.29.7
+ '@mui/private-theming': 5.17.1(@types/react@18.3.31)(react@18.3.1)
+ '@mui/styled-engine': 5.18.0(@emotion/react@11.11.3(@types/react@18.3.31)(react@18.3.1))(@emotion/styled@11.11.0(@emotion/react@11.11.3(@types/react@18.3.31)(react@18.3.1))(@types/react@18.3.31)(react@18.3.1))(react@18.3.1)
+ '@mui/types': 7.2.24(@types/react@18.3.31)
+ '@mui/utils': 5.17.1(@types/react@18.3.31)(react@18.3.1)
+ clsx: 2.1.1
+ csstype: 3.2.3
+ prop-types: 15.8.1
+ react: 18.3.1
+ optionalDependencies:
+ '@emotion/react': 11.11.3(@types/react@18.3.31)(react@18.3.1)
+ '@emotion/styled': 11.11.0(@emotion/react@11.11.3(@types/react@18.3.31)(react@18.3.1))(@types/react@18.3.31)(react@18.3.1)
+ '@types/react': 18.3.31
+
+ '@mui/types@7.2.24(@types/react@18.3.31)':
+ optionalDependencies:
+ '@types/react': 18.3.31
+
+ '@mui/types@7.4.12(@types/react@18.3.31)':
+ dependencies:
+ '@babel/runtime': 7.29.7
+ optionalDependencies:
+ '@types/react': 18.3.31
+
+ '@mui/utils@5.17.1(@types/react@18.3.31)(react@18.3.1)':
+ dependencies:
+ '@babel/runtime': 7.29.7
+ '@mui/types': 7.2.24(@types/react@18.3.31)
+ '@types/prop-types': 15.7.15
+ clsx: 2.1.1
+ prop-types: 15.8.1
+ react: 18.3.1
+ react-is: 19.3.0
+ optionalDependencies:
+ '@types/react': 18.3.31
+
'@napi-rs/lzma-linux-x64-gnu@1.5.1':
optional: true
@@ -4388,6 +5170,8 @@ snapshots:
'@pnpm/network.ca-file': 1.0.2
config-chain: 1.1.13
+ '@popperjs/core@2.11.8': {}
+
'@release-it/conventional-changelog@10.0.6(conventional-commits-filter@5.0.0)(conventional-commits-parser@6.4.0)(release-it@18.1.2(@types/node@26.2.0)(typescript@5.9.3))':
dependencies:
'@conventional-changelog/git-client': 2.7.0(conventional-commits-filter@5.0.0)(conventional-commits-parser@6.4.0)
@@ -4499,30 +5283,117 @@ snapshots:
'@sindresorhus/merge-streams@4.0.0': {}
- '@storybook/blocks@8.6.14(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.14(prettier@3.9.6))':
+ '@storybook/addon-actions@8.6.14(storybook@8.6.14(prettier@3.2.5))':
+ dependencies:
+ '@storybook/global': 5.0.0
+ '@types/uuid': 9.0.8
+ dequal: 2.0.3
+ polished: 4.3.1
+ storybook: 8.6.14(prettier@3.2.5)
+ uuid: 9.0.1
+
+ '@storybook/addon-backgrounds@8.6.14(storybook@8.6.14(prettier@3.2.5))':
+ dependencies:
+ '@storybook/global': 5.0.0
+ memoizerific: 1.11.3
+ storybook: 8.6.14(prettier@3.2.5)
+ ts-dedent: 2.3.0
+
+ '@storybook/addon-controls@8.6.14(storybook@8.6.14(prettier@3.2.5))':
+ dependencies:
+ '@storybook/global': 5.0.0
+ dequal: 2.0.3
+ storybook: 8.6.14(prettier@3.2.5)
+ ts-dedent: 2.3.0
+
+ '@storybook/addon-docs@8.6.14(@types/react@18.3.31)(storybook@8.6.14(prettier@3.2.5))':
+ dependencies:
+ '@mdx-js/react': 3.1.1(@types/react@18.3.31)(react@18.3.1)
+ '@storybook/blocks': 8.6.14(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.14(prettier@3.2.5))
+ '@storybook/csf-plugin': 8.6.14(storybook@8.6.14(prettier@3.2.5))
+ '@storybook/react-dom-shim': 8.6.14(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.14(prettier@3.2.5))
+ react: 18.3.1
+ react-dom: 18.3.1(react@18.3.1)
+ storybook: 8.6.14(prettier@3.2.5)
+ ts-dedent: 2.3.0
+ transitivePeerDependencies:
+ - '@types/react'
+
+ '@storybook/addon-essentials@8.6.14(@types/react@18.3.31)(storybook@8.6.14(prettier@3.2.5))':
+ dependencies:
+ '@storybook/addon-actions': 8.6.14(storybook@8.6.14(prettier@3.2.5))
+ '@storybook/addon-backgrounds': 8.6.14(storybook@8.6.14(prettier@3.2.5))
+ '@storybook/addon-controls': 8.6.14(storybook@8.6.14(prettier@3.2.5))
+ '@storybook/addon-docs': 8.6.14(@types/react@18.3.31)(storybook@8.6.14(prettier@3.2.5))
+ '@storybook/addon-highlight': 8.6.14(storybook@8.6.14(prettier@3.2.5))
+ '@storybook/addon-measure': 8.6.14(storybook@8.6.14(prettier@3.2.5))
+ '@storybook/addon-outline': 8.6.14(storybook@8.6.14(prettier@3.2.5))
+ '@storybook/addon-toolbars': 8.6.14(storybook@8.6.14(prettier@3.2.5))
+ '@storybook/addon-viewport': 8.6.14(storybook@8.6.14(prettier@3.2.5))
+ storybook: 8.6.14(prettier@3.2.5)
+ ts-dedent: 2.3.0
+ transitivePeerDependencies:
+ - '@types/react'
+
+ '@storybook/addon-highlight@8.6.14(storybook@8.6.14(prettier@3.2.5))':
+ dependencies:
+ '@storybook/global': 5.0.0
+ storybook: 8.6.14(prettier@3.2.5)
+
+ '@storybook/addon-interactions@8.6.14(storybook@8.6.14(prettier@3.2.5))':
+ dependencies:
+ '@storybook/global': 5.0.0
+ '@storybook/instrumenter': 8.6.14(storybook@8.6.14(prettier@3.2.5))
+ '@storybook/test': 8.6.14(storybook@8.6.14(prettier@3.2.5))
+ polished: 4.3.1
+ storybook: 8.6.14(prettier@3.2.5)
+ ts-dedent: 2.3.0
+
+ '@storybook/addon-measure@8.6.14(storybook@8.6.14(prettier@3.2.5))':
+ dependencies:
+ '@storybook/global': 5.0.0
+ storybook: 8.6.14(prettier@3.2.5)
+ tiny-invariant: 1.3.3
+
+ '@storybook/addon-outline@8.6.14(storybook@8.6.14(prettier@3.2.5))':
+ dependencies:
+ '@storybook/global': 5.0.0
+ storybook: 8.6.14(prettier@3.2.5)
+ ts-dedent: 2.3.0
+
+ '@storybook/addon-toolbars@8.6.14(storybook@8.6.14(prettier@3.2.5))':
+ dependencies:
+ storybook: 8.6.14(prettier@3.2.5)
+
+ '@storybook/addon-viewport@8.6.14(storybook@8.6.14(prettier@3.2.5))':
+ dependencies:
+ memoizerific: 1.11.3
+ storybook: 8.6.14(prettier@3.2.5)
+
+ '@storybook/blocks@8.6.14(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.14(prettier@3.2.5))':
dependencies:
'@storybook/icons': 1.6.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- storybook: 8.6.14(prettier@3.9.6)
+ storybook: 8.6.14(prettier@3.2.5)
ts-dedent: 2.3.0
optionalDependencies:
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
- '@storybook/builder-vite@8.6.14(storybook@8.6.14(prettier@3.9.6))(vite@6.3.5(@types/node@20.19.43)(tsx@4.23.12)(yaml@2.9.0))':
+ '@storybook/builder-vite@8.6.14(storybook@8.6.14(prettier@3.2.5))(vite@6.3.5(@types/node@20.19.43)(tsx@4.23.12)(yaml@2.9.0))':
dependencies:
- '@storybook/csf-plugin': 8.6.14(storybook@8.6.14(prettier@3.9.6))
+ '@storybook/csf-plugin': 8.6.14(storybook@8.6.14(prettier@3.2.5))
browser-assert: 1.2.1
- storybook: 8.6.14(prettier@3.9.6)
+ storybook: 8.6.14(prettier@3.2.5)
ts-dedent: 2.3.0
vite: 6.3.5(@types/node@20.19.43)(tsx@4.23.12)(yaml@2.9.0)
- '@storybook/components@8.6.14(storybook@8.6.14(prettier@3.9.6))':
+ '@storybook/components@8.6.14(storybook@8.6.14(prettier@3.2.5))':
dependencies:
- storybook: 8.6.14(prettier@3.9.6)
+ storybook: 8.6.14(prettier@3.2.5)
- '@storybook/core@8.6.14(prettier@3.9.6)(storybook@8.6.14(prettier@3.9.6))':
+ '@storybook/core@8.6.14(prettier@3.2.5)(storybook@8.6.14(prettier@3.2.5))':
dependencies:
- '@storybook/theming': 8.6.14(storybook@8.6.14(prettier@3.9.6))
+ '@storybook/theming': 8.6.14(storybook@8.6.14(prettier@3.2.5))
better-opn: 3.0.2
browser-assert: 1.2.1
esbuild: 0.25.12
@@ -4534,16 +5405,16 @@ snapshots:
util: 0.12.5
ws: 8.21.3
optionalDependencies:
- prettier: 3.9.6
+ prettier: 3.2.5
transitivePeerDependencies:
- bufferutil
- storybook
- supports-color
- utf-8-validate
- '@storybook/csf-plugin@8.6.14(storybook@8.6.14(prettier@3.9.6))':
+ '@storybook/csf-plugin@8.6.14(storybook@8.6.14(prettier@3.2.5))':
dependencies:
- storybook: 8.6.14(prettier@3.9.6)
+ storybook: 8.6.14(prettier@3.2.5)
unplugin: 1.16.1
'@storybook/global@5.0.0': {}
@@ -4553,60 +5424,107 @@ snapshots:
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
- '@storybook/manager-api@8.6.14(storybook@8.6.14(prettier@3.9.6))':
+ '@storybook/instrumenter@8.6.14(storybook@8.6.14(prettier@3.2.5))':
+ dependencies:
+ '@storybook/global': 5.0.0
+ '@vitest/utils': 2.1.9
+ storybook: 8.6.14(prettier@3.2.5)
+
+ '@storybook/manager-api@8.6.14(storybook@8.6.14(prettier@3.2.5))':
dependencies:
- storybook: 8.6.14(prettier@3.9.6)
+ storybook: 8.6.14(prettier@3.2.5)
- '@storybook/preview-api@8.6.14(storybook@8.6.14(prettier@3.9.6))':
+ '@storybook/preview-api@8.6.14(storybook@8.6.14(prettier@3.2.5))':
dependencies:
- storybook: 8.6.14(prettier@3.9.6)
+ storybook: 8.6.14(prettier@3.2.5)
- '@storybook/react-dom-shim@8.6.14(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.14(prettier@3.9.6))':
+ '@storybook/react-dom-shim@8.6.14(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.14(prettier@3.2.5))':
dependencies:
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
- storybook: 8.6.14(prettier@3.9.6)
+ storybook: 8.6.14(prettier@3.2.5)
- '@storybook/react-vite@8.6.14(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rollup@4.63.1)(storybook@8.6.14(prettier@3.9.6))(typescript@5.9.3)(vite@6.3.5(@types/node@20.19.43)(tsx@4.23.12)(yaml@2.9.0))':
+ '@storybook/react-vite@8.6.14(@storybook/test@8.6.14(storybook@8.6.14(prettier@3.2.5)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rollup@4.63.1)(storybook@8.6.14(prettier@3.2.5))(typescript@5.9.3)(vite@6.3.5(@types/node@20.19.43)(tsx@4.23.12)(yaml@2.9.0))':
dependencies:
'@joshwooding/vite-plugin-react-docgen-typescript': 0.5.0(typescript@5.9.3)(vite@6.3.5(@types/node@20.19.43)(tsx@4.23.12)(yaml@2.9.0))
'@rollup/pluginutils': 5.4.0(rollup@4.63.1)
- '@storybook/builder-vite': 8.6.14(storybook@8.6.14(prettier@3.9.6))(vite@6.3.5(@types/node@20.19.43)(tsx@4.23.12)(yaml@2.9.0))
- '@storybook/react': 8.6.14(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.14(prettier@3.9.6))(typescript@5.9.3)
+ '@storybook/builder-vite': 8.6.14(storybook@8.6.14(prettier@3.2.5))(vite@6.3.5(@types/node@20.19.43)(tsx@4.23.12)(yaml@2.9.0))
+ '@storybook/react': 8.6.14(@storybook/test@8.6.14(storybook@8.6.14(prettier@3.2.5)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.14(prettier@3.2.5))(typescript@5.9.3)
find-up: 5.0.0
magic-string: 0.30.21
react: 18.3.1
react-docgen: 7.1.1
react-dom: 18.3.1(react@18.3.1)
resolve: 1.22.12
- storybook: 8.6.14(prettier@3.9.6)
+ storybook: 8.6.14(prettier@3.2.5)
tsconfig-paths: 4.2.0
vite: 6.3.5(@types/node@20.19.43)(tsx@4.23.12)(yaml@2.9.0)
+ optionalDependencies:
+ '@storybook/test': 8.6.14(storybook@8.6.14(prettier@3.2.5))
transitivePeerDependencies:
- rollup
- supports-color
- typescript
- '@storybook/react@8.6.14(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.14(prettier@3.9.6))(typescript@5.9.3)':
+ '@storybook/react@8.6.14(@storybook/test@8.6.14(storybook@8.6.14(prettier@3.2.5)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.14(prettier@3.2.5))(typescript@5.9.3)':
dependencies:
- '@storybook/components': 8.6.14(storybook@8.6.14(prettier@3.9.6))
+ '@storybook/components': 8.6.14(storybook@8.6.14(prettier@3.2.5))
'@storybook/global': 5.0.0
- '@storybook/manager-api': 8.6.14(storybook@8.6.14(prettier@3.9.6))
- '@storybook/preview-api': 8.6.14(storybook@8.6.14(prettier@3.9.6))
- '@storybook/react-dom-shim': 8.6.14(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.14(prettier@3.9.6))
- '@storybook/theming': 8.6.14(storybook@8.6.14(prettier@3.9.6))
+ '@storybook/manager-api': 8.6.14(storybook@8.6.14(prettier@3.2.5))
+ '@storybook/preview-api': 8.6.14(storybook@8.6.14(prettier@3.2.5))
+ '@storybook/react-dom-shim': 8.6.14(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.14(prettier@3.2.5))
+ '@storybook/theming': 8.6.14(storybook@8.6.14(prettier@3.2.5))
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
- storybook: 8.6.14(prettier@3.9.6)
+ storybook: 8.6.14(prettier@3.2.5)
optionalDependencies:
+ '@storybook/test': 8.6.14(storybook@8.6.14(prettier@3.2.5))
typescript: 5.9.3
- '@storybook/theming@8.6.14(storybook@8.6.14(prettier@3.9.6))':
+ '@storybook/test@8.6.14(storybook@8.6.14(prettier@3.2.5))':
+ dependencies:
+ '@storybook/global': 5.0.0
+ '@storybook/instrumenter': 8.6.14(storybook@8.6.14(prettier@3.2.5))
+ '@testing-library/dom': 10.4.0
+ '@testing-library/jest-dom': 6.5.0
+ '@testing-library/user-event': 14.5.2(@testing-library/dom@10.4.0)
+ '@vitest/expect': 2.0.5
+ '@vitest/spy': 2.0.5
+ storybook: 8.6.14(prettier@3.2.5)
+
+ '@storybook/theming@8.6.14(storybook@8.6.14(prettier@3.2.5))':
+ dependencies:
+ storybook: 8.6.14(prettier@3.2.5)
+
+ '@testing-library/dom@10.4.0':
+ dependencies:
+ '@babel/code-frame': 7.29.7
+ '@babel/runtime': 7.29.7
+ '@types/aria-query': 5.0.4
+ aria-query: 5.3.0
+ chalk: 4.1.2
+ dom-accessibility-api: 0.5.16
+ lz-string: 1.5.0
+ pretty-format: 27.5.1
+
+ '@testing-library/jest-dom@6.5.0':
+ dependencies:
+ '@adobe/css-tools': 4.5.0
+ aria-query: 5.3.2
+ chalk: 3.0.0
+ css.escape: 1.5.1
+ dom-accessibility-api: 0.6.3
+ lodash: 4.17.21
+ redent: 3.0.0
+
+ '@testing-library/user-event@14.5.2(@testing-library/dom@10.4.0)':
dependencies:
- storybook: 8.6.14(prettier@3.9.6)
+ '@testing-library/dom': 10.4.0
'@tootallnate/quickjs-emscripten@0.23.0': {}
+ '@types/aria-query@5.0.4': {}
+
'@types/babel__core@7.20.5':
dependencies:
'@babel/parser': 7.29.8
@@ -4634,6 +5552,8 @@ snapshots:
'@types/json-schema@7.0.15': {}
+ '@types/mdx@2.0.14': {}
+
'@types/node@20.19.43':
dependencies:
undici-types: 6.21.0
@@ -4648,6 +5568,8 @@ snapshots:
'@types/normalize-package-data@2.4.4': {}
+ '@types/parse-json@4.0.2': {}
+
'@types/parse-path@7.1.0':
dependencies:
parse-path: 7.1.0
@@ -4658,6 +5580,10 @@ snapshots:
dependencies:
'@types/react': 18.3.31
+ '@types/react-transition-group@4.4.12(@types/react@18.3.31)':
+ dependencies:
+ '@types/react': 18.3.31
+
'@types/react@18.3.31':
dependencies:
'@types/prop-types': 15.7.15
@@ -4665,6 +5591,8 @@ snapshots:
'@types/resolve@1.20.6': {}
+ '@types/uuid@9.0.8': {}
+
'@typespec/ts-http-runtime@0.3.8':
dependencies:
http-proxy-agent: 7.0.2
@@ -4747,6 +5675,38 @@ snapshots:
react-dom: 18.3.1(react@18.3.1)
zod: 3.25.76
+ '@vitest/expect@2.0.5':
+ dependencies:
+ '@vitest/spy': 2.0.5
+ '@vitest/utils': 2.0.5
+ chai: 5.3.3
+ tinyrainbow: 1.2.0
+
+ '@vitest/pretty-format@2.0.5':
+ dependencies:
+ tinyrainbow: 1.2.0
+
+ '@vitest/pretty-format@2.1.9':
+ dependencies:
+ tinyrainbow: 1.2.0
+
+ '@vitest/spy@2.0.5':
+ dependencies:
+ tinyspy: 3.0.2
+
+ '@vitest/utils@2.0.5':
+ dependencies:
+ '@vitest/pretty-format': 2.0.5
+ estree-walker: 3.0.3
+ loupe: 3.2.1
+ tinyrainbow: 1.2.0
+
+ '@vitest/utils@2.1.9':
+ dependencies:
+ '@vitest/pretty-format': 2.1.9
+ loupe: 3.2.1
+ tinyrainbow: 1.2.0
+
accepts@2.0.0:
dependencies:
mime-types: 3.0.2
@@ -4794,14 +5754,24 @@ snapshots:
dependencies:
color-convert: 2.0.1
+ ansi-styles@5.2.0: {}
+
ansi-styles@6.2.3: {}
anynum@1.0.1: {}
argparse@2.0.1: {}
+ aria-query@5.3.0:
+ dependencies:
+ dequal: 2.0.3
+
+ aria-query@5.3.2: {}
+
array-ify@1.0.0: {}
+ assertion-error@2.0.1: {}
+
assignment@2.0.0: {}
ast-types@0.13.4:
@@ -4825,6 +5795,12 @@ snapshots:
dependencies:
possible-typed-array-names: 1.1.0
+ babel-plugin-macros@3.1.0:
+ dependencies:
+ '@babel/runtime': 7.29.7
+ cosmiconfig: 7.1.0
+ resolve: 1.22.12
+
balanced-match@1.0.2: {}
baseline-browser-mapping@2.11.21: {}
@@ -4918,6 +5894,19 @@ snapshots:
caniuse-lite@1.0.30001810: {}
+ chai@5.3.3:
+ dependencies:
+ assertion-error: 2.0.1
+ check-error: 2.1.3
+ deep-eql: 5.0.2
+ loupe: 3.2.1
+ pathval: 2.0.1
+
+ chalk@3.0.0:
+ dependencies:
+ ansi-styles: 4.3.0
+ supports-color: 7.2.0
+
chalk@4.1.2:
dependencies:
ansi-styles: 4.3.0
@@ -4927,6 +5916,8 @@ snapshots:
chardet@2.2.0: {}
+ check-error@2.1.3: {}
+
ci-info@4.4.0: {}
cli-boxes@3.0.0: {}
@@ -4939,6 +5930,8 @@ snapshots:
cli-width@4.1.0: {}
+ clsx@2.1.1: {}
+
color-convert@2.0.1:
dependencies:
color-name: 1.1.4
@@ -5024,6 +6017,8 @@ snapshots:
conventional-commits-parser: 6.4.0
meow: 13.2.0
+ convert-source-map@1.9.0: {}
+
convert-source-map@2.0.0: {}
cookie-signature@1.2.2: {}
@@ -5035,6 +6030,14 @@ snapshots:
object-assign: 4.1.1
vary: 1.1.2
+ cosmiconfig@7.1.0:
+ dependencies:
+ '@types/parse-json': 4.0.2
+ import-fresh: 3.3.1
+ parse-json: 5.2.0
+ path-type: 4.0.0
+ yaml: 1.10.3
+
cosmiconfig@9.0.0(typescript@5.9.3):
dependencies:
env-paths: 2.2.1
@@ -5050,6 +6053,8 @@ snapshots:
shebang-command: 2.0.0
which: 2.0.2
+ css.escape@1.5.1: {}
+
csstype@3.2.3: {}
data-uri-to-buffer@6.0.2: {}
@@ -5058,6 +6063,8 @@ snapshots:
dependencies:
ms: 2.1.3
+ deep-eql@5.0.2: {}
+
deep-extend@0.6.0: {}
deep-is@0.1.4: {}
@@ -5087,10 +6094,21 @@ snapshots:
depd@2.0.0: {}
+ dequal@2.0.3: {}
+
doctrine@3.0.0:
dependencies:
esutils: 2.0.3
+ dom-accessibility-api@0.5.16: {}
+
+ dom-accessibility-api@0.6.3: {}
+
+ dom-helpers@5.2.1:
+ dependencies:
+ '@babel/runtime': 7.29.7
+ csstype: 3.2.3
+
dot-prop@5.3.0:
dependencies:
is-obj: 2.0.0
@@ -5286,6 +6304,10 @@ snapshots:
estree-walker@2.0.2: {}
+ estree-walker@3.0.3:
+ dependencies:
+ '@types/estree': 1.0.9
+
esutils@2.0.3: {}
etag@1.8.1: {}
@@ -5433,6 +6455,8 @@ snapshots:
transitivePeerDependencies:
- supports-color
+ find-root@1.1.0: {}
+
find-up@5.0.0:
dependencies:
locate-path: 6.0.0
@@ -5589,6 +6613,12 @@ snapshots:
he@0.5.0: {}
+ highlight.js@11.9.0: {}
+
+ hoist-non-react-statics@3.3.2:
+ dependencies:
+ react-is: 16.13.1
+
hono@4.13.7: {}
hosted-git-info@8.1.0:
@@ -5636,6 +6666,8 @@ snapshots:
imurmurhash@0.1.4: {}
+ indent-string@4.0.0: {}
+
inflight@1.0.6:
dependencies:
once: 1.4.0
@@ -5888,6 +6920,8 @@ snapshots:
dependencies:
js-tokens: 4.0.0
+ loupe@3.2.1: {}
+
lru-cache@10.4.3: {}
lru-cache@5.1.1:
@@ -5896,6 +6930,8 @@ snapshots:
lru-cache@7.18.3: {}
+ lz-string@1.5.0: {}
+
macos-release@3.5.1: {}
magic-string@0.27.0:
@@ -5906,12 +6942,18 @@ snapshots:
dependencies:
'@jridgewell/sourcemap-codec': 1.6.0
+ map-or-similar@1.5.0: {}
+
marked@12.0.2: {}
math-intrinsics@1.1.0: {}
media-typer@1.1.1: {}
+ memoizerific@1.11.3:
+ dependencies:
+ map-or-similar: 1.5.0
+
meow@13.2.0: {}
merge-descriptors@2.0.0: {}
@@ -5941,6 +6983,8 @@ snapshots:
mimic-function@5.0.1: {}
+ min-indent@1.0.1: {}
+
minimatch@3.1.5:
dependencies:
brace-expansion: 1.1.18
@@ -6127,16 +7171,36 @@ snapshots:
path-to-regexp@8.4.2: {}
+ path-type@4.0.0: {}
+
path-type@5.0.0: {}
+ pathval@2.0.1: {}
+
picocolors@1.1.1: {}
picomatch@2.3.2: {}
picomatch@4.0.5: {}
+ pixelmatch@7.2.0:
+ dependencies:
+ pngjs: 7.0.0
+
pkce-challenge@5.0.1: {}
+ playwright-core@1.63.0: {}
+
+ playwright@1.63.0:
+ dependencies:
+ playwright-core: 1.63.0
+
+ pngjs@7.0.0: {}
+
+ polished@4.3.1:
+ dependencies:
+ '@babel/runtime': 7.29.7
+
possible-typed-array-names@1.1.0: {}
postcss@8.5.28:
@@ -6147,14 +7211,28 @@ snapshots:
prelude-ls@1.2.1: {}
+ prettier@3.2.5: {}
+
prettier@3.9.6: {}
+ pretty-format@27.5.1:
+ dependencies:
+ ansi-regex: 5.0.1
+ ansi-styles: 5.2.0
+ react-is: 17.0.2
+
pretty-ms@9.3.0:
dependencies:
parse-ms: 4.0.0
process@0.11.10: {}
+ prop-types@15.8.1:
+ dependencies:
+ loose-envify: 1.4.0
+ object-assign: 4.1.1
+ react-is: 16.13.1
+
proto-list@1.2.4: {}
protocols@2.0.2: {}
@@ -6208,6 +7286,11 @@ snapshots:
minimist: 1.2.8
strip-json-comments: 2.0.1
+ react-colorful@5.6.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
+ dependencies:
+ react: 18.3.1
+ react-dom: 18.3.1(react@18.3.1)
+
react-docgen-typescript@2.4.0(typescript@5.9.3):
dependencies:
typescript: 5.9.3
@@ -6233,6 +7316,23 @@ snapshots:
react: 18.3.1
scheduler: 0.23.2
+ react-is@16.13.1: {}
+
+ react-is@17.0.2: {}
+
+ react-is@18.3.1: {}
+
+ react-is@19.3.0: {}
+
+ react-transition-group@4.4.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
+ dependencies:
+ '@babel/runtime': 7.29.7
+ dom-helpers: 5.2.1
+ loose-envify: 1.4.0
+ prop-types: 15.8.1
+ react: 18.3.1
+ react-dom: 18.3.1(react@18.3.1)
+
react@18.3.1:
dependencies:
loose-envify: 1.4.0
@@ -6255,6 +7355,11 @@ snapshots:
dependencies:
resolve: 1.22.12
+ redent@3.0.0:
+ dependencies:
+ indent-string: 4.0.0
+ strip-indent: 3.0.0
+
registry-auth-token@5.1.1:
dependencies:
'@pnpm/npm-conf': 3.0.3
@@ -6486,6 +7591,8 @@ snapshots:
source-map-js@1.2.1: {}
+ source-map@0.5.7: {}
+
source-map@0.6.1: {}
spdx-correct@3.2.0:
@@ -6506,11 +7613,11 @@ snapshots:
stdin-discarder@0.2.2: {}
- storybook@8.6.14(prettier@3.9.6):
+ storybook@8.6.14(prettier@3.2.5):
dependencies:
- '@storybook/core': 8.6.14(prettier@3.9.6)(storybook@8.6.14(prettier@3.9.6))
+ '@storybook/core': 8.6.14(prettier@3.2.5)(storybook@8.6.14(prettier@3.2.5))
optionalDependencies:
- prettier: 3.9.6
+ prettier: 3.2.5
transitivePeerDependencies:
- bufferutil
- supports-color
@@ -6554,6 +7661,10 @@ snapshots:
strip-final-newline@4.0.0: {}
+ strip-indent@3.0.0:
+ dependencies:
+ min-indent: 1.0.1
+
strip-indent@4.1.1: {}
strip-json-comments@2.0.1: {}
@@ -6570,6 +7681,8 @@ snapshots:
stubborn-utils@1.0.2: {}
+ stylis@4.2.0: {}
+
supports-color@7.2.0:
dependencies:
has-flag: 4.0.0
@@ -6585,6 +7698,10 @@ snapshots:
fdir: 6.5.0(picomatch@4.0.5)
picomatch: 4.0.5
+ tinyrainbow@1.2.0: {}
+
+ tinyspy@3.0.2: {}
+
to-regex-range@5.0.1:
dependencies:
is-number: 7.0.0
@@ -6676,6 +7793,10 @@ snapshots:
url-join@5.0.0: {}
+ use-sync-external-store@1.2.0(react@18.3.1):
+ dependencies:
+ react: 18.3.1
+
util-deprecate@1.0.2: {}
util@0.12.5:
@@ -6686,6 +7807,8 @@ snapshots:
is-typed-array: 1.1.15
which-typed-array: 1.1.22
+ uuid@9.0.1: {}
+
validate-npm-package-license@3.0.4:
dependencies:
spdx-correct: 3.2.0
@@ -6775,6 +7898,8 @@ snapshots:
yallist@3.1.1: {}
+ yaml@1.10.3: {}
+
yaml@2.9.0:
optional: true
@@ -6791,3 +7916,10 @@ snapshots:
zod: 3.25.76
zod@3.25.76: {}
+
+ zustand@4.5.1(@types/react@18.3.31)(react@18.3.1):
+ dependencies:
+ use-sync-external-store: 1.2.0(react@18.3.1)
+ optionalDependencies:
+ '@types/react': 18.3.31
+ react: 18.3.1