@morphika/andami 0.5.0 → 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/README.md +151 -36
- 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 -327
- 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 -0
- package/components/blocks/BeforeAfterBlockRenderer.tsx +274 -0
- package/components/blocks/MarqueeBlockRenderer.tsx +316 -0
- package/components/blocks/ProjectCarouselBlockRenderer.tsx +1 -1
- package/components/builder/BlockCardIcons.tsx +316 -227
- package/components/builder/BlockTypePicker.tsx +3 -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 -275
- 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 +20 -4
- package/components/builder/SectionV2Column.tsx +74 -68
- 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 +34 -17
- package/components/builder/asset-browser/helpers.ts +4 -0
- package/components/builder/asset-browser/types.ts +2 -1
- package/components/builder/blockStyles.tsx +192 -173
- 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 -0
- package/components/builder/editors/BeforeAfterBlockEditor.tsx +360 -0
- 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 +7 -7
- package/components/builder/live-preview/LiveAudioPreview.tsx +120 -0
- package/components/builder/live-preview/LiveBeforeAfterPreview.tsx +176 -0
- 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 +3 -0
- package/lib/animation/hover-effect-presets.ts +210 -210
- package/lib/animation/hover-effect-types.ts +3 -0
- package/lib/builder/block-registrations.ts +468 -335
- 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 +6 -1
- package/lib/config/index.ts +27 -27
- package/lib/sanity/types.ts +156 -1
- package/lib/version.ts +1 -1
- package/package.json +1 -1
- package/sanity/schemas/blocks/audioBlock.ts +69 -0
- package/sanity/schemas/blocks/beforeAfterBlock.ts +121 -0
- package/sanity/schemas/blocks/index.ts +12 -9
- package/sanity/schemas/blocks/marqueeBlock.ts +292 -0
- package/sanity/schemas/index.ts +120 -111
- 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
|
))}
|
|
@@ -27,6 +27,13 @@ interface SectionV2CanvasProps {
|
|
|
27
27
|
fillHeight?: boolean;
|
|
28
28
|
/** Offset added to grid_row when adding columns via gaps (used by cover sections where columns are normalized to grid_row 1) */
|
|
29
29
|
gridRowOffset?: number;
|
|
30
|
+
/**
|
|
31
|
+
* Optional override for the responsive-update action. When provided, used instead of the
|
|
32
|
+
* store's `updateSectionV2Responsive`. Used by CoverSectionCanvas to intercept writes from
|
|
33
|
+
* virtual per-row sections and merge them back into the Cover's flat column list (remapping
|
|
34
|
+
* `grid_row: 1` back to the row's real number and preserving overrides for other rows).
|
|
35
|
+
*/
|
|
36
|
+
onUpdateResponsive?: (sectionKey: string, responsive: PageSectionV2["responsive"]) => void;
|
|
30
37
|
}
|
|
31
38
|
|
|
32
39
|
export default function SectionV2Canvas({
|
|
@@ -34,6 +41,7 @@ export default function SectionV2Canvas({
|
|
|
34
41
|
onAddBlockTarget,
|
|
35
42
|
fillHeight,
|
|
36
43
|
gridRowOffset = 0,
|
|
44
|
+
onUpdateResponsive,
|
|
37
45
|
}: SectionV2CanvasProps) {
|
|
38
46
|
const previewMode = useBuilderStore((s) => s.previewMode);
|
|
39
47
|
const canvasZoom = useBuilderStore((s) => s.canvasZoom);
|
|
@@ -44,7 +52,8 @@ export default function SectionV2Canvas({
|
|
|
44
52
|
const deleteColumnV2 = useBuilderStore((s) => s.deleteColumnV2);
|
|
45
53
|
const resizeColumnV2 = useBuilderStore((s) => s.resizeColumnV2);
|
|
46
54
|
const resizeColumnV2Left = useBuilderStore((s) => s.resizeColumnV2Left);
|
|
47
|
-
const
|
|
55
|
+
const storeUpdateSectionV2Responsive = useBuilderStore((s) => s.updateSectionV2Responsive);
|
|
56
|
+
const updateSectionV2Responsive = onUpdateResponsive ?? storeUpdateSectionV2Responsive;
|
|
48
57
|
const selectColumnV2 = useBuilderStore((s) => s.selectColumnV2);
|
|
49
58
|
const selectBlock = useBuilderStore((s) => s.selectBlock);
|
|
50
59
|
const deleteBlock = useBuilderStore((s) => s.deleteBlock);
|
|
@@ -153,7 +162,10 @@ export default function SectionV2Canvas({
|
|
|
153
162
|
style={{
|
|
154
163
|
display: "grid",
|
|
155
164
|
gridTemplateColumns: `repeat(${gridColumns}, 1fr)`,
|
|
156
|
-
|
|
165
|
+
// `minmax(0, 1fr)` lets the row shrink below intrinsic content min-size
|
|
166
|
+
// (gaps' minHeight, empty-column wrappers), which is required in Cover
|
|
167
|
+
// sections where the row has a strict proportional height.
|
|
168
|
+
...(fillHeight ? { gridTemplateRows: "minmax(0, 1fr)" } : {}),
|
|
157
169
|
columnGap: `${colGap}px`,
|
|
158
170
|
rowGap: `${rowGap}px`,
|
|
159
171
|
position: "relative",
|
|
@@ -237,6 +249,7 @@ export default function SectionV2Canvas({
|
|
|
237
249
|
}
|
|
238
250
|
onResizeRight={handleResizeRight}
|
|
239
251
|
onResizeLeft={handleResizeLeft}
|
|
252
|
+
fillHeight={fillHeight}
|
|
240
253
|
>
|
|
241
254
|
{(col.blocks || []).map((block, blockIdx) => (
|
|
242
255
|
<SortableBlock
|
|
@@ -286,7 +299,10 @@ export default function SectionV2Canvas({
|
|
|
286
299
|
style={{
|
|
287
300
|
gridColumn: `${gap.grid_column} / span ${gap.span}`,
|
|
288
301
|
gridRow: gap.grid_row,
|
|
289
|
-
|
|
302
|
+
// In fillHeight (Cover), the row has a strict proportional
|
|
303
|
+
// height — don't let the gap force a minimum that would push
|
|
304
|
+
// content past the row boundary.
|
|
305
|
+
minHeight: fillHeight ? 0 : 70,
|
|
290
306
|
}}
|
|
291
307
|
className={`rounded-lg border-2 border-dashed text-xs font-medium transition-all flex items-center justify-center cursor-pointer ${
|
|
292
308
|
isGapTarget
|
|
@@ -294,7 +310,7 @@ export default function SectionV2Canvas({
|
|
|
294
310
|
: showAsDropTarget
|
|
295
311
|
? "border-blue-500/40 text-blue-500/60 bg-blue-500/5 opacity-100"
|
|
296
312
|
: isSectionHovered
|
|
297
|
-
? "border-[#
|
|
313
|
+
? "border-[#3580f9]/25 text-[#3580f9]/50 hover:text-[#3580f9] hover:border-[#3580f9]/60 hover:bg-[#3580f9]/5 opacity-100"
|
|
298
314
|
: "border-transparent text-transparent opacity-0 pointer-events-none"
|
|
299
315
|
}`}
|
|
300
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
|
/>
|
|
@@ -131,6 +132,9 @@ interface SectionV2ColumnProps {
|
|
|
131
132
|
onAddBlock: (insertIndex?: number) => void;
|
|
132
133
|
onResizeRight: (columnKey: string, startX: number, startSpan: number, containerEl: HTMLElement) => void;
|
|
133
134
|
onResizeLeft: (columnKey: string, startX: number, startGridCol: number, startSpan: number, containerEl: HTMLElement) => void;
|
|
135
|
+
/** When true, the column lives in a Cover row with strict proportional height —
|
|
136
|
+
* empty-state minHeight is relaxed so the column doesn't overflow its row. */
|
|
137
|
+
fillHeight?: boolean;
|
|
134
138
|
children: ReactNode;
|
|
135
139
|
}
|
|
136
140
|
|
|
@@ -152,6 +156,7 @@ export default function SectionV2Column({
|
|
|
152
156
|
onAddBlock,
|
|
153
157
|
onResizeRight,
|
|
154
158
|
onResizeLeft,
|
|
159
|
+
fillHeight = false,
|
|
155
160
|
children,
|
|
156
161
|
}: SectionV2ColumnProps) {
|
|
157
162
|
const previewMode = useBuilderStore((s) => s.previewMode);
|
|
@@ -285,17 +290,17 @@ export default function SectionV2Column({
|
|
|
285
290
|
<div
|
|
286
291
|
className="pointer-events-none absolute inset-0 z-[1] rounded"
|
|
287
292
|
style={{
|
|
288
|
-
transition: "box-shadow 150ms
|
|
293
|
+
transition: "box-shadow 150ms",
|
|
289
294
|
...(isSwapTarget
|
|
290
|
-
? { 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)" }
|
|
291
296
|
: isBlockOver
|
|
292
297
|
? { boxShadow: `inset 0 0 0 2px ${BUILDER_BLUE}` }
|
|
293
298
|
: isSelected
|
|
294
|
-
? { boxShadow: `inset 0 0 0 2px rgba(
|
|
299
|
+
? { boxShadow: `inset 0 0 0 2px rgba(53, 128, 249, 0.6)` }
|
|
295
300
|
: isHovered
|
|
296
|
-
? { boxShadow: `inset 0 0 0 1.5px rgba(
|
|
301
|
+
? { boxShadow: `inset 0 0 0 1.5px rgba(53, 128, 249, 0.5)` }
|
|
297
302
|
: showFaintOutline
|
|
298
|
-
? {
|
|
303
|
+
? { boxShadow: `inset 0 0 0 1px rgba(53, 128, 249, 0.2)`, borderRadius: 4 }
|
|
299
304
|
: undefined),
|
|
300
305
|
}}
|
|
301
306
|
/>
|
|
@@ -350,64 +355,65 @@ export default function SectionV2Column({
|
|
|
350
355
|
</>
|
|
351
356
|
)}
|
|
352
357
|
|
|
353
|
-
{/*
|
|
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.
|
|
354
362
|
Nested pattern: outer div positions, inner div counter-scales. */}
|
|
355
363
|
<div
|
|
356
|
-
className={`absolute z-[6] transition-opacity ${
|
|
364
|
+
className={`absolute top-0 left-0 z-[6] transition-opacity ${
|
|
357
365
|
showChrome ? "opacity-100" : "opacity-0 pointer-events-none"
|
|
358
366
|
}`}
|
|
359
|
-
style={{
|
|
360
|
-
top: 0,
|
|
361
|
-
right: 0,
|
|
362
|
-
transform: "translate(40%, -40%)",
|
|
363
|
-
}}
|
|
367
|
+
style={{ transform: "translateY(calc(-100% - 2px))" }}
|
|
364
368
|
onClick={(e) => e.stopPropagation()}
|
|
365
369
|
>
|
|
366
|
-
<div style={{ transform: `scale(${1 / canvasZoom})`, transformOrigin: "
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
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
|
-
|
|
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>
|
|
411
417
|
</div>
|
|
412
418
|
</div>
|
|
413
419
|
|
|
@@ -453,7 +459,7 @@ export default function SectionV2Column({
|
|
|
453
459
|
/* Empty column: show + Add Block (flex-1 stretches in fillHeight cover sections) */
|
|
454
460
|
<div
|
|
455
461
|
className="relative flex items-center justify-center flex-1"
|
|
456
|
-
style={{ minHeight: 80, padding: "16px 12px" }}
|
|
462
|
+
style={{ minHeight: fillHeight ? 0 : 80, padding: "16px 12px" }}
|
|
457
463
|
>
|
|
458
464
|
<button
|
|
459
465
|
onClick={handleAddBlockEmpty}
|
|
@@ -469,8 +475,8 @@ export default function SectionV2Column({
|
|
|
469
475
|
padding: "5px 16px",
|
|
470
476
|
pointerEvents: showChrome || showFaintOutline ? "auto" : "none",
|
|
471
477
|
background: "#d2e3ff",
|
|
472
|
-
color: "#
|
|
473
|
-
border: "
|
|
478
|
+
color: "#3580f9",
|
|
479
|
+
border: "1.5px dashed #3580f9",
|
|
474
480
|
}}
|
|
475
481
|
>
|
|
476
482
|
+ Add Block
|
|
@@ -498,8 +504,8 @@ export default function SectionV2Column({
|
|
|
498
504
|
padding: "4px 14px",
|
|
499
505
|
pointerEvents: showChrome ? "auto" : "none",
|
|
500
506
|
background: "#d2e3ff",
|
|
501
|
-
color: "#
|
|
502
|
-
border: "
|
|
507
|
+
color: "#3580f9",
|
|
508
|
+
border: "1.5px dashed #3580f9",
|
|
503
509
|
}}
|
|
504
510
|
>
|
|
505
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
|
|