Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
82 commits
Select commit Hold shift + click to select a range
9b4b728
chore: update readme
gjc14 Jun 21, 2025
91e3a02
feat: init react router v7
gjc14 Jun 14, 2025
7db5c16
feat: shadcn ui components
gjc14 Jun 14, 2025
020a4f2
feat: main layout
gjc14 Jun 15, 2025
14c6509
feat: use mobile
gjc14 Jun 15, 2025
8d1cc79
feat: side bars
gjc14 Jun 15, 2025
51247d1
style: button cursor pointer
gjc14 Jun 15, 2025
dfe4b67
chore: run prettier
gjc14 Jun 15, 2025
4ce3308
feat: add email builder blocks
gjc14 Jun 17, 2025
93afbcb
style: dynamically render canva with tab selected
gjc14 Jun 20, 2025
9543d9e
feat: copied template
gjc14 Jun 20, 2025
3bf2753
feat: rrv7 editor core, blocks and context
gjc14 Jun 21, 2025
1ff6082
refactor: sample -> template sidebar
gjc14 Jun 21, 2025
6350e4c
feat: install mui zustand emotion
gjc14 Jun 21, 2025
53057a8
refactor: using zustand state
gjc14 Jun 21, 2025
ded0952
feat: implement download and upload json
gjc14 Jun 21, 2025
05aff4a
refactor: toggle screen, dropdown menu and canvas
gjc14 Jun 21, 2025
6061bdb
style: canva area parent wrapper with overflow
gjc14 Jun 21, 2025
76d47f7
feat: display html and json canva
gjc14 Jun 21, 2025
1d52266
refactor: moving header to a folder
gjc14 Jun 22, 2025
3a691d7
style: main tab style
gjc14 Jun 22, 2025
04b8b83
fix: accessibility for side bars
gjc14 Jun 22, 2025
2daa62d
style: json / html canva style to className
gjc14 Jun 22, 2025
6937ff9
feat: editorCanva and preview canva
gjc14 Jun 22, 2025
8d9a687
chore: rename lib/utils.ts
gjc14 Jun 22, 2025
50cc7f5
feat: implement template selection
gjc14 Jun 22, 2025
71fd5d2
style: side bar and header
gjc14 Jun 22, 2025
51cdf07
refactor: inspector sidebar to a independent folder
gjc14 Jun 22, 2025
516b6bf
feat: inspectPanel setup
gjc14 Jun 22, 2025
bafff77
feat: added command and dialog ui
gjc14 Jun 22, 2025
563c20a
feat: font family input helper
gjc14 Jun 22, 2025
0da204f
feat: color input helper
gjc14 Jun 22, 2025
a87acd7
feat: stylePanel functionalities
gjc14 Jun 22, 2025
f53874e
fix: editorCanva using <EditorBlock> and canva layout
gjc14 Jun 22, 2025
5361d63
refactor: remove duplicate files
gjc14 Jun 22, 2025
460cc5f
feat: implement share functionality
gjc14 Jun 22, 2025
be113cf
feat: mobile screen
gjc14 Jun 22, 2025
6f87b2a
refactor: rename AddBlockMenu folder
gjc14 Jun 22, 2025
3a0dd36
refactor(add block): placeholder button with new style
gjc14 Jun 22, 2025
b0194ab
style: block menu type buttons
gjc14 Jun 23, 2025
47d9057
style: divider button style
gjc14 Jun 23, 2025
3619309
refactor: block menu and buttons using popover
gjc14 Jun 23, 2025
e9eaa0f
style: EditorBlockWrapper
gjc14 Jun 23, 2025
06e472f
refactor: rename tune menu to toolkit and removed ReaderBlockWrapper
gjc14 Jun 23, 2025
bcdb086
style: bubble menu using radix
gjc14 Jun 23, 2025
de03552
style: header and sidebar
gjc14 Jun 23, 2025
121ec52
fix: style border matches border radius
gjc14 Jun 23, 2025
1c7dc54
feat: generate slug util
gjc14 Jun 23, 2025
257f205
feat: slider input helper
gjc14 Jun 23, 2025
12ad613
feat: panel wrapper helper
gjc14 Jun 23, 2025
d60a3fb
feat: radio input helper
gjc14 Jun 23, 2025
f4147d9
feat(panel): avatar panel WIP
gjc14 Jun 23, 2025
7732803
feat: text input helper
gjc14 Jun 23, 2025
190968d
style: slider input take in icon and hide label
gjc14 Jun 23, 2025
b81d941
feat: style input helper
gjc14 Jun 23, 2025
9b08411
refactor: AvatarPanel
gjc14 Jun 23, 2025
3105d1b
feat: input helper examples
gjc14 Jun 23, 2025
8316b76
feat: style input all features
gjc14 Jun 23, 2025
ba0e7f1
feat: ButtonPanel
gjc14 Jun 23, 2025
395aae4
refactor: radioInput -> toggleGroupInput
gjc14 Jun 23, 2025
c0b72ba
feat: radix icon
gjc14 Jun 23, 2025
e00a508
feat: ContainerPanel
gjc14 Jun 23, 2025
5c4f495
feat: DividerPanel
gjc14 Jun 23, 2025
1263440
feat: textarea input
gjc14 Jun 23, 2025
1a3a921
feat: HtmlPanel
gjc14 Jun 23, 2025
c398447
feat: SpacerPanel
gjc14 Jun 23, 2025
6af62f0
feat: switch component
gjc14 Jun 23, 2025
d3f9472
fix: textareaInput returns string
gjc14 Jun 23, 2025
176767e
feat: TextPanel
gjc14 Jun 23, 2025
0c64519
feat: HeadingPanel
gjc14 Jun 23, 2025
0a0f2c8
fix: heading and button fontweight fallback to bold
gjc14 Jun 23, 2025
78ba7de
feat: boolean input
gjc14 Jun 23, 2025
9cc7bc8
refactor: text panel using BooleanInput
gjc14 Jun 23, 2025
7b41283
feat: ColumsPanel
gjc14 Jun 23, 2025
5b181e6
feat: ImagePanel
gjc14 Jun 23, 2025
670d872
feat: save button
gjc14 Jun 23, 2025
6b31b82
feat: added dark mode
gjc14 Jun 23, 2025
30762c6
style: BubbleMenu tooltip direction
gjc14 Jun 23, 2025
d2afda3
Update README.md
gjc14 Jun 23, 2025
1e19028
feat: zod installation
gjc14 Jun 23, 2025
521f71d
Remove README.md personal infos
gjc14 Jun 23, 2025
05eecda
fix: mobile screen height
gjc14 Jun 23, 2025
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
4 changes: 4 additions & 0 deletions packages/react-router-v7-example/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.react-router
build
node_modules
README.md
6 changes: 6 additions & 0 deletions packages/react-router-v7-example/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
.DS_Store
/node_modules/

# React Router
/.react-router/
/build/
22 changes: 22 additions & 0 deletions packages/react-router-v7-example/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
FROM node:20-alpine AS development-dependencies-env
COPY . /app
WORKDIR /app
RUN npm ci

FROM node:20-alpine AS production-dependencies-env
COPY ./package.json package-lock.json /app/
WORKDIR /app
RUN npm ci --omit=dev

FROM node:20-alpine AS build-env
COPY . /app/
COPY --from=development-dependencies-env /app/node_modules /app/node_modules
WORKDIR /app
RUN npm run build

FROM node:20-alpine
COPY ./package.json package-lock.json /app/
COPY --from=production-dependencies-env /app/node_modules /app/node_modules
COPY --from=build-env /app/build /app/build
WORKDIR /app
CMD ["npm", "run", "start"]
Empty file.
138 changes: 138 additions & 0 deletions packages/react-router-v7-example/app/app.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
@import 'tailwindcss';
@import 'tw-animate-css';

@custom-variant dark (&:is(.dark *));

@theme {
--font-sans:
'Inter', ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol',
'Noto Color Emoji';
}

html,
body {
@apply bg-white dark:bg-gray-950;

@media (prefers-color-scheme: dark) {
color-scheme: dark;
}
}

@theme inline {
--radius-sm: calc(var(--radius) - 4px);
--radius-md: calc(var(--radius) - 2px);
--radius-lg: var(--radius);
--radius-xl: calc(var(--radius) + 4px);
--color-background: var(--background);
--color-foreground: var(--foreground);
--color-card: var(--card);
--color-card-foreground: var(--card-foreground);
--color-popover: var(--popover);
--color-popover-foreground: var(--popover-foreground);
--color-primary: var(--primary);
--color-primary-foreground: var(--primary-foreground);
--color-secondary: var(--secondary);
--color-secondary-foreground: var(--secondary-foreground);
--color-muted: var(--muted);
--color-muted-foreground: var(--muted-foreground);
--color-accent: var(--accent);
--color-accent-foreground: var(--accent-foreground);
--color-destructive: var(--destructive);
--color-border: var(--border);
--color-input: var(--input);
--color-ring: var(--ring);
--color-chart-1: var(--chart-1);
--color-chart-2: var(--chart-2);
--color-chart-3: var(--chart-3);
--color-chart-4: var(--chart-4);
--color-chart-5: var(--chart-5);
--color-sidebar: var(--sidebar);
--color-sidebar-foreground: var(--sidebar-foreground);
--color-sidebar-primary: var(--sidebar-primary);
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
--color-sidebar-accent: var(--sidebar-accent);
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
--color-sidebar-border: var(--sidebar-border);
--color-sidebar-ring: var(--sidebar-ring);
}

:root {
--radius: 0.625rem;
--background: oklch(1 0 0);
--foreground: oklch(0.141 0.005 285.823);
--card: oklch(1 0 0);
--card-foreground: oklch(0.141 0.005 285.823);
--popover: oklch(1 0 0);
--popover-foreground: oklch(0.141 0.005 285.823);
--primary: oklch(0.21 0.006 285.885);
--primary-foreground: oklch(0.985 0 0);
--secondary: oklch(0.967 0.001 286.375);
--secondary-foreground: oklch(0.21 0.006 285.885);
--muted: oklch(0.967 0.001 286.375);
--muted-foreground: oklch(0.552 0.016 285.938);
--accent: oklch(0.967 0.001 286.375);
--accent-foreground: oklch(0.21 0.006 285.885);
--destructive: oklch(0.577 0.245 27.325);
--border: oklch(0.92 0.004 286.32);
--input: oklch(0.92 0.004 286.32);
--ring: oklch(0.705 0.015 286.067);
--chart-1: oklch(0.646 0.222 41.116);
--chart-2: oklch(0.6 0.118 184.704);
--chart-3: oklch(0.398 0.07 227.392);
--chart-4: oklch(0.828 0.189 84.429);
--chart-5: oklch(0.769 0.188 70.08);
--sidebar: oklch(0.985 0 0);
--sidebar-foreground: oklch(0.141 0.005 285.823);
--sidebar-primary: oklch(0.21 0.006 285.885);
--sidebar-primary-foreground: oklch(0.985 0 0);
--sidebar-accent: oklch(0.967 0.001 286.375);
--sidebar-accent-foreground: oklch(0.21 0.006 285.885);
--sidebar-border: oklch(0.92 0.004 286.32);
--sidebar-ring: oklch(0.705 0.015 286.067);
}

.dark {
--background: oklch(0.141 0.005 285.823);
--foreground: oklch(0.985 0 0);
--card: oklch(0.21 0.006 285.885);
--card-foreground: oklch(0.985 0 0);
--popover: oklch(0.21 0.006 285.885);
--popover-foreground: oklch(0.985 0 0);
--primary: oklch(0.92 0.004 286.32);
--primary-foreground: oklch(0.21 0.006 285.885);
--secondary: oklch(0.274 0.006 286.033);
--secondary-foreground: oklch(0.985 0 0);
--muted: oklch(0.274 0.006 286.033);
--muted-foreground: oklch(0.705 0.015 286.067);
--accent: oklch(0.274 0.006 286.033);
--accent-foreground: oklch(0.985 0 0);
--destructive: oklch(0.704 0.191 22.216);
--border: oklch(1 0 0 / 10%);
--input: oklch(1 0 0 / 15%);
--ring: oklch(0.552 0.016 285.938);
--chart-1: oklch(0.488 0.243 264.376);
--chart-2: oklch(0.696 0.17 162.48);
--chart-3: oklch(0.769 0.188 70.08);
--chart-4: oklch(0.627 0.265 303.9);
--chart-5: oklch(0.645 0.246 16.439);
--sidebar: oklch(0.21 0.006 285.885);
--sidebar-foreground: oklch(0.985 0 0);
--sidebar-primary: oklch(0.488 0.243 264.376);
--sidebar-primary-foreground: oklch(0.985 0 0);
--sidebar-accent: oklch(0.274 0.006 286.033);
--sidebar-accent-foreground: oklch(0.985 0 0);
--sidebar-border: oklch(1 0 0 / 10%);
--sidebar-ring: oklch(0.552 0.016 285.938);
}

@layer base {
* {
@apply border-border outline-ring/50;
}
body {
@apply bg-background text-foreground;
}
table {
@apply border-separate;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { Sheet, SheetContent, SheetDescription, SheetTitle } from '~/components/ui/sheet';
import {
setSidebarTab,
toggleInspectorSidebarOpen,
useInspectorSidebarOpen,
useSelectedSidebarTab,
} from '~/context/editor';
import { useIsMobile } from '~/hooks/use-mobile';
import { InspectPanel } from './inspectPanel';
import { StylesPanel } from './stylesPanel';

const tabs = [
{ id: 'styles', label: 'Styles' },
{ id: 'block-configuration', label: 'Inspect' },
] as const;

export function InspectorSidebar() {
const sidebarTab = useSelectedSidebarTab();
const isMobile = useIsMobile();
const isOpen = useInspectorSidebarOpen();

const renderCurrentSidebarPanel = () => {
switch (sidebarTab) {
case 'block-configuration':
return <InspectPanel />;
case 'styles':
return <StylesPanel />;
}
};

const sidebarContent = (
<>
<div className="h-12 border-b px-4 flex items-end">
<div className="flex w-full h-full">
{tabs.map((tab) => (
<button
key={tab.id}
onClick={() => setSidebarTab(tab.id)}
className={`flex items-center justify-center gap-2 px-3 py-2 text-sm font-medium transition-colors border-b cursor-pointer ${
sidebarTab === tab.id
? 'text-foreground border-foreground'
: 'text-muted-foreground border-transparent hover:text-foreground'
}`}
>
{tab.label}
</button>
))}
</div>
</div>
<div className="flex-1 overflow-auto p-4">{renderCurrentSidebarPanel()}</div>
</>
);

if (isMobile) {
return (
<Sheet open={isOpen} onOpenChange={toggleInspectorSidebarOpen}>
<SheetContent side="right" className="w-80 p-0 flex flex-col">
<SheetTitle hidden></SheetTitle>
<SheetDescription hidden></SheetDescription>
{sidebarContent}
</SheetContent>
</Sheet>
);
}

return (
<div
className={`transition-all border-l bg-background flex flex-col ${isOpen ? 'w-80' : 'w-0'}`}
aria-hidden={!isOpen}
inert={!isOpen}
>
{sidebarContent}
</div>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { setDocument, useDocument, useSelectedBlockId } from '~/context/editor';
import type { TEditorBlock } from '~/documents/editor/core';
import AvatarSidebarPanel from './panels/AvatarSidebarPanel';
import ButtonSidebarPanel from './panels/ButtonSidebarPanel';
import ColumnsContainerSidebarPanel from './panels/ColumnsContainerSidebarPanel';
import ContainerSidebarPanel from './panels/ContainerSidebarPanel';
import DividerSidebarPanel from './panels/DividerSidebarPanel';
import HeadingSidebarPanel from './panels/HeadingSidebarPanel';
import HtmlSidebarPanel from './panels/HtmlSidebarPanel';
import ImageSidebarPanel from './panels/ImageSidebarPanel';
import SpacerSidebarPanel from './panels/SpacerSidebarPanel';
import TextSidebarPanel from './panels/TextSidebarPanel';

export function InspectPanel() {
const document = useDocument();
const selectedBlockId = useSelectedBlockId();

if (!selectedBlockId) {
return <p className="text-muted-foreground">Click on a block to inspect.</p>;
}
const block = document[selectedBlockId];
if (!block) {
return (
<p className="text-muted-foreground">
Block with id ${selectedBlockId} was not found. Click on a block to reset.
</p>
);
}

const { data, type } = block;

// TypePanel will pass out new data, and setBlock will update the selected block
const setBlock = (conf: TEditorBlock) => setDocument({ [selectedBlockId]: conf });

switch (type) {
case 'Avatar':
return <AvatarSidebarPanel key={selectedBlockId} data={data} setData={(data) => setBlock({ type, data })} />;
case 'Button':
return <ButtonSidebarPanel key={selectedBlockId} data={data} setData={(data) => setBlock({ type, data })} />;
case 'ColumnsContainer':
return (
<ColumnsContainerSidebarPanel key={selectedBlockId} data={data} setData={(data) => setBlock({ type, data })} />
);
case 'Container':
return <ContainerSidebarPanel key={selectedBlockId} data={data} setData={(data) => setBlock({ type, data })} />;
case 'Divider':
return <DividerSidebarPanel key={selectedBlockId} data={data} setData={(data) => setBlock({ type, data })} />;
case 'Heading':
return <HeadingSidebarPanel key={selectedBlockId} data={data} setData={(data) => setBlock({ type, data })} />;
case 'Html':
return <HtmlSidebarPanel key={selectedBlockId} data={data} setData={(data) => setBlock({ type, data })} />;
case 'Image':
return <ImageSidebarPanel key={selectedBlockId} data={data} setData={(data) => setBlock({ type, data })} />;
case 'Spacer':
return <SpacerSidebarPanel key={selectedBlockId} data={data} setData={(data) => setBlock({ type, data })} />;
case 'Text':
return <TextSidebarPanel key={selectedBlockId} data={data} setData={(data) => setBlock({ type, data })} />;
default:
return (
<div className="space-y-3">
<p className="bg-destructive text-xs text-white px-2 py-1.5">
block type <code className="font-mono">{type}</code> is not supported yet.
</p>
<pre className="text-xs">{JSON.stringify(block, null, ' ')}</pre>;
</div>
);
}
}
Loading