Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 13 additions & 36 deletions packages/editor/examples/src/app.tsx
Original file line number Diff line number Diff line change
@@ -1,44 +1,21 @@
import { useState } from 'react';
import { BasicEditor } from './examples/basic-editor';
import { ColumnLayouts } from './examples/column-layouts';
import { CustomBubbleMenus } from './examples/custom-bubble-menus';
import { SlashCommands } from './examples/slash-commands';
import { sections } from './examples';
import { Sidebar } from './sidebar';
import { useHashRoute } from './use-hash-route';

const examples = [
{ id: 'basic', label: 'Basic editor', component: BasicEditor },
{
id: 'bubble-menus',
label: 'Custom bubble menus',
component: CustomBubbleMenus,
},
{ id: 'columns', label: 'Column layouts', component: ColumnLayouts },
{ id: 'slash', label: 'Slash commands', component: SlashCommands },
] as const;
const allExamples = sections.flatMap((s) => s.examples);

export function App() {
const [active, setActive] = useState(examples[0].id);
const ActiveExample = examples.find((e) => e.id === active)!.component;
const [activeId, setActiveId] = useHashRoute(allExamples[0].id);
const ActiveExample =
allExamples.find((e) => e.id === activeId)?.component ??
allExamples[0].component;

return (
<div className="max-w-225 mx-auto p-8 font-sans text-(--re-text)">
<h1 className="text-2xl mb-6">@react-email/editor examples</h1>
<nav className="flex gap-1 mb-8 border-b border-(--re-border)">
{examples.map((example) => (
<button
key={example.id}
onClick={() => setActive(example.id)}
type="button"
className={`px-4 py-2 border-0 bg-transparent cursor-pointer text-sm ${
active === example.id
? 'font-semibold text-(--re-text) border-b-2 border-b-(--re-text)'
: 'font-normal text-(--re-text-muted) border-b-2 border-b-transparent'
}`}
>
{example.label}
</button>
))}
</nav>
<ActiveExample />
<div className="flex h-screen font-sans text-(--re-text)">
<Sidebar sections={sections} activeId={activeId} onSelect={setActiveId} />
<main className="flex-1 overflow-y-auto p-8 max-w-4xl">
<ActiveExample key={activeId} />
</main>
</div>
);
}
21 changes: 21 additions & 0 deletions packages/editor/examples/src/example-shell.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
interface ExampleShellProps {
title: string;
description: string;
children: React.ReactNode;
}

export function ExampleShell({
title,
description,
children,
}: ExampleShellProps) {
return (
<>
<h2 className="text-lg font-semibold text-(--re-text) mb-1">{title}</h2>
<p className="text-sm text-(--re-text-muted) mb-4">{description}</p>
<div className="border border-(--re-border) rounded-xl p-4 min-h-75">
{children}
</div>
</>
);
}
34 changes: 17 additions & 17 deletions packages/editor/examples/src/examples/basic-editor.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { StarterKit } from '@react-email/editor/extensions';
import { BubbleMenu } from '@react-email/editor/ui';
import { EditorProvider } from '@tiptap/react';
import { ExampleShell } from '../example-shell';

const extensions = [StarterKit];

Expand All @@ -12,29 +12,29 @@ const content = {
content: [
{
type: 'text',
text: 'Select this text to see the bubble menu. Try ',
text: 'This is the simplest editor setup — just StarterKit with no UI overlays. Start typing or edit this text.',
},
],
},
{
type: 'paragraph',
content: [
{
type: 'text',
text: 'The StarterKit includes all core extensions: paragraphs, headings, lists, tables, code blocks, and more.',
},
{ type: 'text', marks: [{ type: 'bold' }], text: 'bold' },
{ type: 'text', text: ', ' },
{ type: 'text', marks: [{ type: 'italic' }], text: 'italic' },
{ type: 'text', text: ', and other formatting options.' },
],
},
],
};

export function BasicEditor() {
return (
<div>
<p className="text-sm text-(--re-text-muted) mb-4">
Minimal setup with coreExtensions and all default bubble menus. Select
text to see the bubble menu.
</p>
<div className="border border-(--re-border) rounded-xl p-4 min-h-75">
<EditorProvider extensions={extensions} content={content}>
<BubbleMenu.Default />
</EditorProvider>
</div>
</div>
<ExampleShell
title="Basic Editor"
description="Minimal setup with StarterKit and no UI overlays."
>
<EditorProvider extensions={extensions} content={content} />
</ExampleShell>
);
}
38 changes: 38 additions & 0 deletions packages/editor/examples/src/examples/bubble-menu.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { StarterKit } from '@react-email/editor/extensions';
import { BubbleMenu } from '@react-email/editor/ui';
import { EditorProvider } from '@tiptap/react';
import { ExampleShell } from '../example-shell';

const extensions = [StarterKit];

const content = {
type: 'doc',
content: [
{
type: 'paragraph',
content: [
{
type: 'text',
text: 'Select this text to see the bubble menu. Try ',
},
{ type: 'text', marks: [{ type: 'bold' }], text: 'bold' },
{ type: 'text', text: ', ' },
{ type: 'text', marks: [{ type: 'italic' }], text: 'italic' },
{ type: 'text', text: ', and other formatting options.' },
],
},
],
};

export function BubbleMenuExample() {
return (
<ExampleShell
title="Bubble Menu"
description="Select text to see the default bubble menu with formatting options."
>
<EditorProvider extensions={extensions} content={content}>
<BubbleMenu.Default />
</EditorProvider>
</ExampleShell>
);
}
83 changes: 30 additions & 53 deletions packages/editor/examples/src/examples/column-layouts.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,9 @@
import {
ColumnsColumn,
FourColumns,
StarterKit,
ThreeColumns,
TwoColumns,
} from '@react-email/editor/extensions';
import { StarterKit } from '@react-email/editor/extensions';
import { EditorProvider, useCurrentEditor } from '@tiptap/react';
import { Columns2, Columns3, Columns4 } from 'lucide-react';
import { ExampleShell } from '../example-shell';

const columnExtensions = [
StarterKit,
TwoColumns,
ThreeColumns,
FourColumns,
ColumnsColumn,
];
const extensions = [StarterKit];

const content = {
type: 'doc',
Expand All @@ -31,6 +20,27 @@ const content = {
],
};

function ToolbarButton({
label,
icon,
onClick,
}: {
label: string;
icon?: React.ReactNode;
onClick: () => void;
}) {
return (
<button
type="button"
onClick={onClick}
className="flex items-center gap-1.5 px-3 py-1.5 border border-(--re-border) rounded-lg bg-(--re-bg) text-(--re-text) cursor-pointer text-[0.8125rem] hover:bg-(--re-hover)"
>
{icon}
{label}
</button>
);
}

function Toolbar() {
const { editor } = useCurrentEditor();
if (!editor) return null;
Expand All @@ -52,54 +62,21 @@ function Toolbar() {
icon={<Columns4 size={16} />}
onClick={() => editor.chain().focus().insertColumns(4).run()}
/>
<ToolbarButton
label="Section"
onClick={() => editor.chain().focus().insertSection().run()}
/>
<ToolbarButton
label="Button"
onClick={() => editor.chain().focus().setButton().run()}
/>
</div>
);
}

export function ColumnLayouts() {
return (
<div>
<p className="text-sm text-(--re-text-muted) mb-4">
Insert multi-column layouts using the toolbar buttons.
</p>
<ExampleShell
title="Column Layouts"
description="Insert multi-column layouts using the toolbar buttons."
>
<EditorProvider
extensions={columnExtensions}
extensions={extensions}
content={content}
slotBefore={<Toolbar />}
editorContainerProps={{
className:
'border border-[var(--re-border)] rounded-xl p-4 min-h-[300px]',
}}
/>
</div>
);
}

function ToolbarButton({
label,
icon,
onClick,
}: {
label: string;
icon?: React.ReactNode;
onClick: () => void;
}) {
return (
<button
type="button"
onClick={onClick}
className="flex items-center gap-1.5 px-3 py-1.5 border border-(--re-border) rounded-lg bg-(--re-bg) text-(--re-text) cursor-pointer text-[0.8125rem] hover:bg-(--re-hover)"
>
{icon}
{label}
</button>
</ExampleShell>
);
}
45 changes: 45 additions & 0 deletions packages/editor/examples/src/examples/custom-bubble-menu.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { StarterKit } from '@react-email/editor/extensions';
import { BubbleMenu } from '@react-email/editor/ui';
import { EditorProvider } from '@tiptap/react';
import { ExampleShell } from '../example-shell';

const extensions = [StarterKit];

const content = {
type: 'doc',
content: [
{
type: 'paragraph',
content: [
{
type: 'text',
text: 'Select text to see a custom bubble menu built from primitives — only bold, italic, underline, and alignment.',
},
],
},
],
};

export function CustomBubbleMenu() {
return (
<ExampleShell
title="Custom Bubble Menu"
description="Building menus from primitives instead of using BubbleMenu.Default."
>
<EditorProvider extensions={extensions} content={content}>
<BubbleMenu.Root>
<BubbleMenu.ItemGroup>
<BubbleMenu.Bold />
<BubbleMenu.Italic />
<BubbleMenu.Underline />
</BubbleMenu.ItemGroup>
<BubbleMenu.ItemGroup>
<BubbleMenu.AlignLeft />
<BubbleMenu.AlignCenter />
<BubbleMenu.AlignRight />
</BubbleMenu.ItemGroup>
</BubbleMenu.Root>
</EditorProvider>
</ExampleShell>
);
}
48 changes: 0 additions & 48 deletions packages/editor/examples/src/examples/custom-bubble-menus.tsx

This file was deleted.

Loading
Loading