@morphika/andami 0.5.1 → 0.5.2
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/app/admin/assets/page.tsx +6 -6
- package/app/admin/database/page.tsx +302 -302
- package/app/admin/error.tsx +53 -53
- package/app/admin/layout.tsx +320 -320
- package/app/admin/navigation/page.tsx +255 -255
- package/app/admin/pages/[slug]/page.tsx +6 -6
- package/app/admin/pages/page.tsx +11 -11
- package/app/admin/projects/page.tsx +14 -14
- package/app/admin/setup/page.tsx +1 -1
- package/app/admin/styles/page.tsx +1 -1
- package/components/admin/MetadataEditor.tsx +6 -6
- package/components/admin/nav-builder/NavBuilder.tsx +1 -1
- package/components/admin/nav-builder/NavBuilderGrid.tsx +3 -3
- package/components/admin/nav-builder/NavGridCell.tsx +48 -48
- package/components/admin/nav-builder/NavGridItem.tsx +4 -4
- package/components/admin/nav-builder/NavItemSettings.tsx +331 -331
- package/components/admin/nav-builder/NavItemTypePicker.tsx +102 -102
- package/components/admin/nav-builder/NavLivePreview.tsx +1 -1
- package/components/admin/nav-builder/NavMobileLivePreview.tsx +226 -226
- package/components/admin/nav-builder/NavMobileSettings.tsx +242 -242
- package/components/admin/nav-builder/NavSettingsFields.tsx +514 -514
- package/components/admin/setup-wizard/BrandingStep.tsx +3 -3
- package/components/admin/setup-wizard/DatabaseStep.tsx +2 -2
- package/components/admin/setup-wizard/DoneStep.tsx +1 -1
- package/components/admin/setup-wizard/SetupWizard.tsx +4 -4
- package/components/admin/setup-wizard/StorageStep.tsx +2 -2
- package/components/admin/setup-wizard/WelcomeStep.tsx +2 -2
- package/components/admin/styles/ColorsEditor.tsx +2 -2
- package/components/admin/styles/FontsEditor.tsx +6 -6
- package/components/admin/styles/GridLayoutEditor.tsx +9 -9
- package/components/admin/styles/LinksButtonsEditor.tsx +5 -5
- package/components/admin/styles/TypographyEditor.tsx +6 -6
- package/components/admin/styles/shared.tsx +68 -68
- package/components/blocks/AudioBlockRenderer.tsx +286 -286
- package/components/blocks/MarqueeBlockRenderer.tsx +316 -0
- package/components/blocks/ProjectCarouselBlockRenderer.tsx +1 -1
- package/components/builder/BlockCardIcons.tsx +316 -316
- package/components/builder/BlockTypePicker.tsx +1 -1
- package/components/builder/BubbleIcons.tsx +90 -0
- package/components/builder/BuilderCanvas.tsx +2 -0
- package/components/builder/CanvasMinimap.tsx +2 -2
- package/components/builder/CoverSectionCanvas.tsx +363 -363
- package/components/builder/DeviceFrame.tsx +1 -1
- package/components/builder/DndWrapper.tsx +3 -3
- package/components/builder/InsertionLines.tsx +1 -1
- package/components/builder/SectionCardIcons.tsx +421 -320
- package/components/builder/SectionEditorBar.tsx +1 -1
- package/components/builder/SectionTypePicker.tsx +4 -4
- package/components/builder/SectionV2Canvas.tsx +1 -1
- package/components/builder/SectionV2Column.tsx +69 -67
- package/components/builder/SortableBlock.tsx +93 -73
- package/components/builder/SortableRow.tsx +27 -26
- package/components/builder/VirtualAssetGrid.tsx +2 -2
- package/components/builder/asset-browser/R2BrowserContent.tsx +11 -11
- package/components/builder/blockStyles.tsx +192 -185
- package/components/builder/color-picker/AlphaSlider.tsx +141 -141
- package/components/builder/color-picker/ColorInputs.tsx +105 -105
- package/components/builder/color-picker/EyedropperButton.tsx +74 -74
- package/components/builder/color-picker/HueSlider.tsx +124 -124
- package/components/builder/color-picker/SaturationCanvas.tsx +142 -142
- package/components/builder/color-picker/SwatchBar.tsx +93 -93
- package/components/builder/editors/AudioBlockEditor.tsx +242 -242
- package/components/builder/editors/BeforeAfterBlockEditor.tsx +360 -360
- package/components/builder/editors/ButtonBlockEditor.tsx +4 -4
- package/components/builder/editors/EnterAnimationPicker.tsx +2 -2
- package/components/builder/editors/HoverEffectPicker.tsx +2 -2
- package/components/builder/editors/ImageBlockEditor.tsx +2 -2
- package/components/builder/editors/ImageGridBlockEditor.tsx +4 -4
- package/components/builder/editors/MarqueeBlockEditor.tsx +621 -0
- package/components/builder/editors/ProjectCarouselBlockEditor.tsx +443 -443
- package/components/builder/editors/ProjectGridEditor.tsx +9 -9
- package/components/builder/editors/SpacerBlockEditor.tsx +5 -5
- package/components/builder/editors/StaggerSettings.tsx +109 -109
- package/components/builder/editors/TextBlockEditor.tsx +3 -3
- package/components/builder/editors/TextStylePicker.tsx +1 -1
- package/components/builder/editors/VideoBlockEditor.tsx +2 -2
- package/components/builder/editors/index.ts +11 -10
- package/components/builder/editors/shared.tsx +6 -6
- package/components/builder/live-preview/LiveAudioPreview.tsx +120 -120
- package/components/builder/live-preview/LiveBeforeAfterPreview.tsx +1 -1
- package/components/builder/live-preview/LiveImageGridPreview.tsx +10 -2
- package/components/builder/live-preview/LiveImagePreview.tsx +1 -1
- package/components/builder/live-preview/LiveMarqueePreview.tsx +39 -0
- package/components/builder/live-preview/LiveProjectCarouselPreview.tsx +1 -1
- package/components/builder/live-preview/LiveVideoPreview.tsx +1 -1
- package/components/builder/live-preview/ProjectCardWrapper.tsx +291 -291
- package/components/builder/settings-panel/AnimationTab.tsx +138 -138
- package/components/builder/settings-panel/BlockLayoutTab.tsx +7 -7
- package/components/builder/settings-panel/CardEntranceSection.tsx +114 -114
- package/components/builder/settings-panel/ColumnV2Settings.tsx +5 -5
- package/components/builder/settings-panel/CoverSectionLayoutTab.tsx +71 -71
- package/components/builder/settings-panel/CoverSectionSettings.tsx +335 -335
- package/components/builder/settings-panel/PageSettings.tsx +3 -3
- package/components/builder/settings-panel/ParallaxSlideSettings.tsx +2 -2
- package/components/builder/settings-panel/SectionV2AnimationTab.tsx +4 -4
- package/components/builder/settings-panel/SectionV2LayoutTab.tsx +356 -356
- package/components/builder/settings-panel/SectionV2Settings.tsx +14 -14
- package/components/builder/settings-panel/TRBLInputs.tsx +1 -1
- package/lib/animation/enter-types.ts +1 -0
- package/lib/animation/hover-effect-presets.ts +210 -210
- package/lib/animation/hover-effect-types.ts +1 -0
- package/lib/builder/block-registrations.ts +468 -417
- package/lib/builder/constants.ts +111 -111
- package/lib/builder/store-sections.ts +2 -2
- package/lib/builder/types-slices.ts +414 -414
- package/lib/builder/types.ts +4 -1
- package/lib/config/index.ts +27 -27
- package/lib/sanity/types.ts +98 -1
- package/lib/version.ts +1 -1
- package/package.json +1 -1
- package/sanity/schemas/blocks/audioBlock.ts +69 -69
- package/sanity/schemas/blocks/index.ts +12 -11
- package/sanity/schemas/blocks/marqueeBlock.ts +292 -0
- package/sanity/schemas/index.ts +120 -117
- package/styles/admin.css +85 -85
- package/styles/animations.css +237 -237
- package/styles/base.css +114 -114
|
@@ -155,7 +155,7 @@ export default function SectionEditorBar({ onSaveComplete }: SectionEditorBarPro
|
|
|
155
155
|
value={name}
|
|
156
156
|
onChange={(e) => setName(e.target.value)}
|
|
157
157
|
placeholder="Section name..."
|
|
158
|
-
className="bg-[#2a2a2a] text-white text-sm px-3 py-1.5 rounded border border-[#3a3a3a] focus:border-[#
|
|
158
|
+
className="bg-[#2a2a2a] text-white text-sm px-3 py-1.5 rounded border border-[#3a3a3a] focus:border-[#3580f9] focus:outline-none w-64 transition-colors text-center"
|
|
159
159
|
/>
|
|
160
160
|
</div>
|
|
161
161
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import { useState, useEffect } from "react";
|
|
4
|
-
import { SECTION_TYPE_REGISTRY } from "../../lib/builder/types";
|
|
4
|
+
import { SECTION_TYPE_REGISTRY, type SectionBlockType } from "../../lib/builder/types";
|
|
5
5
|
import type { CustomSectionListItem } from "../../lib/sanity/types";
|
|
6
6
|
import { SECTION_CARD_ICONS } from "./SectionCardIcons";
|
|
7
7
|
|
|
@@ -83,7 +83,7 @@ function SectionCard({
|
|
|
83
83
|
|
|
84
84
|
interface SectionTypePickerProps {
|
|
85
85
|
onSelectEmptyV2?: (preset: "full" | "halves" | "thirds" | "quarters" | "1/3+2/3" | "2/3+1/3") => void;
|
|
86
|
-
onSelectSection: (blockType:
|
|
86
|
+
onSelectSection: (blockType: SectionBlockType) => void;
|
|
87
87
|
onSelectParallaxGroup?: () => void;
|
|
88
88
|
onSelectCoverSection?: () => void;
|
|
89
89
|
onSelectCustomSection?: (section: CustomSectionListItem) => void;
|
|
@@ -212,14 +212,14 @@ export default function SectionTypePicker({
|
|
|
212
212
|
}
|
|
213
213
|
onClose();
|
|
214
214
|
}}
|
|
215
|
-
className="rounded-xl border border-neutral-200 bg-white p-3 hover:border-[#
|
|
215
|
+
className="rounded-xl border border-neutral-200 bg-white p-3 hover:border-[#3580f9] hover:bg-[#3580f9]/5 transition-colors group shadow-sm"
|
|
216
216
|
title={label}
|
|
217
217
|
>
|
|
218
218
|
<div className="flex gap-1 h-6">
|
|
219
219
|
{widths.map((w, i) => (
|
|
220
220
|
<div
|
|
221
221
|
key={i}
|
|
222
|
-
className="bg-neutral-200 group-hover:bg-[#
|
|
222
|
+
className="bg-neutral-200 group-hover:bg-[#3580f9]/30 rounded-sm transition-colors"
|
|
223
223
|
style={{ flex: w }}
|
|
224
224
|
/>
|
|
225
225
|
))}
|
|
@@ -310,7 +310,7 @@ export default function SectionV2Canvas({
|
|
|
310
310
|
: showAsDropTarget
|
|
311
311
|
? "border-blue-500/40 text-blue-500/60 bg-blue-500/5 opacity-100"
|
|
312
312
|
: isSectionHovered
|
|
313
|
-
? "border-[#
|
|
313
|
+
? "border-[#3580f9]/25 text-[#3580f9]/50 hover:text-[#3580f9] hover:border-[#3580f9]/60 hover:bg-[#3580f9]/5 opacity-100"
|
|
314
314
|
: "border-transparent text-transparent opacity-0 pointer-events-none"
|
|
315
315
|
}`}
|
|
316
316
|
>
|
|
@@ -12,6 +12,7 @@ import type { SectionColumn, ContentBlock, PageSectionV2 } from "../../lib/sanit
|
|
|
12
12
|
import { getColumnVerticalAlign } from "../../lib/builder/layout-styles";
|
|
13
13
|
import { isSectionBlockType } from "../../lib/builder/types";
|
|
14
14
|
import { BUILDER_BLUE } from "../../lib/builder/constants";
|
|
15
|
+
import { BubbleTooltip, CloseIcon, DragDropIcon } from "./BubbleIcons";
|
|
15
16
|
|
|
16
17
|
// ============================================
|
|
17
18
|
// SectionV2Column — Individual column in a V2 section grid
|
|
@@ -84,17 +85,17 @@ function ResizeHandle({
|
|
|
84
85
|
height: isActive ? 16 : isHoveredEdge ? 56 : showChrome ? 56 : 32,
|
|
85
86
|
borderRadius: isActive ? "50%" : 999,
|
|
86
87
|
backgroundColor: isActive
|
|
87
|
-
? "rgba(
|
|
88
|
+
? "rgba(53, 128, 249, 0.9)"
|
|
88
89
|
: isHoveredEdge
|
|
89
|
-
? "rgba(
|
|
90
|
+
? "rgba(53, 128, 249, 0.7)"
|
|
90
91
|
: showChrome
|
|
91
|
-
? "rgba(
|
|
92
|
-
: "rgba(
|
|
92
|
+
? "rgba(53, 128, 249, 0.5)"
|
|
93
|
+
: "rgba(53, 128, 249, 0.2)",
|
|
93
94
|
transition: "width 150ms ease-out, height 150ms ease-out, border-radius 150ms ease-out, background-color 150ms, box-shadow 150ms",
|
|
94
95
|
boxShadow: isActive
|
|
95
|
-
? "0 0 10px rgba(
|
|
96
|
+
? "0 0 10px rgba(53, 128, 249, 0.5)"
|
|
96
97
|
: isHoveredEdge
|
|
97
|
-
? "0 0 6px rgba(
|
|
98
|
+
? "0 0 6px rgba(53, 128, 249, 0.2)"
|
|
98
99
|
: undefined,
|
|
99
100
|
}}
|
|
100
101
|
/>
|
|
@@ -289,17 +290,17 @@ export default function SectionV2Column({
|
|
|
289
290
|
<div
|
|
290
291
|
className="pointer-events-none absolute inset-0 z-[1] rounded"
|
|
291
292
|
style={{
|
|
292
|
-
transition: "box-shadow 150ms
|
|
293
|
+
transition: "box-shadow 150ms",
|
|
293
294
|
...(isSwapTarget
|
|
294
|
-
? { boxShadow: `inset 0 0 0 2px ${BUILDER_BLUE}`, background: "rgba(
|
|
295
|
+
? { boxShadow: `inset 0 0 0 2px ${BUILDER_BLUE}`, background: "rgba(53, 128, 249, 0.08)" }
|
|
295
296
|
: isBlockOver
|
|
296
297
|
? { boxShadow: `inset 0 0 0 2px ${BUILDER_BLUE}` }
|
|
297
298
|
: isSelected
|
|
298
|
-
? { boxShadow: `inset 0 0 0 2px rgba(
|
|
299
|
+
? { boxShadow: `inset 0 0 0 2px rgba(53, 128, 249, 0.6)` }
|
|
299
300
|
: isHovered
|
|
300
|
-
? { boxShadow: `inset 0 0 0 1.5px rgba(
|
|
301
|
+
? { boxShadow: `inset 0 0 0 1.5px rgba(53, 128, 249, 0.5)` }
|
|
301
302
|
: showFaintOutline
|
|
302
|
-
? {
|
|
303
|
+
? { boxShadow: `inset 0 0 0 1px rgba(53, 128, 249, 0.2)`, borderRadius: 4 }
|
|
303
304
|
: undefined),
|
|
304
305
|
}}
|
|
305
306
|
/>
|
|
@@ -354,64 +355,65 @@ export default function SectionV2Column({
|
|
|
354
355
|
</>
|
|
355
356
|
)}
|
|
356
357
|
|
|
357
|
-
{/*
|
|
358
|
+
{/* Column pill — single horizontal pill sitting OUTSIDE the column top-left edge,
|
|
359
|
+
with a 2px gap so its border never touches the column outline. Structure:
|
|
360
|
+
[drag-drop] | Col | [X]. Aligns horizontally with the block pill when the
|
|
361
|
+
first block in the column has no vertical offset.
|
|
358
362
|
Nested pattern: outer div positions, inner div counter-scales. */}
|
|
359
363
|
<div
|
|
360
|
-
className={`absolute z-[6] transition-opacity ${
|
|
364
|
+
className={`absolute top-0 left-0 z-[6] transition-opacity ${
|
|
361
365
|
showChrome ? "opacity-100" : "opacity-0 pointer-events-none"
|
|
362
366
|
}`}
|
|
363
|
-
style={{
|
|
364
|
-
top: 0,
|
|
365
|
-
right: 0,
|
|
366
|
-
transform: "translate(40%, -40%)",
|
|
367
|
-
}}
|
|
367
|
+
style={{ transform: "translateY(calc(-100% - 2px))" }}
|
|
368
368
|
onClick={(e) => e.stopPropagation()}
|
|
369
369
|
>
|
|
370
|
-
<div style={{ transform: `scale(${1 / canvasZoom})`, transformOrigin: "
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
<
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
370
|
+
<div style={{ transform: `scale(${Math.min(2, 1 / canvasZoom)})`, transformOrigin: "bottom left" }}>
|
|
371
|
+
<div
|
|
372
|
+
className="flex items-center rounded-[5px]"
|
|
373
|
+
style={{
|
|
374
|
+
background: "#c0d7ff",
|
|
375
|
+
border: "1.5px solid #3580f9",
|
|
376
|
+
}}
|
|
377
|
+
>
|
|
378
|
+
{/* Drag handle — starts a column drag via @dnd-kit, selects on click */}
|
|
379
|
+
<button
|
|
380
|
+
type="button"
|
|
381
|
+
className="group/bb relative text-[#3580f9] hover:bg-[#3580f9]/15 transition-colors px-1.5 py-1 flex items-center justify-center cursor-grab active:cursor-grabbing rounded-l-[3px]"
|
|
382
|
+
aria-label="Move column"
|
|
383
|
+
onMouseDown={(e) => {
|
|
384
|
+
e.stopPropagation();
|
|
385
|
+
onStartDrag?.(e);
|
|
386
|
+
}}
|
|
387
|
+
onClick={(e) => {
|
|
388
|
+
e.stopPropagation();
|
|
389
|
+
onSelect();
|
|
390
|
+
}}
|
|
391
|
+
>
|
|
392
|
+
<DragDropIcon size={14} />
|
|
393
|
+
<BubbleTooltip>Drag to move</BubbleTooltip>
|
|
394
|
+
</button>
|
|
395
|
+
<div className="w-px self-stretch my-1" style={{ background: "#3580f9" }} />
|
|
396
|
+
{/* Label — clicking selects the column */}
|
|
397
|
+
<button
|
|
398
|
+
type="button"
|
|
399
|
+
onClick={(e) => { e.stopPropagation(); onSelect(); }}
|
|
400
|
+
className="text-[11px] px-2 py-0.5 font-medium hover:bg-[#3580f9]/15 transition-colors"
|
|
401
|
+
style={{ color: "#3580f9" }}
|
|
402
|
+
aria-label="Select column"
|
|
403
|
+
>
|
|
404
|
+
Col
|
|
405
|
+
</button>
|
|
406
|
+
<div className="w-px self-stretch my-1" style={{ background: "#3580f9" }} />
|
|
407
|
+
{/* Delete */}
|
|
408
|
+
<button
|
|
409
|
+
onClick={handleDelete}
|
|
410
|
+
className="group/bb relative text-[#3580f9] hover:text-red-500 hover:bg-red-500/10 transition-colors px-1.5 py-1 flex items-center justify-center rounded-r-[3px]"
|
|
411
|
+
aria-label="Delete column"
|
|
412
|
+
>
|
|
413
|
+
<CloseIcon size={14} />
|
|
414
|
+
<BubbleTooltip>Delete</BubbleTooltip>
|
|
415
|
+
</button>
|
|
416
|
+
</div>
|
|
415
417
|
</div>
|
|
416
418
|
</div>
|
|
417
419
|
|
|
@@ -473,8 +475,8 @@ export default function SectionV2Column({
|
|
|
473
475
|
padding: "5px 16px",
|
|
474
476
|
pointerEvents: showChrome || showFaintOutline ? "auto" : "none",
|
|
475
477
|
background: "#d2e3ff",
|
|
476
|
-
color: "#
|
|
477
|
-
border: "
|
|
478
|
+
color: "#3580f9",
|
|
479
|
+
border: "1.5px dashed #3580f9",
|
|
478
480
|
}}
|
|
479
481
|
>
|
|
480
482
|
+ Add Block
|
|
@@ -502,8 +504,8 @@ export default function SectionV2Column({
|
|
|
502
504
|
padding: "4px 14px",
|
|
503
505
|
pointerEvents: showChrome ? "auto" : "none",
|
|
504
506
|
background: "#d2e3ff",
|
|
505
|
-
color: "#
|
|
506
|
-
border: "
|
|
507
|
+
color: "#3580f9",
|
|
508
|
+
border: "1.5px dashed #3580f9",
|
|
507
509
|
}}
|
|
508
510
|
>
|
|
509
511
|
+ Add Block
|
|
@@ -12,6 +12,7 @@ import BlockLivePreview from "./BlockLivePreview";
|
|
|
12
12
|
import { getBlockAlignmentStyles, hasBlockAlignment } from "../../lib/builder/layout-styles";
|
|
13
13
|
import type { BlockLayout } from "../../lib/sanity/types";
|
|
14
14
|
import { BUILDER_BLOCK } from "../../lib/builder/constants";
|
|
15
|
+
import { ArrowDownIcon, ArrowUpIcon, BubbleTooltip, CloseIcon, CopyIcon } from "./BubbleIcons";
|
|
15
16
|
|
|
16
17
|
interface SortableBlockProps {
|
|
17
18
|
block: ContentBlock;
|
|
@@ -124,7 +125,7 @@ export default function SortableBlock({
|
|
|
124
125
|
style={{ ...style, ...(!isFillBlock ? { position: "relative" as const, zIndex: 1 } : {}) }}
|
|
125
126
|
className={`transition-[opacity,box-shadow] ${
|
|
126
127
|
isDragging
|
|
127
|
-
? "ring-2 ring-[#
|
|
128
|
+
? "ring-2 ring-[#3580f9] ring-offset-1 ring-offset-transparent rounded"
|
|
128
129
|
: ""
|
|
129
130
|
}`}
|
|
130
131
|
onClick={(e) => {
|
|
@@ -145,93 +146,112 @@ export default function SortableBlock({
|
|
|
145
146
|
...(isSelected
|
|
146
147
|
? { boxShadow: `inset 0 0 0 ${Math.max(2, Math.min(5, 3 / canvasZoom))}px ${BUILDER_BLOCK}` }
|
|
147
148
|
: isHovered
|
|
148
|
-
? { boxShadow: `inset 0 0 0 ${Math.max(2, Math.min(5, 3 / canvasZoom))}px rgba(
|
|
149
|
+
? { boxShadow: `inset 0 0 0 ${Math.max(2, Math.min(5, 3 / canvasZoom))}px rgba(53, 128, 249, 0.4)` }
|
|
149
150
|
: {}),
|
|
150
151
|
}}
|
|
151
152
|
/>
|
|
152
153
|
|
|
153
|
-
{/* Floating toolbar —
|
|
154
|
+
{/* Floating toolbar — top-right of block, sitting OUTSIDE (above) with a 2px gap
|
|
155
|
+
so the pill border never overlaps the column/block outlines.
|
|
156
|
+
When the block has no vertical offset, this pill naturally aligns horizontally
|
|
157
|
+
with the column pill (both hover just above the column's top edge).
|
|
154
158
|
Positioning (translate) is separated from counter-scaling (scale) into nested
|
|
155
|
-
elements so
|
|
156
|
-
Delete button is integrated at the end of the toolbar with a gap separator. */}
|
|
159
|
+
elements so zoom changes don't shift the anchor point. */}
|
|
157
160
|
<div
|
|
158
|
-
className={`absolute top-0
|
|
161
|
+
className={`absolute top-0 right-0 z-[6] transition-opacity ${
|
|
159
162
|
showToolbar ? "opacity-100" : "opacity-0 pointer-events-none"
|
|
160
163
|
}`}
|
|
161
|
-
style={{ transform: "
|
|
164
|
+
style={{ transform: "translateY(calc(-100% - 2px))" }}
|
|
162
165
|
onClick={(e) => e.stopPropagation()}
|
|
163
166
|
>
|
|
164
167
|
<div
|
|
165
|
-
|
|
166
|
-
style={{ transform: `scale(${Math.min(2, 1 / canvasZoom)})`, transformOrigin: "top center" }}
|
|
168
|
+
style={{ transform: `scale(${Math.min(2, 1 / canvasZoom)})`, transformOrigin: "bottom right" }}
|
|
167
169
|
>
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
{/* Enter animation badge */}
|
|
177
|
-
{block.enter_animation?.preset && block.enter_animation.preset !== "none" && (
|
|
178
|
-
<span className="text-[10px] text-[#4794e2]/50 px-1 py-0.5 border-l border-[#4794e2]/25" title={`Animation: ${block.enter_animation.preset}`}>
|
|
179
|
-
✦
|
|
180
|
-
</span>
|
|
181
|
-
)}
|
|
182
|
-
{/* Duplicate */}
|
|
183
|
-
{onDuplicate && (
|
|
170
|
+
<div
|
|
171
|
+
className="flex items-center rounded-[5px]"
|
|
172
|
+
style={{
|
|
173
|
+
background: "#c0d7ff",
|
|
174
|
+
border: "1.5px solid #3580f9",
|
|
175
|
+
}}
|
|
176
|
+
>
|
|
177
|
+
{/* Block type label — clicking selects the block */}
|
|
184
178
|
<button
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
179
|
+
type="button"
|
|
180
|
+
onClick={(e) => { e.stopPropagation(); onSelect(); }}
|
|
181
|
+
className="text-[11px] px-2 py-0.5 font-medium rounded-l-[3px] hover:bg-[#3580f9]/15 transition-colors"
|
|
182
|
+
style={{ color: "#3580f9" }}
|
|
183
|
+
aria-label="Select block"
|
|
189
184
|
>
|
|
190
|
-
|
|
185
|
+
{info?.label || block._type}
|
|
191
186
|
</button>
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
<
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
187
|
+
{/* Enter animation badge */}
|
|
188
|
+
{block.enter_animation?.preset && block.enter_animation.preset !== "none" && (
|
|
189
|
+
<>
|
|
190
|
+
<div className="w-px self-stretch my-1" style={{ background: "#3580f9" }} />
|
|
191
|
+
<span
|
|
192
|
+
className="text-[10px] px-1 py-0.5"
|
|
193
|
+
style={{ color: "#3580f9" }}
|
|
194
|
+
title={`Animation: ${block.enter_animation.preset}`}
|
|
195
|
+
>
|
|
196
|
+
✦
|
|
197
|
+
</span>
|
|
198
|
+
</>
|
|
199
|
+
)}
|
|
200
|
+
{/* Duplicate */}
|
|
201
|
+
{onDuplicate && (
|
|
202
|
+
<>
|
|
203
|
+
<div className="w-px self-stretch my-1" style={{ background: "#3580f9" }} />
|
|
204
|
+
<button
|
|
205
|
+
onClick={onDuplicate}
|
|
206
|
+
className="group/bb relative text-[#3580f9] hover:bg-[#3580f9]/15 transition-colors px-1.5 py-1 flex items-center justify-center"
|
|
207
|
+
aria-label="Duplicate block"
|
|
208
|
+
>
|
|
209
|
+
<CopyIcon size={14} />
|
|
210
|
+
<BubbleTooltip>Duplicate (⌘D)</BubbleTooltip>
|
|
211
|
+
</button>
|
|
212
|
+
</>
|
|
213
|
+
)}
|
|
214
|
+
{/* Move up */}
|
|
215
|
+
<div className="w-px self-stretch my-1" style={{ background: "#3580f9" }} />
|
|
216
|
+
<button
|
|
217
|
+
onClick={() => canMoveUp && reorderBlocks(rowKey, colKey, blockIndex, blockIndex - 1)}
|
|
218
|
+
className={`group/bb relative transition-colors px-1.5 py-1 flex items-center justify-center ${
|
|
219
|
+
canMoveUp
|
|
220
|
+
? "text-[#3580f9] hover:bg-[#3580f9]/15 cursor-pointer"
|
|
221
|
+
: "text-[#3580f9]/40 cursor-default"
|
|
222
|
+
}`}
|
|
223
|
+
aria-label="Move block up"
|
|
224
|
+
disabled={!canMoveUp}
|
|
225
|
+
>
|
|
226
|
+
<ArrowUpIcon size={14} />
|
|
227
|
+
{canMoveUp && <BubbleTooltip>Move up</BubbleTooltip>}
|
|
228
|
+
</button>
|
|
229
|
+
{/* Move down */}
|
|
230
|
+
<div className="w-px self-stretch my-1" style={{ background: "#3580f9" }} />
|
|
231
|
+
<button
|
|
232
|
+
onClick={() => canMoveDown && reorderBlocks(rowKey, colKey, blockIndex, blockIndex + 1)}
|
|
233
|
+
className={`group/bb relative transition-colors px-1.5 py-1 flex items-center justify-center ${
|
|
234
|
+
canMoveDown
|
|
235
|
+
? "text-[#3580f9] hover:bg-[#3580f9]/15 cursor-pointer"
|
|
236
|
+
: "text-[#3580f9]/40 cursor-default"
|
|
237
|
+
}`}
|
|
238
|
+
aria-label="Move block down"
|
|
239
|
+
disabled={!canMoveDown}
|
|
240
|
+
>
|
|
241
|
+
<ArrowDownIcon size={14} />
|
|
242
|
+
{canMoveDown && <BubbleTooltip>Move down</BubbleTooltip>}
|
|
243
|
+
</button>
|
|
244
|
+
{/* Delete — red hover for destructive cue */}
|
|
245
|
+
<div className="w-px self-stretch my-1" style={{ background: "#3580f9" }} />
|
|
246
|
+
<button
|
|
247
|
+
onClick={onDelete}
|
|
248
|
+
className="group/bb relative text-[#3580f9] hover:text-red-500 hover:bg-red-500/10 transition-colors px-1.5 py-1 flex items-center justify-center rounded-r-[3px]"
|
|
249
|
+
aria-label="Delete block"
|
|
250
|
+
>
|
|
251
|
+
<CloseIcon size={14} />
|
|
252
|
+
<BubbleTooltip>Delete</BubbleTooltip>
|
|
253
|
+
</button>
|
|
254
|
+
</div>
|
|
235
255
|
</div>
|
|
236
256
|
</div>
|
|
237
257
|
|
|
@@ -14,6 +14,7 @@ import { getRowLayoutStyles } from "../../lib/builder/layout-styles";
|
|
|
14
14
|
import { normalizeMinHeight } from "../../lib/builder/utils";
|
|
15
15
|
import { getSectionV2SettingValue } from "./settings-panel/responsive-helpers";
|
|
16
16
|
import { formatRowPercent } from "../../lib/builder/format";
|
|
17
|
+
import { ArrowDownIcon, ArrowUpIcon, BubbleTooltip, CloseIcon, CopyIcon } from "./BubbleIcons";
|
|
17
18
|
|
|
18
19
|
/**
|
|
19
20
|
* Convert vh-based CSS values to pixels using the simulated device viewport height.
|
|
@@ -261,7 +262,7 @@ export default function SortableRow({
|
|
|
261
262
|
showToolbar ? "opacity-100" : "opacity-0 pointer-events-none"
|
|
262
263
|
}`}
|
|
263
264
|
style={{
|
|
264
|
-
transform: `translateX(calc(-100% - 8px)) scale(${Math.min(
|
|
265
|
+
transform: `translateX(calc(-100% - 8px)) scale(${Math.min(2, 1 / canvasZoom)})`,
|
|
265
266
|
transformOrigin: "top right",
|
|
266
267
|
width: "90px",
|
|
267
268
|
}}
|
|
@@ -279,7 +280,7 @@ export default function SortableRow({
|
|
|
279
280
|
className="flex flex-col items-stretch rounded-lg py-2 px-2.5 gap-1 cursor-grab active:cursor-grabbing"
|
|
280
281
|
style={{
|
|
281
282
|
background: "#e0daff",
|
|
282
|
-
border: "
|
|
283
|
+
border: "1.5px solid #7500d5",
|
|
283
284
|
}}
|
|
284
285
|
{...attributes}
|
|
285
286
|
{...listeners}
|
|
@@ -294,40 +295,40 @@ export default function SortableRow({
|
|
|
294
295
|
<button
|
|
295
296
|
onClick={(e) => { e.stopPropagation(); onDuplicate(); }}
|
|
296
297
|
onPointerDown={(e) => e.stopPropagation()}
|
|
297
|
-
className="flex items-center justify-center
|
|
298
|
+
className="group/bb relative flex items-center justify-center transition-colors"
|
|
298
299
|
style={{ color: "rgba(117, 0, 213, 0.6)" }}
|
|
299
300
|
onMouseEnter={(e) => { e.currentTarget.style.color = "#7500d5"; }}
|
|
300
301
|
onMouseLeave={(e) => { e.currentTarget.style.color = "rgba(117, 0, 213, 0.6)"; }}
|
|
301
|
-
title="Duplicate section"
|
|
302
302
|
aria-label="Duplicate section"
|
|
303
303
|
>
|
|
304
|
-
|
|
304
|
+
<CopyIcon size={14} />
|
|
305
|
+
<BubbleTooltip>Duplicate</BubbleTooltip>
|
|
305
306
|
</button>
|
|
306
307
|
<button
|
|
307
308
|
onClick={(e) => { e.stopPropagation(); onMoveUp(); }}
|
|
308
309
|
onPointerDown={(e) => e.stopPropagation()}
|
|
309
310
|
disabled={isFirst}
|
|
310
|
-
className="flex items-center justify-center
|
|
311
|
+
className="group/bb relative flex items-center justify-center transition-colors disabled:opacity-30 disabled:cursor-not-allowed"
|
|
311
312
|
style={{ color: "rgba(117, 0, 213, 0.6)" }}
|
|
312
313
|
onMouseEnter={(e) => { if (!e.currentTarget.disabled) e.currentTarget.style.color = "#7500d5"; }}
|
|
313
314
|
onMouseLeave={(e) => { if (!e.currentTarget.disabled) e.currentTarget.style.color = "rgba(117, 0, 213, 0.6)"; }}
|
|
314
|
-
title="Move up"
|
|
315
315
|
aria-label="Move section up"
|
|
316
316
|
>
|
|
317
|
-
|
|
317
|
+
<ArrowUpIcon size={14} />
|
|
318
|
+
{!isFirst && <BubbleTooltip>Move up</BubbleTooltip>}
|
|
318
319
|
</button>
|
|
319
320
|
<button
|
|
320
321
|
onClick={(e) => { e.stopPropagation(); onMoveDown(); }}
|
|
321
322
|
onPointerDown={(e) => e.stopPropagation()}
|
|
322
323
|
disabled={isLast}
|
|
323
|
-
className="flex items-center justify-center
|
|
324
|
+
className="group/bb relative flex items-center justify-center transition-colors disabled:opacity-30 disabled:cursor-not-allowed"
|
|
324
325
|
style={{ color: "rgba(117, 0, 213, 0.6)" }}
|
|
325
326
|
onMouseEnter={(e) => { if (!e.currentTarget.disabled) e.currentTarget.style.color = "#7500d5"; }}
|
|
326
327
|
onMouseLeave={(e) => { if (!e.currentTarget.disabled) e.currentTarget.style.color = "rgba(117, 0, 213, 0.6)"; }}
|
|
327
|
-
title="Move down"
|
|
328
328
|
aria-label="Move section down"
|
|
329
329
|
>
|
|
330
|
-
|
|
330
|
+
<ArrowDownIcon size={14} />
|
|
331
|
+
{!isLast && <BubbleTooltip>Move down</BubbleTooltip>}
|
|
331
332
|
</button>
|
|
332
333
|
</div>
|
|
333
334
|
|
|
@@ -358,7 +359,7 @@ export default function SortableRow({
|
|
|
358
359
|
title="Delete section"
|
|
359
360
|
aria-label="Delete section"
|
|
360
361
|
>
|
|
361
|
-
<
|
|
362
|
+
<CloseIcon size={12} /> Delete
|
|
362
363
|
</button>
|
|
363
364
|
</div>
|
|
364
365
|
|
|
@@ -375,7 +376,7 @@ export default function SortableRow({
|
|
|
375
376
|
className="flex flex-col items-stretch rounded-lg py-1.5 px-2 mt-2 gap-0.5"
|
|
376
377
|
style={{
|
|
377
378
|
background: "#e0daff",
|
|
378
|
-
border: "
|
|
379
|
+
border: "1.5px solid #7500d5",
|
|
379
380
|
}}
|
|
380
381
|
onClick={(e) => e.stopPropagation()}
|
|
381
382
|
>
|
|
@@ -391,14 +392,14 @@ export default function SortableRow({
|
|
|
391
392
|
}}
|
|
392
393
|
onPointerDown={(e) => e.stopPropagation()}
|
|
393
394
|
disabled={!canRemoveRow}
|
|
394
|
-
className="flex items-center justify-center
|
|
395
|
+
className="group/bb relative flex items-center justify-center leading-none transition-colors disabled:opacity-30 disabled:cursor-not-allowed"
|
|
395
396
|
style={{ color: "rgba(117, 0, 213, 0.6)" }}
|
|
396
397
|
onMouseEnter={(e) => { if (!e.currentTarget.disabled) e.currentTarget.style.color = "#7500d5"; }}
|
|
397
398
|
onMouseLeave={(e) => { if (!e.currentTarget.disabled) e.currentTarget.style.color = "rgba(117, 0, 213, 0.6)"; }}
|
|
398
|
-
title={canRemoveRow ? "Remove row" : "Cover must have at least 1 row"}
|
|
399
399
|
aria-label="Remove row"
|
|
400
400
|
>
|
|
401
|
-
|
|
401
|
+
<CloseIcon size={12} />
|
|
402
|
+
{canRemoveRow && <BubbleTooltip>Remove row</BubbleTooltip>}
|
|
402
403
|
</button>
|
|
403
404
|
</div>
|
|
404
405
|
))}
|
|
@@ -436,7 +437,7 @@ export default function SortableRow({
|
|
|
436
437
|
className="flex flex-col items-stretch rounded-lg py-1.5 px-2 mt-2 gap-0.5"
|
|
437
438
|
style={{
|
|
438
439
|
background: "#e0daff",
|
|
439
|
-
border: "
|
|
440
|
+
border: "1.5px solid #7500d5",
|
|
440
441
|
}}
|
|
441
442
|
onClick={(e) => e.stopPropagation()}
|
|
442
443
|
>
|
|
@@ -462,14 +463,14 @@ export default function SortableRow({
|
|
|
462
463
|
}}
|
|
463
464
|
onPointerDown={(e) => e.stopPropagation()}
|
|
464
465
|
disabled={idx === 0}
|
|
465
|
-
className="flex items-center justify-center
|
|
466
|
+
className="group/bb relative flex items-center justify-center leading-none transition-colors disabled:opacity-30 disabled:cursor-not-allowed"
|
|
466
467
|
style={{ color: "rgba(117, 0, 213, 0.6)" }}
|
|
467
468
|
onMouseEnter={(e) => { if (!e.currentTarget.disabled) e.currentTarget.style.color = "#7500d5"; }}
|
|
468
469
|
onMouseLeave={(e) => { if (!e.currentTarget.disabled) e.currentTarget.style.color = "rgba(117, 0, 213, 0.6)"; }}
|
|
469
|
-
title="Move slide up"
|
|
470
470
|
aria-label="Move slide up"
|
|
471
471
|
>
|
|
472
|
-
|
|
472
|
+
<ArrowUpIcon size={12} />
|
|
473
|
+
{idx > 0 && <BubbleTooltip>Move up</BubbleTooltip>}
|
|
473
474
|
</button>
|
|
474
475
|
<button
|
|
475
476
|
onClick={(e) => {
|
|
@@ -478,14 +479,14 @@ export default function SortableRow({
|
|
|
478
479
|
}}
|
|
479
480
|
onPointerDown={(e) => e.stopPropagation()}
|
|
480
481
|
disabled={idx === slides.length - 1}
|
|
481
|
-
className="flex items-center justify-center
|
|
482
|
+
className="group/bb relative flex items-center justify-center leading-none transition-colors disabled:opacity-30 disabled:cursor-not-allowed"
|
|
482
483
|
style={{ color: "rgba(117, 0, 213, 0.6)" }}
|
|
483
484
|
onMouseEnter={(e) => { if (!e.currentTarget.disabled) e.currentTarget.style.color = "#7500d5"; }}
|
|
484
485
|
onMouseLeave={(e) => { if (!e.currentTarget.disabled) e.currentTarget.style.color = "rgba(117, 0, 213, 0.6)"; }}
|
|
485
|
-
title="Move slide down"
|
|
486
486
|
aria-label="Move slide down"
|
|
487
487
|
>
|
|
488
|
-
|
|
488
|
+
<ArrowDownIcon size={12} />
|
|
489
|
+
{idx < slides.length - 1 && <BubbleTooltip>Move down</BubbleTooltip>}
|
|
489
490
|
</button>
|
|
490
491
|
<button
|
|
491
492
|
onClick={(e) => {
|
|
@@ -494,14 +495,14 @@ export default function SortableRow({
|
|
|
494
495
|
}}
|
|
495
496
|
onPointerDown={(e) => e.stopPropagation()}
|
|
496
497
|
disabled={!canRemoveSlide}
|
|
497
|
-
className="flex items-center justify-center
|
|
498
|
+
className="group/bb relative flex items-center justify-center leading-none transition-colors disabled:opacity-30 disabled:cursor-not-allowed"
|
|
498
499
|
style={{ color: "rgba(117, 0, 213, 0.6)" }}
|
|
499
500
|
onMouseEnter={(e) => { if (!e.currentTarget.disabled) e.currentTarget.style.color = "#7500d5"; }}
|
|
500
501
|
onMouseLeave={(e) => { if (!e.currentTarget.disabled) e.currentTarget.style.color = "rgba(117, 0, 213, 0.6)"; }}
|
|
501
|
-
title={canRemoveSlide ? "Remove slide" : "Parallax must have at least 1 slide"}
|
|
502
502
|
aria-label="Remove slide"
|
|
503
503
|
>
|
|
504
|
-
|
|
504
|
+
<CloseIcon size={12} />
|
|
505
|
+
{canRemoveSlide && <BubbleTooltip>Remove slide</BubbleTooltip>}
|
|
505
506
|
</button>
|
|
506
507
|
</div>
|
|
507
508
|
</div>
|