WanderHalley commited on
Commit
fe9d17e
·
1 Parent(s): 21a54f1

feat: add state for form controls and iframe management

Browse files

Introduce state variables for HTML input attributes and iframe configuration to support advanced element editing and persistent scroll positioning during reloads.

Files changed (1) hide show
  1. src/App.tsx +273 -24
src/App.tsx CHANGED
@@ -63,6 +63,19 @@ export default function App() {
63
  const [htmlId, setHtmlId] = useState("");
64
  const [htmlTitle, setHtmlTitle] = useState("");
65
 
 
 
 
 
 
 
 
 
 
 
 
 
 
66
  // Styling Editor fields
67
  const [fontSize, setFontSize] = useState("16");
68
  const [fontWeight, setFontWeight] = useState("normal");
@@ -108,6 +121,12 @@ export default function App() {
108
  // Component references
109
  const canvasParentRef = useRef<HTMLDivElement>(null);
110
  const canvasContentRef = useRef<HTMLDivElement>(null);
 
 
 
 
 
 
111
 
112
  // Initialize with landing page template on first run
113
  useEffect(() => {
@@ -122,20 +141,38 @@ export default function App() {
122
  pushHistoryState(defaultTemplate.html, "Template SaaS Carregado");
123
  }, []);
124
 
 
 
 
 
 
 
125
  // Sync canvas nodes inside our editing sandbox relative container when content changes
126
  const visualHtml = useMemo(() => {
127
  if (!htmlCode) return "";
128
  return injectEditorIds(htmlCode);
129
  }, [htmlCode]);
130
 
 
 
 
 
 
131
  // Inject unique data-el-id to each node recursively (stable, deterministic naming system)
132
  function injectEditorIds(htmlStr: string): string {
133
  const parser = new DOMParser();
134
  const doc = parser.parseFromString(htmlStr, "text/html");
135
  let index = 0;
136
 
 
 
 
 
 
 
 
137
  const traverse = (node: Element) => {
138
- if (node.tagName && node.tagName !== "BODY" && node.tagName !== "HTML") {
139
  if (!node.getAttribute("data-el-id")) {
140
  node.setAttribute("data-el-id", `el_node_${index++}`);
141
  } else {
@@ -155,9 +192,12 @@ export default function App() {
155
 
156
  if (doc.body) {
157
  Array.from(doc.body.children).forEach(traverse);
158
- return doc.body.innerHTML;
159
  }
160
- return htmlStr;
 
 
 
 
161
  }
162
 
163
  // Remove editor identifiers before storing or exporting
@@ -170,19 +210,31 @@ export default function App() {
170
  Array.from(node.children).forEach(traverse);
171
  };
172
 
173
- if (doc.body) {
174
- Array.from(doc.body.children).forEach(traverse);
175
- return doc.body.innerHTML;
 
 
 
 
 
 
 
 
 
176
  }
177
- return htmlStr;
178
  }
179
 
180
- // Handle Canvas Operations directly
181
  const editDOM = (currentHtml: string, action: (doc: Document) => void): string => {
182
  const parser = new DOMParser();
183
  const doc = parser.parseFromString(currentHtml, "text/html");
184
  action(doc);
185
- return doc.body.innerHTML;
 
 
 
186
  };
187
 
188
  const pushHistoryState = (newHtml: string, description: string = "Edição") => {
@@ -321,8 +373,28 @@ export default function App() {
321
  setImageAlt("");
322
  }
323
 
324
- // Style Parsing helper, stripping typicalpx suffixes
325
- const comp = window.getComputedStyle(el);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
326
  const inline = el.style;
327
 
328
  setTextColor(rgbToHex(inline.color || comp.color) || "#000000");
@@ -385,23 +457,31 @@ export default function App() {
385
  // Run selection outlining effect calculations relative to canvas wrapper parent
386
  useEffect(() => {
387
  const updateOutlines = () => {
388
- if (viewMode !== "visual" || !canvasParentRef.current) {
389
  setSelectedRect(null);
390
  setHoverRect(null);
391
  return;
392
  }
393
 
394
  const canvasParent = canvasParentRef.current;
 
 
 
 
 
 
395
 
396
  // Update Selected Element bounding card
397
  if (selectedId) {
398
- const selectedEl = canvasParent.querySelector(`[data-el-id="${selectedId}"]`);
 
 
 
399
  if (selectedEl) {
400
  const rect = selectedEl.getBoundingClientRect();
401
- const parentRect = canvasParent.getBoundingClientRect();
402
  setSelectedRect({
403
- top: rect.top - parentRect.top + canvasParent.scrollTop,
404
- left: rect.left - parentRect.left + canvasParent.scrollLeft,
405
  width: rect.width,
406
  height: rect.height,
407
  tagName: selectedEl.tagName.toLowerCase()
@@ -415,13 +495,15 @@ export default function App() {
415
 
416
  // Update Hovered Element bounding card
417
  if (hoverId && hoverId !== selectedId) {
418
- const hoverEl = canvasParent.querySelector(`[data-el-id="${hoverId}"]`);
 
 
 
419
  if (hoverEl) {
420
  const rect = hoverEl.getBoundingClientRect();
421
- const parentRect = canvasParent.getBoundingClientRect();
422
  setHoverRect({
423
- top: rect.top - parentRect.top + canvasParent.scrollTop,
424
- left: rect.left - parentRect.left + canvasParent.scrollLeft,
425
  width: rect.width,
426
  height: rect.height,
427
  tagName: hoverEl.tagName.toLowerCase()
@@ -449,7 +531,7 @@ export default function App() {
449
  canvasObj.removeEventListener("scroll", updateOutlines);
450
  }
451
  };
452
- }, [selectedId, hoverId, visualHtml, viewMode, responsiveMode]);
453
 
454
  // Synchronize visual modifications back to code state
455
  const handleApplyPropertyChange = (updates: {
@@ -480,8 +562,14 @@ export default function App() {
480
  Object.entries(updates.attributes).forEach(([key, val]) => {
481
  if (val === "" || val === undefined) {
482
  el.removeAttribute(key);
 
 
 
483
  } else {
484
  el.setAttribute(key, val);
 
 
 
485
  }
486
  });
487
  }
@@ -842,6 +930,18 @@ export default function App() {
842
  />
843
  </label>
844
 
 
 
 
 
 
 
 
 
 
 
 
 
845
  <button
846
  onClick={handleCopyCode}
847
  className="flex items-center gap-1.5 px-4 h-8 bg-[#212427] hover:bg-zinc-800 border border-zinc-700/60 rounded text-xs font-medium cursor-pointer transition-all"
@@ -1155,6 +1255,81 @@ export default function App() {
1155
  </div>
1156
  )}
1157
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1158
  {/* Custom classes builder - allows direct styling over tailwind classes context */}
1159
  <div>
1160
  <label className="block text-[10px] text-zinc-400 font-bold mb-1">Classes CSS (Tailwind)</label>
@@ -1767,11 +1942,29 @@ export default function App() {
1767
  <span className="text-[9px] bg-[#0e0f10] border border-zinc-850 px-2 py-0.5 rounded text-pink-500 font-mono tracking-widest uppercase">Editável</span>
1768
  </div>
1769
  <textarea
1770
- value={cleanHTMLForExport(htmlCode)}
1771
- onChange={(e) => pushHistoryState(e.target.value, "Inserir código manual")}
1772
  className="w-full flex-1 bg-[#0e0f10] border border-zinc-800 focus:border-pink-500 rounded p-4 font-mono text-xs text-zinc-200 outline-none resize-none select-text scrollbar leading-relaxed selection:bg-pink-600/30 selection:text-white"
1773
- placeholder="Incole ou digite o seu HTML aqui..."
1774
  />
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1775
  </div>
1776
  )}
1777
 
@@ -1959,6 +2152,62 @@ export default function App() {
1959
 
1960
  </main>
1961
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1962
  </div>
1963
  );
1964
  }
 
63
  const [htmlId, setHtmlId] = useState("");
64
  const [htmlTitle, setHtmlTitle] = useState("");
65
 
66
+ // Form / Input fields state
67
+ const [inputPlaceholder, setInputPlaceholder] = useState("");
68
+ const [inputValue, setInputValue] = useState("");
69
+ const [inputName, setInputName] = useState("");
70
+ const [inputType, setInputType] = useState("");
71
+ const [inputChecked, setInputChecked] = useState(false);
72
+ const [inputDataPoints, setInputDataPoints] = useState("");
73
+
74
+ // Paste / Import modal and Code controls state
75
+ const [showPasteModal, setShowPasteModal] = useState(false);
76
+ const [pastedHtml, setPastedHtml] = useState("");
77
+ const [localCode, setLocalCode] = useState("");
78
+
79
  // Styling Editor fields
80
  const [fontSize, setFontSize] = useState("16");
81
  const [fontWeight, setFontWeight] = useState("normal");
 
121
  // Component references
122
  const canvasParentRef = useRef<HTMLDivElement>(null);
123
  const canvasContentRef = useRef<HTMLDivElement>(null);
124
+ const iframeRef = useRef<HTMLIFrameElement>(null);
125
+
126
+ // Scroll retention for iframe reloads
127
+ const lastScrollTopRef = useRef<number>(0);
128
+ const lastScrollLeftRef = useRef<number>(0);
129
+ const [outlineTrigger, setOutlineTrigger] = useState<number>(0);
130
 
131
  // Initialize with landing page template on first run
132
  useEffect(() => {
 
141
  pushHistoryState(defaultTemplate.html, "Template SaaS Carregado");
142
  }, []);
143
 
144
+ // Check if coding input contains full-page indicators wrapper
145
+ const hasFullHtmlWrapper = (htmlStr: string): boolean => {
146
+ const lower = htmlStr.toLowerCase();
147
+ return lower.includes("<doctype") || lower.includes("<html") || lower.includes("<body") || lower.includes("<head") || lower.includes("<style") || lower.includes("<script");
148
+ };
149
+
150
  // Sync canvas nodes inside our editing sandbox relative container when content changes
151
  const visualHtml = useMemo(() => {
152
  if (!htmlCode) return "";
153
  return injectEditorIds(htmlCode);
154
  }, [htmlCode]);
155
 
156
+ // Sync manual local code value when html state changes
157
+ useEffect(() => {
158
+ setLocalCode(cleanHTMLForExport(htmlCode));
159
+ }, [htmlCode]);
160
+
161
  // Inject unique data-el-id to each node recursively (stable, deterministic naming system)
162
  function injectEditorIds(htmlStr: string): string {
163
  const parser = new DOMParser();
164
  const doc = parser.parseFromString(htmlStr, "text/html");
165
  let index = 0;
166
 
167
+ // Auto-inject Tailwind CDN if missing inside head to support preset templates
168
+ if (doc.head && !doc.head.innerHTML.includes("tailwindcss.com")) {
169
+ const tailwindScript = doc.createElement("script");
170
+ tailwindScript.src = "https://cdn.tailwindcss.com";
171
+ doc.head.appendChild(tailwindScript);
172
+ }
173
+
174
  const traverse = (node: Element) => {
175
+ if (node.tagName && node.tagName !== "BODY" && node.tagName !== "HTML" && node.tagName !== "STYLE" && node.tagName !== "SCRIPT" && node.tagName !== "HEAD" && node.tagName !== "META" && node.tagName !== "TITLE" && node.tagName !== "LINK") {
176
  if (!node.getAttribute("data-el-id")) {
177
  node.setAttribute("data-el-id", `el_node_${index++}`);
178
  } else {
 
192
 
193
  if (doc.body) {
194
  Array.from(doc.body.children).forEach(traverse);
 
195
  }
196
+
197
+ if (hasFullHtmlWrapper(htmlStr)) {
198
+ return doc.documentElement.outerHTML;
199
+ }
200
+ return doc.body ? doc.body.innerHTML : htmlStr;
201
  }
202
 
203
  // Remove editor identifiers before storing or exporting
 
210
  Array.from(node.children).forEach(traverse);
211
  };
212
 
213
+ if (doc.documentElement) {
214
+ traverse(doc.documentElement);
215
+ }
216
+
217
+ // Remove auto-injected Tailwind script block on export if requested
218
+ if (doc.head) {
219
+ const scripts = doc.head.querySelectorAll("script[src*='tailwindcss.com']");
220
+ scripts.forEach(s => s.remove());
221
+ }
222
+
223
+ if (hasFullHtmlWrapper(htmlStr)) {
224
+ return doc.documentElement.outerHTML;
225
  }
226
+ return doc.body ? doc.body.innerHTML : htmlStr;
227
  }
228
 
229
+ // Handle Canvas Operations directly over documents
230
  const editDOM = (currentHtml: string, action: (doc: Document) => void): string => {
231
  const parser = new DOMParser();
232
  const doc = parser.parseFromString(currentHtml, "text/html");
233
  action(doc);
234
+ if (hasFullHtmlWrapper(currentHtml)) {
235
+ return doc.documentElement.outerHTML;
236
+ }
237
+ return doc.body ? doc.body.innerHTML : currentHtml;
238
  };
239
 
240
  const pushHistoryState = (newHtml: string, description: string = "Edição") => {
 
373
  setImageAlt("");
374
  }
375
 
376
+ // Form Input options
377
+ const isFormTag = ["INPUT", "TEXTAREA", "SELECT", "BUTTON", "OPTION"].includes(el.tagName);
378
+ if (isFormTag) {
379
+ setInputPlaceholder(el.getAttribute("placeholder") || "");
380
+ setInputValue(el.getAttribute("value") || "");
381
+ setInputName(el.getAttribute("name") || "");
382
+ setInputType(el.getAttribute("type") || "");
383
+ setInputChecked((el as any).checked || el.hasAttribute("checked"));
384
+ setInputDataPoints(el.getAttribute("data-points") || "");
385
+ } else {
386
+ setInputPlaceholder("");
387
+ setInputValue("");
388
+ setInputName("");
389
+ setInputType("");
390
+ setInputChecked(false);
391
+ setInputDataPoints("");
392
+ }
393
+
394
+ // Style Parsing helper, stripping typical px suffixes, compatible with iframe
395
+ const comp = el.ownerDocument?.defaultView
396
+ ? el.ownerDocument.defaultView.getComputedStyle(el)
397
+ : window.getComputedStyle(el);
398
  const inline = el.style;
399
 
400
  setTextColor(rgbToHex(inline.color || comp.color) || "#000000");
 
457
  // Run selection outlining effect calculations relative to canvas wrapper parent
458
  useEffect(() => {
459
  const updateOutlines = () => {
460
+ if (viewMode !== "visual" || !canvasParentRef.current || !canvasContentRef.current) {
461
  setSelectedRect(null);
462
  setHoverRect(null);
463
  return;
464
  }
465
 
466
  const canvasParent = canvasParentRef.current;
467
+ const viewport = canvasContentRef.current;
468
+ const iframe = iframeRef.current || canvasParent.querySelector("iframe");
469
+ const doc = iframe?.contentDocument || iframe?.contentWindow?.document;
470
+
471
+ const viewportRect = viewport.getBoundingClientRect();
472
+ const iframeRect = iframe ? iframe.getBoundingClientRect() : { top: 0, left: 0 };
473
 
474
  // Update Selected Element bounding card
475
  if (selectedId) {
476
+ const selectedEl = doc
477
+ ? doc.querySelector(`[data-el-id="${selectedId}"]`)
478
+ : canvasParent.querySelector(`[data-el-id="${selectedId}"]`);
479
+
480
  if (selectedEl) {
481
  const rect = selectedEl.getBoundingClientRect();
 
482
  setSelectedRect({
483
+ top: rect.top + (iframeRect.top - viewportRect.top),
484
+ left: rect.left + (iframeRect.left - viewportRect.left),
485
  width: rect.width,
486
  height: rect.height,
487
  tagName: selectedEl.tagName.toLowerCase()
 
495
 
496
  // Update Hovered Element bounding card
497
  if (hoverId && hoverId !== selectedId) {
498
+ const hoverEl = doc
499
+ ? doc.querySelector(`[data-el-id="${hoverId}"]`)
500
+ : canvasParent.querySelector(`[data-el-id="${hoverId}"]`);
501
+
502
  if (hoverEl) {
503
  const rect = hoverEl.getBoundingClientRect();
 
504
  setHoverRect({
505
+ top: rect.top + (iframeRect.top - viewportRect.top),
506
+ left: rect.left + (iframeRect.left - viewportRect.left),
507
  width: rect.width,
508
  height: rect.height,
509
  tagName: hoverEl.tagName.toLowerCase()
 
531
  canvasObj.removeEventListener("scroll", updateOutlines);
532
  }
533
  };
534
+ }, [selectedId, hoverId, visualHtml, viewMode, responsiveMode, outlineTrigger]);
535
 
536
  // Synchronize visual modifications back to code state
537
  const handleApplyPropertyChange = (updates: {
 
562
  Object.entries(updates.attributes).forEach(([key, val]) => {
563
  if (val === "" || val === undefined) {
564
  el.removeAttribute(key);
565
+ if (key === "checked" && el.tagName === "INPUT") {
566
+ (el as HTMLInputElement).checked = false;
567
+ }
568
  } else {
569
  el.setAttribute(key, val);
570
+ if (key === "checked" && el.tagName === "INPUT") {
571
+ (el as HTMLInputElement).checked = true;
572
+ }
573
  }
574
  });
575
  }
 
930
  />
931
  </label>
932
 
933
+ <button
934
+ onClick={() => {
935
+ setPastedHtml(cleanHTMLForExport(htmlCode));
936
+ setShowPasteModal(true);
937
+ }}
938
+ className="flex items-center gap-1.5 px-4 h-8 bg-[#212427] hover:bg-zinc-800 border border-zinc-700/60 hover:border-zinc-500 rounded text-xs font-medium cursor-pointer transition-all active:scale-95 duration-100"
939
+ title="Colar ou editar código HTML completo"
940
+ >
941
+ <Code className="w-3.5 h-3.5 text-pink-500" />
942
+ <span>Colar HTML</span>
943
+ </button>
944
+
945
  <button
946
  onClick={handleCopyCode}
947
  className="flex items-center gap-1.5 px-4 h-8 bg-[#212427] hover:bg-zinc-800 border border-zinc-700/60 rounded text-xs font-medium cursor-pointer transition-all"
 
1255
  </div>
1256
  )}
1257
 
1258
+ {/* Form / Input settings */}
1259
+ {["input", "textarea", "select", "button", "option"].includes(selectedRect?.tagName || "") && (
1260
+ <div className="space-y-3">
1261
+ <div>
1262
+ <label className="block text-[10px] text-zinc-400 font-bold mb-1">Nome do Campo (name)</label>
1263
+ <input
1264
+ type="text"
1265
+ value={inputName}
1266
+ onChange={(e) => {
1267
+ setInputName(e.target.value);
1268
+ handleApplyPropertyChange({ attributes: { name: e.target.value } }, "Alterar Nome Campo");
1269
+ }}
1270
+ className="w-full bg-[#181a1c] border border-zinc-800 focus:border-pink-500 rounded px-2 py-1 text-xs text-white outline-none"
1271
+ />
1272
+ </div>
1273
+ <div>
1274
+ <label className="block text-[10px] text-zinc-400 font-bold mb-1">Valor do Campo (value)</label>
1275
+ <input
1276
+ type="text"
1277
+ value={inputValue}
1278
+ onChange={(e) => {
1279
+ setInputValue(e.target.value);
1280
+ handleApplyPropertyChange({ attributes: { value: e.target.value } }, "Alterar Valor Campo");
1281
+ }}
1282
+ className="w-full bg-[#181a1c] border border-zinc-800 focus:border-pink-500 rounded px-2 py-1 text-xs text-white outline-none"
1283
+ />
1284
+ </div>
1285
+ {["input", "textarea"].includes(selectedRect?.tagName || "") && (
1286
+ <div>
1287
+ <label className="block text-[10px] text-zinc-400 font-bold mb-1">Inserir Placeholder</label>
1288
+ <input
1289
+ type="text"
1290
+ value={inputPlaceholder}
1291
+ onChange={(e) => {
1292
+ setInputPlaceholder(e.target.value);
1293
+ handleApplyPropertyChange({ attributes: { placeholder: e.target.value } }, "Alterar Placeholder");
1294
+ }}
1295
+ className="w-full bg-[#181a1c] border border-zinc-800 focus:border-pink-500 rounded px-2 py-1 text-xs text-white outline-none"
1296
+ />
1297
+ </div>
1298
+ )}
1299
+ {inputType === "checkbox" && (
1300
+ <div className="flex items-center gap-2 py-1">
1301
+ <input
1302
+ type="checkbox"
1303
+ checked={inputChecked}
1304
+ id="cb-checked-property"
1305
+ onChange={(e) => {
1306
+ setInputChecked(e.target.checked);
1307
+ handleApplyPropertyChange({
1308
+ attributes: {
1309
+ checked: e.target.checked ? "checked" : ""
1310
+ }
1311
+ }, "Marcar/Desmarcar Checkbox");
1312
+ }}
1313
+ className="w-4 h-4 rounded text-pink-600 focus:ring-pink-500 bg-zinc-800"
1314
+ />
1315
+ <label htmlFor="cb-checked-property" className="text-[10px] text-zinc-400 font-bold cursor-pointer">Marcar por Padrão (Checked)</label>
1316
+ </div>
1317
+ )}
1318
+ <div>
1319
+ <label className="block text-[10px] text-zinc-400 font-bold mb-1">Pontos Personalizados (data-points)</label>
1320
+ <input
1321
+ type="text"
1322
+ value={inputDataPoints}
1323
+ onChange={(e) => {
1324
+ setInputDataPoints(e.target.value);
1325
+ handleApplyPropertyChange({ attributes: { "data-points": e.target.value } }, "Alterar data-points");
1326
+ }}
1327
+ className="w-full bg-[#181a1c] border border-zinc-800 focus:border-pink-500 rounded px-2 py-1 text-xs text-white outline-none"
1328
+ />
1329
+ </div>
1330
+ </div>
1331
+ )}
1332
+
1333
  {/* Custom classes builder - allows direct styling over tailwind classes context */}
1334
  <div>
1335
  <label className="block text-[10px] text-zinc-400 font-bold mb-1">Classes CSS (Tailwind)</label>
 
1942
  <span className="text-[9px] bg-[#0e0f10] border border-zinc-850 px-2 py-0.5 rounded text-pink-500 font-mono tracking-widest uppercase">Editável</span>
1943
  </div>
1944
  <textarea
1945
+ value={localCode}
1946
+ onChange={(e) => setLocalCode(e.target.value)}
1947
  className="w-full flex-1 bg-[#0e0f10] border border-zinc-800 focus:border-pink-500 rounded p-4 font-mono text-xs text-zinc-200 outline-none resize-none select-text scrollbar leading-relaxed selection:bg-pink-600/30 selection:text-white"
1948
+ placeholder="Cole ou digite o seu HTML completo aqui..."
1949
  />
1950
+ <div className="flex items-center justify-end gap-3 mt-3 select-none">
1951
+ {localCode !== cleanHTMLForExport(htmlCode) && (
1952
+ <button
1953
+ onClick={() => setLocalCode(cleanHTMLForExport(htmlCode))}
1954
+ className="px-4 py-1.5 bg-zinc-800 hover:bg-zinc-750 text-zinc-350 rounded text-xs font-semibold cursor-pointer transition-all"
1955
+ >
1956
+ Descartar
1957
+ </button>
1958
+ )}
1959
+ <button
1960
+ onClick={() => pushHistoryState(localCode, "Editar Código Manual")}
1961
+ disabled={localCode === cleanHTMLForExport(htmlCode)}
1962
+ className="px-5 py-1.5 bg-pink-600 hover:bg-pink-550 text-white rounded text-xs font-bold disabled:opacity-30 disabled:cursor-not-allowed cursor-pointer transition-all flex items-center gap-1.5"
1963
+ >
1964
+ <Check className="w-3.5 h-3.5" />
1965
+ Aplicar Código Fonte
1966
+ </button>
1967
+ </div>
1968
  </div>
1969
  )}
1970
 
 
2152
 
2153
  </main>
2154
 
2155
+ {/* Dynamic Paste HTML Modal Dialog */}
2156
+ {showPasteModal && (
2157
+ <div className="fixed inset-0 bg-black/80 flex items-center justify-center z-50 p-4 animate-fade-in backdrop-blur-sm select-none">
2158
+ <div className="bg-[#181a1c] border border-zinc-800 rounded-lg max-w-2xl w-full flex flex-col shadow-2xl overflow-hidden animate-scale-up">
2159
+ <div className="flex items-center justify-between px-5 py-4 border-b border-zinc-850">
2160
+ <div className="flex items-center gap-2">
2161
+ <Code className="w-4 h-4 text-pink-500" />
2162
+ <h3 className="text-sm font-bold text-white uppercase tracking-wider font-sans">
2163
+ Colar ou Editar Código HTML
2164
+ </h3>
2165
+ </div>
2166
+ <button
2167
+ onClick={() => setShowPasteModal(false)}
2168
+ className="text-zinc-500 hover:text-white transition-colors cursor-pointer text-sm font-bold p-1 hover:bg-zinc-800 rounded font-sans"
2169
+ >
2170
+ ✕
2171
+ </button>
2172
+ </div>
2173
+
2174
+ <div className="p-5 space-y-4">
2175
+ <span className="block text-[11px] text-zinc-400 font-sans leading-relaxed">
2176
+ Insira o código HTML completo da página abaixo (incluindo estilos, CDN links e scripts). O editor irá carregar, interpretar e sandboxear o documento automaticamente.
2177
+ </span>
2178
+
2179
+ <textarea
2180
+ value={pastedHtml}
2181
+ onChange={(e) => setPastedHtml(e.target.value)}
2182
+ rows={12}
2183
+ placeholder="Cole seu código HTML aqui..."
2184
+ className="w-full bg-[#0e0f10] border border-zinc-800 focus:border-pink-500 rounded p-4 font-mono text-xs text-zinc-200 outline-none select-text resize-none scrollbar leading-relaxed"
2185
+ />
2186
+ </div>
2187
+
2188
+ <div className="flex items-center justify-end gap-3 px-5 py-4 bg-[#0e0f10] border-t border-zinc-850">
2189
+ <button
2190
+ onClick={() => setShowPasteModal(false)}
2191
+ className="px-4 py-2 bg-zinc-800 hover:bg-zinc-750 text-zinc-350 rounded text-xs font-semibold cursor-pointer transition-all uppercase tracking-wider h-9"
2192
+ >
2193
+ Cancelar
2194
+ </button>
2195
+ <button
2196
+ onClick={() => {
2197
+ parseAndLoadHTML(pastedHtml);
2198
+ setShowPasteModal(false);
2199
+ }}
2200
+ disabled={!pastedHtml.trim()}
2201
+ className="flex items-center gap-2 px-5 py-2 bg-pink-600 hover:bg-pink-550 text-white rounded text-xs font-bold disabled:opacity-30 disabled:cursor-not-allowed cursor-pointer transition-all uppercase tracking-wider h-9"
2202
+ >
2203
+ <Check className="w-4 h-4" />
2204
+ Carregar HTML
2205
+ </button>
2206
+ </div>
2207
+ </div>
2208
+ </div>
2209
+ )}
2210
+
2211
  </div>
2212
  );
2213
  }