astro-tractstack 2.0.20 → 2.0.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/templates/src/components/compositor/preview/PanesPreviewGenerator.tsx +0 -1
- package/templates/src/components/edit/pane/AddPanePanel.tsx +28 -15
- package/templates/src/components/edit/pane/AddPanePanel_new.tsx +138 -125
- package/templates/src/components/edit/pane/RestylePaneModal.tsx +225 -269
- package/templates/src/components/edit/pane/steps/DesignLibraryStep.tsx +0 -2
- package/templates/src/components/edit/state/SaveToLibraryModal.tsx +3 -1
- package/templates/src/utils/compositor/designLibraryHelper.ts +378 -281
package/package.json
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { useState } from 'react';
|
|
2
2
|
import { useStore } from '@nanostores/react';
|
|
3
|
+
import PlusCircleIcon from '@heroicons/react/24/outline/PlusCircleIcon';
|
|
3
4
|
import { settingsPanelStore } from '@/stores/storykeep';
|
|
4
5
|
import AddPaneNewPanel from './AddPanePanel_new';
|
|
5
6
|
import AddPaneBreakPanel from './AddPanePanel_break';
|
|
@@ -58,6 +59,21 @@ const AddPanePanel = ({
|
|
|
58
59
|
|
|
59
60
|
return (
|
|
60
61
|
<div className="add-pane-panel-wrapper border-mydarkgrey border-b-2 border-t-2 border-dotted">
|
|
62
|
+
{isExpanded && (
|
|
63
|
+
<div className="border-mylightgrey border-t border-dotted">
|
|
64
|
+
<div className="group flex w-full flex-wrap items-center gap-2 px-1.5 pb-0.5 pt-1.5">
|
|
65
|
+
<button
|
|
66
|
+
onClick={() => {
|
|
67
|
+
setMode(PaneAddMode.DEFAULT);
|
|
68
|
+
setIsExpanded(false);
|
|
69
|
+
}}
|
|
70
|
+
className="rounded-md bg-gray-200 px-2 py-1 text-sm font-bold text-gray-800"
|
|
71
|
+
>
|
|
72
|
+
< Cancel
|
|
73
|
+
</button>
|
|
74
|
+
</div>
|
|
75
|
+
</div>
|
|
76
|
+
)}
|
|
61
77
|
{mode === PaneAddMode.NEW || (!hasPanes && first && !reset) ? (
|
|
62
78
|
<AddPaneNewPanel
|
|
63
79
|
nodeId={nodeId}
|
|
@@ -98,12 +114,6 @@ const AddPanePanel = ({
|
|
|
98
114
|
) : isExpanded ? (
|
|
99
115
|
<div className="border-mylightgrey border-t border-dotted">
|
|
100
116
|
<div className="group flex w-full flex-wrap items-center gap-2 px-1.5 pb-0.5 pt-1.5">
|
|
101
|
-
<button
|
|
102
|
-
onClick={() => setIsExpanded(false)}
|
|
103
|
-
className="rounded-md bg-gray-200 px-2 py-1 text-sm font-bold text-gray-800"
|
|
104
|
-
>
|
|
105
|
-
< Cancel
|
|
106
|
-
</button>
|
|
107
117
|
<div className={`flex flex-wrap gap-1 transition-opacity`}>
|
|
108
118
|
<button
|
|
109
119
|
onClick={() => setMode(PaneAddMode.NEW)}
|
|
@@ -111,12 +121,6 @@ const AddPanePanel = ({
|
|
|
111
121
|
>
|
|
112
122
|
+ Design New
|
|
113
123
|
</button>
|
|
114
|
-
<button
|
|
115
|
-
onClick={() => setMode(PaneAddMode.PASTE)}
|
|
116
|
-
className="rounded bg-white px-2 py-1 text-sm text-cyan-700 shadow-sm transition-colors hover:bg-cyan-700 hover:text-white"
|
|
117
|
-
>
|
|
118
|
-
+ Paste Pane
|
|
119
|
-
</button>
|
|
120
124
|
{!isContextPane && (
|
|
121
125
|
<>
|
|
122
126
|
<button
|
|
@@ -143,16 +147,25 @@ const AddPanePanel = ({
|
|
|
143
147
|
+ Code Hook
|
|
144
148
|
</button>
|
|
145
149
|
)}
|
|
150
|
+
<button
|
|
151
|
+
onClick={() => setMode(PaneAddMode.PASTE)}
|
|
152
|
+
className="rounded bg-white px-2 py-1 text-sm text-cyan-700 shadow-sm transition-colors hover:bg-cyan-700 hover:text-white"
|
|
153
|
+
>
|
|
154
|
+
+ Paste Pane
|
|
155
|
+
</button>
|
|
146
156
|
</div>
|
|
147
157
|
</div>
|
|
148
158
|
</div>
|
|
149
159
|
) : (
|
|
150
|
-
<div className="border-mylightgrey border-t border-dotted p-
|
|
160
|
+
<div className="border-mylightgrey flex border-t border-dotted p-0.5">
|
|
151
161
|
<button
|
|
152
162
|
onClick={() => setIsExpanded(true)}
|
|
153
|
-
|
|
163
|
+
title="Insert Pane here"
|
|
164
|
+
className="group w-full text-gray-500"
|
|
154
165
|
>
|
|
155
|
-
|
|
166
|
+
<div className="text-mydarkgrey hover:bg-myoffwhite rounded-md transition-colors duration-150 ease-in-out hover:bg-opacity-50 hover:mix-blend-difference">
|
|
167
|
+
<PlusCircleIcon className="mx-auto h-8 w-8" />
|
|
168
|
+
</div>
|
|
156
169
|
</button>
|
|
157
170
|
</div>
|
|
158
171
|
)}
|
|
@@ -18,7 +18,6 @@ import { AiDesignStep, type AiDesignConfig } from './steps/AiDesignStep';
|
|
|
18
18
|
import { parseAiPane, parseAiCopyHtml } from '@/utils/compositor/aiPaneParser';
|
|
19
19
|
import {
|
|
20
20
|
convertStorageToLiveTemplate,
|
|
21
|
-
mergeCopyIntoTemplate,
|
|
22
21
|
convertTemplateToAIShell,
|
|
23
22
|
} from '@/utils/compositor/designLibraryHelper';
|
|
24
23
|
import { DirectInjectStep } from './steps/DirectInjectStep';
|
|
@@ -141,19 +140,21 @@ const AddPaneNewPanel = ({
|
|
|
141
140
|
const [layoutChoice, setLayoutChoice] = useState<LayoutChoice>('standard');
|
|
142
141
|
const [error, setError] = useState<string | null>(null);
|
|
143
142
|
|
|
143
|
+
// Standard / Single Column State
|
|
144
144
|
const [copyMode, setCopyMode] = useState<CopyMode>('prompt');
|
|
145
145
|
const [promptValue, setPromptValue] = useState('');
|
|
146
146
|
const [copyValue, setCopyValue] = useState('');
|
|
147
147
|
|
|
148
|
+
// Grid / 2-Column State (Strictly Prompt-Only)
|
|
148
149
|
const [overallPrompt, setOverallPrompt] = useState(
|
|
149
150
|
prompts.aiPaneCopyPrompt_2cols.presets.heroDefault.default
|
|
150
151
|
);
|
|
151
|
-
const [
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
const [
|
|
155
|
-
|
|
156
|
-
|
|
152
|
+
const [promptValueCol1, setPromptValueCol1] = useState(
|
|
153
|
+
prompts.aiPaneCopyPrompt_2cols.presets.heroDefault.left.prompt
|
|
154
|
+
);
|
|
155
|
+
const [promptValueCol2, setPromptValueCol2] = useState(
|
|
156
|
+
prompts.aiPaneCopyPrompt_2cols.presets.heroDefault.right.prompt
|
|
157
|
+
);
|
|
157
158
|
|
|
158
159
|
const [selectedLibraryEntry, setSelectedLibraryEntry] =
|
|
159
160
|
useState<DesignLibraryEntry | null>(null);
|
|
@@ -226,11 +227,6 @@ const AddPaneNewPanel = ({
|
|
|
226
227
|
handleApplyTemplate(blankTemplate);
|
|
227
228
|
};
|
|
228
229
|
|
|
229
|
-
const handleDesignLibrarySelect = (entry: DesignLibraryEntry) => {
|
|
230
|
-
setSelectedLibraryEntry(entry);
|
|
231
|
-
setStep('copyInput');
|
|
232
|
-
};
|
|
233
|
-
|
|
234
230
|
const handleAiDesignContinue = () => {
|
|
235
231
|
setStep('copyInput');
|
|
236
232
|
};
|
|
@@ -274,6 +270,18 @@ const AddPaneNewPanel = ({
|
|
|
274
270
|
}
|
|
275
271
|
};
|
|
276
272
|
|
|
273
|
+
const handleDesignLibrarySelect = (entry: DesignLibraryEntry) => {
|
|
274
|
+
setSelectedLibraryEntry(entry);
|
|
275
|
+
|
|
276
|
+
if (entry.template.gridLayout) {
|
|
277
|
+
setLayoutChoice('grid');
|
|
278
|
+
} else {
|
|
279
|
+
setLayoutChoice('standard');
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
setStep('copyInput');
|
|
283
|
+
};
|
|
284
|
+
|
|
277
285
|
const handleFinalGenerate = useCallback(async () => {
|
|
278
286
|
setError(null);
|
|
279
287
|
setStep('loading');
|
|
@@ -284,59 +292,99 @@ const AddPaneNewPanel = ({
|
|
|
284
292
|
throw new Error('No design library item was selected.');
|
|
285
293
|
}
|
|
286
294
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
liveTemplate.markdown.markdownBody = copyValue;
|
|
293
|
-
}
|
|
294
|
-
handleApplyTemplate(liveTemplate);
|
|
295
|
-
return;
|
|
296
|
-
}
|
|
295
|
+
const liveTemplate = convertStorageToLiveTemplate(
|
|
296
|
+
selectedLibraryEntry.template
|
|
297
|
+
);
|
|
298
|
+
const shellResult = convertTemplateToAIShell(liveTemplate);
|
|
299
|
+
const layout = 'Text Only';
|
|
297
300
|
|
|
298
|
-
if (
|
|
299
|
-
const
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
301
|
+
if (layoutChoice === 'grid' && liveTemplate.gridLayout) {
|
|
302
|
+
const copyPromptDetails = prompts.aiPaneCopyPrompt_2cols;
|
|
303
|
+
const preset = copyPromptDetails.presets.heroDefault;
|
|
304
|
+
const copyResults: string[] = [];
|
|
305
|
+
|
|
306
|
+
// Only prompt mode supported for grid now
|
|
307
|
+
const promptsToRun: {
|
|
308
|
+
prompt: string;
|
|
309
|
+
presetKey: ColumnPresetKey;
|
|
310
|
+
}[] = [
|
|
311
|
+
{
|
|
312
|
+
prompt: promptValueCol1,
|
|
313
|
+
presetKey: 'left',
|
|
314
|
+
},
|
|
315
|
+
{
|
|
316
|
+
prompt: promptValueCol2,
|
|
317
|
+
presetKey: 'right',
|
|
318
|
+
},
|
|
319
|
+
];
|
|
320
|
+
|
|
321
|
+
for (const item of promptsToRun) {
|
|
322
|
+
const columnPreset = preset[item.presetKey];
|
|
323
|
+
const formattedCopyPrompt = copyPromptDetails.user_template
|
|
324
|
+
.replace('{{SHELL_JSON}}', shellResult)
|
|
325
|
+
.replace('{{COPY_INPUT}}', overallPrompt)
|
|
326
|
+
.replace('{{COLUMN_PROMPT}}', item.prompt)
|
|
327
|
+
.replace(
|
|
328
|
+
'{{DESIGN_INPUT}}',
|
|
329
|
+
"N/A - Use the provided Shell JSON's design."
|
|
330
|
+
)
|
|
331
|
+
.replace('{{LAYOUT_TYPE}}', layout)
|
|
332
|
+
.replace('{{COLUMN_EXAMPLE}}', columnPreset.example);
|
|
333
|
+
|
|
334
|
+
const copyResult = await callAskLemurAPI(
|
|
335
|
+
formattedCopyPrompt,
|
|
336
|
+
copyPromptDetails.system || '',
|
|
337
|
+
false,
|
|
338
|
+
isSandboxMode
|
|
305
339
|
);
|
|
340
|
+
copyResults.push(copyResult);
|
|
306
341
|
}
|
|
307
342
|
|
|
308
|
-
const
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
343
|
+
const finalPane = parseAiPane(shellResult, copyResults, layout);
|
|
344
|
+
handleApplyTemplate(finalPane);
|
|
345
|
+
} else if (layoutChoice === 'standard' && liveTemplate.markdown) {
|
|
346
|
+
if (copyMode === 'raw') {
|
|
347
|
+
liveTemplate.markdown.markdownBody = copyValue;
|
|
348
|
+
handleApplyTemplate(liveTemplate);
|
|
349
|
+
return;
|
|
313
350
|
}
|
|
314
351
|
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
"N/A - Use the provided Shell JSON's design."
|
|
322
|
-
)
|
|
323
|
-
.replace('{{LAYOUT_TYPE}}', layout)
|
|
324
|
-
.replace('{{SHELL_JSON}}', shellJson);
|
|
352
|
+
if (copyMode === 'prompt') {
|
|
353
|
+
if (!shellResult || shellResult === '{}') {
|
|
354
|
+
throw new Error(
|
|
355
|
+
'Could not generate a valid AI shell from this design.'
|
|
356
|
+
);
|
|
357
|
+
}
|
|
325
358
|
|
|
326
|
-
|
|
327
|
-
formattedCopyPrompt
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
359
|
+
const copyPromptDetails = prompts.aiPaneCopyPrompt;
|
|
360
|
+
const formattedCopyPrompt = copyPromptDetails.user_template
|
|
361
|
+
.replace('{{COPY_INPUT}}', promptValue)
|
|
362
|
+
.replace(
|
|
363
|
+
'{{DESIGN_INPUT}}',
|
|
364
|
+
"N/A - Use the provided Shell JSON's design."
|
|
365
|
+
)
|
|
366
|
+
.replace('{{LAYOUT_TYPE}}', layout)
|
|
367
|
+
.replace('{{SHELL_JSON}}', shellResult);
|
|
332
368
|
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
369
|
+
const copyResult = await callAskLemurAPI(
|
|
370
|
+
formattedCopyPrompt,
|
|
371
|
+
copyPromptDetails.system || '',
|
|
372
|
+
false,
|
|
373
|
+
isSandboxMode
|
|
374
|
+
);
|
|
375
|
+
|
|
376
|
+
const newNodes = parseAiCopyHtml(
|
|
377
|
+
copyResult,
|
|
378
|
+
liveTemplate.markdown.id
|
|
379
|
+
);
|
|
380
|
+
const finalPane = cloneDeep(liveTemplate);
|
|
381
|
+
finalPane.markdown!.nodes = newNodes;
|
|
382
|
+
handleApplyTemplate(finalPane);
|
|
383
|
+
}
|
|
384
|
+
} else {
|
|
385
|
+
throw new Error(
|
|
386
|
+
'Template and layout mismatch. Please go back and try again.'
|
|
336
387
|
);
|
|
337
|
-
const finalPane = cloneDeep(liveTemplate);
|
|
338
|
-
finalPane.markdown!.nodes = newNodes;
|
|
339
|
-
handleApplyTemplate(finalPane);
|
|
340
388
|
}
|
|
341
389
|
} else if (initialChoice === 'ai') {
|
|
342
390
|
let designInput = `Generate a design using a **${aiDesignConfig.harmony.toLowerCase()}** color scheme with a **${aiDesignConfig.theme.toLowerCase()}** theme.`;
|
|
@@ -396,32 +444,22 @@ const AddPaneNewPanel = ({
|
|
|
396
444
|
const preset = copyPromptDetails.presets.heroDefault;
|
|
397
445
|
const copyResults: string[] = [];
|
|
398
446
|
|
|
447
|
+
// Grid is strictly prompt-based
|
|
399
448
|
const promptsToRun: {
|
|
400
449
|
prompt: string;
|
|
401
|
-
copy: string;
|
|
402
|
-
mode: CopyMode;
|
|
403
450
|
presetKey: ColumnPresetKey;
|
|
404
451
|
}[] = [
|
|
405
452
|
{
|
|
406
453
|
prompt: promptValueCol1,
|
|
407
|
-
copy: copyValueCol1,
|
|
408
|
-
mode: copyModeCol1,
|
|
409
454
|
presetKey: 'left',
|
|
410
455
|
},
|
|
411
456
|
{
|
|
412
457
|
prompt: promptValueCol2,
|
|
413
|
-
copy: copyValueCol2,
|
|
414
|
-
mode: copyModeCol2,
|
|
415
458
|
presetKey: 'right',
|
|
416
459
|
},
|
|
417
460
|
];
|
|
418
461
|
|
|
419
462
|
for (const item of promptsToRun) {
|
|
420
|
-
if (item.mode === 'raw') {
|
|
421
|
-
copyResults.push(item.copy);
|
|
422
|
-
continue;
|
|
423
|
-
}
|
|
424
|
-
|
|
425
463
|
const columnPreset = preset[item.presetKey];
|
|
426
464
|
const formattedCopyPrompt = copyPromptDetails.user_template
|
|
427
465
|
.replace('{{SHELL_JSON}}', shellResult)
|
|
@@ -454,12 +492,8 @@ const AddPaneNewPanel = ({
|
|
|
454
492
|
promptValue,
|
|
455
493
|
copyValue,
|
|
456
494
|
overallPrompt,
|
|
457
|
-
copyModeCol1,
|
|
458
495
|
promptValueCol1,
|
|
459
|
-
copyValueCol1,
|
|
460
|
-
copyModeCol2,
|
|
461
496
|
promptValueCol2,
|
|
462
|
-
copyValueCol2,
|
|
463
497
|
isSandboxMode,
|
|
464
498
|
initialChoice,
|
|
465
499
|
layoutChoice,
|
|
@@ -550,58 +584,47 @@ const AddPaneNewPanel = ({
|
|
|
550
584
|
const renderContentStep = () => {
|
|
551
585
|
if (layoutChoice === 'grid') {
|
|
552
586
|
const isGenerateDisabled =
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
(copyModeCol2 === 'raw' && !copyValueCol2.trim()) ||
|
|
557
|
-
!overallPrompt.trim();
|
|
587
|
+
!overallPrompt.trim() ||
|
|
588
|
+
!promptValueCol1.trim() ||
|
|
589
|
+
!promptValueCol2.trim();
|
|
558
590
|
|
|
559
591
|
return (
|
|
560
592
|
<div className="space-y-4 p-4">
|
|
561
593
|
<div>
|
|
562
|
-
<
|
|
594
|
+
<label className="mb-2 block text-sm font-bold text-gray-700">
|
|
563
595
|
Overall Component Brief
|
|
564
|
-
</
|
|
596
|
+
</label>
|
|
565
597
|
<textarea
|
|
566
598
|
value={overallPrompt}
|
|
567
599
|
onChange={(e) => setOverallPrompt(e.target.value)}
|
|
568
|
-
placeholder="e.g., A compelling hero section for a website about Tract Stack..."
|
|
569
600
|
rows={3}
|
|
570
601
|
className="block w-full rounded-md border-gray-300 p-2 shadow-sm focus:border-cyan-500 focus:ring-cyan-500 sm:text-sm"
|
|
571
602
|
/>
|
|
603
|
+
<p className="mt-1 text-xs text-gray-500">
|
|
604
|
+
This context is applied to both columns.
|
|
605
|
+
</p>
|
|
572
606
|
</div>
|
|
573
607
|
<div className="grid grid-cols-1 gap-6 md:grid-cols-2">
|
|
574
608
|
<div>
|
|
575
|
-
<
|
|
576
|
-
Left Column
|
|
577
|
-
</
|
|
578
|
-
<
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
copyValue={copyValueCol1}
|
|
584
|
-
onCopyValueChange={setCopyValueCol1}
|
|
585
|
-
defaultPrompt={
|
|
586
|
-
prompts.aiPaneCopyPrompt_2cols.presets.heroDefault.left.prompt
|
|
587
|
-
}
|
|
609
|
+
<label className="mb-2 block text-sm font-bold text-gray-700">
|
|
610
|
+
Left Column Prompt
|
|
611
|
+
</label>
|
|
612
|
+
<textarea
|
|
613
|
+
value={promptValueCol1}
|
|
614
|
+
onChange={(e) => setPromptValueCol1(e.target.value)}
|
|
615
|
+
rows={4}
|
|
616
|
+
className="block w-full rounded-md border-gray-300 p-2 shadow-sm focus:border-cyan-500 focus:ring-cyan-500 sm:text-sm"
|
|
588
617
|
/>
|
|
589
618
|
</div>
|
|
590
619
|
<div>
|
|
591
|
-
<
|
|
592
|
-
Right Column
|
|
593
|
-
</
|
|
594
|
-
<
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
copyValue={copyValueCol2}
|
|
600
|
-
onCopyValueChange={setCopyValueCol2}
|
|
601
|
-
defaultPrompt={
|
|
602
|
-
prompts.aiPaneCopyPrompt_2cols.presets.heroDefault.right
|
|
603
|
-
.prompt
|
|
604
|
-
}
|
|
620
|
+
<label className="mb-2 block text-sm font-bold text-gray-700">
|
|
621
|
+
Right Column Prompt
|
|
622
|
+
</label>
|
|
623
|
+
<textarea
|
|
624
|
+
value={promptValueCol2}
|
|
625
|
+
onChange={(e) => setPromptValueCol2(e.target.value)}
|
|
626
|
+
rows={4}
|
|
627
|
+
className="block w-full rounded-md border-gray-300 p-2 shadow-sm focus:border-cyan-500 focus:ring-cyan-500 sm:text-sm"
|
|
605
628
|
/>
|
|
606
629
|
</div>
|
|
607
630
|
</div>
|
|
@@ -759,25 +782,15 @@ const AddPaneNewPanel = ({
|
|
|
759
782
|
return (
|
|
760
783
|
<div className="bg-white p-2 shadow-inner">
|
|
761
784
|
<div className="group mb-2 flex w-full items-center gap-1 rounded-md bg-white p-1.5">
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
onClick={() => setParentMode(PaneAddMode.DEFAULT, first)}
|
|
772
|
-
className="w-fit rounded bg-gray-100 px-3 py-1 text-sm text-gray-700 transition-colors hover:bg-gray-200"
|
|
773
|
-
>
|
|
774
|
-
← Go Back
|
|
775
|
-
</button>
|
|
776
|
-
<div className="font-action ml-4 flex-none rounded px-2 py-2.5 text-sm font-bold text-cyan-700 shadow-sm">
|
|
777
|
-
+ Design New Pane
|
|
778
|
-
</div>
|
|
779
|
-
</>
|
|
780
|
-
)}
|
|
785
|
+
<button
|
|
786
|
+
onClick={() => setParentMode(PaneAddMode.DEFAULT, first)}
|
|
787
|
+
className="w-fit rounded bg-gray-100 px-3 py-1 text-sm text-gray-700 transition-colors hover:bg-gray-200"
|
|
788
|
+
>
|
|
789
|
+
← Go Back
|
|
790
|
+
</button>
|
|
791
|
+
<div className="font-action ml-4 flex-none rounded px-2 py-2.5 text-sm font-bold text-cyan-700 shadow-sm">
|
|
792
|
+
+ Design New Pane
|
|
793
|
+
</div>
|
|
781
794
|
</div>
|
|
782
795
|
<div className="min-h-96 rounded-md border bg-gray-50">
|
|
783
796
|
{renderStep()}
|