@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.
Files changed (147) hide show
  1. package/README.md +27 -2
  2. package/app/admin/assets/page.tsx +6 -6
  3. package/app/admin/database/page.tsx +302 -302
  4. package/app/admin/error.tsx +53 -53
  5. package/app/admin/layout.tsx +332 -320
  6. package/app/admin/navigation/page.tsx +255 -255
  7. package/app/admin/pages/[slug]/page.tsx +44 -27
  8. package/app/admin/pages/page.tsx +24 -19
  9. package/app/admin/projects/page.tsx +30 -21
  10. package/app/admin/setup/page.tsx +1 -1
  11. package/app/admin/styles/page.tsx +1 -1
  12. package/app/api/admin/assets/register/route.ts +51 -14
  13. package/app/api/admin/assets/registry/route.ts +4 -1
  14. package/app/api/admin/assets/relink/confirm/route.ts +4 -1
  15. package/app/api/admin/assets/relink/route.ts +4 -1
  16. package/app/api/admin/assets/scan/route.ts +4 -1
  17. package/app/api/admin/backups/restore-data/route.ts +4 -1
  18. package/app/api/admin/r2/connect/route.ts +4 -1
  19. package/app/api/admin/r2/delete/route.ts +4 -1
  20. package/app/api/admin/r2/rename/route.ts +4 -1
  21. package/app/api/admin/r2/upload-url/route.ts +4 -1
  22. package/app/api/admin/revalidate/route.ts +4 -1
  23. package/app/api/admin/storage/switch/route.ts +4 -1
  24. package/app/api/custom-sections/[id]/route.ts +5 -6
  25. package/components/admin/MetadataEditor.tsx +6 -6
  26. package/components/admin/PublishToggle.tsx +2 -2
  27. package/components/admin/nav-builder/NavBuilder.tsx +1 -1
  28. package/components/admin/nav-builder/NavBuilderGrid.tsx +3 -3
  29. package/components/admin/nav-builder/NavGridCell.tsx +48 -48
  30. package/components/admin/nav-builder/NavGridItem.tsx +8 -6
  31. package/components/admin/nav-builder/NavItemSettings.tsx +331 -331
  32. package/components/admin/nav-builder/NavItemTypePicker.tsx +102 -102
  33. package/components/admin/nav-builder/NavLivePreview.tsx +1 -1
  34. package/components/admin/nav-builder/NavMobileLivePreview.tsx +226 -226
  35. package/components/admin/nav-builder/NavMobileSettings.tsx +242 -242
  36. package/components/admin/nav-builder/NavSettingsFields.tsx +518 -514
  37. package/components/admin/setup-wizard/BrandingStep.tsx +3 -3
  38. package/components/admin/setup-wizard/DatabaseStep.tsx +2 -2
  39. package/components/admin/setup-wizard/DoneStep.tsx +1 -1
  40. package/components/admin/setup-wizard/SetupWizard.tsx +4 -4
  41. package/components/admin/setup-wizard/StorageStep.tsx +2 -2
  42. package/components/admin/setup-wizard/WelcomeStep.tsx +2 -2
  43. package/components/admin/styles/ColorsEditor.tsx +9 -8
  44. package/components/admin/styles/FontsEditor.tsx +9 -7
  45. package/components/admin/styles/GridLayoutEditor.tsx +9 -9
  46. package/components/admin/styles/LinksButtonsEditor.tsx +5 -5
  47. package/components/admin/styles/TypographyEditor.tsx +6 -6
  48. package/components/admin/styles/shared.tsx +68 -68
  49. package/components/blocks/AudioBlockRenderer.tsx +286 -286
  50. package/components/blocks/CoverSectionRenderer.tsx +7 -1
  51. package/components/blocks/MarqueeBlockRenderer.tsx +316 -0
  52. package/components/blocks/ProjectCarouselBlockRenderer.tsx +1 -1
  53. package/components/blocks/SectionV2Renderer.tsx +8 -1
  54. package/components/builder/BlockCardIcons.tsx +316 -316
  55. package/components/builder/BlockTypePicker.tsx +1 -1
  56. package/components/builder/BubbleIcons.tsx +104 -0
  57. package/components/builder/BuilderCanvas.tsx +2 -0
  58. package/components/builder/CanvasMinimap.tsx +66 -49
  59. package/components/builder/CanvasToolbar.tsx +31 -41
  60. package/components/builder/CoverSectionCanvas.tsx +363 -363
  61. package/components/builder/DeviceFrame.tsx +1 -1
  62. package/components/builder/DndWrapper.tsx +3 -3
  63. package/components/builder/InsertionLines.tsx +1 -1
  64. package/components/builder/SectionCardIcons.tsx +421 -320
  65. package/components/builder/SectionEditorBar.tsx +5 -3
  66. package/components/builder/SectionTypePicker.tsx +7 -5
  67. package/components/builder/SectionV2Canvas.tsx +1 -1
  68. package/components/builder/SectionV2Column.tsx +82 -68
  69. package/components/builder/SettingsPanel.tsx +21 -17
  70. package/components/builder/SortableBlock.tsx +93 -73
  71. package/components/builder/SortableRow.tsx +33 -35
  72. package/components/builder/VirtualAssetGrid.tsx +10 -4
  73. package/components/builder/asset-browser/R2BrowserContent.tsx +18 -14
  74. package/components/builder/blockStyles.tsx +192 -185
  75. package/components/builder/color-picker/AlphaSlider.tsx +141 -141
  76. package/components/builder/color-picker/ColorInputs.tsx +105 -105
  77. package/components/builder/color-picker/EyedropperButton.tsx +75 -74
  78. package/components/builder/color-picker/HueSlider.tsx +124 -124
  79. package/components/builder/color-picker/SaturationCanvas.tsx +142 -142
  80. package/components/builder/color-picker/SwatchBar.tsx +98 -93
  81. package/components/builder/color-picker/UnifiedColorPicker.tsx +11 -6
  82. package/components/builder/editors/AudioBlockEditor.tsx +242 -242
  83. package/components/builder/editors/BeforeAfterBlockEditor.tsx +360 -360
  84. package/components/builder/editors/ButtonBlockEditor.tsx +4 -4
  85. package/components/builder/editors/EnterAnimationPicker.tsx +2 -2
  86. package/components/builder/editors/HoverEffectPicker.tsx +2 -2
  87. package/components/builder/editors/ImageBlockEditor.tsx +2 -2
  88. package/components/builder/editors/ImageGridBlockEditor.tsx +8 -6
  89. package/components/builder/editors/MarqueeBlockEditor.tsx +622 -0
  90. package/components/builder/editors/ProjectCarouselBlockEditor.tsx +443 -443
  91. package/components/builder/editors/ProjectGridEditor.tsx +21 -16
  92. package/components/builder/editors/SpacerBlockEditor.tsx +29 -27
  93. package/components/builder/editors/StaggerSettings.tsx +109 -109
  94. package/components/builder/editors/TextBlockEditor.tsx +22 -17
  95. package/components/builder/editors/TextStylePicker.tsx +1 -1
  96. package/components/builder/editors/VideoBlockEditor.tsx +2 -2
  97. package/components/builder/editors/index.ts +11 -10
  98. package/components/builder/editors/shared.tsx +10 -8
  99. package/components/builder/live-preview/LiveAudioPreview.tsx +120 -120
  100. package/components/builder/live-preview/LiveBeforeAfterPreview.tsx +1 -1
  101. package/components/builder/live-preview/LiveImageGridPreview.tsx +10 -2
  102. package/components/builder/live-preview/LiveImagePreview.tsx +4 -2
  103. package/components/builder/live-preview/LiveMarqueePreview.tsx +39 -0
  104. package/components/builder/live-preview/LiveProjectCarouselPreview.tsx +1 -1
  105. package/components/builder/live-preview/LiveVideoPreview.tsx +1 -1
  106. package/components/builder/live-preview/ProjectCardWrapper.tsx +293 -291
  107. package/components/builder/live-preview/RichTextBubbleMenu.tsx +10 -6
  108. package/components/builder/live-preview/shared.tsx +5 -2
  109. package/components/builder/settings-panel/AnimationTab.tsx +138 -138
  110. package/components/builder/settings-panel/BlockLayoutTab.tsx +11 -9
  111. package/components/builder/settings-panel/CardEntranceSection.tsx +114 -114
  112. package/components/builder/settings-panel/ColumnV2LayoutTab.tsx +242 -0
  113. package/components/builder/settings-panel/ColumnV2Settings.tsx +5 -5
  114. package/components/builder/settings-panel/CoverSectionLayoutTab.tsx +71 -71
  115. package/components/builder/settings-panel/CoverSectionSettings.tsx +337 -335
  116. package/components/builder/settings-panel/PageSettings.tsx +3 -3
  117. package/components/builder/settings-panel/ParallaxSlideSettings.tsx +2 -2
  118. package/components/builder/settings-panel/SectionV2AnimationTab.tsx +4 -4
  119. package/components/builder/settings-panel/SectionV2LayoutTab.tsx +356 -356
  120. package/components/builder/settings-panel/SectionV2Settings.tsx +25 -20
  121. package/components/builder/settings-panel/TRBLInputs.tsx +1 -1
  122. package/components/builder/settings-panel/index.ts +1 -0
  123. package/lib/animation/enter-types.ts +1 -0
  124. package/lib/animation/hover-effect-presets.ts +210 -210
  125. package/lib/animation/hover-effect-types.ts +1 -0
  126. package/lib/builder/block-registrations.ts +468 -417
  127. package/lib/builder/constants.ts +111 -111
  128. package/lib/builder/serializer/normalizers.ts +14 -0
  129. package/lib/builder/serializer/serializers.ts +27 -0
  130. package/lib/builder/store-sections.ts +23 -2
  131. package/lib/builder/types-slices.ts +428 -414
  132. package/lib/builder/types.ts +4 -1
  133. package/lib/config/index.ts +27 -27
  134. package/lib/sanity/queries.ts +48 -0
  135. package/lib/sanity/types.ts +112 -1
  136. package/lib/version.ts +1 -1
  137. package/package.json +7 -5
  138. package/sanity/schemas/blocks/audioBlock.ts +69 -69
  139. package/sanity/schemas/blocks/index.ts +12 -11
  140. package/sanity/schemas/blocks/marqueeBlock.ts +292 -0
  141. package/sanity/schemas/index.ts +120 -117
  142. package/sanity/schemas/objects/coverSection.ts +32 -0
  143. package/sanity/schemas/objects/parallaxSlide.ts +32 -0
  144. package/sanity/schemas/pageSectionV2.ts +32 -0
  145. package/styles/admin.css +85 -85
  146. package/styles/animations.css +237 -237
  147. 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(1.5, 1 / canvasZoom)})`,
265
+ transform: `translateX(calc(-100% - 8px)) scale(${Math.min(2, 1 / canvasZoom)})`,
265
266
  transformOrigin: "top right",
266
- width: "90px",
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: "1px solid #7500d5",
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 text-[12px] transition-colors"
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 text-[12px] transition-colors disabled:opacity-30 disabled:cursor-not-allowed"
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 text-[12px] transition-colors disabled:opacity-30 disabled:cursor-not-allowed"
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
- <span style={{ color: "rgba(117, 0, 213, 0.4)" }}>-</span> Delete
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: "1px solid #7500d5",
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 text-[12px] leading-none transition-colors disabled:opacity-30 disabled:cursor-not-allowed"
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
- title={canAddRow ? "Add row" : "Cover supports up to 5 rows"}
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: "1px solid #7500d5",
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 text-[10px] leading-none transition-colors disabled:opacity-30 disabled:cursor-not-allowed"
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 text-[10px] leading-none transition-colors disabled:opacity-30 disabled:cursor-not-allowed"
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 text-[12px] leading-none transition-colors disabled:opacity-30 disabled:cursor-not-allowed"
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
- title={`Background: ${bgColor}`}
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-[#076bff] ring-offset-2 shadow-lg"
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-[#076bff]" : "bg-black/30 border border-white/50"
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
- title={
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-[#076bff]/10 border-2 border-dashed border-[#076bff] rounded-lg backdrop-blur-[2px]">
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-[#076bff]/10 flex items-center justify-center">
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-[#076bff]">
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" title="Create a new folder" type="button">
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-[#076bff] px-3 py-1.5 text-[11px] text-white font-medium uppercase tracking-wider hover:bg-[#076bff]/90 transition-colors disabled:opacity-50"
305
- title={`Upload files${currentFolder ? ` to ${currentFolder}` : ""}`}
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)} title="Dismiss">
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-[#076bff] border-t-transparent rounded-full animate-spin" />
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-[#076bff] rounded-full transition-all duration-300" style={{ width: `${u.progress}%` }} />
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-[#076bff] hover:underline">Retry</button>
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-[#076bff]"
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-[#076bff] text-white disabled:opacity-50" type="button">Create</button>
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-[#076bff]"
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-[#076bff] text-white disabled:opacity-50" type="button">Rename</button>
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
  )}