Skip to content

Commit 83728ce

Browse files
committed
feat: Refactor WYSIWYG Editor UI
1 parent 7d98889 commit 83728ce

14 files changed

Lines changed: 169 additions & 117 deletions

src/app/page.tsx

Lines changed: 41 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,10 @@ import { useIsMobile } from "@/hooks/use-mobile";
3636
import SocialLinks from "@/components/form-builder/sidebar/socialLinks";
3737
import { OpenJsonDialog } from "@/components/form-builder/dialogs/open-json-dialog";
3838
import { useForm } from "react-hook-form";
39-
import { getGridRows, updateColSpans } from "@/lib/utils";
39+
import { cn, getGridRows, updateColSpans } from "@/lib/utils";
4040
import { RenderEditorComponent } from "@/components/form-builder/helpers/render-editor-component";
4141
import { FormComponentModel } from "@/models/FormComponent";
42+
import { EditorToolbar } from "@/components/form-builder/form-components/wysiwyg/editor-toolbar";
4243

4344
export default function FormBuilderPage() {
4445
const isMobile = useIsMobile();
@@ -60,7 +61,8 @@ export default function FormBuilderPage() {
6061
code: string;
6162
dependenciesImports: DependenciesImports;
6263
}>({ code: "", dependenciesImports: {} });
63-
const [draggingDOMElement, setDraggingDOMElement] = useState<HTMLElement | null>(null);
64+
const [draggingDOMElement, setDraggingDOMElement] =
65+
useState<HTMLElement | null>(null);
6466
const form = useForm();
6567

6668
// Memoize static values
@@ -85,7 +87,7 @@ export default function FormBuilderPage() {
8587
const moveComponent = useFormBuilderStore((state) => state.moveComponent);
8688
const addComponent = useFormBuilderStore((state) => state.addComponent);
8789
const gridRows = getGridRows(components, viewport);
88-
90+
const editor = useFormBuilderStore((state) => state.editor);
8991
const handleGenerateCode = async () => {
9092
const generatedCode = await generateFormCode(components);
9193
setGeneratedCode(generatedCode);
@@ -99,12 +101,8 @@ export default function FormBuilderPage() {
99101
},
100102
});
101103

102-
103104
// Memoize sensors array
104-
const sensors = useMemo(
105-
() => [pointerSensor],
106-
[pointerSensor]
107-
);
105+
const sensors = useMemo(() => [pointerSensor], [pointerSensor]);
108106

109107
// Memoize drag end handler
110108
const handleDragEnd = (event: any) => {
@@ -223,7 +221,9 @@ export default function FormBuilderPage() {
223221
const handleDragStart = useCallback(
224222
(event: DragStartEvent) => {
225223
selectComponent(null);
226-
const element = document.querySelector(`[data-item-id="${event.active.data.current?.component.id}"]`);
224+
const element = document.querySelector(
225+
`[data-item-id="${event.active.data.current?.component.id}"]`
226+
);
227227
if (element) {
228228
setDraggingDOMElement(element as HTMLElement);
229229
}
@@ -233,7 +233,7 @@ export default function FormBuilderPage() {
233233

234234
return (
235235
<div>
236-
<div className="fixed top-0 w-full flex flex-row gap-2 justify-between bg-white border-b z-10">
236+
<div className="fixed top-0 w-full flex flex-row gap-2 justify-between bg-white border-b z-30">
237237
<div className="flex flex-row gap-2 items-center justify-center md:justify-start p-2 px-4 border-r w-full md:w-[300px]">
238238
<BlocksIcon className="h-6 w-6" strokeWidth={2} />
239239
<h2 className="text-lg font-semibold">
@@ -243,24 +243,35 @@ export default function FormBuilderPage() {
243243
</sup>
244244
</h2>
245245
</div>
246-
<div className="p-2 flex-1 grid-cols-2 xl:grid-cols-3 hidden md:grid">
247-
<div className="hidden xl:block col-span-1">
246+
<div className="p-2 flex-1 grid grid-cols-7 2xl:grid-cols-3">
247+
<div
248+
className={cn("hidden 2xl:block col-span-1", editor && "2xl:hidden")}
249+
>
248250
{process.env.NODE_ENV === "development" && <OpenJsonDialog />}
249251
</div>
250-
<div className="col-span-1 flex xl:justify-center">
251-
<div className=" text-center flex flex-row items-center justify-center gap-1 border rounded-md h-9 px-4">
252-
<div
253-
className="max-w-80 overflow-y-hidden whitespace-nowrap text-sm outline-none scrollbar-hide"
254-
contentEditable
255-
suppressContentEditableWarning
256-
onBlur={(e) => updateFormTitle(e.target.innerText)}
257-
>
258-
{formTitle}
252+
<div className="col-span-5 2xl:col-span-1 2xl:col-start-2 flex 2xl:justify-center">
253+
{editor ? (
254+
<EditorToolbar editor={editor} isEditable={true} />
255+
) : (
256+
<div className="text-center flex flex-row items-center justify-center gap-1 border rounded-md h-9 px-4">
257+
<div
258+
className="max-w-80 overflow-y-hidden whitespace-nowrap text-sm outline-none scrollbar-hide"
259+
contentEditable
260+
suppressContentEditableWarning
261+
onBlur={(e) => updateFormTitle(e.target.innerText)}
262+
>
263+
{formTitle}
264+
</div>
265+
<span className="text-muted-foreground text-xs">.tsx</span>
259266
</div>
260-
<span className="text-muted-foreground text-xs">.tsx</span>
261-
</div>
267+
)}
262268
</div>
263-
<div className="col-span-1 hidden md:flex justify-end gap-4 ">
269+
<div
270+
className={cn(
271+
"col-span-2 2xl:col-span-1 hidden md:flex justify-end gap-4",
272+
editor && ""
273+
)}
274+
>
264275
{process.env.NODE_ENV === "development" && (
265276
<Button
266277
variant="ghost"
@@ -335,7 +346,12 @@ export default function FormBuilderPage() {
335346
<DragOverlay>
336347
{draggingDOMElement && (
337348
<div className="bg-white p-2 rounded-md shadow opacity-80">
338-
<div dangerouslySetInnerHTML={{ __html: draggingDOMElement.innerHTML }} className="max-h-52 overflow-hidden" />
349+
<div
350+
dangerouslySetInnerHTML={{
351+
__html: draggingDOMElement.innerHTML,
352+
}}
353+
className="max-h-52 overflow-hidden"
354+
/>
339355
</div>
340356
)}
341357
</DragOverlay>

src/components/form-builder/canvas/sortable-row.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,8 +128,9 @@ export const RowColumn = ({
128128
colStartClasses,
129129
mode === "editor" && "group/component hover:outline-1 hover:outline-offset-6 hover:outline-primary cursor-pointer",
130130
columnIsDragging && "cursor-grabbing",
131+
selectedComponent && "opacity-30",
131132
selectedComponent?.id === component.id &&
132-
"outline-1 outline-offset-6 outline-primary",
133+
"outline-1 outline-offset-6 outline-primary z-20 opacity-100",
133134
)}
134135
key={component.id}
135136
data-component-id={component.id}

src/components/form-builder/form-components/form-text.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import {
33
generateTWClassesForAllViewports,
44
escapeHtml,
55
} from "@/lib/utils";
6-
import { DesignPropertiesViews, FormRow } from "@/types/form-builder.types";
6+
import { DesignPropertiesViews } from "@/types/form-builder.types";
77
import { FormComponentModel } from "@/models/FormComponent";
88
import { HtmlGroup } from "../sidebar/groups/html-group";
99
import { GridGroup } from "../sidebar/groups/grid-group";

src/components/form-builder/form-components/wysiwyg/editor-toolbar.tsx

Lines changed: 7 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,8 @@ const ToolbarButton = memo(
4343
variant="ghost"
4444
size="sm"
4545
className={cn(
46-
"text-slate-500 hover:text-slate-700",
47-
isActive ? "bg-slate-100 text-slate-700" : "bg-white"
46+
"text-muted-foreground hover:text-primary",
47+
isActive ? "bg-muted text-primary" : "bg-white"
4848
)}
4949
>
5050
{children}
@@ -84,20 +84,19 @@ const ToolbarDropdownMenu = memo(
8484
<div
8585
className={cn(
8686
buttonVariants({ variant: "ghost", size: "sm" }),
87-
activeItem ? "bg-slate-100" : "bg-white"
87+
activeItem ? "bg-muted" : "bg-white"
8888
)}
8989
>
9090
<Icon
9191
name={
9292
(activeItem?.type === "option" && activeItem.icon) || defaultIcon
9393
}
94-
className={activeItem ? "text-slate-700" : "text-slate-500"}
94+
className={"text-primary"}
9595
/>
96-
<Icon name="ChevronDown" className="w-2 h-2" />
96+
<Icon name="ChevronDown" className="w-2 h-2 text-primary" />
9797
</div>
9898
</DropdownMenuTrigger>
9999
<DropdownMenuContent portalProps={{ container: menuRef?.current }}>
100-
<div>
101100
{options.map((option) => {
102101
return (
103102
<div key={option.id}>
@@ -129,7 +128,6 @@ const ToolbarDropdownMenu = memo(
129128
</div>
130129
);
131130
})}
132-
</div>
133131
</DropdownMenuContent>
134132
</DropdownMenu>
135133
);
@@ -156,14 +154,9 @@ export function EditorToolbar({
156154
const menuRef = useRef<HTMLDivElement>(null);
157155

158156
return (
159-
<BubbleMenu
160-
editor={editor}
161-
tippyOptions={{ duration: 100, maxWidth: "none" }}
162-
updateDelay={0}
163-
className={cn(isEditable ? "" : "hidden")}
164-
>
157+
165158
<div
166-
className="flex gap-2 p-2 bg-white rounded-md shadow-sm relative items-center"
159+
className="flex gap-2 relative items-center z-20"
167160
ref={menuRef}
168161
>
169162
<ToolbarDropdownMenu options={options} menuRef={menuRef} />
@@ -207,6 +200,5 @@ export function EditorToolbar({
207200
<Quote />
208201
</ToolbarButton>
209202
</div>
210-
</BubbleMenu>
211203
);
212204
}

src/components/form-builder/form-components/wysiwyg/form-wysiwyg-editor.tsx

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import TextAlign from '@tiptap/extension-text-align'
99
import "./form-wysiwyg-editor.css";
1010
import { EditorToolbar } from "./editor-toolbar";
1111
import customClass from "./extensions/textCustomStyle";
12+
import { useFormBuilderStore } from "@/stores/form-builder-store";
1213
interface FormWysiwygEditorProps {
1314
isEditable?: boolean;
1415
value: string;
@@ -98,6 +99,8 @@ export const FormWysiwygEditor: React.FC<FormWysiwygEditorProps> = memo(
9899
[]
99100
);
100101

102+
const setEditor = useFormBuilderStore((state) => state.setEditor);
103+
101104
const editor = useEditor({
102105
editable: isEditable,
103106
immediatelyRender: true,
@@ -106,14 +109,16 @@ export const FormWysiwygEditor: React.FC<FormWysiwygEditorProps> = memo(
106109
onUpdate: ({ editor }) => {
107110
onChange(editor.getHTML());
108111
},
109-
onFocus: () => {
112+
onFocus: ({ editor }) => {
110113
onFocus?.();
114+
setEditor(editor);
111115
},
112-
onBlur: () => {
116+
onBlur: ({ editor }) => {
113117
onBlur?.();
114118
},
115119
}, [isEditable]);
116120

121+
117122
// Only update content when value prop changes and it's different from our local content
118123

119124
if (!editor) {
@@ -122,7 +127,6 @@ export const FormWysiwygEditor: React.FC<FormWysiwygEditorProps> = memo(
122127

123128
return (
124129
<>
125-
<EditorToolbar editor={editor} isEditable={isEditable} />
126130
<EditorContent editor={editor} />
127131
</>
128132
);

src/components/form-builder/helpers/generate-react-code.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { FormRow } from "@/types/form-builder.types";
21
import { FormComponentModel } from "@/models/FormComponent";
32
import { getComponentViews } from "@/config/available-components";
43
import { cn, generateTWClassesForAllViewports } from "@/lib/utils";

src/components/form-builder/helpers/render-editor-component.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ export function RenderEditorComponent({ form, component }: FormComponentProps) {
5454
)}
5555
/>
5656
) : (
57-
<div className={cn("relative flex flex-col h-full", (selectedComponent?.id === component.id && mode === "editor") && "cursor-text")} key={component.id} data-item-id={component.id}>
57+
<div className={cn("relative flex flex-col h-full", (selectedComponent?.id === component.id && mode === "editor") && "cursor-text bg-white")} key={component.id} data-item-id={component.id}>
5858
<FormWysiwygEditor
5959
value={component.content || ""}
6060
onChange={(content) => {

src/components/form-builder/mainCanvas.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ export function MainCanvas() {
4343
const components = useFormBuilderStore((state) => state.components);
4444

4545
return (
46-
<div className="flex gap-4 h-full flex-col 3xl:flex-row">
46+
<div className="flex gap-4 flex-col 3xl:flex-row">
4747
<div
4848
className={`h-full w-full`}
4949
onClick={() => {

src/components/form-builder/sidebar/sidebarLeft.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ export function SidebarLeft() {
118118
};
119119

120120
return (
121-
<Sidebar className="bg-white top-14 bottom-14">
121+
<Sidebar className="bg-white top-13 bottom-14 z-20">
122122
<div className="flex flex-col h-[calc(100%-56px)]">
123123
<SidebarContent className="gap-0">
124124
{componentGroups.map((group) => (

src/components/form-builder/sidebar/sidebarRight.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ export function SidebarRight() {
9292
}
9393

9494
return (
95-
<Sidebar side="right" className="border-l top-14">
95+
<Sidebar side="right" className="border-l top-13">
9696
<SidebarContent>{sidebarContent}</SidebarContent>
9797
</Sidebar>
9898
);

0 commit comments

Comments
 (0)