@morphika/andami 0.5.1 → 0.5.3
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/README.md +27 -2
- 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 +332 -320
- package/app/admin/navigation/page.tsx +255 -255
- package/app/admin/pages/[slug]/page.tsx +44 -27
- package/app/admin/pages/page.tsx +24 -19
- package/app/admin/projects/page.tsx +30 -21
- package/app/admin/setup/page.tsx +1 -1
- package/app/admin/styles/page.tsx +1 -1
- package/app/api/admin/assets/register/route.ts +51 -14
- package/app/api/admin/assets/registry/route.ts +4 -1
- package/app/api/admin/assets/relink/confirm/route.ts +4 -1
- package/app/api/admin/assets/relink/route.ts +4 -1
- package/app/api/admin/assets/scan/route.ts +4 -1
- package/app/api/admin/backups/restore-data/route.ts +4 -1
- package/app/api/admin/r2/connect/route.ts +4 -1
- package/app/api/admin/r2/delete/route.ts +4 -1
- package/app/api/admin/r2/rename/route.ts +4 -1
- package/app/api/admin/r2/upload-url/route.ts +4 -1
- package/app/api/admin/revalidate/route.ts +4 -1
- package/app/api/admin/storage/switch/route.ts +4 -1
- package/app/api/custom-sections/[id]/route.ts +5 -6
- package/components/admin/MetadataEditor.tsx +6 -6
- package/components/admin/PublishToggle.tsx +2 -2
- 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 +8 -6
- 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 +518 -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 +9 -8
- package/components/admin/styles/FontsEditor.tsx +9 -7
- 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/CoverSectionRenderer.tsx +7 -1
- package/components/blocks/MarqueeBlockRenderer.tsx +316 -0
- package/components/blocks/ProjectCarouselBlockRenderer.tsx +1 -1
- package/components/blocks/SectionV2Renderer.tsx +8 -1
- package/components/builder/BlockCardIcons.tsx +316 -316
- package/components/builder/BlockTypePicker.tsx +1 -1
- package/components/builder/BubbleIcons.tsx +104 -0
- package/components/builder/BuilderCanvas.tsx +2 -0
- package/components/builder/CanvasMinimap.tsx +66 -49
- package/components/builder/CanvasToolbar.tsx +31 -41
- 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 +5 -3
- package/components/builder/SectionTypePicker.tsx +7 -5
- package/components/builder/SectionV2Canvas.tsx +1 -1
- package/components/builder/SectionV2Column.tsx +82 -68
- package/components/builder/SettingsPanel.tsx +21 -17
- package/components/builder/SortableBlock.tsx +93 -73
- package/components/builder/SortableRow.tsx +33 -35
- package/components/builder/VirtualAssetGrid.tsx +10 -4
- package/components/builder/asset-browser/R2BrowserContent.tsx +18 -14
- 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 +75 -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 +98 -93
- package/components/builder/color-picker/UnifiedColorPicker.tsx +11 -6
- 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 +8 -6
- package/components/builder/editors/MarqueeBlockEditor.tsx +622 -0
- package/components/builder/editors/ProjectCarouselBlockEditor.tsx +443 -443
- package/components/builder/editors/ProjectGridEditor.tsx +21 -16
- package/components/builder/editors/SpacerBlockEditor.tsx +29 -27
- package/components/builder/editors/StaggerSettings.tsx +109 -109
- package/components/builder/editors/TextBlockEditor.tsx +22 -17
- 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 +10 -8
- 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 +4 -2
- 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 +293 -291
- package/components/builder/live-preview/RichTextBubbleMenu.tsx +10 -6
- package/components/builder/live-preview/shared.tsx +5 -2
- package/components/builder/settings-panel/AnimationTab.tsx +138 -138
- package/components/builder/settings-panel/BlockLayoutTab.tsx +11 -9
- package/components/builder/settings-panel/CardEntranceSection.tsx +114 -114
- package/components/builder/settings-panel/ColumnV2LayoutTab.tsx +242 -0
- 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 +337 -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 +25 -20
- package/components/builder/settings-panel/TRBLInputs.tsx +1 -1
- package/components/builder/settings-panel/index.ts +1 -0
- 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/serializer/normalizers.ts +14 -0
- package/lib/builder/serializer/serializers.ts +27 -0
- package/lib/builder/store-sections.ts +23 -2
- package/lib/builder/types-slices.ts +428 -414
- package/lib/builder/types.ts +4 -1
- package/lib/config/index.ts +27 -27
- package/lib/sanity/queries.ts +48 -0
- package/lib/sanity/types.ts +112 -1
- package/lib/version.ts +1 -1
- package/package.json +7 -5
- 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/sanity/schemas/objects/coverSection.ts +32 -0
- package/sanity/schemas/objects/parallaxSlide.ts +32 -0
- package/sanity/schemas/pageSectionV2.ts +32 -0
- package/styles/admin.css +85 -85
- package/styles/animations.css +237 -237
- package/styles/base.css +114 -114
|
@@ -4,6 +4,7 @@ import { useState, useCallback, useRef, useEffect } from "react";
|
|
|
4
4
|
import { useBuilderStore } from "../../lib/builder/store";
|
|
5
5
|
import { getCsrfToken } from "../../lib/csrf-client";
|
|
6
6
|
import { ADMIN_ACCENT } from "../../lib/builder/constants";
|
|
7
|
+
import { BubbleTooltip } from "./BubbleIcons";
|
|
7
8
|
|
|
8
9
|
// ============================================
|
|
9
10
|
// SectionEditorBar — Top bar for custom section editor mode
|
|
@@ -134,10 +135,11 @@ export default function SectionEditorBar({ onSaveComplete }: SectionEditorBarPro
|
|
|
134
135
|
<span className="text-[#444]">/</span>
|
|
135
136
|
<button
|
|
136
137
|
onClick={handleCancel}
|
|
137
|
-
className="text-[#666] hover:text-[#aaa] transition-colors cursor-pointer truncate max-w-[140px]"
|
|
138
|
-
|
|
138
|
+
className="group/bb relative text-[#666] hover:text-[#aaa] transition-colors cursor-pointer truncate max-w-[140px]"
|
|
139
|
+
aria-label={`Back to ${pageTitle || "page"}`}
|
|
139
140
|
>
|
|
140
141
|
{pageTitle || "Page"}
|
|
142
|
+
<BubbleTooltip>{`Back to ${pageTitle || "page"}`}</BubbleTooltip>
|
|
141
143
|
</button>
|
|
142
144
|
<span className="text-[#444]">/</span>
|
|
143
145
|
<span className="text-[#999] font-medium">
|
|
@@ -155,7 +157,7 @@ export default function SectionEditorBar({ onSaveComplete }: SectionEditorBarPro
|
|
|
155
157
|
value={name}
|
|
156
158
|
onChange={(e) => setName(e.target.value)}
|
|
157
159
|
placeholder="Section name..."
|
|
158
|
-
className="bg-[#2a2a2a] text-white text-sm px-3 py-1.5 rounded border border-[#3a3a3a] focus:border-[#
|
|
160
|
+
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
161
|
/>
|
|
160
162
|
</div>
|
|
161
163
|
|
|
@@ -1,9 +1,10 @@
|
|
|
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
|
+
import { BubbleTooltip } from "./BubbleIcons";
|
|
7
8
|
|
|
8
9
|
// ── V2 layout presets (use cascade preset names) ──
|
|
9
10
|
type V2Preset = "full" | "halves" | "thirds" | "quarters" | "1/3+2/3" | "2/3+1/3";
|
|
@@ -83,7 +84,7 @@ function SectionCard({
|
|
|
83
84
|
|
|
84
85
|
interface SectionTypePickerProps {
|
|
85
86
|
onSelectEmptyV2?: (preset: "full" | "halves" | "thirds" | "quarters" | "1/3+2/3" | "2/3+1/3") => void;
|
|
86
|
-
onSelectSection: (blockType:
|
|
87
|
+
onSelectSection: (blockType: SectionBlockType) => void;
|
|
87
88
|
onSelectParallaxGroup?: () => void;
|
|
88
89
|
onSelectCoverSection?: () => void;
|
|
89
90
|
onSelectCustomSection?: (section: CustomSectionListItem) => void;
|
|
@@ -212,14 +213,14 @@ export default function SectionTypePicker({
|
|
|
212
213
|
}
|
|
213
214
|
onClose();
|
|
214
215
|
}}
|
|
215
|
-
className="rounded-xl border border-neutral-200 bg-white p-3 hover:border-[#
|
|
216
|
-
|
|
216
|
+
className="group/bb relative rounded-xl border border-neutral-200 bg-white p-3 hover:border-[#3580f9] hover:bg-[#3580f9]/5 transition-colors group shadow-sm"
|
|
217
|
+
aria-label={label}
|
|
217
218
|
>
|
|
218
219
|
<div className="flex gap-1 h-6">
|
|
219
220
|
{widths.map((w, i) => (
|
|
220
221
|
<div
|
|
221
222
|
key={i}
|
|
222
|
-
className="bg-neutral-200 group-hover:bg-[#
|
|
223
|
+
className="bg-neutral-200 group-hover:bg-[#3580f9]/30 rounded-sm transition-colors"
|
|
223
224
|
style={{ flex: w }}
|
|
224
225
|
/>
|
|
225
226
|
))}
|
|
@@ -227,6 +228,7 @@ export default function SectionTypePicker({
|
|
|
227
228
|
<p className="text-xs text-neutral-500 mt-1.5 group-hover:text-neutral-700">
|
|
228
229
|
{label}
|
|
229
230
|
</p>
|
|
231
|
+
<BubbleTooltip>{label}</BubbleTooltip>
|
|
230
232
|
</button>
|
|
231
233
|
))}
|
|
232
234
|
</div>
|
|
@@ -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
|
>
|
|
@@ -9,9 +9,14 @@ import {
|
|
|
9
9
|
import { useBuilderStore } from "../../lib/builder/store";
|
|
10
10
|
import { makeBlockId, makeColumnDroppableId } from "./DndWrapper";
|
|
11
11
|
import type { SectionColumn, ContentBlock, PageSectionV2 } from "../../lib/sanity/types";
|
|
12
|
-
import {
|
|
12
|
+
import {
|
|
13
|
+
getColumnVerticalAlign,
|
|
14
|
+
getBackgroundStyles,
|
|
15
|
+
getBorderStyles,
|
|
16
|
+
} from "../../lib/builder/layout-styles";
|
|
13
17
|
import { isSectionBlockType } from "../../lib/builder/types";
|
|
14
18
|
import { BUILDER_BLUE } from "../../lib/builder/constants";
|
|
19
|
+
import { BubbleTooltip, CloseIcon, DragDropIcon } from "./BubbleIcons";
|
|
15
20
|
|
|
16
21
|
// ============================================
|
|
17
22
|
// SectionV2Column — Individual column in a V2 section grid
|
|
@@ -84,17 +89,17 @@ function ResizeHandle({
|
|
|
84
89
|
height: isActive ? 16 : isHoveredEdge ? 56 : showChrome ? 56 : 32,
|
|
85
90
|
borderRadius: isActive ? "50%" : 999,
|
|
86
91
|
backgroundColor: isActive
|
|
87
|
-
? "rgba(
|
|
92
|
+
? "rgba(53, 128, 249, 0.9)"
|
|
88
93
|
: isHoveredEdge
|
|
89
|
-
? "rgba(
|
|
94
|
+
? "rgba(53, 128, 249, 0.7)"
|
|
90
95
|
: showChrome
|
|
91
|
-
? "rgba(
|
|
92
|
-
: "rgba(
|
|
96
|
+
? "rgba(53, 128, 249, 0.5)"
|
|
97
|
+
: "rgba(53, 128, 249, 0.2)",
|
|
93
98
|
transition: "width 150ms ease-out, height 150ms ease-out, border-radius 150ms ease-out, background-color 150ms, box-shadow 150ms",
|
|
94
99
|
boxShadow: isActive
|
|
95
|
-
? "0 0 10px rgba(
|
|
100
|
+
? "0 0 10px rgba(53, 128, 249, 0.5)"
|
|
96
101
|
: isHoveredEdge
|
|
97
|
-
? "0 0 6px rgba(
|
|
102
|
+
? "0 0 6px rgba(53, 128, 249, 0.2)"
|
|
98
103
|
: undefined,
|
|
99
104
|
}}
|
|
100
105
|
/>
|
|
@@ -229,6 +234,12 @@ export default function SectionV2Column({
|
|
|
229
234
|
// Column-level vertical alignment from blocks' align_v settings
|
|
230
235
|
const colJustify = getColumnVerticalAlign(column.blocks || []);
|
|
231
236
|
|
|
237
|
+
// Column-level background + border (desktop-only — no responsive overrides).
|
|
238
|
+
const columnLayoutStyles: React.CSSProperties = {
|
|
239
|
+
...getBackgroundStyles(column),
|
|
240
|
+
...getBorderStyles(column),
|
|
241
|
+
};
|
|
242
|
+
|
|
232
243
|
// ---- Preview mode ----
|
|
233
244
|
if (previewMode) {
|
|
234
245
|
return (
|
|
@@ -243,6 +254,7 @@ export default function SectionV2Column({
|
|
|
243
254
|
...(colJustify ? { justifyContent: colJustify } : {}),
|
|
244
255
|
height: "100%",
|
|
245
256
|
minHeight: 0,
|
|
257
|
+
...columnLayoutStyles,
|
|
246
258
|
}}
|
|
247
259
|
>
|
|
248
260
|
<SortableContext items={blockIds} strategy={verticalListSortingStrategy}>
|
|
@@ -279,6 +291,7 @@ export default function SectionV2Column({
|
|
|
279
291
|
transition: isDraggedColumn
|
|
280
292
|
? "none"
|
|
281
293
|
: "opacity 150ms, box-shadow 150ms, transform 150ms ease-out",
|
|
294
|
+
...columnLayoutStyles,
|
|
282
295
|
}}
|
|
283
296
|
ref={setBlockDropRef}
|
|
284
297
|
onClick={handleClick}
|
|
@@ -289,17 +302,17 @@ export default function SectionV2Column({
|
|
|
289
302
|
<div
|
|
290
303
|
className="pointer-events-none absolute inset-0 z-[1] rounded"
|
|
291
304
|
style={{
|
|
292
|
-
transition: "box-shadow 150ms
|
|
305
|
+
transition: "box-shadow 150ms",
|
|
293
306
|
...(isSwapTarget
|
|
294
|
-
? { boxShadow: `inset 0 0 0 2px ${BUILDER_BLUE}`, background: "rgba(
|
|
307
|
+
? { boxShadow: `inset 0 0 0 2px ${BUILDER_BLUE}`, background: "rgba(53, 128, 249, 0.08)" }
|
|
295
308
|
: isBlockOver
|
|
296
309
|
? { boxShadow: `inset 0 0 0 2px ${BUILDER_BLUE}` }
|
|
297
310
|
: isSelected
|
|
298
|
-
? { boxShadow: `inset 0 0 0 2px rgba(
|
|
311
|
+
? { boxShadow: `inset 0 0 0 2px rgba(53, 128, 249, 0.6)` }
|
|
299
312
|
: isHovered
|
|
300
|
-
? { boxShadow: `inset 0 0 0 1.5px rgba(
|
|
313
|
+
? { boxShadow: `inset 0 0 0 1.5px rgba(53, 128, 249, 0.5)` }
|
|
301
314
|
: showFaintOutline
|
|
302
|
-
? {
|
|
315
|
+
? { boxShadow: `inset 0 0 0 1px rgba(53, 128, 249, 0.2)`, borderRadius: 4 }
|
|
303
316
|
: undefined),
|
|
304
317
|
}}
|
|
305
318
|
/>
|
|
@@ -354,64 +367,65 @@ export default function SectionV2Column({
|
|
|
354
367
|
</>
|
|
355
368
|
)}
|
|
356
369
|
|
|
357
|
-
{/*
|
|
370
|
+
{/* Column pill — single horizontal pill sitting OUTSIDE the column top-left edge,
|
|
371
|
+
with a 2px gap so its border never touches the column outline. Structure:
|
|
372
|
+
[drag-drop] | Col | [X]. Aligns horizontally with the block pill when the
|
|
373
|
+
first block in the column has no vertical offset.
|
|
358
374
|
Nested pattern: outer div positions, inner div counter-scales. */}
|
|
359
375
|
<div
|
|
360
|
-
className={`absolute z-[6] transition-opacity ${
|
|
376
|
+
className={`absolute top-0 left-0 z-[6] transition-opacity ${
|
|
361
377
|
showChrome ? "opacity-100" : "opacity-0 pointer-events-none"
|
|
362
378
|
}`}
|
|
363
|
-
style={{
|
|
364
|
-
top: 0,
|
|
365
|
-
right: 0,
|
|
366
|
-
transform: "translate(40%, -40%)",
|
|
367
|
-
}}
|
|
379
|
+
style={{ transform: "translateY(calc(-100% - 2px))" }}
|
|
368
380
|
onClick={(e) => e.stopPropagation()}
|
|
369
381
|
>
|
|
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
|
-
|
|
382
|
+
<div style={{ transform: `scale(${Math.min(2, 1 / canvasZoom)})`, transformOrigin: "bottom left" }}>
|
|
383
|
+
<div
|
|
384
|
+
className="flex items-center rounded-[5px]"
|
|
385
|
+
style={{
|
|
386
|
+
background: "#c0d7ff",
|
|
387
|
+
border: "1.5px solid #3580f9",
|
|
388
|
+
}}
|
|
389
|
+
>
|
|
390
|
+
{/* Drag handle — starts a column drag via @dnd-kit, selects on click */}
|
|
391
|
+
<button
|
|
392
|
+
type="button"
|
|
393
|
+
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]"
|
|
394
|
+
aria-label="Move column"
|
|
395
|
+
onMouseDown={(e) => {
|
|
396
|
+
e.stopPropagation();
|
|
397
|
+
onStartDrag?.(e);
|
|
398
|
+
}}
|
|
399
|
+
onClick={(e) => {
|
|
400
|
+
e.stopPropagation();
|
|
401
|
+
onSelect();
|
|
402
|
+
}}
|
|
403
|
+
>
|
|
404
|
+
<DragDropIcon size={14} />
|
|
405
|
+
<BubbleTooltip>Drag to move</BubbleTooltip>
|
|
406
|
+
</button>
|
|
407
|
+
<div className="w-px self-stretch my-1" style={{ background: "#3580f9" }} />
|
|
408
|
+
{/* Label — clicking selects the column */}
|
|
409
|
+
<button
|
|
410
|
+
type="button"
|
|
411
|
+
onClick={(e) => { e.stopPropagation(); onSelect(); }}
|
|
412
|
+
className="text-[11px] px-2 py-0.5 font-medium hover:bg-[#3580f9]/15 transition-colors"
|
|
413
|
+
style={{ color: "#3580f9" }}
|
|
414
|
+
aria-label="Select column"
|
|
415
|
+
>
|
|
416
|
+
Col
|
|
417
|
+
</button>
|
|
418
|
+
<div className="w-px self-stretch my-1" style={{ background: "#3580f9" }} />
|
|
419
|
+
{/* Delete */}
|
|
420
|
+
<button
|
|
421
|
+
onClick={handleDelete}
|
|
422
|
+
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]"
|
|
423
|
+
aria-label="Delete column"
|
|
424
|
+
>
|
|
425
|
+
<CloseIcon size={14} />
|
|
426
|
+
<BubbleTooltip>Delete</BubbleTooltip>
|
|
427
|
+
</button>
|
|
428
|
+
</div>
|
|
415
429
|
</div>
|
|
416
430
|
</div>
|
|
417
431
|
|
|
@@ -473,8 +487,8 @@ export default function SectionV2Column({
|
|
|
473
487
|
padding: "5px 16px",
|
|
474
488
|
pointerEvents: showChrome || showFaintOutline ? "auto" : "none",
|
|
475
489
|
background: "#d2e3ff",
|
|
476
|
-
color: "#
|
|
477
|
-
border: "
|
|
490
|
+
color: "#3580f9",
|
|
491
|
+
border: "1.5px dashed #3580f9",
|
|
478
492
|
}}
|
|
479
493
|
>
|
|
480
494
|
+ Add Block
|
|
@@ -502,8 +516,8 @@ export default function SectionV2Column({
|
|
|
502
516
|
padding: "4px 14px",
|
|
503
517
|
pointerEvents: showChrome ? "auto" : "none",
|
|
504
518
|
background: "#d2e3ff",
|
|
505
|
-
color: "#
|
|
506
|
-
border: "
|
|
519
|
+
color: "#3580f9",
|
|
520
|
+
border: "1.5px dashed #3580f9",
|
|
507
521
|
}}
|
|
508
522
|
>
|
|
509
523
|
+ Add Block
|
|
@@ -25,6 +25,7 @@ import { useBuilderStore } from "../../lib/builder/store";
|
|
|
25
25
|
import { useSettingsPanelSelection } from "./settings-panel/useSettingsPanelSelection";
|
|
26
26
|
import { AnimationTab } from "./settings-panel/AnimationTab";
|
|
27
27
|
import { ColumnV2AnimationTab } from "./settings-panel/ColumnV2AnimationTab";
|
|
28
|
+
import { ColumnV2LayoutTab } from "./settings-panel/ColumnV2LayoutTab";
|
|
28
29
|
import { CustomSectionSettings } from "./settings-panel/CustomSectionSettings";
|
|
29
30
|
import {
|
|
30
31
|
BlockLayoutTab,
|
|
@@ -40,6 +41,7 @@ import {
|
|
|
40
41
|
CoverSectionSettings,
|
|
41
42
|
} from "./settings-panel";
|
|
42
43
|
import CoverSectionLayoutTab from "./settings-panel/CoverSectionLayoutTab";
|
|
44
|
+
import { BubbleTooltip } from "./BubbleIcons";
|
|
43
45
|
|
|
44
46
|
type SettingsTab = "settings" | "layout" | "seo" | "animation";
|
|
45
47
|
|
|
@@ -75,11 +77,11 @@ export default function SettingsPanel() {
|
|
|
75
77
|
}
|
|
76
78
|
}, [selectionKey]);
|
|
77
79
|
|
|
78
|
-
// Columns have Settings + Animation — fall back if
|
|
80
|
+
// Columns have Settings + Layout + Animation — fall back if SEO tab was active
|
|
79
81
|
// Parallax group header has only Settings — fall back if Layout/SEO or Animation tab was active
|
|
80
82
|
// Page level has Settings + SEO + Animation — fall back if Layout tab was active
|
|
81
83
|
useEffect(() => {
|
|
82
|
-
if (isColumnOnly &&
|
|
84
|
+
if (isColumnOnly && activeTab === "seo") {
|
|
83
85
|
setActiveTab("settings");
|
|
84
86
|
}
|
|
85
87
|
if (isParallaxGroupOnly && (activeTab === "layout" || activeTab === "seo" || activeTab === "animation")) {
|
|
@@ -134,13 +136,14 @@ export default function SettingsPanel() {
|
|
|
134
136
|
return (
|
|
135
137
|
<button
|
|
136
138
|
onClick={onDelete}
|
|
137
|
-
className="p-1.5 rounded-md hover:bg-red-500/20 transition-colors
|
|
138
|
-
|
|
139
|
+
className="group/bb relative p-1.5 rounded-md hover:bg-red-500/20 transition-colors"
|
|
140
|
+
aria-label={deleteTitle}
|
|
139
141
|
>
|
|
140
|
-
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" className="text-black/40 group-hover:text-[var(--admin-error)] transition-colors">
|
|
142
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" className="text-black/40 group-hover/bb:text-[var(--admin-error)] transition-colors">
|
|
141
143
|
<polyline points="3 6 5 6 21 6" />
|
|
142
144
|
<path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" />
|
|
143
145
|
</svg>
|
|
146
|
+
<BubbleTooltip>{deleteTitle}</BubbleTooltip>
|
|
144
147
|
</button>
|
|
145
148
|
);
|
|
146
149
|
})()}
|
|
@@ -204,11 +207,6 @@ export default function SettingsPanel() {
|
|
|
204
207
|
});
|
|
205
208
|
}
|
|
206
209
|
}
|
|
207
|
-
// Columns: remove Layout tab (keep Settings + Animation)
|
|
208
|
-
if (isColumnOnly) {
|
|
209
|
-
const layoutIdx = tabs.findIndex((t) => t.id === "layout");
|
|
210
|
-
if (layoutIdx >= 0) tabs.splice(layoutIdx, 1);
|
|
211
|
-
}
|
|
212
210
|
// Parallax group header: only Settings (no Layout, no Animation)
|
|
213
211
|
if (isParallaxGroupOnly) {
|
|
214
212
|
const layoutIdx = tabs.findIndex((t) => t.id === "layout");
|
|
@@ -268,9 +266,11 @@ export default function SettingsPanel() {
|
|
|
268
266
|
{selectedParallaxGroup && !selectedParallaxSlide && !selectedBlock ? (
|
|
269
267
|
<ParallaxGroupSettings group={selectedParallaxGroup} />
|
|
270
268
|
) : selectedParallaxSlide && selectedColumnV2 && !selectedBlock ? (
|
|
271
|
-
// Column inside a parallax slide — Settings
|
|
269
|
+
// Column inside a parallax slide — Settings / Layout / Animation
|
|
272
270
|
activeTab === "animation" ? (
|
|
273
271
|
<ColumnV2AnimationTab section={selectedParallaxSlide.virtualSection} column={selectedColumnV2} />
|
|
272
|
+
) : activeTab === "layout" ? (
|
|
273
|
+
<ColumnV2LayoutTab section={selectedParallaxSlide.virtualSection} column={selectedColumnV2} />
|
|
274
274
|
) : (
|
|
275
275
|
<ColumnV2Settings section={selectedParallaxSlide.virtualSection} column={selectedColumnV2} />
|
|
276
276
|
)
|
|
@@ -320,15 +320,19 @@ export default function SettingsPanel() {
|
|
|
320
320
|
<CoverSectionSettings section={selectedCoverSection} />
|
|
321
321
|
)
|
|
322
322
|
) :
|
|
323
|
-
/* ---- V2 Section / Column / Block routing ---- */
|
|
323
|
+
/* ---- V2 / Cover Section / Column / Block routing ---- */
|
|
324
324
|
/* BUG-V2-003 fix: When a block inside a V2 column is selected, show BlockSettings
|
|
325
|
-
instead of ColumnV2Settings. Block selection takes priority over column.
|
|
326
|
-
|
|
327
|
-
|
|
325
|
+
instead of ColumnV2Settings. Block selection takes priority over column.
|
|
326
|
+
Cover-column fix: use effectiveSectionV2 so columns inside a cover section
|
|
327
|
+
route to ColumnV2Settings instead of falling through to PageSettings. */
|
|
328
|
+
selectedColumnV2 && effectiveSectionV2 && !selectedBlock ? (
|
|
329
|
+
// V2 Column or Cover Section column selected (no block) — Settings / Layout / Animation
|
|
328
330
|
activeTab === "animation" ? (
|
|
329
|
-
<ColumnV2AnimationTab section={
|
|
331
|
+
<ColumnV2AnimationTab section={effectiveSectionV2} column={selectedColumnV2} />
|
|
332
|
+
) : activeTab === "layout" ? (
|
|
333
|
+
<ColumnV2LayoutTab section={effectiveSectionV2} column={selectedColumnV2} />
|
|
330
334
|
) : (
|
|
331
|
-
<ColumnV2Settings section={
|
|
335
|
+
<ColumnV2Settings section={effectiveSectionV2} column={selectedColumnV2} />
|
|
332
336
|
)
|
|
333
337
|
) : selectedSectionV2 && !selectedBlock ? (
|
|
334
338
|
// V2 Section selected — route by active tab
|
|
@@ -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="group/bb relative text-[10px] px-1 py-0.5"
|
|
193
|
+
style={{ color: "#3580f9" }}
|
|
194
|
+
>
|
|
195
|
+
✦
|
|
196
|
+
<BubbleTooltip>{`Animation: ${block.enter_animation.preset}`}</BubbleTooltip>
|
|
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
|
|