@morphika/andami 0.5.0 → 0.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (122) hide show
  1. package/README.md +151 -36
  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 +320 -327
  6. package/app/admin/navigation/page.tsx +255 -255
  7. package/app/admin/pages/[slug]/page.tsx +6 -6
  8. package/app/admin/pages/page.tsx +11 -11
  9. package/app/admin/projects/page.tsx +14 -14
  10. package/app/admin/setup/page.tsx +1 -1
  11. package/app/admin/styles/page.tsx +1 -1
  12. package/components/admin/MetadataEditor.tsx +6 -6
  13. package/components/admin/nav-builder/NavBuilder.tsx +1 -1
  14. package/components/admin/nav-builder/NavBuilderGrid.tsx +3 -3
  15. package/components/admin/nav-builder/NavGridCell.tsx +48 -48
  16. package/components/admin/nav-builder/NavGridItem.tsx +4 -4
  17. package/components/admin/nav-builder/NavItemSettings.tsx +331 -331
  18. package/components/admin/nav-builder/NavItemTypePicker.tsx +102 -102
  19. package/components/admin/nav-builder/NavLivePreview.tsx +1 -1
  20. package/components/admin/nav-builder/NavMobileLivePreview.tsx +226 -226
  21. package/components/admin/nav-builder/NavMobileSettings.tsx +242 -242
  22. package/components/admin/nav-builder/NavSettingsFields.tsx +514 -514
  23. package/components/admin/setup-wizard/BrandingStep.tsx +3 -3
  24. package/components/admin/setup-wizard/DatabaseStep.tsx +2 -2
  25. package/components/admin/setup-wizard/DoneStep.tsx +1 -1
  26. package/components/admin/setup-wizard/SetupWizard.tsx +4 -4
  27. package/components/admin/setup-wizard/StorageStep.tsx +2 -2
  28. package/components/admin/setup-wizard/WelcomeStep.tsx +2 -2
  29. package/components/admin/styles/ColorsEditor.tsx +2 -2
  30. package/components/admin/styles/FontsEditor.tsx +6 -6
  31. package/components/admin/styles/GridLayoutEditor.tsx +9 -9
  32. package/components/admin/styles/LinksButtonsEditor.tsx +5 -5
  33. package/components/admin/styles/TypographyEditor.tsx +6 -6
  34. package/components/admin/styles/shared.tsx +68 -68
  35. package/components/blocks/AudioBlockRenderer.tsx +286 -0
  36. package/components/blocks/BeforeAfterBlockRenderer.tsx +274 -0
  37. package/components/blocks/MarqueeBlockRenderer.tsx +316 -0
  38. package/components/blocks/ProjectCarouselBlockRenderer.tsx +1 -1
  39. package/components/builder/BlockCardIcons.tsx +316 -227
  40. package/components/builder/BlockTypePicker.tsx +3 -1
  41. package/components/builder/BubbleIcons.tsx +90 -0
  42. package/components/builder/BuilderCanvas.tsx +2 -0
  43. package/components/builder/CanvasMinimap.tsx +2 -2
  44. package/components/builder/CoverSectionCanvas.tsx +363 -275
  45. package/components/builder/DeviceFrame.tsx +1 -1
  46. package/components/builder/DndWrapper.tsx +3 -3
  47. package/components/builder/InsertionLines.tsx +1 -1
  48. package/components/builder/SectionCardIcons.tsx +421 -320
  49. package/components/builder/SectionEditorBar.tsx +1 -1
  50. package/components/builder/SectionTypePicker.tsx +4 -4
  51. package/components/builder/SectionV2Canvas.tsx +20 -4
  52. package/components/builder/SectionV2Column.tsx +74 -68
  53. package/components/builder/SortableBlock.tsx +93 -73
  54. package/components/builder/SortableRow.tsx +27 -26
  55. package/components/builder/VirtualAssetGrid.tsx +2 -2
  56. package/components/builder/asset-browser/R2BrowserContent.tsx +34 -17
  57. package/components/builder/asset-browser/helpers.ts +4 -0
  58. package/components/builder/asset-browser/types.ts +2 -1
  59. package/components/builder/blockStyles.tsx +192 -173
  60. package/components/builder/color-picker/AlphaSlider.tsx +141 -141
  61. package/components/builder/color-picker/ColorInputs.tsx +105 -105
  62. package/components/builder/color-picker/EyedropperButton.tsx +74 -74
  63. package/components/builder/color-picker/HueSlider.tsx +124 -124
  64. package/components/builder/color-picker/SaturationCanvas.tsx +142 -142
  65. package/components/builder/color-picker/SwatchBar.tsx +93 -93
  66. package/components/builder/editors/AudioBlockEditor.tsx +242 -0
  67. package/components/builder/editors/BeforeAfterBlockEditor.tsx +360 -0
  68. package/components/builder/editors/ButtonBlockEditor.tsx +4 -4
  69. package/components/builder/editors/EnterAnimationPicker.tsx +2 -2
  70. package/components/builder/editors/HoverEffectPicker.tsx +2 -2
  71. package/components/builder/editors/ImageBlockEditor.tsx +2 -2
  72. package/components/builder/editors/ImageGridBlockEditor.tsx +4 -4
  73. package/components/builder/editors/MarqueeBlockEditor.tsx +621 -0
  74. package/components/builder/editors/ProjectCarouselBlockEditor.tsx +443 -443
  75. package/components/builder/editors/ProjectGridEditor.tsx +9 -9
  76. package/components/builder/editors/SpacerBlockEditor.tsx +5 -5
  77. package/components/builder/editors/StaggerSettings.tsx +109 -109
  78. package/components/builder/editors/TextBlockEditor.tsx +3 -3
  79. package/components/builder/editors/TextStylePicker.tsx +1 -1
  80. package/components/builder/editors/VideoBlockEditor.tsx +2 -2
  81. package/components/builder/editors/index.ts +11 -10
  82. package/components/builder/editors/shared.tsx +7 -7
  83. package/components/builder/live-preview/LiveAudioPreview.tsx +120 -0
  84. package/components/builder/live-preview/LiveBeforeAfterPreview.tsx +176 -0
  85. package/components/builder/live-preview/LiveImageGridPreview.tsx +10 -2
  86. package/components/builder/live-preview/LiveImagePreview.tsx +1 -1
  87. package/components/builder/live-preview/LiveMarqueePreview.tsx +39 -0
  88. package/components/builder/live-preview/LiveProjectCarouselPreview.tsx +1 -1
  89. package/components/builder/live-preview/LiveVideoPreview.tsx +1 -1
  90. package/components/builder/live-preview/ProjectCardWrapper.tsx +291 -291
  91. package/components/builder/settings-panel/AnimationTab.tsx +138 -138
  92. package/components/builder/settings-panel/BlockLayoutTab.tsx +7 -7
  93. package/components/builder/settings-panel/CardEntranceSection.tsx +114 -114
  94. package/components/builder/settings-panel/ColumnV2Settings.tsx +5 -5
  95. package/components/builder/settings-panel/CoverSectionLayoutTab.tsx +71 -71
  96. package/components/builder/settings-panel/CoverSectionSettings.tsx +335 -335
  97. package/components/builder/settings-panel/PageSettings.tsx +3 -3
  98. package/components/builder/settings-panel/ParallaxSlideSettings.tsx +2 -2
  99. package/components/builder/settings-panel/SectionV2AnimationTab.tsx +4 -4
  100. package/components/builder/settings-panel/SectionV2LayoutTab.tsx +356 -356
  101. package/components/builder/settings-panel/SectionV2Settings.tsx +14 -14
  102. package/components/builder/settings-panel/TRBLInputs.tsx +1 -1
  103. package/lib/animation/enter-types.ts +3 -0
  104. package/lib/animation/hover-effect-presets.ts +210 -210
  105. package/lib/animation/hover-effect-types.ts +3 -0
  106. package/lib/builder/block-registrations.ts +468 -335
  107. package/lib/builder/constants.ts +111 -111
  108. package/lib/builder/store-sections.ts +2 -2
  109. package/lib/builder/types-slices.ts +414 -414
  110. package/lib/builder/types.ts +6 -1
  111. package/lib/config/index.ts +27 -27
  112. package/lib/sanity/types.ts +156 -1
  113. package/lib/version.ts +1 -1
  114. package/package.json +1 -1
  115. package/sanity/schemas/blocks/audioBlock.ts +69 -0
  116. package/sanity/schemas/blocks/beforeAfterBlock.ts +121 -0
  117. package/sanity/schemas/blocks/index.ts +12 -9
  118. package/sanity/schemas/blocks/marqueeBlock.ts +292 -0
  119. package/sanity/schemas/index.ts +120 -111
  120. package/styles/admin.css +85 -85
  121. package/styles/animations.css +237 -237
  122. package/styles/base.css +114 -114
@@ -0,0 +1,176 @@
1
+ "use client";
2
+
3
+ import { adminAssetUrl, adminThumbUrl } from "../../../lib/assets";
4
+ import type { BeforeAfterBlock } from "../../../lib/sanity/types";
5
+
6
+ /**
7
+ * LiveBeforeAfterPreview — Static preview for builder canvas.
8
+ *
9
+ * Shows both assets with a fixed 50% split (or the configured `initial_position`)
10
+ * and a divider line + knob — no drag interaction in the builder. Videos are
11
+ * represented by a poster-style thumbnail with a play glyph (no streaming /
12
+ * autoplay inside the builder).
13
+ */
14
+
15
+ const widthStyleMap: Record<string, { width: string; margin?: string }> = {
16
+ full: { width: "100%" },
17
+ contained: { width: "75%", margin: "0 auto" },
18
+ small: { width: "50%", margin: "0 auto" },
19
+ };
20
+
21
+ const aspectMap: Record<string, string | undefined> = {
22
+ auto: undefined,
23
+ "16:9": "16/9",
24
+ "4:3": "4/3",
25
+ "1:1": "1/1",
26
+ "21:9": "21/9",
27
+ };
28
+
29
+ function clamp(n: number, lo = 0, hi = 100): number {
30
+ return Math.max(lo, Math.min(hi, n));
31
+ }
32
+
33
+ function PreviewMedia({
34
+ type,
35
+ path,
36
+ alt,
37
+ }: {
38
+ type: "image" | "video";
39
+ path: string;
40
+ alt: string;
41
+ }) {
42
+ const src = adminThumbUrl(path) || adminAssetUrl(path);
43
+ const commonStyle: React.CSSProperties = {
44
+ position: "absolute",
45
+ inset: 0,
46
+ width: "100%",
47
+ height: "100%",
48
+ objectFit: "cover",
49
+ display: "block",
50
+ pointerEvents: "none",
51
+ userSelect: "none",
52
+ };
53
+ return (
54
+ <>
55
+ {/* eslint-disable-next-line @next/next/no-img-element */}
56
+ <img src={src} alt={alt} loading="lazy" decoding="async" draggable={false} style={commonStyle} />
57
+ {type === "video" && (
58
+ <div style={{ position: "absolute", inset: 0, display: "flex", alignItems: "center", justifyContent: "center", pointerEvents: "none" }}>
59
+ <div style={{ width: 44, height: 44, borderRadius: "50%", background: "rgba(0,0,0,0.55)", display: "flex", alignItems: "center", justifyContent: "center" }}>
60
+ <span style={{ color: "#FFFFFF", fontSize: 16, marginLeft: 2 }}>&#9654;</span>
61
+ </div>
62
+ </div>
63
+ )}
64
+ </>
65
+ );
66
+ }
67
+
68
+ export default function LiveBeforeAfterPreview({ block }: { block: BeforeAfterBlock }) {
69
+ const beforeType = block.before_media_type ?? "image";
70
+ const afterType = block.after_media_type ?? "image";
71
+ const orientation = block.orientation ?? "horizontal";
72
+ const position = clamp(block.initial_position ?? 50);
73
+ const handleColor = block.handle_color || "#FFFFFF";
74
+
75
+ const isFill = block.width === "fill";
76
+ const widthStyle = isFill ? {} : (widthStyleMap[block.width ?? "full"] || widthStyleMap.full);
77
+ const aspect = isFill ? undefined : aspectMap[block.aspect_ratio ?? "16:9"] ?? "16/9";
78
+
79
+ const rawRadius = block.border_radius ? String(block.border_radius).replace(/[a-z%]+$/i, "") : "";
80
+ const borderRadius = rawRadius && !isNaN(Number(rawRadius)) ? `${rawRadius}px` : undefined;
81
+
82
+ const hasBefore = !!block.before_asset_path;
83
+ const hasAfter = !!block.after_asset_path;
84
+
85
+ // Empty state: no assets on either side
86
+ if (!hasBefore && !hasAfter) {
87
+ const wrapperStyle: React.CSSProperties = isFill
88
+ ? { position: "absolute", inset: 0 }
89
+ : { width: "100%" };
90
+ return (
91
+ <div style={wrapperStyle}>
92
+ <div className="w-full h-full min-h-[240px] flex flex-col items-center justify-center gap-2.5" style={{ background: "#f4f4f4" }}>
93
+ <svg width="56" height="56" viewBox="0 0 56 56" fill="none" aria-hidden="true">
94
+ <rect x="6" y="10" width="44" height="36" rx="3" stroke="#b0b5bd" strokeWidth="1.5" fill="#FFFFFF" />
95
+ <line x1="28" y1="10" x2="28" y2="46" stroke="#b0b5bd" strokeWidth="1.5" />
96
+ <circle cx="28" cy="28" r="5" fill="#FFFFFF" stroke="#b0b5bd" strokeWidth="1.5" />
97
+ </svg>
98
+ <span className="text-[11px] text-neutral-500">Before / After — pick two assets</span>
99
+ </div>
100
+ </div>
101
+ );
102
+ }
103
+
104
+ const afterClip = orientation === "horizontal"
105
+ ? `inset(0 0 0 ${position}%)`
106
+ : `inset(${position}% 0 0 0)`;
107
+
108
+ const dividerStyle: React.CSSProperties = orientation === "horizontal"
109
+ ? { position: "absolute", top: 0, bottom: 0, left: `${position}%`, width: 2, transform: "translateX(-1px)", background: handleColor, pointerEvents: "none" }
110
+ : { position: "absolute", left: 0, right: 0, top: `${position}%`, height: 2, transform: "translateY(-1px)", background: handleColor, pointerEvents: "none" };
111
+
112
+ const knobStyle: React.CSSProperties = {
113
+ position: "absolute",
114
+ left: "50%",
115
+ top: "50%",
116
+ transform: "translate(-50%, -50%)",
117
+ width: 32,
118
+ height: 32,
119
+ borderRadius: "50%",
120
+ background: handleColor,
121
+ boxShadow: "0 2px 8px rgba(0,0,0,0.35)",
122
+ display: "flex",
123
+ alignItems: "center",
124
+ justifyContent: "center",
125
+ pointerEvents: "none",
126
+ };
127
+
128
+ const ArrowIcon = orientation === "horizontal" ? (
129
+ <svg width="14" height="14" viewBox="0 0 18 18" fill="none" aria-hidden="true">
130
+ <path d="M5 4 L1 9 L5 14" stroke="#111" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round" />
131
+ <path d="M13 4 L17 9 L13 14" stroke="#111" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round" />
132
+ </svg>
133
+ ) : (
134
+ <svg width="14" height="14" viewBox="0 0 18 18" fill="none" aria-hidden="true">
135
+ <path d="M4 5 L9 1 L14 5" stroke="#111" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round" />
136
+ <path d="M4 13 L9 17 L14 13" stroke="#111" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round" />
137
+ </svg>
138
+ );
139
+
140
+ const frameStyle: React.CSSProperties = isFill
141
+ ? { position: "absolute", inset: 0, borderRadius, overflow: "hidden", background: "#222" }
142
+ : { position: "relative", ...widthStyle, aspectRatio: aspect, borderRadius, overflow: "hidden", background: "#222" };
143
+
144
+ const shadowClass = block.shadow ? "shadow-lg" : "";
145
+
146
+ return (
147
+ <div className={shadowClass} style={frameStyle}>
148
+ {/* Before layer */}
149
+ <div style={{ position: "absolute", inset: 0 }}>
150
+ {hasBefore ? (
151
+ <PreviewMedia type={beforeType} path={block.before_asset_path} alt={block.before_alt || ""} />
152
+ ) : (
153
+ <div style={{ position: "absolute", inset: 0, background: "#e7e9ed", display: "flex", alignItems: "center", justifyContent: "center" }}>
154
+ <span style={{ fontSize: 11, color: "#8a8f98" }}>No before asset</span>
155
+ </div>
156
+ )}
157
+ </div>
158
+
159
+ {/* After layer — clipped by position */}
160
+ <div style={{ position: "absolute", inset: 0, clipPath: afterClip, WebkitClipPath: afterClip }}>
161
+ {hasAfter ? (
162
+ <PreviewMedia type={afterType} path={block.after_asset_path} alt={block.after_alt || ""} />
163
+ ) : (
164
+ <div style={{ position: "absolute", inset: 0, background: "#d8dbe0", display: "flex", alignItems: "center", justifyContent: "center" }}>
165
+ <span style={{ fontSize: 11, color: "#8a8f98" }}>No after asset</span>
166
+ </div>
167
+ )}
168
+ </div>
169
+
170
+ {/* Divider + knob */}
171
+ <div style={dividerStyle}>
172
+ <div style={knobStyle}>{ArrowIcon}</div>
173
+ </div>
174
+ </div>
175
+ );
176
+ }
@@ -55,8 +55,16 @@ export default function LiveImageGridPreview({ block }: { block: ImageGridBlock
55
55
  const images = block.images || [];
56
56
  if (images.length === 0) {
57
57
  return (
58
- <div className="border border-dashed border-neutral-700 rounded bg-neutral-900/50 flex items-center justify-center py-8">
59
- <span className="text-neutral-600 text-xs">Empty image grid</span>
58
+ <div style={{ width: "100%" }}>
59
+ <div className="w-full h-full min-h-[240px] flex flex-col items-center justify-center gap-2.5" style={{ background: "#f4f4f4" }}>
60
+ <svg width="56" height="56" viewBox="0 0 56 56" fill="none" aria-hidden="true">
61
+ <rect x="8" y="8" width="18" height="18" rx="2" stroke="#b0b5bd" strokeWidth="1.5" fill="#FFFFFF" />
62
+ <rect x="30" y="8" width="18" height="18" rx="2" stroke="#b0b5bd" strokeWidth="1.5" fill="#FFFFFF" />
63
+ <rect x="8" y="30" width="18" height="18" rx="2" stroke="#b0b5bd" strokeWidth="1.5" fill="#FFFFFF" />
64
+ <rect x="30" y="30" width="18" height="18" rx="2" stroke="#b0b5bd" strokeWidth="1.5" fill="#FFFFFF" />
65
+ </svg>
66
+ <span className="text-[11px] text-neutral-500">No images yet</span>
67
+ </div>
60
68
  </div>
61
69
  );
62
70
  }
@@ -20,7 +20,7 @@ export default function LiveImagePreview({ block }: { block: ImageBlock }) {
20
20
  : { width: "100%" };
21
21
  return (
22
22
  <div style={wrapperStyle}>
23
- <div className="w-full h-full min-h-[240px] rounded flex flex-col items-center justify-center gap-2.5" style={{ background: "#f4f4f4" }}>
23
+ <div className="w-full h-full min-h-[240px] flex flex-col items-center justify-center gap-2.5" style={{ background: "#f4f4f4" }}>
24
24
  <svg width="56" height="56" viewBox="0 0 56 56" fill="none" aria-hidden="true">
25
25
  <rect x="6" y="10" width="44" height="36" rx="3" stroke="#b0b5bd" strokeWidth="1.5" fill="#FFFFFF" />
26
26
  <circle cx="18" cy="21" r="3" fill="#b0b5bd" />
@@ -0,0 +1,39 @@
1
+ "use client";
2
+
3
+ /**
4
+ * LiveMarqueePreview — Builder canvas preview for marqueeBlock.
5
+ *
6
+ * Unlike LiveProjectCarouselPreview (which renders mockup cards because
7
+ * the public carousel hits the projects API), the marquee content IS the
8
+ * user's items — same text, same images. So this preview just delegates
9
+ * to the public renderer with two tweaks:
10
+ *
11
+ * - speedMultiplier = 0.3 → motion is visible but gentle, won't distract
12
+ * while editing. User's configured speed still drives the public site.
13
+ * - alwaysPlay = true → disables the IntersectionObserver pause, since
14
+ * the builder canvas has its own scrolling and the observer can yield
15
+ * false "off-screen" reports.
16
+ *
17
+ * Intentionally thin — mirrors the one-liner delegation pattern used for
18
+ * simple block types whose builder preview and public render don't diverge.
19
+ */
20
+
21
+ import type { MarqueeBlock } from "../../../lib/sanity/types";
22
+ import type { DeviceViewport } from "../../../lib/builder/types";
23
+ import MarqueeBlockRenderer from "../../blocks/MarqueeBlockRenderer";
24
+
25
+ export interface LiveMarqueePreviewProps {
26
+ block: MarqueeBlock;
27
+ viewport?: DeviceViewport;
28
+ editable?: boolean;
29
+ }
30
+
31
+ export default function LiveMarqueePreview({ block }: LiveMarqueePreviewProps) {
32
+ return (
33
+ <MarqueeBlockRenderer
34
+ block={block}
35
+ speedMultiplier={0.3}
36
+ alwaysPlay
37
+ />
38
+ );
39
+ }
@@ -141,7 +141,7 @@ export default function LiveProjectCarouselPreview({
141
141
  <div ref={containerCallbackRef} className="relative w-full">
142
142
  {/* Scroll track — same mechanics as public renderer so layout is faithful */}
143
143
  <div
144
- className="flex overflow-x-auto"
144
+ className="flex overflow-x-auto py-3"
145
145
  style={{
146
146
  gap: `${gap}px`,
147
147
  scrollSnapType: snapScroll ? "x mandatory" : undefined,
@@ -26,7 +26,7 @@ export default function LiveVideoPreview({ block }: { block: VideoBlock }) {
26
26
  : { width: "100%" };
27
27
  return (
28
28
  <div style={wrapperStyle}>
29
- <div className="w-full h-full min-h-[240px] rounded flex flex-col items-center justify-center gap-2.5" style={{ background: "#f4f4f4" }}>
29
+ <div className="w-full h-full min-h-[240px] flex flex-col items-center justify-center gap-2.5" style={{ background: "#f4f4f4" }}>
30
30
  <svg width="56" height="56" viewBox="0 0 56 56" fill="none" aria-hidden="true">
31
31
  <circle cx="28" cy="28" r="22" fill="#FFFFFF" stroke="#b0b5bd" strokeWidth="1.5" />
32
32
  <path d="M24 20 L37 28 L24 36 Z" fill="#b0b5bd" />