@morphika/andami 0.5.1 → 0.5.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +27 -2
- package/app/admin/assets/page.tsx +6 -6
- package/app/admin/database/page.tsx +302 -302
- package/app/admin/error.tsx +53 -53
- package/app/admin/layout.tsx +332 -320
- package/app/admin/navigation/page.tsx +255 -255
- package/app/admin/pages/[slug]/page.tsx +44 -27
- package/app/admin/pages/page.tsx +24 -19
- package/app/admin/projects/page.tsx +30 -21
- package/app/admin/setup/page.tsx +1 -1
- package/app/admin/styles/page.tsx +1 -1
- package/app/api/admin/assets/register/route.ts +51 -14
- package/app/api/admin/assets/registry/route.ts +4 -1
- package/app/api/admin/assets/relink/confirm/route.ts +4 -1
- package/app/api/admin/assets/relink/route.ts +4 -1
- package/app/api/admin/assets/scan/route.ts +4 -1
- package/app/api/admin/backups/restore-data/route.ts +4 -1
- package/app/api/admin/r2/connect/route.ts +4 -1
- package/app/api/admin/r2/delete/route.ts +4 -1
- package/app/api/admin/r2/rename/route.ts +4 -1
- package/app/api/admin/r2/upload-url/route.ts +4 -1
- package/app/api/admin/revalidate/route.ts +4 -1
- package/app/api/admin/storage/switch/route.ts +4 -1
- package/app/api/custom-sections/[id]/route.ts +5 -6
- package/components/admin/MetadataEditor.tsx +6 -6
- package/components/admin/PublishToggle.tsx +2 -2
- package/components/admin/nav-builder/NavBuilder.tsx +1 -1
- package/components/admin/nav-builder/NavBuilderGrid.tsx +3 -3
- package/components/admin/nav-builder/NavGridCell.tsx +48 -48
- package/components/admin/nav-builder/NavGridItem.tsx +8 -6
- package/components/admin/nav-builder/NavItemSettings.tsx +331 -331
- package/components/admin/nav-builder/NavItemTypePicker.tsx +102 -102
- package/components/admin/nav-builder/NavLivePreview.tsx +1 -1
- package/components/admin/nav-builder/NavMobileLivePreview.tsx +226 -226
- package/components/admin/nav-builder/NavMobileSettings.tsx +242 -242
- package/components/admin/nav-builder/NavSettingsFields.tsx +518 -514
- package/components/admin/setup-wizard/BrandingStep.tsx +3 -3
- package/components/admin/setup-wizard/DatabaseStep.tsx +2 -2
- package/components/admin/setup-wizard/DoneStep.tsx +1 -1
- package/components/admin/setup-wizard/SetupWizard.tsx +4 -4
- package/components/admin/setup-wizard/StorageStep.tsx +2 -2
- package/components/admin/setup-wizard/WelcomeStep.tsx +2 -2
- package/components/admin/styles/ColorsEditor.tsx +9 -8
- package/components/admin/styles/FontsEditor.tsx +9 -7
- package/components/admin/styles/GridLayoutEditor.tsx +9 -9
- package/components/admin/styles/LinksButtonsEditor.tsx +5 -5
- package/components/admin/styles/TypographyEditor.tsx +6 -6
- package/components/admin/styles/shared.tsx +68 -68
- package/components/blocks/AudioBlockRenderer.tsx +286 -286
- package/components/blocks/CoverSectionRenderer.tsx +7 -1
- package/components/blocks/MarqueeBlockRenderer.tsx +316 -0
- package/components/blocks/ProjectCarouselBlockRenderer.tsx +1 -1
- package/components/blocks/SectionV2Renderer.tsx +8 -1
- package/components/builder/BlockCardIcons.tsx +316 -316
- package/components/builder/BlockTypePicker.tsx +1 -1
- package/components/builder/BubbleIcons.tsx +104 -0
- package/components/builder/BuilderCanvas.tsx +2 -0
- package/components/builder/CanvasMinimap.tsx +66 -49
- package/components/builder/CanvasToolbar.tsx +31 -41
- package/components/builder/CoverSectionCanvas.tsx +363 -363
- package/components/builder/DeviceFrame.tsx +1 -1
- package/components/builder/DndWrapper.tsx +3 -3
- package/components/builder/InsertionLines.tsx +1 -1
- package/components/builder/SectionCardIcons.tsx +421 -320
- package/components/builder/SectionEditorBar.tsx +5 -3
- package/components/builder/SectionTypePicker.tsx +7 -5
- package/components/builder/SectionV2Canvas.tsx +1 -1
- package/components/builder/SectionV2Column.tsx +82 -68
- package/components/builder/SettingsPanel.tsx +21 -17
- package/components/builder/SortableBlock.tsx +93 -73
- package/components/builder/SortableRow.tsx +33 -35
- package/components/builder/VirtualAssetGrid.tsx +10 -4
- package/components/builder/asset-browser/R2BrowserContent.tsx +18 -14
- package/components/builder/blockStyles.tsx +192 -185
- package/components/builder/color-picker/AlphaSlider.tsx +141 -141
- package/components/builder/color-picker/ColorInputs.tsx +105 -105
- package/components/builder/color-picker/EyedropperButton.tsx +75 -74
- package/components/builder/color-picker/HueSlider.tsx +124 -124
- package/components/builder/color-picker/SaturationCanvas.tsx +142 -142
- package/components/builder/color-picker/SwatchBar.tsx +98 -93
- package/components/builder/color-picker/UnifiedColorPicker.tsx +11 -6
- package/components/builder/editors/AudioBlockEditor.tsx +242 -242
- package/components/builder/editors/BeforeAfterBlockEditor.tsx +360 -360
- package/components/builder/editors/ButtonBlockEditor.tsx +4 -4
- package/components/builder/editors/EnterAnimationPicker.tsx +2 -2
- package/components/builder/editors/HoverEffectPicker.tsx +2 -2
- package/components/builder/editors/ImageBlockEditor.tsx +2 -2
- package/components/builder/editors/ImageGridBlockEditor.tsx +8 -6
- package/components/builder/editors/MarqueeBlockEditor.tsx +622 -0
- package/components/builder/editors/ProjectCarouselBlockEditor.tsx +443 -443
- package/components/builder/editors/ProjectGridEditor.tsx +21 -16
- package/components/builder/editors/SpacerBlockEditor.tsx +29 -27
- package/components/builder/editors/StaggerSettings.tsx +109 -109
- package/components/builder/editors/TextBlockEditor.tsx +22 -17
- package/components/builder/editors/TextStylePicker.tsx +1 -1
- package/components/builder/editors/VideoBlockEditor.tsx +2 -2
- package/components/builder/editors/index.ts +11 -10
- package/components/builder/editors/shared.tsx +10 -8
- package/components/builder/live-preview/LiveAudioPreview.tsx +120 -120
- package/components/builder/live-preview/LiveBeforeAfterPreview.tsx +1 -1
- package/components/builder/live-preview/LiveImageGridPreview.tsx +10 -2
- package/components/builder/live-preview/LiveImagePreview.tsx +4 -2
- package/components/builder/live-preview/LiveMarqueePreview.tsx +39 -0
- package/components/builder/live-preview/LiveProjectCarouselPreview.tsx +1 -1
- package/components/builder/live-preview/LiveVideoPreview.tsx +1 -1
- package/components/builder/live-preview/ProjectCardWrapper.tsx +293 -291
- package/components/builder/live-preview/RichTextBubbleMenu.tsx +10 -6
- package/components/builder/live-preview/shared.tsx +5 -2
- package/components/builder/settings-panel/AnimationTab.tsx +138 -138
- package/components/builder/settings-panel/BlockLayoutTab.tsx +11 -9
- package/components/builder/settings-panel/CardEntranceSection.tsx +114 -114
- package/components/builder/settings-panel/ColumnV2LayoutTab.tsx +242 -0
- package/components/builder/settings-panel/ColumnV2Settings.tsx +5 -5
- package/components/builder/settings-panel/CoverSectionLayoutTab.tsx +71 -71
- package/components/builder/settings-panel/CoverSectionSettings.tsx +337 -335
- package/components/builder/settings-panel/PageSettings.tsx +3 -3
- package/components/builder/settings-panel/ParallaxSlideSettings.tsx +2 -2
- package/components/builder/settings-panel/SectionV2AnimationTab.tsx +4 -4
- package/components/builder/settings-panel/SectionV2LayoutTab.tsx +356 -356
- package/components/builder/settings-panel/SectionV2Settings.tsx +25 -20
- package/components/builder/settings-panel/TRBLInputs.tsx +1 -1
- package/components/builder/settings-panel/index.ts +1 -0
- package/lib/animation/enter-types.ts +1 -0
- package/lib/animation/hover-effect-presets.ts +210 -210
- package/lib/animation/hover-effect-types.ts +1 -0
- package/lib/builder/block-registrations.ts +468 -417
- package/lib/builder/constants.ts +111 -111
- package/lib/builder/serializer/normalizers.ts +14 -0
- package/lib/builder/serializer/serializers.ts +27 -0
- package/lib/builder/store-sections.ts +23 -2
- package/lib/builder/types-slices.ts +428 -414
- package/lib/builder/types.ts +4 -1
- package/lib/config/index.ts +27 -27
- package/lib/sanity/queries.ts +48 -0
- package/lib/sanity/types.ts +112 -1
- package/lib/version.ts +1 -1
- package/package.json +7 -5
- package/sanity/schemas/blocks/audioBlock.ts +69 -69
- package/sanity/schemas/blocks/index.ts +12 -11
- package/sanity/schemas/blocks/marqueeBlock.ts +292 -0
- package/sanity/schemas/index.ts +120 -117
- package/sanity/schemas/objects/coverSection.ts +32 -0
- package/sanity/schemas/objects/parallaxSlide.ts +32 -0
- package/sanity/schemas/pageSectionV2.ts +32 -0
- package/styles/admin.css +85 -85
- package/styles/animations.css +237 -237
- package/styles/base.css +114 -114
|
@@ -14,6 +14,7 @@ import { getRowLayoutStyles } from "../../lib/builder/layout-styles";
|
|
|
14
14
|
import { normalizeMinHeight } from "../../lib/builder/utils";
|
|
15
15
|
import { getSectionV2SettingValue } from "./settings-panel/responsive-helpers";
|
|
16
16
|
import { formatRowPercent } from "../../lib/builder/format";
|
|
17
|
+
import { ArrowDownIcon, ArrowUpIcon, BubbleTooltip, CloseIcon, CopyIcon } from "./BubbleIcons";
|
|
17
18
|
|
|
18
19
|
/**
|
|
19
20
|
* Convert vh-based CSS values to pixels using the simulated device viewport height.
|
|
@@ -261,9 +262,9 @@ export default function SortableRow({
|
|
|
261
262
|
showToolbar ? "opacity-100" : "opacity-0 pointer-events-none"
|
|
262
263
|
}`}
|
|
263
264
|
style={{
|
|
264
|
-
transform: `translateX(calc(-100% - 8px)) scale(${Math.min(
|
|
265
|
+
transform: `translateX(calc(-100% - 8px)) scale(${Math.min(2, 1 / canvasZoom)})`,
|
|
265
266
|
transformOrigin: "top right",
|
|
266
|
-
width: "
|
|
267
|
+
width: "105px",
|
|
267
268
|
}}
|
|
268
269
|
onClick={(e) => {
|
|
269
270
|
e.stopPropagation();
|
|
@@ -279,7 +280,7 @@ export default function SortableRow({
|
|
|
279
280
|
className="flex flex-col items-stretch rounded-lg py-2 px-2.5 gap-1 cursor-grab active:cursor-grabbing"
|
|
280
281
|
style={{
|
|
281
282
|
background: "#e0daff",
|
|
282
|
-
border: "
|
|
283
|
+
border: "1.5px solid #7500d5",
|
|
283
284
|
}}
|
|
284
285
|
{...attributes}
|
|
285
286
|
{...listeners}
|
|
@@ -294,40 +295,40 @@ export default function SortableRow({
|
|
|
294
295
|
<button
|
|
295
296
|
onClick={(e) => { e.stopPropagation(); onDuplicate(); }}
|
|
296
297
|
onPointerDown={(e) => e.stopPropagation()}
|
|
297
|
-
className="flex items-center justify-center
|
|
298
|
+
className="group/bb relative flex items-center justify-center transition-colors"
|
|
298
299
|
style={{ color: "rgba(117, 0, 213, 0.6)" }}
|
|
299
300
|
onMouseEnter={(e) => { e.currentTarget.style.color = "#7500d5"; }}
|
|
300
301
|
onMouseLeave={(e) => { e.currentTarget.style.color = "rgba(117, 0, 213, 0.6)"; }}
|
|
301
|
-
title="Duplicate section"
|
|
302
302
|
aria-label="Duplicate section"
|
|
303
303
|
>
|
|
304
|
-
|
|
304
|
+
<CopyIcon size={14} />
|
|
305
|
+
<BubbleTooltip>Duplicate</BubbleTooltip>
|
|
305
306
|
</button>
|
|
306
307
|
<button
|
|
307
308
|
onClick={(e) => { e.stopPropagation(); onMoveUp(); }}
|
|
308
309
|
onPointerDown={(e) => e.stopPropagation()}
|
|
309
310
|
disabled={isFirst}
|
|
310
|
-
className="flex items-center justify-center
|
|
311
|
+
className="group/bb relative flex items-center justify-center transition-colors disabled:opacity-30 disabled:cursor-not-allowed"
|
|
311
312
|
style={{ color: "rgba(117, 0, 213, 0.6)" }}
|
|
312
313
|
onMouseEnter={(e) => { if (!e.currentTarget.disabled) e.currentTarget.style.color = "#7500d5"; }}
|
|
313
314
|
onMouseLeave={(e) => { if (!e.currentTarget.disabled) e.currentTarget.style.color = "rgba(117, 0, 213, 0.6)"; }}
|
|
314
|
-
title="Move up"
|
|
315
315
|
aria-label="Move section up"
|
|
316
316
|
>
|
|
317
|
-
|
|
317
|
+
<ArrowUpIcon size={14} />
|
|
318
|
+
{!isFirst && <BubbleTooltip>Move up</BubbleTooltip>}
|
|
318
319
|
</button>
|
|
319
320
|
<button
|
|
320
321
|
onClick={(e) => { e.stopPropagation(); onMoveDown(); }}
|
|
321
322
|
onPointerDown={(e) => e.stopPropagation()}
|
|
322
323
|
disabled={isLast}
|
|
323
|
-
className="flex items-center justify-center
|
|
324
|
+
className="group/bb relative flex items-center justify-center transition-colors disabled:opacity-30 disabled:cursor-not-allowed"
|
|
324
325
|
style={{ color: "rgba(117, 0, 213, 0.6)" }}
|
|
325
326
|
onMouseEnter={(e) => { if (!e.currentTarget.disabled) e.currentTarget.style.color = "#7500d5"; }}
|
|
326
327
|
onMouseLeave={(e) => { if (!e.currentTarget.disabled) e.currentTarget.style.color = "rgba(117, 0, 213, 0.6)"; }}
|
|
327
|
-
title="Move down"
|
|
328
328
|
aria-label="Move section down"
|
|
329
329
|
>
|
|
330
|
-
|
|
330
|
+
<ArrowDownIcon size={14} />
|
|
331
|
+
{!isLast && <BubbleTooltip>Move down</BubbleTooltip>}
|
|
331
332
|
</button>
|
|
332
333
|
</div>
|
|
333
334
|
|
|
@@ -340,7 +341,6 @@ export default function SortableRow({
|
|
|
340
341
|
style={{ color: "rgba(117, 0, 213, 0.6)" }}
|
|
341
342
|
onMouseEnter={(e) => { e.currentTarget.style.color = "#7500d5"; }}
|
|
342
343
|
onMouseLeave={(e) => { e.currentTarget.style.color = "rgba(117, 0, 213, 0.6)"; }}
|
|
343
|
-
title={`Add ${addColumnLabel.toLowerCase()}`}
|
|
344
344
|
aria-label={`Add ${addColumnLabel.toLowerCase()}`}
|
|
345
345
|
>
|
|
346
346
|
<span style={{ color: "rgba(117, 0, 213, 0.4)" }}>+</span> {addColumnLabel}
|
|
@@ -355,10 +355,9 @@ export default function SortableRow({
|
|
|
355
355
|
style={{ color: "rgba(117, 0, 213, 0.6)" }}
|
|
356
356
|
onMouseEnter={(e) => { e.currentTarget.style.color = "#7500d5"; }}
|
|
357
357
|
onMouseLeave={(e) => { e.currentTarget.style.color = "rgba(117, 0, 213, 0.6)"; }}
|
|
358
|
-
title="Delete section"
|
|
359
358
|
aria-label="Delete section"
|
|
360
359
|
>
|
|
361
|
-
<
|
|
360
|
+
<CloseIcon size={12} /> Delete
|
|
362
361
|
</button>
|
|
363
362
|
</div>
|
|
364
363
|
|
|
@@ -375,7 +374,7 @@ export default function SortableRow({
|
|
|
375
374
|
className="flex flex-col items-stretch rounded-lg py-1.5 px-2 mt-2 gap-0.5"
|
|
376
375
|
style={{
|
|
377
376
|
background: "#e0daff",
|
|
378
|
-
border: "
|
|
377
|
+
border: "1.5px solid #7500d5",
|
|
379
378
|
}}
|
|
380
379
|
onClick={(e) => e.stopPropagation()}
|
|
381
380
|
>
|
|
@@ -391,14 +390,14 @@ export default function SortableRow({
|
|
|
391
390
|
}}
|
|
392
391
|
onPointerDown={(e) => e.stopPropagation()}
|
|
393
392
|
disabled={!canRemoveRow}
|
|
394
|
-
className="flex items-center justify-center
|
|
393
|
+
className="group/bb relative flex items-center justify-center leading-none transition-colors disabled:opacity-30 disabled:cursor-not-allowed"
|
|
395
394
|
style={{ color: "rgba(117, 0, 213, 0.6)" }}
|
|
396
395
|
onMouseEnter={(e) => { if (!e.currentTarget.disabled) e.currentTarget.style.color = "#7500d5"; }}
|
|
397
396
|
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
397
|
aria-label="Remove row"
|
|
400
398
|
>
|
|
401
|
-
|
|
399
|
+
<CloseIcon size={12} />
|
|
400
|
+
{canRemoveRow && <BubbleTooltip>Remove row</BubbleTooltip>}
|
|
402
401
|
</button>
|
|
403
402
|
</div>
|
|
404
403
|
))}
|
|
@@ -415,8 +414,7 @@ export default function SortableRow({
|
|
|
415
414
|
style={{ color: "rgba(117, 0, 213, 0.6)" }}
|
|
416
415
|
onMouseEnter={(e) => { if (!e.currentTarget.disabled) e.currentTarget.style.color = "#7500d5"; }}
|
|
417
416
|
onMouseLeave={(e) => { if (!e.currentTarget.disabled) e.currentTarget.style.color = "rgba(117, 0, 213, 0.6)"; }}
|
|
418
|
-
|
|
419
|
-
aria-label="Add row"
|
|
417
|
+
aria-label={canAddRow ? "Add row" : "Cover supports up to 5 rows"}
|
|
420
418
|
>
|
|
421
419
|
<span style={{ color: "rgba(117, 0, 213, 0.4)" }}>+</span> Row
|
|
422
420
|
</button>
|
|
@@ -436,7 +434,7 @@ export default function SortableRow({
|
|
|
436
434
|
className="flex flex-col items-stretch rounded-lg py-1.5 px-2 mt-2 gap-0.5"
|
|
437
435
|
style={{
|
|
438
436
|
background: "#e0daff",
|
|
439
|
-
border: "
|
|
437
|
+
border: "1.5px solid #7500d5",
|
|
440
438
|
}}
|
|
441
439
|
onClick={(e) => e.stopPropagation()}
|
|
442
440
|
>
|
|
@@ -462,14 +460,14 @@ export default function SortableRow({
|
|
|
462
460
|
}}
|
|
463
461
|
onPointerDown={(e) => e.stopPropagation()}
|
|
464
462
|
disabled={idx === 0}
|
|
465
|
-
className="flex items-center justify-center
|
|
463
|
+
className="group/bb relative flex items-center justify-center leading-none transition-colors disabled:opacity-30 disabled:cursor-not-allowed"
|
|
466
464
|
style={{ color: "rgba(117, 0, 213, 0.6)" }}
|
|
467
465
|
onMouseEnter={(e) => { if (!e.currentTarget.disabled) e.currentTarget.style.color = "#7500d5"; }}
|
|
468
466
|
onMouseLeave={(e) => { if (!e.currentTarget.disabled) e.currentTarget.style.color = "rgba(117, 0, 213, 0.6)"; }}
|
|
469
|
-
title="Move slide up"
|
|
470
467
|
aria-label="Move slide up"
|
|
471
468
|
>
|
|
472
|
-
|
|
469
|
+
<ArrowUpIcon size={12} />
|
|
470
|
+
{idx > 0 && <BubbleTooltip>Move up</BubbleTooltip>}
|
|
473
471
|
</button>
|
|
474
472
|
<button
|
|
475
473
|
onClick={(e) => {
|
|
@@ -478,14 +476,14 @@ export default function SortableRow({
|
|
|
478
476
|
}}
|
|
479
477
|
onPointerDown={(e) => e.stopPropagation()}
|
|
480
478
|
disabled={idx === slides.length - 1}
|
|
481
|
-
className="flex items-center justify-center
|
|
479
|
+
className="group/bb relative flex items-center justify-center leading-none transition-colors disabled:opacity-30 disabled:cursor-not-allowed"
|
|
482
480
|
style={{ color: "rgba(117, 0, 213, 0.6)" }}
|
|
483
481
|
onMouseEnter={(e) => { if (!e.currentTarget.disabled) e.currentTarget.style.color = "#7500d5"; }}
|
|
484
482
|
onMouseLeave={(e) => { if (!e.currentTarget.disabled) e.currentTarget.style.color = "rgba(117, 0, 213, 0.6)"; }}
|
|
485
|
-
title="Move slide down"
|
|
486
483
|
aria-label="Move slide down"
|
|
487
484
|
>
|
|
488
|
-
|
|
485
|
+
<ArrowDownIcon size={12} />
|
|
486
|
+
{idx < slides.length - 1 && <BubbleTooltip>Move down</BubbleTooltip>}
|
|
489
487
|
</button>
|
|
490
488
|
<button
|
|
491
489
|
onClick={(e) => {
|
|
@@ -494,14 +492,14 @@ export default function SortableRow({
|
|
|
494
492
|
}}
|
|
495
493
|
onPointerDown={(e) => e.stopPropagation()}
|
|
496
494
|
disabled={!canRemoveSlide}
|
|
497
|
-
className="flex items-center justify-center
|
|
495
|
+
className="group/bb relative flex items-center justify-center leading-none transition-colors disabled:opacity-30 disabled:cursor-not-allowed"
|
|
498
496
|
style={{ color: "rgba(117, 0, 213, 0.6)" }}
|
|
499
497
|
onMouseEnter={(e) => { if (!e.currentTarget.disabled) e.currentTarget.style.color = "#7500d5"; }}
|
|
500
498
|
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
499
|
aria-label="Remove slide"
|
|
503
500
|
>
|
|
504
|
-
|
|
501
|
+
<CloseIcon size={12} />
|
|
502
|
+
{canRemoveSlide && <BubbleTooltip>Remove slide</BubbleTooltip>}
|
|
505
503
|
</button>
|
|
506
504
|
</div>
|
|
507
505
|
</div>
|
|
@@ -516,7 +514,6 @@ export default function SortableRow({
|
|
|
516
514
|
style={{ color: "rgba(117, 0, 213, 0.6)" }}
|
|
517
515
|
onMouseEnter={(e) => { e.currentTarget.style.color = "#7500d5"; }}
|
|
518
516
|
onMouseLeave={(e) => { e.currentTarget.style.color = "rgba(117, 0, 213, 0.6)"; }}
|
|
519
|
-
title="Add slide"
|
|
520
517
|
aria-label="Add slide"
|
|
521
518
|
>
|
|
522
519
|
<span style={{ color: "rgba(117, 0, 213, 0.4)" }}>+</span> Slide
|
|
@@ -530,10 +527,11 @@ export default function SortableRow({
|
|
|
530
527
|
{bgColor !== "transparent" && isSelected && (
|
|
531
528
|
<div className="absolute top-1 right-1 z-[5]" style={{ transform: `scale(${1 / canvasZoom})`, transformOrigin: "top right" }}>
|
|
532
529
|
<span
|
|
533
|
-
className="w-4 h-4 rounded-full border-2 border-white/50 block shadow-sm"
|
|
530
|
+
className="group/bb relative w-4 h-4 rounded-full border-2 border-white/50 block shadow-sm"
|
|
534
531
|
style={{ backgroundColor: bgColor }}
|
|
535
|
-
|
|
536
|
-
|
|
532
|
+
>
|
|
533
|
+
<BubbleTooltip>{`Background: ${bgColor}`}</BubbleTooltip>
|
|
534
|
+
</span>
|
|
537
535
|
</div>
|
|
538
536
|
)}
|
|
539
537
|
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
import { useState, useEffect, useRef, useCallback, useMemo } from "react";
|
|
16
16
|
import type { RegisteredAsset } from "../../lib/sanity/types";
|
|
17
17
|
import { BREAKPOINTS } from "../../lib/builder/constants";
|
|
18
|
+
import { BubbleTooltip } from "./BubbleIcons";
|
|
18
19
|
|
|
19
20
|
// ============================================
|
|
20
21
|
// Types
|
|
@@ -315,7 +316,7 @@ function AssetGridItem({
|
|
|
315
316
|
onContextMenu={onContextMenu ? (e) => onContextMenu(e, asset) : undefined}
|
|
316
317
|
className={`relative flex flex-col rounded-lg overflow-hidden transition-all ${
|
|
317
318
|
isSelected
|
|
318
|
-
? "ring-2 ring-[#
|
|
319
|
+
? "ring-2 ring-[#3580f9] ring-offset-2 shadow-lg"
|
|
319
320
|
: "hover:shadow-md"
|
|
320
321
|
}`}
|
|
321
322
|
>
|
|
@@ -323,7 +324,7 @@ function AssetGridItem({
|
|
|
323
324
|
{multiSelect && (
|
|
324
325
|
<div
|
|
325
326
|
className={`absolute top-1.5 left-1.5 z-10 w-5 h-5 rounded flex items-center justify-center text-white text-[10px] font-bold transition-colors ${
|
|
326
|
-
isSelected ? "bg-[#
|
|
327
|
+
isSelected ? "bg-[#3580f9]" : "bg-black/30 border border-white/50"
|
|
327
328
|
}`}
|
|
328
329
|
>
|
|
329
330
|
{isSelected && (
|
|
@@ -345,10 +346,10 @@ function AssetGridItem({
|
|
|
345
346
|
{/* Thumbnail status badge — raster images only */}
|
|
346
347
|
{isImageType(asset.extension) && asset.extension !== "svg" && (
|
|
347
348
|
<div
|
|
348
|
-
className={`absolute bottom-1.5 right-1.5 w-4 h-4 rounded-full flex items-center justify-center backdrop-blur-sm ${
|
|
349
|
+
className={`group/bb absolute bottom-1.5 right-1.5 w-4 h-4 rounded-full flex items-center justify-center backdrop-blur-sm ${
|
|
349
350
|
asset.has_thumbnail ? "bg-green-500/80" : "bg-amber-500/80"
|
|
350
351
|
}`}
|
|
351
|
-
|
|
352
|
+
aria-label={
|
|
352
353
|
asset.has_thumbnail
|
|
353
354
|
? "Thumbnail available"
|
|
354
355
|
: "No thumbnail — loading full resolution"
|
|
@@ -382,6 +383,11 @@ function AssetGridItem({
|
|
|
382
383
|
<circle cx="12" cy="16" r="0.5" fill="white" />
|
|
383
384
|
</svg>
|
|
384
385
|
)}
|
|
386
|
+
<BubbleTooltip>
|
|
387
|
+
{asset.has_thumbnail
|
|
388
|
+
? "Thumbnail available"
|
|
389
|
+
: "No thumbnail — loading full resolution"}
|
|
390
|
+
</BubbleTooltip>
|
|
385
391
|
</div>
|
|
386
392
|
)}
|
|
387
393
|
</div>
|
|
@@ -12,6 +12,7 @@ import { useR2Operations } from "./useR2Operations";
|
|
|
12
12
|
import { useR2DragDrop } from "./useR2DragDrop";
|
|
13
13
|
import { R2ContextMenu, type ContextMenuState } from "./R2ContextMenu";
|
|
14
14
|
import { ADMIN_ACCENT, BUILDER_GREEN } from "../../../lib/builder/constants";
|
|
15
|
+
import { BubbleTooltip } from "../BubbleIcons";
|
|
15
16
|
|
|
16
17
|
// ============================================
|
|
17
18
|
// R2 Browser — Composition shell
|
|
@@ -232,16 +233,16 @@ export function R2BrowserContent({
|
|
|
232
233
|
|
|
233
234
|
{/* Drag & drop overlay */}
|
|
234
235
|
{dnd.dragOver && (
|
|
235
|
-
<div className="absolute inset-0 z-50 flex items-center justify-center bg-[#
|
|
236
|
+
<div className="absolute inset-0 z-50 flex items-center justify-center bg-[#3580f9]/10 border-2 border-dashed border-[#3580f9] rounded-lg backdrop-blur-[2px]">
|
|
236
237
|
<div className="flex flex-col items-center gap-3">
|
|
237
|
-
<div className="w-16 h-16 rounded-full bg-[#
|
|
238
|
+
<div className="w-16 h-16 rounded-full bg-[#3580f9]/10 flex items-center justify-center">
|
|
238
239
|
<svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke={ADMIN_ACCENT} strokeWidth="1.5">
|
|
239
240
|
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
|
|
240
241
|
<polyline points="17 8 12 3 7 8" />
|
|
241
242
|
<line x1="12" y1="3" x2="12" y2="15" />
|
|
242
243
|
</svg>
|
|
243
244
|
</div>
|
|
244
|
-
<p className="text-sm font-medium text-[#
|
|
245
|
+
<p className="text-sm font-medium text-[#3580f9]">
|
|
245
246
|
Drop files or folders here{currentFolder ? ` to ${currentFolder}` : ""}
|
|
246
247
|
</p>
|
|
247
248
|
<p className="text-xs text-neutral-500">Supported formats: JPG, PNG, WebP, GIF, SVG, MP4, WebM, MOV, MP3, WAV, OGG, M4A, AAC, FLAC</p>
|
|
@@ -291,18 +292,19 @@ export function R2BrowserContent({
|
|
|
291
292
|
))}
|
|
292
293
|
</div>
|
|
293
294
|
<div className="flex items-center gap-2">
|
|
294
|
-
<button onClick={ops.openNewFolderInput} disabled={ops.actionLoading} className="inline-flex items-center gap-1.5 rounded-lg bg-neutral-100 px-3 py-1.5 text-[11px] text-neutral-700 font-medium uppercase tracking-wider hover:bg-neutral-200 transition-colors disabled:opacity-50"
|
|
295
|
+
<button onClick={ops.openNewFolderInput} disabled={ops.actionLoading} className="group/bb relative inline-flex items-center gap-1.5 rounded-lg bg-neutral-100 px-3 py-1.5 text-[11px] text-neutral-700 font-medium uppercase tracking-wider hover:bg-neutral-200 transition-colors disabled:opacity-50" aria-label="Create a new folder" type="button">
|
|
295
296
|
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
|
296
297
|
<path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z" />
|
|
297
298
|
<line x1="12" y1="11" x2="12" y2="17" /><line x1="9" y1="14" x2="15" y2="14" />
|
|
298
299
|
</svg>
|
|
299
300
|
New Folder
|
|
301
|
+
<BubbleTooltip>Create a new folder</BubbleTooltip>
|
|
300
302
|
</button>
|
|
301
303
|
<button
|
|
302
304
|
onClick={() => ops.fileInputRef.current?.click()}
|
|
303
305
|
disabled={uploading.some((u) => u.status === "uploading" || u.status === "registering")}
|
|
304
|
-
className="inline-flex items-center gap-1.5 rounded-lg bg-[#
|
|
305
|
-
|
|
306
|
+
className="group/bb relative inline-flex items-center gap-1.5 rounded-lg bg-[#3580f9] px-3 py-1.5 text-[11px] text-white font-medium uppercase tracking-wider hover:bg-[#3580f9]/90 transition-colors disabled:opacity-50"
|
|
307
|
+
aria-label={`Upload files${currentFolder ? ` to ${currentFolder}` : ""}`}
|
|
306
308
|
type="button"
|
|
307
309
|
>
|
|
308
310
|
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
|
@@ -311,6 +313,7 @@ export function R2BrowserContent({
|
|
|
311
313
|
<line x1="12" y1="3" x2="12" y2="15" />
|
|
312
314
|
</svg>
|
|
313
315
|
Upload
|
|
316
|
+
<BubbleTooltip>{`Upload files${currentFolder ? ` to ${currentFolder}` : ""}`}</BubbleTooltip>
|
|
314
317
|
</button>
|
|
315
318
|
</div>
|
|
316
319
|
</div>
|
|
@@ -323,11 +326,12 @@ export function R2BrowserContent({
|
|
|
323
326
|
{u.status === "done" ? (
|
|
324
327
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke={BUILDER_GREEN} strokeWidth="2.5"><polyline points="20 6 9 17 4 12" /></svg>
|
|
325
328
|
) : u.status === "error" ? (
|
|
326
|
-
<button onClick={() => onClearUploadError?.(u.id)}
|
|
329
|
+
<button onClick={() => onClearUploadError?.(u.id)} className="group/bb relative" aria-label="Dismiss">
|
|
327
330
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="#ef4444" strokeWidth="2"><line x1="18" y1="6" x2="6" y2="18" /><line x1="6" y1="6" x2="18" y2="18" /></svg>
|
|
331
|
+
<BubbleTooltip>Dismiss</BubbleTooltip>
|
|
328
332
|
</button>
|
|
329
333
|
) : (
|
|
330
|
-
<div className="w-3.5 h-3.5 border-2 border-[#
|
|
334
|
+
<div className="w-3.5 h-3.5 border-2 border-[#3580f9] border-t-transparent rounded-full animate-spin" />
|
|
331
335
|
)}
|
|
332
336
|
<span className="text-[11px] text-neutral-600 truncate flex-1 min-w-0">
|
|
333
337
|
{u.file.name}
|
|
@@ -336,7 +340,7 @@ export function R2BrowserContent({
|
|
|
336
340
|
</span>
|
|
337
341
|
{(u.status === "uploading" || u.status === "registering") && (
|
|
338
342
|
<div className="w-24 h-1.5 bg-neutral-200 rounded-full overflow-hidden">
|
|
339
|
-
<div className="h-full bg-[#
|
|
343
|
+
<div className="h-full bg-[#3580f9] rounded-full transition-all duration-300" style={{ width: `${u.progress}%` }} />
|
|
340
344
|
</div>
|
|
341
345
|
)}
|
|
342
346
|
<span className="text-[10px] text-neutral-400 tabular-nums">{formatFileSize(u.file.size)}</span>
|
|
@@ -356,7 +360,7 @@ export function R2BrowserContent({
|
|
|
356
360
|
{error && (
|
|
357
361
|
<div className="flex flex-col items-center justify-center h-40 gap-3 px-8">
|
|
358
362
|
<span className="text-xs text-red-500 text-center max-w-md leading-relaxed">{error}</span>
|
|
359
|
-
<button onClick={onRetry} className="text-xs text-[#
|
|
363
|
+
<button onClick={onRetry} className="text-xs text-[#3580f9] hover:underline">Retry</button>
|
|
360
364
|
</div>
|
|
361
365
|
)}
|
|
362
366
|
|
|
@@ -372,9 +376,9 @@ export function R2BrowserContent({
|
|
|
372
376
|
ref={newFolderInputRef}
|
|
373
377
|
type="text" value={ops.newFolderName} onChange={(e) => ops.setNewFolderName(e.target.value)}
|
|
374
378
|
onKeyDown={(e) => { e.stopPropagation(); if (e.key === "Enter") ops.handleCreateFolder(); if (e.key === "Escape") ops.cancelNewFolderInput(); }}
|
|
375
|
-
placeholder="Folder name..." className="flex-1 text-sm text-neutral-900 bg-white border border-neutral-300 rounded px-2 py-1 focus:outline-none focus:border-[#
|
|
379
|
+
placeholder="Folder name..." className="flex-1 text-sm text-neutral-900 bg-white border border-neutral-300 rounded px-2 py-1 focus:outline-none focus:border-[#3580f9]"
|
|
376
380
|
/>
|
|
377
|
-
<button onClick={ops.handleCreateFolder} disabled={!ops.newFolderName.trim() || ops.actionLoading} className="text-xs px-3 py-1 rounded bg-[#
|
|
381
|
+
<button onClick={ops.handleCreateFolder} disabled={!ops.newFolderName.trim() || ops.actionLoading} className="text-xs px-3 py-1 rounded bg-[#3580f9] text-white disabled:opacity-50" type="button">Create</button>
|
|
378
382
|
<button onClick={ops.cancelNewFolderInput} className="text-xs px-2 py-1 text-neutral-500 hover:text-neutral-800" type="button">Cancel</button>
|
|
379
383
|
</div>
|
|
380
384
|
)}
|
|
@@ -387,9 +391,9 @@ export function R2BrowserContent({
|
|
|
387
391
|
ref={renameInputRef}
|
|
388
392
|
type="text" value={ops.renameValue} onChange={(e) => ops.setRenameValue(e.target.value)}
|
|
389
393
|
onKeyDown={(e) => { e.stopPropagation(); if (e.key === "Enter") ops.handleRename(); if (e.key === "Escape") ops.cancelRename(); }}
|
|
390
|
-
className="flex-1 text-sm text-neutral-900 bg-white border border-neutral-300 rounded px-2 py-1 focus:outline-none focus:border-[#
|
|
394
|
+
className="flex-1 text-sm text-neutral-900 bg-white border border-neutral-300 rounded px-2 py-1 focus:outline-none focus:border-[#3580f9]"
|
|
391
395
|
/>
|
|
392
|
-
<button onClick={ops.handleRename} disabled={!ops.renameValue.trim() || ops.actionLoading} className="text-xs px-3 py-1 rounded bg-[#
|
|
396
|
+
<button onClick={ops.handleRename} disabled={!ops.renameValue.trim() || ops.actionLoading} className="text-xs px-3 py-1 rounded bg-[#3580f9] text-white disabled:opacity-50" type="button">Rename</button>
|
|
393
397
|
<button onClick={ops.cancelRename} className="text-xs px-2 py-1 text-neutral-500 hover:text-neutral-800" type="button">Cancel</button>
|
|
394
398
|
</div>
|
|
395
399
|
)}
|