From bf4d07165988c540afde0fa55008ebb2ebfb1af6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Melo?= Date: Mon, 6 Apr 2026 17:05:50 -0300 Subject: [PATCH 1/7] feat(editor): replace document-inspector example with three inspector examples --- .../src/examples/document-inspector.tsx | 256 --------------- .../editor/examples/src/examples/index.ts | 20 +- .../src/examples/inspector-composed.tsx | 165 ++++++++++ .../src/examples/inspector-custom.tsx | 310 ++++++++++++++++++ .../src/examples/inspector-defaults.tsx | 74 +++++ 5 files changed, 565 insertions(+), 260 deletions(-) delete mode 100644 packages/editor/examples/src/examples/document-inspector.tsx create mode 100644 packages/editor/examples/src/examples/inspector-composed.tsx create mode 100644 packages/editor/examples/src/examples/inspector-custom.tsx create mode 100644 packages/editor/examples/src/examples/inspector-defaults.tsx diff --git a/packages/editor/examples/src/examples/document-inspector.tsx b/packages/editor/examples/src/examples/document-inspector.tsx deleted file mode 100644 index aefcbe4ed8..0000000000 --- a/packages/editor/examples/src/examples/document-inspector.tsx +++ /dev/null @@ -1,256 +0,0 @@ -import { StarterKit } from '@react-email/editor/extensions'; -import { EmailTheming } from '@react-email/editor/plugins'; -import { Inspector } from '@react-email/editor/ui'; -import { EditorContent, EditorContext, useEditor } from '@tiptap/react'; -import { useId } from 'react'; - -const extensions = [StarterKit, EmailTheming]; - -const content = { - type: 'doc', - content: [ - { - type: 'paragraph', - content: [ - { - type: 'text', - text: 'This example uses Inspector.Provider and Inspector.Document to render document-level style properties through a render-props API.', - }, - ], - }, - ], -}; - -export function DocumentInspector() { - const editor = useEditor({ - extensions, - content, - }); - - return ( -
-

- Using Inspector.Provider and{' '} - Inspector.Document to render document-level global styles - with predefined sections and a render-props API. -

- -
-
- -
- - -
-
-
- ); -} - -/* ------------------------------------------------------------------ */ -/* Small presentational helpers for the predefined sections */ -/* ------------------------------------------------------------------ */ - -function Section({ - title, - children, -}: { - title: string; - children: React.ReactNode; -}) { - return ( -
-

- {title} -

-
{children}
-
- ); -} - -function ColorRow({ - label, - value, - onChange, -}: { - label: string; - value: string | number | undefined; - onChange: (v: string) => void; -}) { - const strValue = value == null ? '' : String(value); - const id = useId(); - return ( -
- - - onChange(e.target.value)} - className="w-6 h-6 border-0 p-0 cursor-pointer" - /> - onChange(e.target.value)} - className="w-20 text-xs bg-transparent border border-(--re-border) rounded px-1.5 py-1 text-(--re-text)" - /> - -
- ); -} - -function NumberRow({ - label, - value, - unit, - onChange, -}: { - label: string; - value: string | number | undefined; - unit?: string; - onChange: (v: number | '') => void; -}) { - const id = useId(); - return ( -
- - - { - const raw = e.target.value; - onChange(raw === '' ? '' : Number.parseFloat(raw)); - }} - className="w-16 text-xs bg-transparent border border-(--re-border) rounded px-1.5 py-1 text-(--re-text)" - /> - {unit && {unit}} - -
- ); -} - -function normalizeHex(value: string): string { - if (!value) return '#000000'; - const v = value.trim(); - const shortHex = /^#([0-9a-f])([0-9a-f])([0-9a-f])$/i.exec(v); - if (shortHex) { - return `#${shortHex[1]}${shortHex[1]}${shortHex[2]}${shortHex[2]}${shortHex[3]}${shortHex[3]}`; - } - if (/^#[0-9a-f]{6}$/i.test(v)) return v; - return '#000000'; -} diff --git a/packages/editor/examples/src/examples/index.ts b/packages/editor/examples/src/examples/index.ts index b71c9837ff..cf2b09a5c4 100644 --- a/packages/editor/examples/src/examples/index.ts +++ b/packages/editor/examples/src/examples/index.ts @@ -3,11 +3,13 @@ import { BubbleMenuExample } from './bubble-menu'; import { ColumnLayouts } from './column-layouts'; import { CustomBubbleMenu } from './custom-bubble-menu'; import { CustomExtensions } from './custom-extensions'; -import { DocumentInspector } from './document-inspector'; import { EmailExport } from './email-export'; import { EmailThemingExample } from './email-theming'; import { FullEmailBuilder } from './full-email-builder'; import { Buttons } from './images-and-buttons'; +import { InspectorComposed } from './inspector-composed'; +import { InspectorCustom } from './inspector-custom'; +import { InspectorDefaults } from './inspector-defaults'; import { LinkEditing } from './link-editing'; import { OneLineEditor } from './one-line-editor'; import { OneLineEditorFull } from './one-line-editor-full'; @@ -92,9 +94,19 @@ export const sections: ExampleSection[] = [ component: CustomExtensions, }, { - id: 'document-inspector', - label: 'Document Inspector', - component: DocumentInspector, + id: 'inspector-defaults', + label: 'Inspector — Defaults', + component: InspectorDefaults, + }, + { + id: 'inspector-composed', + label: 'Inspector — Composed', + component: InspectorComposed, + }, + { + id: 'inspector-custom', + label: 'Inspector — Custom', + component: InspectorCustom, }, { id: 'full-email-builder', diff --git a/packages/editor/examples/src/examples/inspector-composed.tsx b/packages/editor/examples/src/examples/inspector-composed.tsx new file mode 100644 index 0000000000..d905a7243e --- /dev/null +++ b/packages/editor/examples/src/examples/inspector-composed.tsx @@ -0,0 +1,165 @@ +import { StarterKit } from '@react-email/editor/extensions'; +import { EmailTheming } from '@react-email/editor/plugins'; +import { Inspector } from '@react-email/editor/ui'; +import { EditorContent, EditorContext, useEditor } from '@tiptap/react'; +import { ExampleShell } from '../example-shell'; + +const extensions = [StarterKit, EmailTheming]; + +const content = ` +

Composed Inspector

+

This example picks specific sections and adds a custom one. Click an element to see the composed sidebar.

+
Click me
+ Placeholder +`; + +export function InspectorComposed() { + const editor = useEditor({ + extensions, + content, + }); + + return ( + + +
+
+ +
+ + +
+
+
+ ); +} + +function Breadcrumb() { + return ( + + ); +} + +function normalizeHex(value: string): string { + if (!value) return '#000000'; + const v = value.trim(); + const shortHex = /^#([0-9a-f])([0-9a-f])([0-9a-f])$/i.exec(v); + if (shortHex) { + return `#${shortHex[1]}${shortHex[1]}${shortHex[2]}${shortHex[2]}${shortHex[3]}${shortHex[3]}`; + } + if (/^#[0-9a-f]{6}$/i.test(v)) return v; + return '#000000'; +} diff --git a/packages/editor/examples/src/examples/inspector-custom.tsx b/packages/editor/examples/src/examples/inspector-custom.tsx new file mode 100644 index 0000000000..c3da0f9762 --- /dev/null +++ b/packages/editor/examples/src/examples/inspector-custom.tsx @@ -0,0 +1,310 @@ +import { StarterKit } from '@react-email/editor/extensions'; +import { EmailTheming } from '@react-email/editor/plugins'; +import { Inspector } from '@react-email/editor/ui'; +import { EditorContent, EditorContext, useEditor } from '@tiptap/react'; +import { ExampleShell } from '../example-shell'; + +const extensions = [StarterKit, EmailTheming]; + +const content = ` +

Custom Inspector

+

This inspector is built entirely from scratch — no pre-built sections or primitives. Just render-props data and plain HTML inputs.

+
Click me
+ Placeholder +`; + +export function InspectorCustom() { + const editor = useEditor({ + extensions, + content, + }); + + return ( + + +
+
+ +
+ + +
+
+
+ ); +} + +function Row({ + label, + children, +}: { + label: string; + children: React.ReactNode; +}) { + return ( +
+ {label} + {children} +
+ ); +} + +function ColorPicker({ + value, + onChange, +}: { + value: string; + onChange: (v: string) => void; +}) { + const normalized = normalizeHex(value); + return ( + + onChange(e.target.value)} + className="w-5 h-5 border-0 p-0 cursor-pointer" + /> + onChange(e.target.value)} + className="w-16 text-xs bg-transparent border border-(--re-border) rounded px-1 py-0.5" + /> + + ); +} + +function NumberField({ + value, + onChange, + unit, +}: { + value: string | number | undefined; + onChange: (v: number | '') => void; + unit?: string; +}) { + return ( + + { + const raw = e.target.value; + onChange(raw === '' ? '' : Number.parseFloat(raw)); + }} + className="w-14 text-xs bg-transparent border border-(--re-border) rounded px-1 py-0.5" + /> + {unit && {unit}} + + ); +} + +function MarkButton({ + label, + active, + onClick, + className = '', +}: { + label: string; + active: boolean; + onClick: () => void; + className?: string; +}) { + return ( + + ); +} + +function normalizeHex(value: string): string { + if (!value) return '#000000'; + const v = value.trim(); + const shortHex = /^#([0-9a-f])([0-9a-f])([0-9a-f])$/i.exec(v); + if (shortHex) { + return `#${shortHex[1]}${shortHex[1]}${shortHex[2]}${shortHex[2]}${shortHex[3]}${shortHex[3]}`; + } + if (/^#[0-9a-f]{6}$/i.test(v)) return v; + return '#000000'; +} diff --git a/packages/editor/examples/src/examples/inspector-defaults.tsx b/packages/editor/examples/src/examples/inspector-defaults.tsx new file mode 100644 index 0000000000..b31a2af3b2 --- /dev/null +++ b/packages/editor/examples/src/examples/inspector-defaults.tsx @@ -0,0 +1,74 @@ +import { StarterKit } from '@react-email/editor/extensions'; +import { EmailTheming } from '@react-email/editor/plugins'; +import { Inspector } from '@react-email/editor/ui'; +import { EditorContent, EditorContext, useEditor } from '@tiptap/react'; +import { ExampleShell } from '../example-shell'; + +const extensions = [StarterKit, EmailTheming]; + +const content = ` +

Inspector Defaults

+

Click on any element to inspect it. The sidebar renders sensible defaults for each node type — no configuration needed.

+
Click me
+

Try selecting text to see the text inspector, or click the background to see document-level styles.

+ Placeholder +`; + +export function InspectorDefaults() { + const editor = useEditor({ + extensions, + content, + }); + + return ( + + +
+
+ +
+ + +
+
+
+ ); +} + +function Breadcrumb() { + return ( + + ); +} From 3ca1ae0f231fb47ac5c917dc43f22a470e6274c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Melo?= Date: Mon, 6 Apr 2026 17:11:19 -0300 Subject: [PATCH 2/7] fix(editor): improve inspector CSS and export stylesheet --- packages/editor/examples/src/index.css | 1 + packages/editor/package.json | 1 + .../editor/src/ui/inspector/inspector.css | 28 +++++++++++++------ 3 files changed, 21 insertions(+), 9 deletions(-) diff --git a/packages/editor/examples/src/index.css b/packages/editor/examples/src/index.css index eed41be3c7..ceaa063752 100644 --- a/packages/editor/examples/src/index.css +++ b/packages/editor/examples/src/index.css @@ -1,5 +1,6 @@ @import "tailwindcss"; @import "@react-email/editor/themes/default.css"; +@import "@react-email/editor/styles/inspector.css"; html, body { diff --git a/packages/editor/package.json b/packages/editor/package.json index 2b00cce9a9..085fa8b15b 100644 --- a/packages/editor/package.json +++ b/packages/editor/package.json @@ -74,6 +74,7 @@ "./styles/button-bubble-menu.css": "./dist/ui/button-bubble-menu/button-bubble-menu.css", "./styles/image-bubble-menu.css": "./dist/ui/image-bubble-menu/image-bubble-menu.css", "./styles/slash-command.css": "./dist/ui/slash-command/slash-command.css", + "./styles/inspector.css": "./dist/ui/inspector/inspector.css", "./themes/default.css": "./dist/ui/themes/default.css" }, "license": "MIT", diff --git a/packages/editor/src/ui/inspector/inspector.css b/packages/editor/src/ui/inspector/inspector.css index 7138964363..78be1961dd 100644 --- a/packages/editor/src/ui/inspector/inspector.css +++ b/packages/editor/src/ui/inspector/inspector.css @@ -32,7 +32,6 @@ color: inherit; min-width: 0; flex: 1; - max-width: 60%; } [data-re-inspector-input]:focus { @@ -42,7 +41,7 @@ [data-re-inspector-input][data-type="number"] { font-family: monospace; - width: 4rem; + width: 3.5rem; flex: 0 0 auto; } @@ -50,7 +49,6 @@ height: 100px; max-height: 200px; resize: vertical; - max-width: 60%; } /* ---------------------------------------------------------------- @@ -60,13 +58,15 @@ [data-re-inspector-number] { display: inline-flex; align-items: center; - gap: 0.125rem; + gap: 0.25rem; } [data-re-inspector-unit] { font-size: 0.6875rem; font-family: monospace; - opacity: 0.6; + color: var(--re-text-muted, #6b6b6b); + cursor: ew-resize; + user-select: none; } /* ---------------------------------------------------------------- @@ -82,7 +82,6 @@ color: inherit; min-width: 0; flex: 1; - max-width: 60%; } [data-re-inspector-select]:focus { @@ -101,8 +100,8 @@ } [data-re-inspector-color-trigger] { - width: 1.5rem; - height: 1.5rem; + width: 1.25rem; + height: 1.25rem; border-radius: 0.25rem; border: 1px solid var(--re-border, #e5e5e5); cursor: pointer; @@ -111,7 +110,7 @@ } [data-re-inspector-color-hex] { - width: 5rem; + width: 4.5rem; font-size: 0.75rem; font-family: monospace; padding: 0.25rem 0.375rem; @@ -232,6 +231,9 @@ [data-re-inspector-label] { font-size: 0.75rem; color: var(--re-text-muted, #6b6b6b); + white-space: nowrap; + flex-shrink: 0; + min-width: 4rem; } [data-re-inspector-text][data-color="muted"] { @@ -247,6 +249,7 @@ align-items: center; justify-content: space-between; gap: 0.5rem; + font-size: 0.75rem; } /* ---------------------------------------------------------------- @@ -259,6 +262,11 @@ gap: 0.5rem; } +[data-re-inspector-section] + [data-re-inspector-section] { + padding-top: 0.75rem; + border-top: 1px solid var(--re-border, #e5e5e5); +} + [data-re-inspector-section-header] { display: flex; align-items: center; @@ -272,6 +280,8 @@ cursor: pointer; color: inherit; font: inherit; + font-weight: 600; + font-size: 0.75rem; } [data-re-inspector-section-body] { From c58a4c688656a1f5e624961c760c719237bf94f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Melo?= Date: Mon, 6 Apr 2026 17:11:55 -0300 Subject: [PATCH 3/7] fix(editor): include inspector CSS in default theme --- packages/editor/examples/src/index.css | 1 - packages/editor/src/ui/themes/default.css | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/editor/examples/src/index.css b/packages/editor/examples/src/index.css index ceaa063752..eed41be3c7 100644 --- a/packages/editor/examples/src/index.css +++ b/packages/editor/examples/src/index.css @@ -1,6 +1,5 @@ @import "tailwindcss"; @import "@react-email/editor/themes/default.css"; -@import "@react-email/editor/styles/inspector.css"; html, body { diff --git a/packages/editor/src/ui/themes/default.css b/packages/editor/src/ui/themes/default.css index 22c42f7fd8..1ca51f821d 100644 --- a/packages/editor/src/ui/themes/default.css +++ b/packages/editor/src/ui/themes/default.css @@ -9,6 +9,7 @@ /* Layer 0: layout (inlined at build time via postcss-import) */ @import "../bubble-menu/bubble-menu.css"; @import "../slash-command/slash-command.css"; +@import "../inspector/inspector.css"; /* ---------------------------------------------------------------- * CSS custom properties — light defaults From d789dfcda6dc904b416e7d53d611e6c0a39145f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Melo?= Date: Mon, 6 Apr 2026 17:13:25 -0300 Subject: [PATCH 4/7] feat(editor): add inspector sidebar to full email builder example --- .../src/examples/full-email-builder.tsx | 60 +++++++++++++++---- 1 file changed, 50 insertions(+), 10 deletions(-) diff --git a/packages/editor/examples/src/examples/full-email-builder.tsx b/packages/editor/examples/src/examples/full-email-builder.tsx index 8692610aed..e27d71ffa1 100644 --- a/packages/editor/examples/src/examples/full-email-builder.tsx +++ b/packages/editor/examples/src/examples/full-email-builder.tsx @@ -4,6 +4,7 @@ import { EmailTheming } from '@react-email/editor/plugins'; import { BubbleMenu, defaultSlashCommands, + Inspector, SlashCommand, } from '@react-email/editor/ui'; import { EditorProvider, useCurrentEditor } from '@tiptap/react'; @@ -20,7 +21,41 @@ const content = ` `; -function ControlPanel() { +function Sidebar() { + return ( + + ); +} + +function ExportPanel() { const { editor } = useCurrentEditor(); const [html, setHtml] = useState(''); const [exporting, setExporting] = useState(false); @@ -61,7 +96,7 @@ export function FullEmailBuilder() { return (
- - - - - +
+
+ + + + +
+ +
+
); From e3a77493088442bb178a2a49fa5c2db3993aae5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Melo?= Date: Mon, 6 Apr 2026 17:14:28 -0300 Subject: [PATCH 5/7] =?UTF-8?q?fix(editor):=20fix=20full=20email=20builder?= =?UTF-8?q?=20layout=20=E2=80=94=20sidebar=20fills=20height?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/editor/examples/src/examples/full-email-builder.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/editor/examples/src/examples/full-email-builder.tsx b/packages/editor/examples/src/examples/full-email-builder.tsx index e27d71ffa1..e492d1fcec 100644 --- a/packages/editor/examples/src/examples/full-email-builder.tsx +++ b/packages/editor/examples/src/examples/full-email-builder.tsx @@ -123,7 +123,7 @@ export function FullEmailBuilder() { -
+
+
- ); From c3f29d84dcdca85ff1b8f2ec66a4d0decd268351 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Melo?= Date: Mon, 6 Apr 2026 17:15:41 -0300 Subject: [PATCH 6/7] fix(editor): use fixed height for full email builder editor+sidebar layout --- packages/editor/examples/src/examples/full-email-builder.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/editor/examples/src/examples/full-email-builder.tsx b/packages/editor/examples/src/examples/full-email-builder.tsx index e492d1fcec..7822a81bf3 100644 --- a/packages/editor/examples/src/examples/full-email-builder.tsx +++ b/packages/editor/examples/src/examples/full-email-builder.tsx @@ -123,7 +123,7 @@ export function FullEmailBuilder() {
-
+
Date: Mon, 6 Apr 2026 17:20:12 -0300 Subject: [PATCH 7/7] fix(editor): fix full email builder layout using EditorContext.Provider --- .../src/examples/full-email-builder.tsx | 24 +++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/packages/editor/examples/src/examples/full-email-builder.tsx b/packages/editor/examples/src/examples/full-email-builder.tsx index 7822a81bf3..a5afb720c6 100644 --- a/packages/editor/examples/src/examples/full-email-builder.tsx +++ b/packages/editor/examples/src/examples/full-email-builder.tsx @@ -7,7 +7,12 @@ import { Inspector, SlashCommand, } from '@react-email/editor/ui'; -import { EditorProvider, useCurrentEditor } from '@tiptap/react'; +import { + EditorContent, + EditorContext, + useCurrentEditor, + useEditor, +} from '@tiptap/react'; import { useState } from 'react'; import { ExampleShell } from '../example-shell'; @@ -92,6 +97,13 @@ function ExportPanel() { export function FullEmailBuilder() { const [theme, setTheme] = useState('basic'); const extensions = [StarterKit, EmailTheming.configure({ theme })]; + const editor = useEditor( + { + extensions, + content, + }, + [theme], + ); return (
- -
+ +
+
- + ); }