@morphika/andami 0.2.26 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/app/admin/pages/[slug]/page.tsx +41 -47
- package/app/api/admin/assets/scan/route.ts +40 -13
- package/app/api/admin/custom-sections/[slug]/route.ts +4 -1
- package/app/api/admin/custom-sections/route.ts +4 -1
- package/app/api/admin/pages/[slug]/route.ts +7 -1
- package/app/api/admin/pages/route.ts +4 -1
- package/app/api/admin/r2/connect/route.ts +19 -1
- package/app/api/admin/r2/disconnect/route.ts +3 -0
- package/app/api/admin/r2/rename/route.ts +52 -13
- package/app/api/admin/r2/upload-url/route.ts +8 -1
- package/app/api/admin/settings/route.ts +4 -1
- package/app/api/admin/styles/route.ts +4 -1
- package/components/admin/styles/GridLayoutEditor.tsx +46 -46
- package/components/blocks/BlockRenderer.tsx +15 -2
- package/components/blocks/CoverSectionRenderer.tsx +75 -3
- package/components/blocks/ImageGridBlockRenderer.tsx +17 -11
- package/components/blocks/ParallaxGroupRenderer.tsx +45 -10
- package/components/blocks/ProjectCarouselBlockRenderer.tsx +527 -0
- package/components/blocks/ShaderCanvas.tsx +10 -6
- package/components/builder/BlockCardIcons.tsx +227 -0
- package/components/builder/BlockLivePreview.tsx +5 -0
- package/components/builder/BlockTypePicker.tsx +36 -63
- package/components/builder/BuilderCanvas.tsx +6 -2
- package/components/builder/ColumnDragOverlay.tsx +3 -3
- package/components/builder/CoverRowResizeHandle.tsx +5 -2
- package/components/builder/CoverSectionCanvas.tsx +45 -52
- package/components/builder/DndWrapper.tsx +1 -1
- package/components/builder/InsertionLines.tsx +1 -1
- package/components/builder/ParallaxGroupCanvas.tsx +12 -71
- package/components/builder/ReadOnlyFrame.tsx +4 -23
- package/components/builder/SectionCardIcons.tsx +320 -0
- package/components/builder/SectionEditorBar.tsx +17 -12
- package/components/builder/SectionTypePicker.tsx +34 -138
- package/components/builder/SectionV2Canvas.tsx +1 -1
- package/components/builder/SectionV2Column.tsx +19 -30
- package/components/builder/SettingsPanel.tsx +8 -32
- package/components/builder/SortableBlock.tsx +42 -50
- package/components/builder/SortableRow.tsx +207 -19
- package/components/builder/blockStyles.tsx +59 -180
- package/components/builder/editors/ProjectCarouselBlockEditor.tsx +443 -0
- package/components/builder/editors/index.ts +1 -0
- package/components/builder/iconPrimitives.tsx +78 -0
- package/components/builder/live-preview/LiveImagePreview.tsx +16 -2
- package/components/builder/live-preview/LiveProjectCarouselPreview.tsx +227 -0
- package/components/builder/live-preview/LiveVideoPreview.tsx +15 -2
- package/components/builder/live-preview/index.ts +1 -0
- package/components/builder/settings-panel/BlockSettings.tsx +7 -0
- package/components/builder/settings-panel/ColumnV2Settings.tsx +5 -5
- package/components/builder/settings-panel/CoverSectionSettings.tsx +28 -1
- package/components/builder/settings-panel/SectionV2Settings.tsx +14 -14
- package/lib/animation/enter-types.ts +1 -0
- package/lib/animation/hover-effect-types.ts +1 -0
- package/lib/assets.ts +17 -2
- package/lib/builder/block-registrations.ts +268 -0
- package/lib/builder/block-registry.ts +195 -0
- package/lib/builder/constants.ts +22 -15
- package/lib/builder/defaults.ts +21 -0
- package/lib/builder/format.ts +25 -0
- package/lib/builder/history.ts +0 -3
- package/lib/builder/index.ts +16 -0
- package/lib/builder/layout-styles.ts +1 -1
- package/lib/builder/registry.ts +44 -0
- package/lib/builder/section-visibility.ts +36 -0
- package/lib/builder/serializer/normalizers.ts +15 -6
- package/lib/builder/serializer/serializers.ts +3 -3
- package/lib/builder/store-blocks.ts +16 -9
- package/lib/builder/store-cover.ts +76 -8
- package/lib/builder/store-sections.ts +1 -1
- package/lib/builder/store.ts +0 -2
- package/lib/builder/types.ts +9 -5
- package/lib/csrf.ts +31 -0
- package/lib/sanity/types.ts +54 -2
- package/lib/security.ts +50 -0
- package/lib/version.ts +1 -1
- package/package.json +1 -1
- package/sanity/schemas/blocks/index.ts +2 -1
- package/sanity/schemas/blocks/projectCarouselBlock.ts +218 -0
- package/sanity/schemas/index.ts +4 -1
- package/sanity/schemas/objects/coverSection.ts +35 -3
- package/sanity/schemas/pageSectionV2.ts +1 -0
- package/components/builder/ParallaxSlideHeader.tsx +0 -113
|
@@ -8,11 +8,12 @@ import { useBuilderStore } from "../../lib/builder/store";
|
|
|
8
8
|
import { DEFAULT_GRID_WIDTH } from "../../lib/builder/constants";
|
|
9
9
|
import { DEVICE_HEIGHTS, isSectionBlockSection } from "../../lib/builder/types";
|
|
10
10
|
import type { ReactNode } from "react";
|
|
11
|
-
import type { ContentItem, PageSectionV2, CustomSectionInstance, ParallaxGroup } from "../../lib/sanity/types";
|
|
12
|
-
import { isPageSectionV2, isCustomSectionInstance, isParallaxGroup } from "../../lib/sanity/types";
|
|
11
|
+
import type { ContentItem, PageSectionV2, CustomSectionInstance, ParallaxGroup, CoverSection } from "../../lib/sanity/types";
|
|
12
|
+
import { isPageSectionV2, isCustomSectionInstance, isParallaxGroup, isCoverSection } from "../../lib/sanity/types";
|
|
13
13
|
import { getRowLayoutStyles } from "../../lib/builder/layout-styles";
|
|
14
14
|
import { normalizeMinHeight } from "../../lib/builder/utils";
|
|
15
15
|
import { getSectionV2SettingValue } from "./settings-panel/responsive-helpers";
|
|
16
|
+
import { formatRowPercent } from "../../lib/builder/format";
|
|
16
17
|
|
|
17
18
|
/**
|
|
18
19
|
* Convert vh-based CSS values to pixels using the simulated device viewport height.
|
|
@@ -40,6 +41,9 @@ function getSectionLabel(item: ContentItem): string | null {
|
|
|
40
41
|
if (isParallaxGroup(item)) {
|
|
41
42
|
return "Parallax Showcase";
|
|
42
43
|
}
|
|
44
|
+
if (isCoverSection(item)) {
|
|
45
|
+
return "Cover Section";
|
|
46
|
+
}
|
|
43
47
|
if (isPageSectionV2(item)) {
|
|
44
48
|
const section = item as PageSectionV2;
|
|
45
49
|
if (isSectionBlockSection(section)) {
|
|
@@ -52,6 +56,7 @@ function getSectionLabel(item: ContentItem): string | null {
|
|
|
52
56
|
return null;
|
|
53
57
|
}
|
|
54
58
|
|
|
59
|
+
|
|
55
60
|
interface SortableRowProps {
|
|
56
61
|
rowKey: string;
|
|
57
62
|
row: ContentItem;
|
|
@@ -90,6 +95,13 @@ export default function SortableRow({
|
|
|
90
95
|
const canvasZoom = useBuilderStore((s) => s.canvasZoom);
|
|
91
96
|
const activeViewport = useBuilderStore((s) => s.activeViewport);
|
|
92
97
|
const customSectionCache = useBuilderStore((s) => s._customSectionCache);
|
|
98
|
+
const addCoverRow = useBuilderStore((s) => s.addCoverRow);
|
|
99
|
+
const removeCoverRow = useBuilderStore((s) => s.removeCoverRow);
|
|
100
|
+
const addParallaxSlide = useBuilderStore((s) => s.addParallaxSlide);
|
|
101
|
+
const removeParallaxSlide = useBuilderStore((s) => s.removeParallaxSlide);
|
|
102
|
+
const moveParallaxSlide = useBuilderStore((s) => s.moveParallaxSlide);
|
|
103
|
+
const selectRow = useBuilderStore((s) => s.selectRow);
|
|
104
|
+
const selectedRowKey = useBuilderStore((s) => s.selectedRowKey);
|
|
93
105
|
const [isHovered, setIsHovered] = useState(false);
|
|
94
106
|
const {
|
|
95
107
|
attributes,
|
|
@@ -236,20 +248,20 @@ export default function SortableRow({
|
|
|
236
248
|
style={{
|
|
237
249
|
inset: `${-Math.max(2, Math.min(5, 3 / canvasZoom))}px`,
|
|
238
250
|
...(isSelected
|
|
239
|
-
? { boxShadow: `inset 0 0 0 ${Math.max(2, Math.min(5, 3 / canvasZoom))}px #
|
|
251
|
+
? { boxShadow: `inset 0 0 0 ${Math.max(2, Math.min(5, 3 / canvasZoom))}px #7500d5` }
|
|
240
252
|
: isHovered
|
|
241
|
-
? { boxShadow: `inset 0 0 0 ${Math.max(2, Math.min(5, 3 / canvasZoom))}px rgba(
|
|
253
|
+
? { boxShadow: `inset 0 0 0 ${Math.max(2, Math.min(5, 3 / canvasZoom))}px rgba(117, 0, 213, 0.4)` }
|
|
242
254
|
: {}),
|
|
243
255
|
}}
|
|
244
256
|
/>
|
|
245
257
|
|
|
246
|
-
{/* Section toolbar —
|
|
258
|
+
{/* Section toolbar — floating pill aligned top-left outside the row (8px gap) */}
|
|
247
259
|
<div
|
|
248
260
|
className={`absolute top-0 left-0 z-[5] flex flex-col items-stretch transition-opacity ${
|
|
249
261
|
showToolbar ? "opacity-100" : "opacity-0 pointer-events-none"
|
|
250
262
|
}`}
|
|
251
263
|
style={{
|
|
252
|
-
transform: `translateX(-100%) scale(${Math.min(1.5, 1 / canvasZoom)})`,
|
|
264
|
+
transform: `translateX(calc(-100% - 8px)) scale(${Math.min(1.5, 1 / canvasZoom)})`,
|
|
253
265
|
transformOrigin: "top right",
|
|
254
266
|
width: "90px",
|
|
255
267
|
}}
|
|
@@ -264,18 +276,16 @@ export default function SortableRow({
|
|
|
264
276
|
>
|
|
265
277
|
{/* Main toolbar — drag + actions */}
|
|
266
278
|
<div
|
|
267
|
-
className="flex flex-col items-stretch rounded-
|
|
279
|
+
className="flex flex-col items-stretch rounded-lg py-2 px-2.5 gap-1 cursor-grab active:cursor-grabbing"
|
|
268
280
|
style={{
|
|
269
|
-
background: "
|
|
270
|
-
|
|
271
|
-
border: "1px solid rgba(255,255,255,0.06)",
|
|
272
|
-
borderRight: "none",
|
|
281
|
+
background: "#e0daff",
|
|
282
|
+
border: "1px solid #7500d5",
|
|
273
283
|
}}
|
|
274
284
|
{...attributes}
|
|
275
285
|
{...listeners}
|
|
276
286
|
>
|
|
277
287
|
{/* Section label — shows specific type for page sections */}
|
|
278
|
-
<span className="text-[11px] select-none leading-tight pointer-events-none font-medium tracking-wide" style={{ color: "
|
|
288
|
+
<span className="text-[11px] select-none leading-tight pointer-events-none font-medium tracking-wide" style={{ color: "#7500d5" }}>
|
|
279
289
|
{sectionLabel || "Section"}
|
|
280
290
|
</span>
|
|
281
291
|
|
|
@@ -284,7 +294,10 @@ export default function SortableRow({
|
|
|
284
294
|
<button
|
|
285
295
|
onClick={(e) => { e.stopPropagation(); onDuplicate(); }}
|
|
286
296
|
onPointerDown={(e) => e.stopPropagation()}
|
|
287
|
-
className="flex items-center justify-center text-[12px]
|
|
297
|
+
className="flex items-center justify-center text-[12px] transition-colors"
|
|
298
|
+
style={{ color: "rgba(117, 0, 213, 0.6)" }}
|
|
299
|
+
onMouseEnter={(e) => { e.currentTarget.style.color = "#7500d5"; }}
|
|
300
|
+
onMouseLeave={(e) => { e.currentTarget.style.color = "rgba(117, 0, 213, 0.6)"; }}
|
|
288
301
|
title="Duplicate section"
|
|
289
302
|
aria-label="Duplicate section"
|
|
290
303
|
>
|
|
@@ -294,7 +307,10 @@ export default function SortableRow({
|
|
|
294
307
|
onClick={(e) => { e.stopPropagation(); onMoveUp(); }}
|
|
295
308
|
onPointerDown={(e) => e.stopPropagation()}
|
|
296
309
|
disabled={isFirst}
|
|
297
|
-
className="flex items-center justify-center text-[12px]
|
|
310
|
+
className="flex items-center justify-center text-[12px] transition-colors disabled:opacity-30 disabled:cursor-not-allowed"
|
|
311
|
+
style={{ color: "rgba(117, 0, 213, 0.6)" }}
|
|
312
|
+
onMouseEnter={(e) => { if (!e.currentTarget.disabled) e.currentTarget.style.color = "#7500d5"; }}
|
|
313
|
+
onMouseLeave={(e) => { if (!e.currentTarget.disabled) e.currentTarget.style.color = "rgba(117, 0, 213, 0.6)"; }}
|
|
298
314
|
title="Move up"
|
|
299
315
|
aria-label="Move section up"
|
|
300
316
|
>
|
|
@@ -304,7 +320,10 @@ export default function SortableRow({
|
|
|
304
320
|
onClick={(e) => { e.stopPropagation(); onMoveDown(); }}
|
|
305
321
|
onPointerDown={(e) => e.stopPropagation()}
|
|
306
322
|
disabled={isLast}
|
|
307
|
-
className="flex items-center justify-center text-[12px]
|
|
323
|
+
className="flex items-center justify-center text-[12px] transition-colors disabled:opacity-30 disabled:cursor-not-allowed"
|
|
324
|
+
style={{ color: "rgba(117, 0, 213, 0.6)" }}
|
|
325
|
+
onMouseEnter={(e) => { if (!e.currentTarget.disabled) e.currentTarget.style.color = "#7500d5"; }}
|
|
326
|
+
onMouseLeave={(e) => { if (!e.currentTarget.disabled) e.currentTarget.style.color = "rgba(117, 0, 213, 0.6)"; }}
|
|
308
327
|
title="Move down"
|
|
309
328
|
aria-label="Move section down"
|
|
310
329
|
>
|
|
@@ -317,11 +336,14 @@ export default function SortableRow({
|
|
|
317
336
|
<button
|
|
318
337
|
onClick={(e) => { e.stopPropagation(); onAddColumn(); }}
|
|
319
338
|
onPointerDown={(e) => e.stopPropagation()}
|
|
320
|
-
className="flex items-center gap-1 text-[11px]
|
|
339
|
+
className="flex items-center gap-1 text-[11px] transition-colors py-0.5"
|
|
340
|
+
style={{ color: "rgba(117, 0, 213, 0.6)" }}
|
|
341
|
+
onMouseEnter={(e) => { e.currentTarget.style.color = "#7500d5"; }}
|
|
342
|
+
onMouseLeave={(e) => { e.currentTarget.style.color = "rgba(117, 0, 213, 0.6)"; }}
|
|
321
343
|
title={`Add ${addColumnLabel.toLowerCase()}`}
|
|
322
344
|
aria-label={`Add ${addColumnLabel.toLowerCase()}`}
|
|
323
345
|
>
|
|
324
|
-
<span
|
|
346
|
+
<span style={{ color: "rgba(117, 0, 213, 0.4)" }}>+</span> {addColumnLabel}
|
|
325
347
|
</button>
|
|
326
348
|
)}
|
|
327
349
|
|
|
@@ -329,13 +351,179 @@ export default function SortableRow({
|
|
|
329
351
|
<button
|
|
330
352
|
onClick={(e) => { e.stopPropagation(); onDelete(); }}
|
|
331
353
|
onPointerDown={(e) => e.stopPropagation()}
|
|
332
|
-
className="flex items-center gap-1 text-[11px]
|
|
354
|
+
className="flex items-center gap-1 text-[11px] transition-colors py-0.5"
|
|
355
|
+
style={{ color: "rgba(117, 0, 213, 0.6)" }}
|
|
356
|
+
onMouseEnter={(e) => { e.currentTarget.style.color = "#7500d5"; }}
|
|
357
|
+
onMouseLeave={(e) => { e.currentTarget.style.color = "rgba(117, 0, 213, 0.6)"; }}
|
|
333
358
|
title="Delete section"
|
|
334
359
|
aria-label="Delete section"
|
|
335
360
|
>
|
|
336
|
-
<span
|
|
361
|
+
<span style={{ color: "rgba(117, 0, 213, 0.4)" }}>-</span> Delete
|
|
337
362
|
</button>
|
|
338
363
|
</div>
|
|
364
|
+
|
|
365
|
+
{/* Cover rows pill — replaces the former top "Cover Section" banner.
|
|
366
|
+
Lists each row with its height percent + remove button, plus a
|
|
367
|
+
"+ Row" action at the bottom. Only rendered for Cover sections. */}
|
|
368
|
+
{isCoverSection(row) && (() => {
|
|
369
|
+
const coverSection = row as CoverSection;
|
|
370
|
+
const coverRows = coverSection.cover_rows || [];
|
|
371
|
+
const canAddRow = coverRows.length < 5;
|
|
372
|
+
const canRemoveRow = coverRows.length > 1;
|
|
373
|
+
return (
|
|
374
|
+
<div
|
|
375
|
+
className="flex flex-col items-stretch rounded-lg py-1.5 px-2 mt-2 gap-0.5"
|
|
376
|
+
style={{
|
|
377
|
+
background: "#e0daff",
|
|
378
|
+
border: "1px solid #7500d5",
|
|
379
|
+
}}
|
|
380
|
+
onClick={(e) => e.stopPropagation()}
|
|
381
|
+
>
|
|
382
|
+
{coverRows.map((r) => (
|
|
383
|
+
<div key={r._key} className="flex items-center justify-between gap-1 py-0.5">
|
|
384
|
+
<span className="text-[11px] font-medium" style={{ color: "#7500d5" }}>
|
|
385
|
+
{formatRowPercent(r.height_percent)}% Row
|
|
386
|
+
</span>
|
|
387
|
+
<button
|
|
388
|
+
onClick={(e) => {
|
|
389
|
+
e.stopPropagation();
|
|
390
|
+
if (canRemoveRow) removeCoverRow(coverSection._key, r._key);
|
|
391
|
+
}}
|
|
392
|
+
onPointerDown={(e) => e.stopPropagation()}
|
|
393
|
+
disabled={!canRemoveRow}
|
|
394
|
+
className="flex items-center justify-center text-[12px] leading-none transition-colors disabled:opacity-30 disabled:cursor-not-allowed"
|
|
395
|
+
style={{ color: "rgba(117, 0, 213, 0.6)" }}
|
|
396
|
+
onMouseEnter={(e) => { if (!e.currentTarget.disabled) e.currentTarget.style.color = "#7500d5"; }}
|
|
397
|
+
onMouseLeave={(e) => { if (!e.currentTarget.disabled) e.currentTarget.style.color = "rgba(117, 0, 213, 0.6)"; }}
|
|
398
|
+
title={canRemoveRow ? "Remove row" : "Cover must have at least 1 row"}
|
|
399
|
+
aria-label="Remove row"
|
|
400
|
+
>
|
|
401
|
+
×
|
|
402
|
+
</button>
|
|
403
|
+
</div>
|
|
404
|
+
))}
|
|
405
|
+
|
|
406
|
+
{/* + Row */}
|
|
407
|
+
<button
|
|
408
|
+
onClick={(e) => {
|
|
409
|
+
e.stopPropagation();
|
|
410
|
+
if (canAddRow) addCoverRow(coverSection._key);
|
|
411
|
+
}}
|
|
412
|
+
onPointerDown={(e) => e.stopPropagation()}
|
|
413
|
+
disabled={!canAddRow}
|
|
414
|
+
className="flex items-center gap-1 text-[11px] transition-colors py-0.5 disabled:opacity-30 disabled:cursor-not-allowed"
|
|
415
|
+
style={{ color: "rgba(117, 0, 213, 0.6)" }}
|
|
416
|
+
onMouseEnter={(e) => { if (!e.currentTarget.disabled) e.currentTarget.style.color = "#7500d5"; }}
|
|
417
|
+
onMouseLeave={(e) => { if (!e.currentTarget.disabled) e.currentTarget.style.color = "rgba(117, 0, 213, 0.6)"; }}
|
|
418
|
+
title={canAddRow ? "Add row" : "Cover supports up to 5 rows"}
|
|
419
|
+
aria-label="Add row"
|
|
420
|
+
>
|
|
421
|
+
<span style={{ color: "rgba(117, 0, 213, 0.4)" }}>+</span> Row
|
|
422
|
+
</button>
|
|
423
|
+
</div>
|
|
424
|
+
);
|
|
425
|
+
})()}
|
|
426
|
+
|
|
427
|
+
{/* Parallax slides pill — replaces the former group banner + per-slide
|
|
428
|
+
headers + bottom "+ Add Slide" button. Lists each slide with
|
|
429
|
+
click-to-select, reorder arrows, delete, and a "+ Slide" action. */}
|
|
430
|
+
{isParallaxGroup(row) && (() => {
|
|
431
|
+
const parallaxGroup = row as ParallaxGroup;
|
|
432
|
+
const slides = parallaxGroup.slides || [];
|
|
433
|
+
const canRemoveSlide = slides.length > 1;
|
|
434
|
+
return (
|
|
435
|
+
<div
|
|
436
|
+
className="flex flex-col items-stretch rounded-lg py-1.5 px-2 mt-2 gap-0.5"
|
|
437
|
+
style={{
|
|
438
|
+
background: "#e0daff",
|
|
439
|
+
border: "1px solid #7500d5",
|
|
440
|
+
}}
|
|
441
|
+
onClick={(e) => e.stopPropagation()}
|
|
442
|
+
>
|
|
443
|
+
{slides.map((slide, idx) => {
|
|
444
|
+
const isActive = selectedRowKey === slide._key;
|
|
445
|
+
return (
|
|
446
|
+
<div
|
|
447
|
+
key={slide._key}
|
|
448
|
+
onClick={(e) => { e.stopPropagation(); selectRow(slide._key); }}
|
|
449
|
+
className="flex items-center justify-between gap-1 py-0.5 px-1 -mx-1 rounded cursor-pointer transition-colors"
|
|
450
|
+
style={{ background: isActive ? "rgba(117, 0, 213, 0.15)" : "transparent" }}
|
|
451
|
+
onMouseEnter={(e) => { if (!isActive) e.currentTarget.style.background = "rgba(117, 0, 213, 0.08)"; }}
|
|
452
|
+
onMouseLeave={(e) => { if (!isActive) e.currentTarget.style.background = "transparent"; }}
|
|
453
|
+
>
|
|
454
|
+
<span className="text-[11px] font-medium" style={{ color: "#7500d5" }}>
|
|
455
|
+
Slide {idx + 1}
|
|
456
|
+
</span>
|
|
457
|
+
<div className="flex items-center gap-0.5">
|
|
458
|
+
<button
|
|
459
|
+
onClick={(e) => {
|
|
460
|
+
e.stopPropagation();
|
|
461
|
+
if (idx > 0) moveParallaxSlide(parallaxGroup._key, slide._key, "up");
|
|
462
|
+
}}
|
|
463
|
+
onPointerDown={(e) => e.stopPropagation()}
|
|
464
|
+
disabled={idx === 0}
|
|
465
|
+
className="flex items-center justify-center text-[10px] leading-none transition-colors disabled:opacity-30 disabled:cursor-not-allowed"
|
|
466
|
+
style={{ color: "rgba(117, 0, 213, 0.6)" }}
|
|
467
|
+
onMouseEnter={(e) => { if (!e.currentTarget.disabled) e.currentTarget.style.color = "#7500d5"; }}
|
|
468
|
+
onMouseLeave={(e) => { if (!e.currentTarget.disabled) e.currentTarget.style.color = "rgba(117, 0, 213, 0.6)"; }}
|
|
469
|
+
title="Move slide up"
|
|
470
|
+
aria-label="Move slide up"
|
|
471
|
+
>
|
|
472
|
+
↑
|
|
473
|
+
</button>
|
|
474
|
+
<button
|
|
475
|
+
onClick={(e) => {
|
|
476
|
+
e.stopPropagation();
|
|
477
|
+
if (idx < slides.length - 1) moveParallaxSlide(parallaxGroup._key, slide._key, "down");
|
|
478
|
+
}}
|
|
479
|
+
onPointerDown={(e) => e.stopPropagation()}
|
|
480
|
+
disabled={idx === slides.length - 1}
|
|
481
|
+
className="flex items-center justify-center text-[10px] leading-none transition-colors disabled:opacity-30 disabled:cursor-not-allowed"
|
|
482
|
+
style={{ color: "rgba(117, 0, 213, 0.6)" }}
|
|
483
|
+
onMouseEnter={(e) => { if (!e.currentTarget.disabled) e.currentTarget.style.color = "#7500d5"; }}
|
|
484
|
+
onMouseLeave={(e) => { if (!e.currentTarget.disabled) e.currentTarget.style.color = "rgba(117, 0, 213, 0.6)"; }}
|
|
485
|
+
title="Move slide down"
|
|
486
|
+
aria-label="Move slide down"
|
|
487
|
+
>
|
|
488
|
+
↓
|
|
489
|
+
</button>
|
|
490
|
+
<button
|
|
491
|
+
onClick={(e) => {
|
|
492
|
+
e.stopPropagation();
|
|
493
|
+
if (canRemoveSlide) removeParallaxSlide(parallaxGroup._key, slide._key);
|
|
494
|
+
}}
|
|
495
|
+
onPointerDown={(e) => e.stopPropagation()}
|
|
496
|
+
disabled={!canRemoveSlide}
|
|
497
|
+
className="flex items-center justify-center text-[12px] leading-none transition-colors disabled:opacity-30 disabled:cursor-not-allowed"
|
|
498
|
+
style={{ color: "rgba(117, 0, 213, 0.6)" }}
|
|
499
|
+
onMouseEnter={(e) => { if (!e.currentTarget.disabled) e.currentTarget.style.color = "#7500d5"; }}
|
|
500
|
+
onMouseLeave={(e) => { if (!e.currentTarget.disabled) e.currentTarget.style.color = "rgba(117, 0, 213, 0.6)"; }}
|
|
501
|
+
title={canRemoveSlide ? "Remove slide" : "Parallax must have at least 1 slide"}
|
|
502
|
+
aria-label="Remove slide"
|
|
503
|
+
>
|
|
504
|
+
×
|
|
505
|
+
</button>
|
|
506
|
+
</div>
|
|
507
|
+
</div>
|
|
508
|
+
);
|
|
509
|
+
})}
|
|
510
|
+
|
|
511
|
+
{/* + Slide */}
|
|
512
|
+
<button
|
|
513
|
+
onClick={(e) => { e.stopPropagation(); addParallaxSlide(parallaxGroup._key); }}
|
|
514
|
+
onPointerDown={(e) => e.stopPropagation()}
|
|
515
|
+
className="flex items-center gap-1 text-[11px] transition-colors py-0.5 mt-0.5"
|
|
516
|
+
style={{ color: "rgba(117, 0, 213, 0.6)" }}
|
|
517
|
+
onMouseEnter={(e) => { e.currentTarget.style.color = "#7500d5"; }}
|
|
518
|
+
onMouseLeave={(e) => { e.currentTarget.style.color = "rgba(117, 0, 213, 0.6)"; }}
|
|
519
|
+
title="Add slide"
|
|
520
|
+
aria-label="Add slide"
|
|
521
|
+
>
|
|
522
|
+
<span style={{ color: "rgba(117, 0, 213, 0.4)" }}>+</span> Slide
|
|
523
|
+
</button>
|
|
524
|
+
</div>
|
|
525
|
+
);
|
|
526
|
+
})()}
|
|
339
527
|
</div>
|
|
340
528
|
|
|
341
529
|
{/* Row bg color indicator */}
|
|
@@ -3,8 +3,33 @@
|
|
|
3
3
|
/**
|
|
4
4
|
* Shared block visual styles — gradients and SVG icon components.
|
|
5
5
|
* Used by BlockTypePicker (add block cards) and SettingsPanel (header).
|
|
6
|
+
*
|
|
7
|
+
* The compact block/section icons exported here are thin wrappers that render
|
|
8
|
+
* the full card icons (`BlockCardIcons.tsx` / `SectionCardIcons.tsx`) at a
|
|
9
|
+
* smaller size. This keeps iconography 100% consistent between the modal
|
|
10
|
+
* cards and the settings panel header — same visual, just scaled.
|
|
11
|
+
*
|
|
12
|
+
* `size` represents the HEIGHT in pixels. Width is derived from the
|
|
13
|
+
* card icon's 220×120 aspect ratio (≈ height × 1.833).
|
|
6
14
|
*/
|
|
7
15
|
|
|
16
|
+
import {
|
|
17
|
+
TextBlockCardIcon,
|
|
18
|
+
ImageBlockCardIcon,
|
|
19
|
+
ImageGridBlockCardIcon,
|
|
20
|
+
VideoBlockCardIcon,
|
|
21
|
+
SpacerBlockCardIcon,
|
|
22
|
+
ButtonBlockCardIcon,
|
|
23
|
+
} from "./BlockCardIcons";
|
|
24
|
+
import {
|
|
25
|
+
CoverSectionCardIcon,
|
|
26
|
+
EmptySectionV2CardIcon,
|
|
27
|
+
ParallaxGroupCardIcon,
|
|
28
|
+
ProjectCarouselCardIcon,
|
|
29
|
+
ProjectGridCardIcon,
|
|
30
|
+
SavedSectionCardIcon,
|
|
31
|
+
} from "./SectionCardIcons";
|
|
32
|
+
|
|
8
33
|
// ── Gradient backgrounds per block type ──
|
|
9
34
|
|
|
10
35
|
export const BLOCK_GRADIENTS: Record<string, string> = {
|
|
@@ -24,170 +49,51 @@ export const BLOCK_GRADIENTS: Record<string, string> = {
|
|
|
24
49
|
page: "linear-gradient(135deg, #f0e8d8 0%, #e8dcc8 50%, #e0d0b8 100%)",
|
|
25
50
|
};
|
|
26
51
|
|
|
27
|
-
// ──
|
|
52
|
+
// ── Compact wrappers that render the full card icon at a smaller size ──
|
|
53
|
+
//
|
|
54
|
+
// Card icons are 220×120 (landscape ≈11:6). `size` here is the HEIGHT in px;
|
|
55
|
+
// width is derived automatically to preserve aspect.
|
|
28
56
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
<feDropShadow dx="0" dy="1.5" stdDeviation="1.2" floodColor="rgba(80,40,140,0.3)" />
|
|
39
|
-
</filter>
|
|
40
|
-
</defs>
|
|
41
|
-
<path d="M 6,5 L 34,5 L 34,8 L 33,9.5 L 23,9.5 L 23,33 L 26.5,33 L 28,34.5 L 28,37 L 12,37 L 12,34.5 L 13.5,33 L 17,33 L 17,9.5 L 7,9.5 L 6,8 Z" fill="url(#tGrad)" filter="url(#textDrop)" />
|
|
42
|
-
<path d="M 6,5 L 8,3 L 14,3 L 14,5 Z" fill="url(#tGrad)" opacity="0.85" />
|
|
43
|
-
<path d="M 34,5 L 32,3 L 26,3 L 26,5 Z" fill="url(#tGrad)" opacity="0.85" />
|
|
44
|
-
<path d="M 12,37 L 13,38.5 L 18,38.5 L 17,37 Z" fill="url(#tGrad)" opacity="0.8" />
|
|
45
|
-
<path d="M 28,37 L 27,38.5 L 22,38.5 L 23,37 Z" fill="url(#tGrad)" opacity="0.8" />
|
|
46
|
-
<path d="M 6,5 L 34,5 L 34,6.5 L 6,6.5 Z" fill="white" opacity="0.22" />
|
|
47
|
-
<path d="M 17,9.5 L 19,9.5 L 19,33 L 17,33 Z" fill="white" opacity="0.12" />
|
|
48
|
-
</svg>
|
|
49
|
-
);
|
|
57
|
+
const ASPECT = 220 / 120;
|
|
58
|
+
|
|
59
|
+
function scaleToHeight(size: number): React.CSSProperties {
|
|
60
|
+
return {
|
|
61
|
+
width: Math.round(size * ASPECT),
|
|
62
|
+
height: size,
|
|
63
|
+
display: "inline-block",
|
|
64
|
+
flexShrink: 0,
|
|
65
|
+
};
|
|
50
66
|
}
|
|
51
67
|
|
|
68
|
+
export function TextBlockIcon({ size = 28 }: { size?: number }) {
|
|
69
|
+
return <span style={scaleToHeight(size)}><TextBlockCardIcon /></span>;
|
|
70
|
+
}
|
|
52
71
|
export function ImageBlockIcon({ size = 28 }: { size?: number }) {
|
|
53
|
-
return (
|
|
54
|
-
<svg width={size} height={size} viewBox="0 0 40 40" fill="none">
|
|
55
|
-
<defs>
|
|
56
|
-
<linearGradient id="mtnBack" x1="10" y1="8" x2="30" y2="32">
|
|
57
|
-
<stop offset="0%" stopColor="#6abf6a" />
|
|
58
|
-
<stop offset="100%" stopColor="#3d9e3d" />
|
|
59
|
-
</linearGradient>
|
|
60
|
-
<linearGradient id="mtnFront" x1="5" y1="12" x2="25" y2="34">
|
|
61
|
-
<stop offset="0%" stopColor="#4da84d" />
|
|
62
|
-
<stop offset="100%" stopColor="#2d7e2d" />
|
|
63
|
-
</linearGradient>
|
|
64
|
-
<filter id="mtnDrop">
|
|
65
|
-
<feDropShadow dx="0" dy="1.5" stdDeviation="1.5" floodColor="rgba(0,0,0,0.15)" />
|
|
66
|
-
</filter>
|
|
67
|
-
</defs>
|
|
68
|
-
<polygon points="20,6 35,32 5,32" fill="url(#mtnBack)" filter="url(#mtnDrop)" />
|
|
69
|
-
<polygon points="20,6 24,13 16,13" fill="white" opacity="0.5" />
|
|
70
|
-
<polygon points="12,14 26,32 -2,32" fill="url(#mtnFront)" filter="url(#mtnDrop)" />
|
|
71
|
-
<polygon points="12,14 15,19 9,19" fill="white" opacity="0.45" />
|
|
72
|
-
</svg>
|
|
73
|
-
);
|
|
72
|
+
return <span style={scaleToHeight(size)}><ImageBlockCardIcon /></span>;
|
|
74
73
|
}
|
|
75
|
-
|
|
76
74
|
export function ImageGridBlockIcon({ size = 28 }: { size?: number }) {
|
|
77
|
-
return (
|
|
78
|
-
<svg width={size} height={size} viewBox="0 0 40 40" fill="none">
|
|
79
|
-
<defs>
|
|
80
|
-
<linearGradient id="gridFill" x1="0" y1="0" x2="40" y2="40">
|
|
81
|
-
<stop offset="0%" stopColor="#6ea8e8" />
|
|
82
|
-
<stop offset="100%" stopColor="#4080c8" />
|
|
83
|
-
</linearGradient>
|
|
84
|
-
<filter id="gridDrop">
|
|
85
|
-
<feDropShadow dx="0" dy="1" stdDeviation="1" floodColor="rgba(0,0,0,0.12)" />
|
|
86
|
-
</filter>
|
|
87
|
-
</defs>
|
|
88
|
-
<rect x="4" y="4" width="14" height="14" rx="3" fill="url(#gridFill)" opacity="0.85" filter="url(#gridDrop)" />
|
|
89
|
-
<rect x="22" y="4" width="14" height="14" rx="3" fill="url(#gridFill)" opacity="0.65" filter="url(#gridDrop)" />
|
|
90
|
-
<rect x="4" y="22" width="14" height="14" rx="3" fill="url(#gridFill)" opacity="0.55" filter="url(#gridDrop)" />
|
|
91
|
-
<rect x="22" y="22" width="14" height="14" rx="3" fill="url(#gridFill)" opacity="0.75" filter="url(#gridDrop)" />
|
|
92
|
-
</svg>
|
|
93
|
-
);
|
|
75
|
+
return <span style={scaleToHeight(size)}><ImageGridBlockCardIcon /></span>;
|
|
94
76
|
}
|
|
95
|
-
|
|
96
77
|
export function VideoBlockIcon({ size = 28 }: { size?: number }) {
|
|
97
|
-
return (
|
|
98
|
-
<svg width={size} height={size} viewBox="0 0 40 40" fill="none">
|
|
99
|
-
<defs>
|
|
100
|
-
<linearGradient id="playGrad" x1="12" y1="8" x2="32" y2="32">
|
|
101
|
-
<stop offset="0%" stopColor="#f06060" />
|
|
102
|
-
<stop offset="100%" stopColor="#d83838" />
|
|
103
|
-
</linearGradient>
|
|
104
|
-
<filter id="playDrop">
|
|
105
|
-
<feDropShadow dx="0" dy="1.5" stdDeviation="2" floodColor="rgba(200,50,50,0.3)" />
|
|
106
|
-
</filter>
|
|
107
|
-
</defs>
|
|
108
|
-
<path d="M12,6 L34,20 L12,34 Z" fill="url(#playGrad)" filter="url(#playDrop)" />
|
|
109
|
-
<path d="M12,6 L34,20 L12,20 Z" fill="white" opacity="0.15" />
|
|
110
|
-
</svg>
|
|
111
|
-
);
|
|
78
|
+
return <span style={scaleToHeight(size)}><VideoBlockCardIcon /></span>;
|
|
112
79
|
}
|
|
113
|
-
|
|
114
80
|
export function SpacerBlockIcon({ size = 28 }: { size?: number }) {
|
|
115
|
-
return (
|
|
116
|
-
<svg width={size} height={size} viewBox="0 0 40 40" fill="none">
|
|
117
|
-
<defs>
|
|
118
|
-
<filter id="spacerDrop">
|
|
119
|
-
<feDropShadow dx="0" dy="1" stdDeviation="1" floodColor="rgba(0,0,0,0.1)" />
|
|
120
|
-
</filter>
|
|
121
|
-
</defs>
|
|
122
|
-
<path d="M20,4 L27,13 L23,13 L23,17 L17,17 L17,13 L13,13 Z" fill="#9898b8" opacity="0.7" filter="url(#spacerDrop)" />
|
|
123
|
-
<path d="M20,36 L27,27 L23,27 L23,23 L17,23 L17,27 L13,27 Z" fill="#9898b8" opacity="0.7" filter="url(#spacerDrop)" />
|
|
124
|
-
<line x1="8" y1="20" x2="32" y2="20" stroke="#b0b0c8" strokeWidth="1.5" strokeDasharray="3 2" opacity="0.5" />
|
|
125
|
-
</svg>
|
|
126
|
-
);
|
|
81
|
+
return <span style={scaleToHeight(size)}><SpacerBlockCardIcon /></span>;
|
|
127
82
|
}
|
|
128
|
-
|
|
129
83
|
export function ButtonBlockIcon({ size = 28 }: { size?: number }) {
|
|
130
|
-
return (
|
|
131
|
-
<svg width={size} height={size} viewBox="0 0 40 40" fill="none">
|
|
132
|
-
<defs>
|
|
133
|
-
<linearGradient id="toggleGrad" x1="0" y1="10" x2="40" y2="30">
|
|
134
|
-
<stop offset="0%" stopColor="#3cc87c" />
|
|
135
|
-
<stop offset="100%" stopColor="#28a85c" />
|
|
136
|
-
</linearGradient>
|
|
137
|
-
<filter id="toggleDrop">
|
|
138
|
-
<feDropShadow dx="0" dy="1.5" stdDeviation="1.5" floodColor="rgba(0,0,0,0.15)" />
|
|
139
|
-
</filter>
|
|
140
|
-
<filter id="knobDrop">
|
|
141
|
-
<feDropShadow dx="0" dy="1" stdDeviation="1" floodColor="rgba(0,0,0,0.2)" />
|
|
142
|
-
</filter>
|
|
143
|
-
</defs>
|
|
144
|
-
<rect x="3" y="11" width="34" height="18" rx="9" fill="url(#toggleGrad)" filter="url(#toggleDrop)" />
|
|
145
|
-
<rect x="3" y="11" width="34" height="9" rx="9" fill="white" opacity="0.12" />
|
|
146
|
-
<circle cx="28" cy="20" r="7" fill="white" filter="url(#knobDrop)" />
|
|
147
|
-
<circle cx="27" cy="18.5" r="2.5" fill="white" opacity="0.5" />
|
|
148
|
-
</svg>
|
|
149
|
-
);
|
|
84
|
+
return <span style={scaleToHeight(size)}><ButtonBlockCardIcon /></span>;
|
|
150
85
|
}
|
|
151
86
|
|
|
152
|
-
// ── Non-block context icons ──
|
|
87
|
+
// ── Non-block context icons (compact wrappers of the section card icons) ──
|
|
153
88
|
|
|
154
89
|
export function CoverSectionSettingsIcon({ size = 28 }: { size?: number }) {
|
|
155
|
-
return (
|
|
156
|
-
<svg width={size} height={size} viewBox="0 0 40 40" fill="none">
|
|
157
|
-
<defs>
|
|
158
|
-
<linearGradient id="csSettingsGrad" x1="5" y1="5" x2="35" y2="35">
|
|
159
|
-
<stop offset="0%" stopColor="#0d9488" />
|
|
160
|
-
<stop offset="100%" stopColor="#0f766e" />
|
|
161
|
-
</linearGradient>
|
|
162
|
-
</defs>
|
|
163
|
-
<rect x="3" y="3" width="34" height="34" rx="6" fill="url(#csSettingsGrad)" opacity="0.12" />
|
|
164
|
-
<rect x="3" y="3" width="34" height="34" rx="6" stroke="url(#csSettingsGrad)" strokeWidth="1.5" fill="none" opacity="0.4" />
|
|
165
|
-
<rect x="7" y="7" width="26" height="16" rx="2" fill="url(#csSettingsGrad)" opacity="0.2" />
|
|
166
|
-
<rect x="7" y="25" width="26" height="8" rx="2" fill="url(#csSettingsGrad)" opacity="0.35" />
|
|
167
|
-
<line x1="9" y1="24" x2="31" y2="24" stroke="#0d9488" strokeWidth="1" opacity="0.4" strokeDasharray="2 2" />
|
|
168
|
-
</svg>
|
|
169
|
-
);
|
|
90
|
+
return <span style={scaleToHeight(size)}><CoverSectionCardIcon /></span>;
|
|
170
91
|
}
|
|
171
92
|
|
|
93
|
+
/** Plain V2 section (row-level) — uses the Empty Section card icon so the
|
|
94
|
+
* settings panel matches the Add Section modal iconography. */
|
|
172
95
|
export function RowIcon({ size = 28 }: { size?: number }) {
|
|
173
|
-
return (
|
|
174
|
-
<svg width={size} height={size} viewBox="0 0 40 40" fill="none">
|
|
175
|
-
<defs>
|
|
176
|
-
<linearGradient id="rowGrad" x1="4" y1="4" x2="36" y2="36">
|
|
177
|
-
<stop offset="0%" stopColor="#8888b0" />
|
|
178
|
-
<stop offset="100%" stopColor="#6868a0" />
|
|
179
|
-
</linearGradient>
|
|
180
|
-
<filter id="rowDrop">
|
|
181
|
-
<feDropShadow dx="0" dy="1" stdDeviation="1" floodColor="rgba(0,0,0,0.12)" />
|
|
182
|
-
</filter>
|
|
183
|
-
</defs>
|
|
184
|
-
<rect x="3" y="10" width="34" height="6" rx="2" fill="url(#rowGrad)" opacity="0.6" filter="url(#rowDrop)" />
|
|
185
|
-
<rect x="3" y="20" width="15" height="10" rx="2" fill="url(#rowGrad)" opacity="0.85" filter="url(#rowDrop)" />
|
|
186
|
-
<rect x="22" y="20" width="15" height="10" rx="2" fill="url(#rowGrad)" opacity="0.85" filter="url(#rowDrop)" />
|
|
187
|
-
<rect x="3" y="20" width="15" height="4" rx="2" fill="white" opacity="0.15" />
|
|
188
|
-
<rect x="22" y="20" width="15" height="4" rx="2" fill="white" opacity="0.15" />
|
|
189
|
-
</svg>
|
|
190
|
-
);
|
|
96
|
+
return <span style={scaleToHeight(size)}><EmptySectionV2CardIcon /></span>;
|
|
191
97
|
}
|
|
192
98
|
|
|
193
99
|
export function ColumnIcon({ size = 28 }: { size?: number }) {
|
|
@@ -234,47 +140,19 @@ export function PageIcon({ size = 28 }: { size?: number }) {
|
|
|
234
140
|
// ── Lookup maps ──
|
|
235
141
|
|
|
236
142
|
export function ProjectGridBlockIcon({ size = 28 }: { size?: number }) {
|
|
237
|
-
return (
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
<stop offset="100%" stopColor="#b06e08" />
|
|
243
|
-
</linearGradient>
|
|
244
|
-
</defs>
|
|
245
|
-
<rect x="3" y="3" width="34" height="14" rx="2" fill="url(#pgGrad)" opacity="0.9" />
|
|
246
|
-
<rect x="3" y="21" width="16" height="16" rx="2" fill="url(#pgGrad)" opacity="0.7" />
|
|
247
|
-
<rect x="22" y="21" width="15" height="16" rx="2" fill="url(#pgGrad)" opacity="0.5" />
|
|
248
|
-
</svg>
|
|
249
|
-
);
|
|
143
|
+
return <span style={scaleToHeight(size)}><ProjectGridCardIcon /></span>;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export function ProjectCarouselBlockIcon({ size = 28 }: { size?: number }) {
|
|
147
|
+
return <span style={scaleToHeight(size)}><ProjectCarouselCardIcon /></span>;
|
|
250
148
|
}
|
|
251
149
|
|
|
252
150
|
export function ParallaxGroupIcon({ size = 28 }: { size?: number }) {
|
|
253
|
-
return (
|
|
254
|
-
<svg width={size} height={size} viewBox="0 0 40 40" fill="none">
|
|
255
|
-
<defs>
|
|
256
|
-
<linearGradient id="pxGrad" x1="5" y1="5" x2="35" y2="35">
|
|
257
|
-
<stop offset="0%" stopColor="#9060d8" />
|
|
258
|
-
<stop offset="100%" stopColor="#7040b8" />
|
|
259
|
-
</linearGradient>
|
|
260
|
-
</defs>
|
|
261
|
-
<rect x="3" y="3" width="34" height="10" rx="2" fill="url(#pxGrad)" opacity="0.9" />
|
|
262
|
-
<rect x="3" y="16" width="34" height="10" rx="2" fill="url(#pxGrad)" opacity="0.6" />
|
|
263
|
-
<rect x="3" y="29" width="34" height="8" rx="2" fill="url(#pxGrad)" opacity="0.35" />
|
|
264
|
-
<path d="M18 6 L24 8 L18 10 Z" fill="white" opacity="0.5" />
|
|
265
|
-
<path d="M18 19 L24 21 L18 23 Z" fill="white" opacity="0.5" />
|
|
266
|
-
</svg>
|
|
267
|
-
);
|
|
151
|
+
return <span style={scaleToHeight(size)}><ParallaxGroupCardIcon /></span>;
|
|
268
152
|
}
|
|
269
153
|
|
|
270
154
|
export function CustomSectionInstanceIcon({ size = 28 }: { size?: number }) {
|
|
271
|
-
return (
|
|
272
|
-
<svg width={size} height={size} viewBox="0 0 40 40" fill="none">
|
|
273
|
-
<rect x="3" y="6" width="34" height="28" rx="4" stroke="#8b5cf6" strokeWidth="2" fill="none" opacity="0.7" />
|
|
274
|
-
<path d="M16 17a3 3 0 0 0 4.5.32l1.8-1.8a3 3 0 0 0-4.24-4.24l-1.03 1.03" stroke="#8b5cf6" strokeWidth="1.8" strokeLinecap="round" fill="none" />
|
|
275
|
-
<path d="M24 23a3 3 0 0 0-4.5-.32l-1.8 1.8a3 3 0 0 0 4.24 4.24l1.03-1.03" stroke="#8b5cf6" strokeWidth="1.8" strokeLinecap="round" fill="none" />
|
|
276
|
-
</svg>
|
|
277
|
-
);
|
|
155
|
+
return <span style={scaleToHeight(size)}><SavedSectionCardIcon /></span>;
|
|
278
156
|
}
|
|
279
157
|
|
|
280
158
|
export const BLOCK_ICON_COMPONENTS: Record<string, React.FC<{ size?: number }>> = {
|
|
@@ -285,6 +163,7 @@ export const BLOCK_ICON_COMPONENTS: Record<string, React.FC<{ size?: number }>>
|
|
|
285
163
|
spacerBlock: SpacerBlockIcon,
|
|
286
164
|
buttonBlock: ButtonBlockIcon,
|
|
287
165
|
projectGridBlock: ProjectGridBlockIcon,
|
|
166
|
+
projectCarouselBlock: ProjectCarouselBlockIcon,
|
|
288
167
|
parallaxGroup: ParallaxGroupIcon,
|
|
289
168
|
coverSection: CoverSectionSettingsIcon,
|
|
290
169
|
customSectionInstance: CustomSectionInstanceIcon,
|