@morphika/andami 0.1.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/LICENSE +21 -0
- package/README.md +50 -0
- package/admin/assets.ts +4 -0
- package/admin/database.ts +4 -0
- package/admin/index.ts +6 -0
- package/admin/login.ts +4 -0
- package/admin/navigation.ts +4 -0
- package/admin/pages-editor.ts +4 -0
- package/admin/pages.ts +4 -0
- package/admin/projects-editor.ts +4 -0
- package/admin/projects.ts +4 -0
- package/admin/settings.ts +4 -0
- package/admin/setup.ts +4 -0
- package/admin/storage.ts +4 -0
- package/admin/styles.ts +4 -0
- package/app/(site)/[slug]/loading.tsx +20 -0
- package/app/(site)/[slug]/page.tsx +83 -0
- package/app/(site)/error.tsx +32 -0
- package/app/(site)/layout.tsx +53 -0
- package/app/(site)/loading.tsx +20 -0
- package/app/(site)/not-found.tsx +41 -0
- package/app/(site)/page.tsx +43 -0
- package/app/(site)/preview/page.tsx +99 -0
- package/app/(site)/work/[slug]/loading.tsx +23 -0
- package/app/(site)/work/[slug]/page.tsx +84 -0
- package/app/admin/assets/page.tsx +573 -0
- package/app/admin/database/page.tsx +302 -0
- package/app/admin/error.tsx +53 -0
- package/app/admin/layout.tsx +273 -0
- package/app/admin/login/page.tsx +88 -0
- package/app/admin/navigation/page.tsx +157 -0
- package/app/admin/page.tsx +17 -0
- package/app/admin/pages/[slug]/page.tsx +849 -0
- package/app/admin/pages/page.tsx +588 -0
- package/app/admin/projects/[slug]/page.tsx +3 -0
- package/app/admin/projects/page.tsx +669 -0
- package/app/admin/settings/page.tsx +132 -0
- package/app/admin/setup/page.tsx +64 -0
- package/app/admin/storage/page.tsx +518 -0
- package/app/admin/styles/page.tsx +243 -0
- package/app/api/admin/assets/file/route.ts +81 -0
- package/app/api/admin/assets/health/route.ts +170 -0
- package/app/api/admin/assets/register/route.ts +163 -0
- package/app/api/admin/assets/registry/route.ts +98 -0
- package/app/api/admin/assets/relink/confirm/route.ts +242 -0
- package/app/api/admin/assets/relink/route.ts +202 -0
- package/app/api/admin/assets/scan/route.ts +271 -0
- package/app/api/admin/auth/route.ts +160 -0
- package/app/api/admin/custom-sections/[slug]/route.ts +159 -0
- package/app/api/admin/custom-sections/route.ts +127 -0
- package/app/api/admin/database/route.ts +53 -0
- package/app/api/admin/pages/[slug]/duplicate/route.ts +91 -0
- package/app/api/admin/pages/[slug]/route.ts +617 -0
- package/app/api/admin/pages/[slug]/set-home/route.ts +76 -0
- package/app/api/admin/pages/route.ts +129 -0
- package/app/api/admin/preview/route.ts +53 -0
- package/app/api/admin/r2/connect/route.ts +181 -0
- package/app/api/admin/r2/delete/route.ts +198 -0
- package/app/api/admin/r2/disconnect/route.ts +42 -0
- package/app/api/admin/r2/rename/route.ts +265 -0
- package/app/api/admin/r2/status/route.ts +106 -0
- package/app/api/admin/r2/upload-url/route.ts +148 -0
- package/app/api/admin/revalidate/route.ts +55 -0
- package/app/api/admin/settings/route.ts +279 -0
- package/app/api/admin/setup/complete/route.ts +51 -0
- package/app/api/admin/setup/route.ts +118 -0
- package/app/api/admin/storage/switch/route.ts +117 -0
- package/app/api/admin/styles/fonts/route.ts +97 -0
- package/app/api/admin/styles/route.ts +304 -0
- package/app/api/assets/[...path]/route.ts +98 -0
- package/app/api/custom-sections/[id]/route.ts +43 -0
- package/app/api/draft-mode/disable/route.ts +10 -0
- package/app/api/draft-mode/enable/route.ts +26 -0
- package/app/api/projects/route.ts +42 -0
- package/app/api/styles/route.ts +88 -0
- package/app/favicon.ico +0 -0
- package/app/globals.css +7 -0
- package/app/layout.tsx +53 -0
- package/app/robots.ts +17 -0
- package/app/sitemap.ts +48 -0
- package/app/studio/[[...index]]/page.tsx +8 -0
- package/components/admin/MetadataEditor.tsx +173 -0
- package/components/admin/PublishToggle.tsx +130 -0
- package/components/admin/icons.tsx +40 -0
- package/components/admin/nav-builder/NavBuilder.tsx +182 -0
- package/components/admin/nav-builder/NavBuilderGrid.tsx +326 -0
- package/components/admin/nav-builder/NavGeneralSettings.tsx +275 -0
- package/components/admin/nav-builder/NavGridCell.tsx +48 -0
- package/components/admin/nav-builder/NavGridItem.tsx +189 -0
- package/components/admin/nav-builder/NavItemSettings.tsx +288 -0
- package/components/admin/nav-builder/NavItemTypePicker.tsx +102 -0
- package/components/admin/nav-builder/NavLivePreview.tsx +125 -0
- package/components/admin/nav-builder/NavSettingsFields.tsx +248 -0
- package/components/admin/nav-builder/NavSettingsPanel.tsx +127 -0
- package/components/admin/nav-builder/index.ts +10 -0
- package/components/admin/nav-builder/nav-builder-utils.ts +238 -0
- package/components/admin/setup-wizard/BrandingStep.tsx +218 -0
- package/components/admin/setup-wizard/DatabaseStep.tsx +331 -0
- package/components/admin/setup-wizard/DoneStep.tsx +187 -0
- package/components/admin/setup-wizard/SetupWizard.tsx +166 -0
- package/components/admin/setup-wizard/StorageStep.tsx +308 -0
- package/components/admin/setup-wizard/WelcomeStep.tsx +96 -0
- package/components/admin/setup-wizard/index.ts +9 -0
- package/components/admin/styles/ColorsEditor.tsx +214 -0
- package/components/admin/styles/FontsEditor.tsx +258 -0
- package/components/admin/styles/GridLayoutEditor.tsx +292 -0
- package/components/admin/styles/LinksButtonsEditor.tsx +120 -0
- package/components/admin/styles/TypographyEditor.tsx +266 -0
- package/components/admin/styles/index.ts +9 -0
- package/components/admin/styles/shared.tsx +68 -0
- package/components/blocks/BlockRenderer.tsx +404 -0
- package/components/blocks/ButtonBlockRenderer.tsx +52 -0
- package/components/blocks/CoverBlockRenderer.tsx +239 -0
- package/components/blocks/CustomSectionInstanceRenderer.tsx +82 -0
- package/components/blocks/EnterAnimationWrapper.tsx +140 -0
- package/components/blocks/HoverAnimationWrapper.tsx +308 -0
- package/components/blocks/ImageBlockRenderer.tsx +61 -0
- package/components/blocks/ImageGridBlockRenderer.tsx +545 -0
- package/components/blocks/PageBackground.tsx +28 -0
- package/components/blocks/PageNavAnimation.tsx +35 -0
- package/components/blocks/PageNavColor.tsx +24 -0
- package/components/blocks/PageRenderer.tsx +142 -0
- package/components/blocks/ParallaxGroupRenderer.tsx +448 -0
- package/components/blocks/ParallaxSlideRenderer.tsx +175 -0
- package/components/blocks/ProjectGridBlockRenderer.tsx +556 -0
- package/components/blocks/SectionRenderer.tsx +170 -0
- package/components/blocks/SectionV2Renderer.tsx +330 -0
- package/components/blocks/ShaderCanvas.tsx +392 -0
- package/components/blocks/SpacerBlockRenderer.tsx +17 -0
- package/components/blocks/TextBlockRenderer.tsx +87 -0
- package/components/blocks/TypewriterRichText.tsx +464 -0
- package/components/blocks/TypewriterWrapper.tsx +149 -0
- package/components/blocks/VideoBlockRenderer.tsx +304 -0
- package/components/blocks/index.ts +2 -0
- package/components/builder/AssetBrowser.tsx +2 -0
- package/components/builder/BlockLivePreview.tsx +101 -0
- package/components/builder/BlockTypePicker.tsx +178 -0
- package/components/builder/BuilderCanvas.tsx +354 -0
- package/components/builder/CanvasMinimap.tsx +200 -0
- package/components/builder/CanvasToolbar.tsx +202 -0
- package/components/builder/ColorPicker.tsx +243 -0
- package/components/builder/ColorSwatchPicker.tsx +274 -0
- package/components/builder/ColumnDragContext.tsx +51 -0
- package/components/builder/ColumnDragOverlay.tsx +110 -0
- package/components/builder/CustomSectionInstanceCard.tsx +97 -0
- package/components/builder/DeviceFrame.tsx +123 -0
- package/components/builder/DndWrapper.tsx +337 -0
- package/components/builder/InsertionLines.tsx +186 -0
- package/components/builder/ParallaxGroupCanvas.tsx +228 -0
- package/components/builder/ParallaxSlideHeader.tsx +113 -0
- package/components/builder/ReadOnlyFrame.tsx +417 -0
- package/components/builder/SectionEditorBar.tsx +288 -0
- package/components/builder/SectionTypePicker.tsx +422 -0
- package/components/builder/SectionV2Canvas.tsx +297 -0
- package/components/builder/SectionV2Column.tsx +488 -0
- package/components/builder/SettingsPanel.tsx +911 -0
- package/components/builder/SortableBlock.tsx +230 -0
- package/components/builder/SortableRow.tsx +362 -0
- package/components/builder/VirtualAssetGrid.tsx +397 -0
- package/components/builder/asset-browser/AssetBrowser.tsx +178 -0
- package/components/builder/asset-browser/FileLightbox.tsx +116 -0
- package/components/builder/asset-browser/FolderTreeItem.tsx +55 -0
- package/components/builder/asset-browser/R2BrowserContent.tsx +436 -0
- package/components/builder/asset-browser/R2ContextMenu.tsx +98 -0
- package/components/builder/asset-browser/VideoThumbnail.tsx +63 -0
- package/components/builder/asset-browser/helpers.ts +88 -0
- package/components/builder/asset-browser/index.ts +1 -0
- package/components/builder/asset-browser/types.ts +49 -0
- package/components/builder/asset-browser/useAssetBrowser.ts +344 -0
- package/components/builder/asset-browser/useR2DragDrop.ts +116 -0
- package/components/builder/asset-browser/useR2Operations.ts +189 -0
- package/components/builder/blockStyles.tsx +295 -0
- package/components/builder/editors/ButtonBlockEditor.tsx +184 -0
- package/components/builder/editors/CoverBlockEditor.tsx +488 -0
- package/components/builder/editors/EnterAnimationPicker.tsx +297 -0
- package/components/builder/editors/HoverEffectPicker.tsx +209 -0
- package/components/builder/editors/ImageBlockEditor.tsx +206 -0
- package/components/builder/editors/ImageGridBlockEditor.tsx +386 -0
- package/components/builder/editors/ProjectGridEditor.tsx +648 -0
- package/components/builder/editors/SpacerBlockEditor.tsx +167 -0
- package/components/builder/editors/StaggerSettings.tsx +108 -0
- package/components/builder/editors/TextAlignmentIcons.tsx +39 -0
- package/components/builder/editors/TextBlockEditor.tsx +462 -0
- package/components/builder/editors/TextStylePicker.tsx +183 -0
- package/components/builder/editors/VideoBlockEditor.tsx +278 -0
- package/components/builder/editors/index.ts +10 -0
- package/components/builder/editors/shared.tsx +345 -0
- package/components/builder/hooks/useColumnDrag.ts +472 -0
- package/components/builder/hooks/useColumnResize.ts +221 -0
- package/components/builder/index.ts +12 -0
- package/components/builder/live-preview/LiveButtonPreview.tsx +38 -0
- package/components/builder/live-preview/LiveCoverPreview.tsx +146 -0
- package/components/builder/live-preview/LiveImageGridPreview.tsx +123 -0
- package/components/builder/live-preview/LiveImagePreview.tsx +107 -0
- package/components/builder/live-preview/LiveProjectGridPreview.tsx +1010 -0
- package/components/builder/live-preview/LiveSpacerPreview.tsx +9 -0
- package/components/builder/live-preview/LiveTextEditor.tsx +198 -0
- package/components/builder/live-preview/LiveVideoPreview.tsx +98 -0
- package/components/builder/live-preview/index.ts +10 -0
- package/components/builder/live-preview/shared.tsx +153 -0
- package/components/builder/settings-panel/BlockLayoutTab.tsx +532 -0
- package/components/builder/settings-panel/BlockSettings.tsx +94 -0
- package/components/builder/settings-panel/ColumnV2Settings.tsx +160 -0
- package/components/builder/settings-panel/LayoutTab.tsx +310 -0
- package/components/builder/settings-panel/PageSettings.tsx +200 -0
- package/components/builder/settings-panel/ParallaxGroupSettings.tsx +118 -0
- package/components/builder/settings-panel/ParallaxSlideSettings.tsx +178 -0
- package/components/builder/settings-panel/SectionV2AnimationTab.tsx +103 -0
- package/components/builder/settings-panel/SectionV2LayoutTab.tsx +312 -0
- package/components/builder/settings-panel/SectionV2Settings.tsx +323 -0
- package/components/builder/settings-panel/TRBLInputs.tsx +51 -0
- package/components/builder/settings-panel/index.ts +19 -0
- package/components/builder/settings-panel/responsive-helpers.ts +524 -0
- package/components/ui/CustomCursor.tsx +118 -0
- package/components/ui/NavContentLightbox.tsx +152 -0
- package/components/ui/Navbar.tsx +582 -0
- package/components/ui/PortfolioTracker.tsx +87 -0
- package/components/ui/ScrollToTop.tsx +47 -0
- package/lib/animation/enter-presets.ts +147 -0
- package/lib/animation/enter-resolve.ts +90 -0
- package/lib/animation/enter-types.ts +128 -0
- package/lib/animation/hover-effect-presets.ts +210 -0
- package/lib/animation/hover-effect-types.ts +126 -0
- package/lib/asset-retry.ts +111 -0
- package/lib/assets.ts +92 -0
- package/lib/audit.ts +35 -0
- package/lib/auth-token.ts +94 -0
- package/lib/auth.ts +13 -0
- package/lib/builder/cascade-helpers.ts +51 -0
- package/lib/builder/cascade.ts +533 -0
- package/lib/builder/constants.ts +103 -0
- package/lib/builder/defaults.ts +182 -0
- package/lib/builder/history.ts +48 -0
- package/lib/builder/index.ts +21 -0
- package/lib/builder/layout-styles.ts +344 -0
- package/lib/builder/masonry.ts +166 -0
- package/lib/builder/responsive.ts +156 -0
- package/lib/builder/serializer.ts +845 -0
- package/lib/builder/store-blocks.ts +193 -0
- package/lib/builder/store-canvas.ts +319 -0
- package/lib/builder/store-helpers.ts +490 -0
- package/lib/builder/store-sections.ts +709 -0
- package/lib/builder/store.ts +333 -0
- package/lib/builder/templates.ts +297 -0
- package/lib/builder/types.ts +374 -0
- package/lib/builder/utils.ts +37 -0
- package/lib/color-utils.ts +116 -0
- package/lib/config/index.ts +57 -0
- package/lib/config/types.ts +122 -0
- package/lib/contexts/AssetContext.tsx +79 -0
- package/lib/contexts/NavAnimationContext.tsx +44 -0
- package/lib/contexts/NavColorContext.tsx +38 -0
- package/lib/contexts/PageExitContext.tsx +194 -0
- package/lib/contexts/ThumbStatusContext.tsx +83 -0
- package/lib/csrf-client.ts +34 -0
- package/lib/csrf.ts +68 -0
- package/lib/format-utils.ts +24 -0
- package/lib/hooks/useViewport.ts +42 -0
- package/lib/logger.ts +81 -0
- package/lib/revalidate.ts +23 -0
- package/lib/sanitize.ts +91 -0
- package/lib/sanity/client.ts +8 -0
- package/lib/sanity/queries.ts +486 -0
- package/lib/sanity/types.ts +869 -0
- package/lib/sanity/writeClient.ts +24 -0
- package/lib/security.ts +402 -0
- package/lib/setup/detect.ts +156 -0
- package/lib/shader/glsl/index.ts +27 -0
- package/lib/shader/glsl/pixelate.ts +51 -0
- package/lib/shader/glsl/rgb-shift.ts +45 -0
- package/lib/shader/glsl/ripple.ts +46 -0
- package/lib/shader/glsl/vertex.ts +14 -0
- package/lib/storage/index.ts +211 -0
- package/lib/storage/r2-adapter.ts +286 -0
- package/lib/storage/types.ts +125 -0
- package/lib/styles/provider.tsx +267 -0
- package/lib/thumbnails/generate.ts +151 -0
- package/lib/utils.ts +6 -0
- package/package.json +212 -0
- package/sanity/compose.ts +65 -0
- package/sanity/sanity.config.ts +126 -0
- package/sanity/schemas/assetRegistry.ts +301 -0
- package/sanity/schemas/blocks/blockLayout.ts +90 -0
- package/sanity/schemas/blocks/buttonBlock.ts +82 -0
- package/sanity/schemas/blocks/coverBlock.ts +229 -0
- package/sanity/schemas/blocks/imageBlock.ts +58 -0
- package/sanity/schemas/blocks/imageGridBlock.ts +112 -0
- package/sanity/schemas/blocks/index.ts +9 -0
- package/sanity/schemas/blocks/projectGridBlock.ts +251 -0
- package/sanity/schemas/blocks/spacerBlock.ts +41 -0
- package/sanity/schemas/blocks/textBlock.ts +139 -0
- package/sanity/schemas/blocks/videoBlock.ts +80 -0
- package/sanity/schemas/customSection.ts +69 -0
- package/sanity/schemas/customSectionInstance.ts +163 -0
- package/sanity/schemas/index.ts +111 -0
- package/sanity/schemas/objects/enterAnimationConfig.ts +72 -0
- package/sanity/schemas/objects/hoverEffectConfig.ts +90 -0
- package/sanity/schemas/objects/parallaxGroup.ts +66 -0
- package/sanity/schemas/objects/parallaxSlide.ts +217 -0
- package/sanity/schemas/objects/typewriterConfig.ts +38 -0
- package/sanity/schemas/page.ts +162 -0
- package/sanity/schemas/pageSection.ts +157 -0
- package/sanity/schemas/pageSectionV2.ts +269 -0
- package/sanity/schemas/siteSettings.ts +256 -0
- package/sanity/schemas/siteStyles.ts +212 -0
- package/site/error.ts +4 -0
- package/site/index.ts +8 -0
- package/site/not-found.ts +4 -0
- package/site/page.ts +4 -0
- package/site/preview.ts +4 -0
- package/site/robots.ts +4 -0
- package/site/sitemap.ts +4 -0
- package/site/work.ts +4 -0
- package/studio/index.ts +4 -0
- package/styles/admin.css +85 -0
- package/styles/animations.css +237 -0
- package/styles/base.css +148 -0
- package/styles/globals.css +10 -0
- package/tsconfig.json +25 -0
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import React, { useState } from "react";
|
|
4
|
+
import { useSortable } from "@dnd-kit/sortable";
|
|
5
|
+
import { CSS } from "@dnd-kit/utilities";
|
|
6
|
+
import { makeBlockId } from "./DndWrapper";
|
|
7
|
+
import { ALL_BLOCK_INFO, isSectionBlockType } from "../../lib/builder/types";
|
|
8
|
+
import type { DeviceViewport } from "../../lib/builder/types";
|
|
9
|
+
import { useBuilderStore } from "../../lib/builder/store";
|
|
10
|
+
import type { ContentBlock } from "../../lib/sanity/types";
|
|
11
|
+
import BlockLivePreview from "./BlockLivePreview";
|
|
12
|
+
import { getBlockAlignmentStyles, hasBlockAlignment } from "../../lib/builder/layout-styles";
|
|
13
|
+
import type { BlockLayout } from "../../lib/sanity/types";
|
|
14
|
+
import { BUILDER_ORANGE } from "../../lib/builder/constants";
|
|
15
|
+
|
|
16
|
+
interface SortableBlockProps {
|
|
17
|
+
block: ContentBlock;
|
|
18
|
+
rowKey: string;
|
|
19
|
+
colKey: string;
|
|
20
|
+
blockIndex: number;
|
|
21
|
+
totalBlocks: number;
|
|
22
|
+
isSelected: boolean;
|
|
23
|
+
onSelect: () => void;
|
|
24
|
+
onDelete: () => void;
|
|
25
|
+
onContextMenu?: (e: React.MouseEvent) => void;
|
|
26
|
+
onDuplicate?: () => void;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export default function SortableBlock({
|
|
30
|
+
block,
|
|
31
|
+
rowKey,
|
|
32
|
+
colKey,
|
|
33
|
+
blockIndex,
|
|
34
|
+
totalBlocks,
|
|
35
|
+
isSelected,
|
|
36
|
+
onSelect,
|
|
37
|
+
onDelete,
|
|
38
|
+
onContextMenu,
|
|
39
|
+
onDuplicate,
|
|
40
|
+
}: SortableBlockProps) {
|
|
41
|
+
const previewMode = useBuilderStore((s) => s.previewMode);
|
|
42
|
+
const activeViewport = useBuilderStore((s) => s.activeViewport) as DeviceViewport;
|
|
43
|
+
const canvasZoom = useBuilderStore((s) => s.canvasZoom);
|
|
44
|
+
const reorderBlocks = useBuilderStore((s) => s.reorderBlocks);
|
|
45
|
+
const [isHovered, setIsHovered] = useState(false);
|
|
46
|
+
const id = makeBlockId(rowKey, colKey, block._key);
|
|
47
|
+
|
|
48
|
+
const canMoveUp = blockIndex > 0;
|
|
49
|
+
const canMoveDown = blockIndex < totalBlocks - 1;
|
|
50
|
+
const {
|
|
51
|
+
attributes,
|
|
52
|
+
listeners,
|
|
53
|
+
setNodeRef,
|
|
54
|
+
transform,
|
|
55
|
+
transition,
|
|
56
|
+
isDragging,
|
|
57
|
+
} = useSortable({ id, disabled: previewMode });
|
|
58
|
+
|
|
59
|
+
const info = ALL_BLOCK_INFO.find((b) => b.type === block._type);
|
|
60
|
+
|
|
61
|
+
// Block alignment styles — applied on the SortableBlock wrapper (direct flex child of column).
|
|
62
|
+
// This is the canonical layer for alignment; BlockLivePreview should NOT duplicate these.
|
|
63
|
+
const blockLayout = (block as unknown as Record<string, unknown>).layout as BlockLayout | undefined;
|
|
64
|
+
const alignStyles = hasBlockAlignment(blockLayout) ? getBlockAlignmentStyles(blockLayout) : {};
|
|
65
|
+
// Only force width:100% when no horizontal alignment — align-self needs width:auto to shrink
|
|
66
|
+
const hasHAlign = blockLayout?.align_h && blockLayout.align_h !== "left";
|
|
67
|
+
|
|
68
|
+
const style: React.CSSProperties = {
|
|
69
|
+
transform: CSS.Transform.toString(transform),
|
|
70
|
+
transition,
|
|
71
|
+
opacity: isDragging ? 0.3 : 1,
|
|
72
|
+
...(!hasHAlign ? { width: "100%" } : {}),
|
|
73
|
+
minWidth: 0,
|
|
74
|
+
...alignStyles,
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const showToolbar = isSelected || isHovered;
|
|
78
|
+
|
|
79
|
+
// ---- Preview Mode: pure content, no chrome ----
|
|
80
|
+
if (previewMode) {
|
|
81
|
+
return (
|
|
82
|
+
<div ref={setNodeRef} style={style}>
|
|
83
|
+
<BlockLivePreview block={block} viewport={activeViewport} />
|
|
84
|
+
</div>
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// ---- Section blocks (projectGrid, parallax): NO block chrome ----
|
|
89
|
+
// The parent SortableRow handles the purple toolbar; this just renders content.
|
|
90
|
+
// Click still selects the block so SettingsPanel shows block settings.
|
|
91
|
+
const isSectionBlock = isSectionBlockType(block._type);
|
|
92
|
+
if (isSectionBlock) {
|
|
93
|
+
return (
|
|
94
|
+
<div
|
|
95
|
+
ref={setNodeRef}
|
|
96
|
+
style={style}
|
|
97
|
+
className="relative"
|
|
98
|
+
onClick={(e) => { e.stopPropagation(); onSelect(); }}
|
|
99
|
+
>
|
|
100
|
+
<BlockLivePreview block={block} viewport={activeViewport} editable />
|
|
101
|
+
</div>
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// ---- Design Mode: WYSIWYG — full content with subtle hover overlay + floating controls ----
|
|
106
|
+
return (
|
|
107
|
+
<div
|
|
108
|
+
ref={setNodeRef}
|
|
109
|
+
style={style}
|
|
110
|
+
className={`relative transition-[opacity,box-shadow] ${
|
|
111
|
+
isDragging
|
|
112
|
+
? "ring-2 ring-[#e28b00] ring-offset-1 ring-offset-transparent rounded"
|
|
113
|
+
: ""
|
|
114
|
+
}`}
|
|
115
|
+
onClick={(e) => {
|
|
116
|
+
e.stopPropagation();
|
|
117
|
+
// If user clicked inside a contentEditable element (inline text editing),
|
|
118
|
+
// still select the block but don't steal focus from the editable.
|
|
119
|
+
onSelect();
|
|
120
|
+
}}
|
|
121
|
+
onContextMenu={onContextMenu}
|
|
122
|
+
onMouseEnter={() => setIsHovered(true)}
|
|
123
|
+
onMouseLeave={() => setIsHovered(false)}
|
|
124
|
+
>
|
|
125
|
+
{/* Hover/selection outline overlay — scales with zoom */}
|
|
126
|
+
<div
|
|
127
|
+
className="pointer-events-none absolute inset-0 z-[3] rounded transition-[box-shadow]"
|
|
128
|
+
style={
|
|
129
|
+
isSelected
|
|
130
|
+
? { boxShadow: `inset 0 0 0 ${Math.max(2, Math.min(5, 3 / canvasZoom))}px ${BUILDER_ORANGE}` }
|
|
131
|
+
: isHovered
|
|
132
|
+
? { boxShadow: `inset 0 0 0 ${Math.max(2, Math.min(5, 3 / canvasZoom))}px rgba(226, 139, 0, 0.4)` }
|
|
133
|
+
: undefined
|
|
134
|
+
}
|
|
135
|
+
/>
|
|
136
|
+
|
|
137
|
+
{/* Floating toolbar — centered INSIDE top of block, appears on hover or when selected.
|
|
138
|
+
Positioning (translate) is separated from counter-scaling (scale) into nested
|
|
139
|
+
elements so that zoom changes don't shift the anchor point.
|
|
140
|
+
Delete button is integrated at the end of the toolbar with a gap separator. */}
|
|
141
|
+
<div
|
|
142
|
+
className={`absolute top-0 left-1/2 z-[6] transition-opacity ${
|
|
143
|
+
showToolbar ? "opacity-100" : "opacity-0 pointer-events-none"
|
|
144
|
+
}`}
|
|
145
|
+
style={{ transform: "translateX(-50%)", marginTop: 4 }}
|
|
146
|
+
onClick={(e) => e.stopPropagation()}
|
|
147
|
+
>
|
|
148
|
+
<div
|
|
149
|
+
className="flex items-center gap-1.5"
|
|
150
|
+
style={{ transform: `scale(${Math.min(2, 1 / canvasZoom)})`, transformOrigin: "top center" }}
|
|
151
|
+
>
|
|
152
|
+
<div className="flex items-center bg-[#e28b00] rounded shadow-lg overflow-hidden">
|
|
153
|
+
{/* Move up arrow */}
|
|
154
|
+
<button
|
|
155
|
+
onClick={() => canMoveUp && reorderBlocks(rowKey, colKey, blockIndex, blockIndex - 1)}
|
|
156
|
+
className={`transition-colors px-1 py-0.5 text-[11px] ${
|
|
157
|
+
canMoveUp
|
|
158
|
+
? "text-white/80 hover:text-white hover:bg-white/10 cursor-pointer"
|
|
159
|
+
: "text-white/25 cursor-default"
|
|
160
|
+
}`}
|
|
161
|
+
title="Move block up"
|
|
162
|
+
aria-label="Move block up"
|
|
163
|
+
disabled={!canMoveUp}
|
|
164
|
+
>
|
|
165
|
+
<svg width="10" height="10" viewBox="0 0 10 10" fill="none">
|
|
166
|
+
<path d="M5 2L2 6h6L5 2z" fill="currentColor" />
|
|
167
|
+
</svg>
|
|
168
|
+
</button>
|
|
169
|
+
{/* Move down arrow */}
|
|
170
|
+
<button
|
|
171
|
+
onClick={() => canMoveDown && reorderBlocks(rowKey, colKey, blockIndex, blockIndex + 1)}
|
|
172
|
+
className={`transition-colors px-1 py-0.5 text-[11px] border-l border-white/20 ${
|
|
173
|
+
canMoveDown
|
|
174
|
+
? "text-white/80 hover:text-white hover:bg-white/10 cursor-pointer"
|
|
175
|
+
: "text-white/25 cursor-default"
|
|
176
|
+
}`}
|
|
177
|
+
title="Move block down"
|
|
178
|
+
aria-label="Move block down"
|
|
179
|
+
disabled={!canMoveDown}
|
|
180
|
+
>
|
|
181
|
+
<svg width="10" height="10" viewBox="0 0 10 10" fill="none">
|
|
182
|
+
<path d="M5 8L2 4h6L5 8z" fill="currentColor" />
|
|
183
|
+
</svg>
|
|
184
|
+
</button>
|
|
185
|
+
{/* Block type label */}
|
|
186
|
+
<span className="text-[11px] text-white/70 px-1.5 py-0.5 border-l border-white/20">
|
|
187
|
+
{info?.icon || "▪"} {info?.label || block._type}
|
|
188
|
+
</span>
|
|
189
|
+
{/* Enter animation badge */}
|
|
190
|
+
{block.enter_animation?.preset && block.enter_animation.preset !== "none" && (
|
|
191
|
+
<span className="text-[10px] text-white/50 px-1 py-0.5 border-l border-white/15" title={`Animation: ${block.enter_animation.preset}`}>
|
|
192
|
+
✦
|
|
193
|
+
</span>
|
|
194
|
+
)}
|
|
195
|
+
{/* Duplicate */}
|
|
196
|
+
{onDuplicate && (
|
|
197
|
+
<button
|
|
198
|
+
onClick={onDuplicate}
|
|
199
|
+
className="text-white/70 hover:text-white transition-colors px-1.5 py-0.5 text-[11px] border-l border-white/20 hover:bg-white/10"
|
|
200
|
+
title="Duplicate block (Ctrl+D)"
|
|
201
|
+
aria-label="Duplicate block"
|
|
202
|
+
>
|
|
203
|
+
⧉
|
|
204
|
+
</button>
|
|
205
|
+
)}
|
|
206
|
+
</div>
|
|
207
|
+
{/* Delete button — inline at end of toolbar with gap separator */}
|
|
208
|
+
<button
|
|
209
|
+
onClick={onDelete}
|
|
210
|
+
className="w-5 h-5 rounded-full bg-red-500 text-white flex items-center justify-center hover:bg-red-600 transition-colors shadow-md shrink-0"
|
|
211
|
+
title="Delete block"
|
|
212
|
+
aria-label="Delete block"
|
|
213
|
+
>
|
|
214
|
+
<svg width="10" height="10" viewBox="0 0 10 10">
|
|
215
|
+
<path
|
|
216
|
+
d="M2 2l6 6M8 2l-6 6"
|
|
217
|
+
stroke="currentColor"
|
|
218
|
+
strokeWidth="1.5"
|
|
219
|
+
strokeLinecap="round"
|
|
220
|
+
/>
|
|
221
|
+
</svg>
|
|
222
|
+
</button>
|
|
223
|
+
</div>
|
|
224
|
+
</div>
|
|
225
|
+
|
|
226
|
+
{/* Live content — rendered exactly as it would appear on the public site */}
|
|
227
|
+
<BlockLivePreview block={block} viewport={activeViewport} editable />
|
|
228
|
+
</div>
|
|
229
|
+
);
|
|
230
|
+
}
|
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useState, useMemo } from "react";
|
|
4
|
+
import { useSortable } from "@dnd-kit/sortable";
|
|
5
|
+
import { CSS } from "@dnd-kit/utilities";
|
|
6
|
+
import { makeRowId } from "./DndWrapper";
|
|
7
|
+
import { useBuilderStore } from "../../lib/builder/store";
|
|
8
|
+
import { DEFAULT_GRID_WIDTH } from "../../lib/builder/constants";
|
|
9
|
+
import { DEVICE_HEIGHTS } from "../../lib/builder/types";
|
|
10
|
+
import type { ReactNode } from "react";
|
|
11
|
+
import type { ContentItem, PageSection, PageSectionV2, CustomSectionInstance, ParallaxGroup } from "../../lib/sanity/types";
|
|
12
|
+
import { isPageSection, isPageSectionV2, isCustomSectionInstance, isParallaxGroup } from "../../lib/sanity/types";
|
|
13
|
+
import { getRowLayoutStyles } from "../../lib/builder/layout-styles";
|
|
14
|
+
import { normalizeMinHeight } from "../../lib/builder/utils";
|
|
15
|
+
import { getSectionV2SettingValue, getRowSettingValue } from "./settings-panel/responsive-helpers";
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Convert vh-based CSS values to pixels using the simulated device viewport height.
|
|
19
|
+
* In the builder canvas, 100vh should map to the device frame height, not the browser viewport.
|
|
20
|
+
* Returns the original value unchanged if it's not a vh unit.
|
|
21
|
+
*/
|
|
22
|
+
function vhToBuilderPx(value: string | undefined, deviceHeight: number): string | undefined {
|
|
23
|
+
if (!value) return value;
|
|
24
|
+
const vhMatch = value.match(/^(\d+(?:\.\d+)?)vh$/);
|
|
25
|
+
if (vhMatch) {
|
|
26
|
+
const vhNum = parseFloat(vhMatch[1]);
|
|
27
|
+
return `${Math.round((vhNum / 100) * deviceHeight)}px`;
|
|
28
|
+
}
|
|
29
|
+
return value;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Section type labels */
|
|
33
|
+
const SECTION_TYPE_LABELS: Record<string, string> = {
|
|
34
|
+
projectGrid: "Project Grid",
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Get the section label for a content item.
|
|
39
|
+
* For PageSections: uses section_type directly (no structural inference).
|
|
40
|
+
* For PageSectionV2: returns "Section".
|
|
41
|
+
*/
|
|
42
|
+
function getSectionLabel(item: ContentItem): string | null {
|
|
43
|
+
if (isCustomSectionInstance(item)) {
|
|
44
|
+
return (item as CustomSectionInstance).custom_section_title || "Saved Section";
|
|
45
|
+
}
|
|
46
|
+
if (isParallaxGroup(item)) {
|
|
47
|
+
return "Parallax Showcase";
|
|
48
|
+
}
|
|
49
|
+
if (isPageSectionV2(item)) {
|
|
50
|
+
return "Section";
|
|
51
|
+
}
|
|
52
|
+
if (isPageSection(item)) {
|
|
53
|
+
return SECTION_TYPE_LABELS[item.section_type] || "Section";
|
|
54
|
+
}
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
interface SortableRowProps {
|
|
59
|
+
rowKey: string;
|
|
60
|
+
row: ContentItem;
|
|
61
|
+
isSelected: boolean;
|
|
62
|
+
columnCount: number;
|
|
63
|
+
onSelect: () => void;
|
|
64
|
+
onDelete: () => void;
|
|
65
|
+
onAddColumn: () => void;
|
|
66
|
+
onDuplicate: () => void;
|
|
67
|
+
onMoveUp: () => void;
|
|
68
|
+
onMoveDown: () => void;
|
|
69
|
+
isFirst: boolean;
|
|
70
|
+
isLast: boolean;
|
|
71
|
+
children: ReactNode;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export default function SortableRow({
|
|
75
|
+
rowKey,
|
|
76
|
+
row,
|
|
77
|
+
isSelected,
|
|
78
|
+
columnCount,
|
|
79
|
+
onSelect,
|
|
80
|
+
onDelete,
|
|
81
|
+
onAddColumn,
|
|
82
|
+
onDuplicate,
|
|
83
|
+
onMoveUp,
|
|
84
|
+
onMoveDown,
|
|
85
|
+
isFirst,
|
|
86
|
+
isLast,
|
|
87
|
+
children,
|
|
88
|
+
}: SortableRowProps) {
|
|
89
|
+
const previewMode = useBuilderStore((s) => s.previewMode);
|
|
90
|
+
const canvasZoom = useBuilderStore((s) => s.canvasZoom);
|
|
91
|
+
const activeViewport = useBuilderStore((s) => s.activeViewport);
|
|
92
|
+
const gridSettings = useBuilderStore((s) => s.gridSettings);
|
|
93
|
+
const customSectionCache = useBuilderStore((s) => s._customSectionCache);
|
|
94
|
+
const [isHovered, setIsHovered] = useState(false);
|
|
95
|
+
const {
|
|
96
|
+
attributes,
|
|
97
|
+
listeners,
|
|
98
|
+
setNodeRef,
|
|
99
|
+
transform,
|
|
100
|
+
transition,
|
|
101
|
+
isDragging,
|
|
102
|
+
} = useSortable({ id: makeRowId(rowKey), disabled: previewMode });
|
|
103
|
+
|
|
104
|
+
const style = {
|
|
105
|
+
transform: CSS.Transform.toString(transform),
|
|
106
|
+
transition,
|
|
107
|
+
opacity: isDragging ? 0.4 : 1,
|
|
108
|
+
zIndex: isDragging ? 50 : undefined,
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
// Determine if this is a PageSection, PageSectionV2, or a Row
|
|
112
|
+
const isV2Section = isPageSectionV2(row);
|
|
113
|
+
const isSection = isPageSection(row);
|
|
114
|
+
const sectionLabel = getSectionLabel(row);
|
|
115
|
+
|
|
116
|
+
// For sections: use section settings — viewport-aware for both V1 and V2 sections
|
|
117
|
+
const resolvedSettings = useMemo(() => {
|
|
118
|
+
const overridableKeys = [
|
|
119
|
+
"spacing_top", "spacing_right", "spacing_bottom", "spacing_left",
|
|
120
|
+
"offset_top", "offset_right", "offset_bottom", "offset_left",
|
|
121
|
+
"background_color", "background_opacity",
|
|
122
|
+
"border_color", "border_width", "border_style", "border_sides", "border_radius",
|
|
123
|
+
] as const;
|
|
124
|
+
|
|
125
|
+
if (isV2Section) {
|
|
126
|
+
const v2 = row as PageSectionV2;
|
|
127
|
+
const base = v2.settings || {};
|
|
128
|
+
// Merge responsive overrides on top of base settings for the active viewport.
|
|
129
|
+
const merged: Record<string, unknown> = { ...base };
|
|
130
|
+
for (const key of overridableKeys) {
|
|
131
|
+
merged[key] = getSectionV2SettingValue(v2, activeViewport, key, (base as unknown as Record<string, unknown>)[key]);
|
|
132
|
+
}
|
|
133
|
+
return merged;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (isSection) {
|
|
137
|
+
const ps = row as PageSection;
|
|
138
|
+
const base = ps.settings || {};
|
|
139
|
+
// BUG-013 continuation: Merge responsive overrides for V1 PageSections too
|
|
140
|
+
const merged: Record<string, unknown> = { ...base };
|
|
141
|
+
for (const key of overridableKeys) {
|
|
142
|
+
merged[key] = getRowSettingValue(ps, activeViewport, key, (base as Record<string, unknown>)[key]);
|
|
143
|
+
}
|
|
144
|
+
return merged;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// CustomSectionInstance: merge base section settings from cache + per-instance overrides
|
|
148
|
+
// Then apply viewport-aware responsive resolution (same pattern as V2 sections)
|
|
149
|
+
if (isCustomSectionInstance(row)) {
|
|
150
|
+
const inst = row as CustomSectionInstance;
|
|
151
|
+
const cachedBase = customSectionCache[inst.custom_section_id] || {};
|
|
152
|
+
// Merge: base from cache → per-instance overrides on top
|
|
153
|
+
const base: Record<string, unknown> = { ...cachedBase, ...inst.settings_overrides };
|
|
154
|
+
|
|
155
|
+
// Apply viewport-aware responsive overrides if available
|
|
156
|
+
if (activeViewport !== "desktop" && inst.responsive_overrides) {
|
|
157
|
+
const viewportKey = activeViewport as "tablet" | "phone";
|
|
158
|
+
const viewportOverride = inst.responsive_overrides[viewportKey];
|
|
159
|
+
if (viewportOverride?.settings) {
|
|
160
|
+
for (const key of overridableKeys) {
|
|
161
|
+
const overrideVal = (viewportOverride.settings as Record<string, unknown>)[key];
|
|
162
|
+
if (overrideVal !== undefined && overrideVal !== null) {
|
|
163
|
+
base[key] = overrideVal;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return base;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return {};
|
|
172
|
+
}, [isV2Section, isSection, row, activeViewport, customSectionCache]);
|
|
173
|
+
|
|
174
|
+
const bgColor = (resolvedSettings as Record<string, unknown>).background_color as string || "transparent";
|
|
175
|
+
|
|
176
|
+
// Sections (V1 + V2) are always full-width
|
|
177
|
+
const maxWidth = "100%";
|
|
178
|
+
const gridPadding = "0px";
|
|
179
|
+
// Sections don't have min_height
|
|
180
|
+
const rawMinHeight = undefined;
|
|
181
|
+
const normalizedMinHeight = normalizeMinHeight(rawMinHeight);
|
|
182
|
+
// In the builder canvas, convert vh units to device frame pixels so 100vh matches
|
|
183
|
+
// the simulated viewport height rather than the actual browser viewport.
|
|
184
|
+
const minHeight = vhToBuilderPx(normalizedMinHeight, DEVICE_HEIGHTS[activeViewport]);
|
|
185
|
+
|
|
186
|
+
// New layout styles (spacing TRBL, background w/ opacity+image, offset, border)
|
|
187
|
+
const layoutStyles = getRowLayoutStyles(resolvedSettings as Record<string, unknown> || {});
|
|
188
|
+
|
|
189
|
+
const showToolbar = isSelected || isHovered;
|
|
190
|
+
const coverRow = isSection ? true : false;
|
|
191
|
+
|
|
192
|
+
// ---- Preview Mode: clean rendering with row styles applied ----
|
|
193
|
+
if (previewMode) {
|
|
194
|
+
// Cover rows: full-width, no padding, no container (matches RowRenderer)
|
|
195
|
+
if (coverRow) {
|
|
196
|
+
return (
|
|
197
|
+
<div
|
|
198
|
+
ref={setNodeRef}
|
|
199
|
+
style={{
|
|
200
|
+
...style,
|
|
201
|
+
backgroundColor: bgColor !== "transparent" ? bgColor : undefined,
|
|
202
|
+
}}
|
|
203
|
+
>
|
|
204
|
+
{children}
|
|
205
|
+
</div>
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
// Build merged styles for preview mode
|
|
209
|
+
const previewRowStyle: React.CSSProperties = {
|
|
210
|
+
...style,
|
|
211
|
+
...layoutStyles,
|
|
212
|
+
minHeight,
|
|
213
|
+
};
|
|
214
|
+
// Legacy fallback: if no layout background set, use old bgColor
|
|
215
|
+
if (!layoutStyles.backgroundColor && !layoutStyles.backgroundImage) {
|
|
216
|
+
if (bgColor !== "transparent") previewRowStyle.backgroundColor = bgColor;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return (
|
|
220
|
+
<div ref={setNodeRef} style={previewRowStyle}>
|
|
221
|
+
<div style={{ maxWidth, margin: "0 auto", paddingLeft: maxWidth !== "100%" ? gridPadding : undefined, paddingRight: maxWidth !== "100%" ? gridPadding : undefined }}>
|
|
222
|
+
{children}
|
|
223
|
+
</div>
|
|
224
|
+
</div>
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// ---- Design Mode: WYSIWYG — same visual rendering as Preview, with hover overlays ----
|
|
229
|
+
// Build merged styles for design mode
|
|
230
|
+
const designRowStyle: React.CSSProperties = {
|
|
231
|
+
...style,
|
|
232
|
+
...layoutStyles,
|
|
233
|
+
minHeight: coverRow ? undefined : minHeight,
|
|
234
|
+
};
|
|
235
|
+
// Legacy fallback: if no layout background set, use old bgColor
|
|
236
|
+
if (!layoutStyles.backgroundColor && !layoutStyles.backgroundImage) {
|
|
237
|
+
if (bgColor !== "transparent") designRowStyle.backgroundColor = bgColor;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return (
|
|
241
|
+
<div
|
|
242
|
+
ref={setNodeRef}
|
|
243
|
+
style={designRowStyle}
|
|
244
|
+
className={`relative transition-[opacity,box-shadow] ${
|
|
245
|
+
isDragging ? "ring-2 ring-[#93278f] ring-offset-2 ring-offset-[#0a0a0a]" : ""
|
|
246
|
+
}`}
|
|
247
|
+
onClick={(e) => { e.stopPropagation(); onSelect(); }}
|
|
248
|
+
onMouseEnter={() => setIsHovered(true)}
|
|
249
|
+
onMouseLeave={() => setIsHovered(false)}
|
|
250
|
+
>
|
|
251
|
+
{/* Selection/hover outline — scales with zoom */}
|
|
252
|
+
<div
|
|
253
|
+
className="pointer-events-none absolute inset-0 z-[1] transition-[box-shadow]"
|
|
254
|
+
style={
|
|
255
|
+
isSelected
|
|
256
|
+
? { boxShadow: `inset 0 0 0 ${Math.max(2, Math.min(5, 3 / canvasZoom))}px #93278f` }
|
|
257
|
+
: isHovered
|
|
258
|
+
? { boxShadow: `inset 0 0 0 ${Math.max(2, Math.min(5, 3 / canvasZoom))}px rgba(147, 39, 143, 0.4)` }
|
|
259
|
+
: undefined
|
|
260
|
+
}
|
|
261
|
+
/>
|
|
262
|
+
|
|
263
|
+
{/* Section toolbar — wide pill aligned top-left outside the row */}
|
|
264
|
+
<div
|
|
265
|
+
className={`absolute top-0 left-0 z-[5] flex flex-col items-stretch transition-opacity ${
|
|
266
|
+
showToolbar ? "opacity-100" : "opacity-0 pointer-events-none"
|
|
267
|
+
}`}
|
|
268
|
+
style={{
|
|
269
|
+
transform: `translateX(-100%) scale(${Math.min(1.5, 1 / canvasZoom)})`,
|
|
270
|
+
transformOrigin: "top right",
|
|
271
|
+
width: "90px",
|
|
272
|
+
}}
|
|
273
|
+
onClick={(e) => { e.stopPropagation(); onSelect(); }}
|
|
274
|
+
>
|
|
275
|
+
{/* Main toolbar — drag + actions */}
|
|
276
|
+
<div
|
|
277
|
+
className="flex flex-col items-stretch bg-[#93278f]/90 rounded-l-lg shadow-lg py-2 px-2.5 gap-1 cursor-grab active:cursor-grabbing"
|
|
278
|
+
{...attributes}
|
|
279
|
+
{...listeners}
|
|
280
|
+
>
|
|
281
|
+
{/* Section label — shows specific type for page sections */}
|
|
282
|
+
<span className="text-[11px] text-white select-none leading-tight pointer-events-none font-medium tracking-wide">
|
|
283
|
+
{sectionLabel || "Section"}
|
|
284
|
+
</span>
|
|
285
|
+
|
|
286
|
+
{/* Action row: Duplicate + Move up + Move down */}
|
|
287
|
+
<div className="flex items-center gap-1 py-0.5">
|
|
288
|
+
<button
|
|
289
|
+
onClick={(e) => { e.stopPropagation(); onDuplicate(); }}
|
|
290
|
+
onPointerDown={(e) => e.stopPropagation()}
|
|
291
|
+
className="flex items-center justify-center text-[12px] text-white/80 hover:text-white transition-colors"
|
|
292
|
+
title="Duplicate section"
|
|
293
|
+
aria-label="Duplicate section"
|
|
294
|
+
>
|
|
295
|
+
⧉
|
|
296
|
+
</button>
|
|
297
|
+
<button
|
|
298
|
+
onClick={(e) => { e.stopPropagation(); onMoveUp(); }}
|
|
299
|
+
onPointerDown={(e) => e.stopPropagation()}
|
|
300
|
+
disabled={isFirst}
|
|
301
|
+
className="flex items-center justify-center text-[12px] text-white/80 hover:text-white transition-colors disabled:opacity-30 disabled:cursor-not-allowed"
|
|
302
|
+
title="Move up"
|
|
303
|
+
aria-label="Move section up"
|
|
304
|
+
>
|
|
305
|
+
↑
|
|
306
|
+
</button>
|
|
307
|
+
<button
|
|
308
|
+
onClick={(e) => { e.stopPropagation(); onMoveDown(); }}
|
|
309
|
+
onPointerDown={(e) => e.stopPropagation()}
|
|
310
|
+
disabled={isLast}
|
|
311
|
+
className="flex items-center justify-center text-[12px] text-white/80 hover:text-white transition-colors disabled:opacity-30 disabled:cursor-not-allowed"
|
|
312
|
+
title="Move down"
|
|
313
|
+
aria-label="Move section down"
|
|
314
|
+
>
|
|
315
|
+
↓
|
|
316
|
+
</button>
|
|
317
|
+
</div>
|
|
318
|
+
|
|
319
|
+
{/* Add column — shown for V2 sections and regular rows, hidden for closed sections (V1 PageSection) */}
|
|
320
|
+
{(!sectionLabel || isV2Section) && (
|
|
321
|
+
<button
|
|
322
|
+
onClick={(e) => { e.stopPropagation(); onAddColumn(); }}
|
|
323
|
+
onPointerDown={(e) => e.stopPropagation()}
|
|
324
|
+
className="flex items-center gap-1 text-[11px] text-white/80 hover:text-white transition-colors py-0.5"
|
|
325
|
+
title="Add column"
|
|
326
|
+
aria-label="Add column"
|
|
327
|
+
>
|
|
328
|
+
<span className="text-white/50">+</span> Col
|
|
329
|
+
</button>
|
|
330
|
+
)}
|
|
331
|
+
|
|
332
|
+
{/* Delete */}
|
|
333
|
+
<button
|
|
334
|
+
onClick={(e) => { e.stopPropagation(); onDelete(); }}
|
|
335
|
+
onPointerDown={(e) => e.stopPropagation()}
|
|
336
|
+
className="flex items-center gap-1 text-[11px] text-white/80 hover:text-red-300 transition-colors py-0.5"
|
|
337
|
+
title="Delete section"
|
|
338
|
+
aria-label="Delete section"
|
|
339
|
+
>
|
|
340
|
+
<span className="text-white/50">-</span> Delete
|
|
341
|
+
</button>
|
|
342
|
+
</div>
|
|
343
|
+
</div>
|
|
344
|
+
|
|
345
|
+
{/* Row bg color indicator */}
|
|
346
|
+
{bgColor !== "transparent" && isSelected && (
|
|
347
|
+
<div className="absolute top-1 right-1 z-[5]" style={{ transform: `scale(${1 / canvasZoom})`, transformOrigin: "top right" }}>
|
|
348
|
+
<span
|
|
349
|
+
className="w-4 h-4 rounded-full border-2 border-white/50 block shadow-sm"
|
|
350
|
+
style={{ backgroundColor: bgColor }}
|
|
351
|
+
title={`Background: ${bgColor}`}
|
|
352
|
+
/>
|
|
353
|
+
</div>
|
|
354
|
+
)}
|
|
355
|
+
|
|
356
|
+
{/* Content — same layout as Preview */}
|
|
357
|
+
<div style={coverRow ? undefined : { maxWidth, margin: "0 auto", paddingLeft: maxWidth !== "100%" ? gridPadding : undefined, paddingRight: maxWidth !== "100%" ? gridPadding : undefined }} className="relative z-[2]">
|
|
358
|
+
{children}
|
|
359
|
+
</div>
|
|
360
|
+
</div>
|
|
361
|
+
);
|
|
362
|
+
}
|