@morphika/andami 0.5.1 → 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 (117) hide show
  1. package/app/admin/assets/page.tsx +6 -6
  2. package/app/admin/database/page.tsx +302 -302
  3. package/app/admin/error.tsx +53 -53
  4. package/app/admin/layout.tsx +320 -320
  5. package/app/admin/navigation/page.tsx +255 -255
  6. package/app/admin/pages/[slug]/page.tsx +6 -6
  7. package/app/admin/pages/page.tsx +11 -11
  8. package/app/admin/projects/page.tsx +14 -14
  9. package/app/admin/setup/page.tsx +1 -1
  10. package/app/admin/styles/page.tsx +1 -1
  11. package/components/admin/MetadataEditor.tsx +6 -6
  12. package/components/admin/nav-builder/NavBuilder.tsx +1 -1
  13. package/components/admin/nav-builder/NavBuilderGrid.tsx +3 -3
  14. package/components/admin/nav-builder/NavGridCell.tsx +48 -48
  15. package/components/admin/nav-builder/NavGridItem.tsx +4 -4
  16. package/components/admin/nav-builder/NavItemSettings.tsx +331 -331
  17. package/components/admin/nav-builder/NavItemTypePicker.tsx +102 -102
  18. package/components/admin/nav-builder/NavLivePreview.tsx +1 -1
  19. package/components/admin/nav-builder/NavMobileLivePreview.tsx +226 -226
  20. package/components/admin/nav-builder/NavMobileSettings.tsx +242 -242
  21. package/components/admin/nav-builder/NavSettingsFields.tsx +514 -514
  22. package/components/admin/setup-wizard/BrandingStep.tsx +3 -3
  23. package/components/admin/setup-wizard/DatabaseStep.tsx +2 -2
  24. package/components/admin/setup-wizard/DoneStep.tsx +1 -1
  25. package/components/admin/setup-wizard/SetupWizard.tsx +4 -4
  26. package/components/admin/setup-wizard/StorageStep.tsx +2 -2
  27. package/components/admin/setup-wizard/WelcomeStep.tsx +2 -2
  28. package/components/admin/styles/ColorsEditor.tsx +2 -2
  29. package/components/admin/styles/FontsEditor.tsx +6 -6
  30. package/components/admin/styles/GridLayoutEditor.tsx +9 -9
  31. package/components/admin/styles/LinksButtonsEditor.tsx +5 -5
  32. package/components/admin/styles/TypographyEditor.tsx +6 -6
  33. package/components/admin/styles/shared.tsx +68 -68
  34. package/components/blocks/AudioBlockRenderer.tsx +286 -286
  35. package/components/blocks/MarqueeBlockRenderer.tsx +316 -0
  36. package/components/blocks/ProjectCarouselBlockRenderer.tsx +1 -1
  37. package/components/builder/BlockCardIcons.tsx +316 -316
  38. package/components/builder/BlockTypePicker.tsx +1 -1
  39. package/components/builder/BubbleIcons.tsx +90 -0
  40. package/components/builder/BuilderCanvas.tsx +2 -0
  41. package/components/builder/CanvasMinimap.tsx +2 -2
  42. package/components/builder/CoverSectionCanvas.tsx +363 -363
  43. package/components/builder/DeviceFrame.tsx +1 -1
  44. package/components/builder/DndWrapper.tsx +3 -3
  45. package/components/builder/InsertionLines.tsx +1 -1
  46. package/components/builder/SectionCardIcons.tsx +421 -320
  47. package/components/builder/SectionEditorBar.tsx +1 -1
  48. package/components/builder/SectionTypePicker.tsx +4 -4
  49. package/components/builder/SectionV2Canvas.tsx +1 -1
  50. package/components/builder/SectionV2Column.tsx +69 -67
  51. package/components/builder/SortableBlock.tsx +93 -73
  52. package/components/builder/SortableRow.tsx +27 -26
  53. package/components/builder/VirtualAssetGrid.tsx +2 -2
  54. package/components/builder/asset-browser/R2BrowserContent.tsx +11 -11
  55. package/components/builder/blockStyles.tsx +192 -185
  56. package/components/builder/color-picker/AlphaSlider.tsx +141 -141
  57. package/components/builder/color-picker/ColorInputs.tsx +105 -105
  58. package/components/builder/color-picker/EyedropperButton.tsx +74 -74
  59. package/components/builder/color-picker/HueSlider.tsx +124 -124
  60. package/components/builder/color-picker/SaturationCanvas.tsx +142 -142
  61. package/components/builder/color-picker/SwatchBar.tsx +93 -93
  62. package/components/builder/editors/AudioBlockEditor.tsx +242 -242
  63. package/components/builder/editors/BeforeAfterBlockEditor.tsx +360 -360
  64. package/components/builder/editors/ButtonBlockEditor.tsx +4 -4
  65. package/components/builder/editors/EnterAnimationPicker.tsx +2 -2
  66. package/components/builder/editors/HoverEffectPicker.tsx +2 -2
  67. package/components/builder/editors/ImageBlockEditor.tsx +2 -2
  68. package/components/builder/editors/ImageGridBlockEditor.tsx +4 -4
  69. package/components/builder/editors/MarqueeBlockEditor.tsx +621 -0
  70. package/components/builder/editors/ProjectCarouselBlockEditor.tsx +443 -443
  71. package/components/builder/editors/ProjectGridEditor.tsx +9 -9
  72. package/components/builder/editors/SpacerBlockEditor.tsx +5 -5
  73. package/components/builder/editors/StaggerSettings.tsx +109 -109
  74. package/components/builder/editors/TextBlockEditor.tsx +3 -3
  75. package/components/builder/editors/TextStylePicker.tsx +1 -1
  76. package/components/builder/editors/VideoBlockEditor.tsx +2 -2
  77. package/components/builder/editors/index.ts +11 -10
  78. package/components/builder/editors/shared.tsx +6 -6
  79. package/components/builder/live-preview/LiveAudioPreview.tsx +120 -120
  80. package/components/builder/live-preview/LiveBeforeAfterPreview.tsx +1 -1
  81. package/components/builder/live-preview/LiveImageGridPreview.tsx +10 -2
  82. package/components/builder/live-preview/LiveImagePreview.tsx +1 -1
  83. package/components/builder/live-preview/LiveMarqueePreview.tsx +39 -0
  84. package/components/builder/live-preview/LiveProjectCarouselPreview.tsx +1 -1
  85. package/components/builder/live-preview/LiveVideoPreview.tsx +1 -1
  86. package/components/builder/live-preview/ProjectCardWrapper.tsx +291 -291
  87. package/components/builder/settings-panel/AnimationTab.tsx +138 -138
  88. package/components/builder/settings-panel/BlockLayoutTab.tsx +7 -7
  89. package/components/builder/settings-panel/CardEntranceSection.tsx +114 -114
  90. package/components/builder/settings-panel/ColumnV2Settings.tsx +5 -5
  91. package/components/builder/settings-panel/CoverSectionLayoutTab.tsx +71 -71
  92. package/components/builder/settings-panel/CoverSectionSettings.tsx +335 -335
  93. package/components/builder/settings-panel/PageSettings.tsx +3 -3
  94. package/components/builder/settings-panel/ParallaxSlideSettings.tsx +2 -2
  95. package/components/builder/settings-panel/SectionV2AnimationTab.tsx +4 -4
  96. package/components/builder/settings-panel/SectionV2LayoutTab.tsx +356 -356
  97. package/components/builder/settings-panel/SectionV2Settings.tsx +14 -14
  98. package/components/builder/settings-panel/TRBLInputs.tsx +1 -1
  99. package/lib/animation/enter-types.ts +1 -0
  100. package/lib/animation/hover-effect-presets.ts +210 -210
  101. package/lib/animation/hover-effect-types.ts +1 -0
  102. package/lib/builder/block-registrations.ts +468 -417
  103. package/lib/builder/constants.ts +111 -111
  104. package/lib/builder/store-sections.ts +2 -2
  105. package/lib/builder/types-slices.ts +414 -414
  106. package/lib/builder/types.ts +4 -1
  107. package/lib/config/index.ts +27 -27
  108. package/lib/sanity/types.ts +98 -1
  109. package/lib/version.ts +1 -1
  110. package/package.json +1 -1
  111. package/sanity/schemas/blocks/audioBlock.ts +69 -69
  112. package/sanity/schemas/blocks/index.ts +12 -11
  113. package/sanity/schemas/blocks/marqueeBlock.ts +292 -0
  114. package/sanity/schemas/index.ts +120 -117
  115. package/styles/admin.css +85 -85
  116. package/styles/animations.css +237 -237
  117. package/styles/base.css +114 -114
@@ -1,102 +1,102 @@
1
- "use client";
2
-
3
- import { useEffect, useRef } from "react";
4
-
5
- interface NavItemTypePickerProps {
6
- column: number;
7
- onSelect: (type: "logo" | "menu-item", defaultSpan: number) => void;
8
- onClose: () => void;
9
- hasLogo: boolean; // prevent adding multiple logos
10
- }
11
-
12
- const ITEM_TYPES = [
13
- {
14
- type: "logo" as const,
15
- label: "Logo",
16
- description: "Brand logo (text or image)",
17
- defaultSpan: 3,
18
- icon: (
19
- <svg width="20" height="20" viewBox="0 0 20 20" fill="none">
20
- <rect x="2" y="4" width="16" height="12" rx="2" stroke="currentColor" strokeWidth="1.5" />
21
- <path d="M6 10h8M6 13h4" stroke="currentColor" strokeWidth="1.2" strokeLinecap="round" />
22
- <circle cx="10" cy="7.5" r="1.5" stroke="currentColor" strokeWidth="1.2" />
23
- </svg>
24
- ),
25
- },
26
- {
27
- type: "menu-item" as const,
28
- label: "Menu Item",
29
- description: "Navigation link",
30
- defaultSpan: 1,
31
- icon: (
32
- <svg width="20" height="20" viewBox="0 0 20 20" fill="none">
33
- <path d="M4 7h12M4 10h8M4 13h10" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
34
- </svg>
35
- ),
36
- },
37
- ];
38
-
39
- export default function NavItemTypePicker({
40
- column,
41
- onSelect,
42
- onClose,
43
- hasLogo,
44
- }: NavItemTypePickerProps) {
45
- const ref = useRef<HTMLDivElement>(null);
46
-
47
- // Close on Escape
48
- useEffect(() => {
49
- const handleKey = (e: KeyboardEvent) => {
50
- if (e.key === "Escape") onClose();
51
- };
52
- window.addEventListener("keydown", handleKey);
53
- return () => window.removeEventListener("keydown", handleKey);
54
- }, [onClose]);
55
-
56
- // Focus trap
57
- useEffect(() => {
58
- ref.current?.focus();
59
- }, []);
60
-
61
- const types = hasLogo
62
- ? ITEM_TYPES.filter((t) => t.type !== "logo")
63
- : ITEM_TYPES;
64
-
65
- return (
66
- <div
67
- className="fixed inset-0 z-50 flex items-center justify-center"
68
- style={{ background: "rgba(0,0,0,0.2)", backdropFilter: "blur(4px)" }}
69
- onClick={onClose}
70
- >
71
- <div
72
- ref={ref}
73
- tabIndex={-1}
74
- className="outline-none bg-white border border-neutral-200 rounded-2xl p-6 shadow-2xl"
75
- style={{ width: 320 }}
76
- onClick={(e) => e.stopPropagation()}
77
- >
78
- <div className="text-[13px] font-semibold text-neutral-900 mb-1">Add Nav Item</div>
79
- <div className="text-[11px] text-neutral-500 mb-4">Column {column}</div>
80
- <div className="flex flex-col gap-2">
81
- {types.map((t) => (
82
- <button
83
- key={t.type}
84
- onClick={() => onSelect(t.type, t.defaultSpan)}
85
- className="flex items-center gap-3 p-3 rounded-xl border border-neutral-200 bg-white text-left transition-colors hover:border-[#076bff] hover:bg-[#076bff]/[0.02]"
86
- >
87
- <div className="w-10 h-10 rounded-lg bg-neutral-50 border border-neutral-100 flex items-center justify-center text-[#076bff] shrink-0">
88
- {t.icon}
89
- </div>
90
- <div>
91
- <div className="text-[13px] font-medium text-neutral-900">{t.label}</div>
92
- <div className="text-[11px] text-neutral-500 mt-0.5">
93
- {t.description}
94
- </div>
95
- </div>
96
- </button>
97
- ))}
98
- </div>
99
- </div>
100
- </div>
101
- );
102
- }
1
+ "use client";
2
+
3
+ import { useEffect, useRef } from "react";
4
+
5
+ interface NavItemTypePickerProps {
6
+ column: number;
7
+ onSelect: (type: "logo" | "menu-item", defaultSpan: number) => void;
8
+ onClose: () => void;
9
+ hasLogo: boolean; // prevent adding multiple logos
10
+ }
11
+
12
+ const ITEM_TYPES = [
13
+ {
14
+ type: "logo" as const,
15
+ label: "Logo",
16
+ description: "Brand logo (text or image)",
17
+ defaultSpan: 3,
18
+ icon: (
19
+ <svg width="20" height="20" viewBox="0 0 20 20" fill="none">
20
+ <rect x="2" y="4" width="16" height="12" rx="2" stroke="currentColor" strokeWidth="1.5" />
21
+ <path d="M6 10h8M6 13h4" stroke="currentColor" strokeWidth="1.2" strokeLinecap="round" />
22
+ <circle cx="10" cy="7.5" r="1.5" stroke="currentColor" strokeWidth="1.2" />
23
+ </svg>
24
+ ),
25
+ },
26
+ {
27
+ type: "menu-item" as const,
28
+ label: "Menu Item",
29
+ description: "Navigation link",
30
+ defaultSpan: 1,
31
+ icon: (
32
+ <svg width="20" height="20" viewBox="0 0 20 20" fill="none">
33
+ <path d="M4 7h12M4 10h8M4 13h10" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
34
+ </svg>
35
+ ),
36
+ },
37
+ ];
38
+
39
+ export default function NavItemTypePicker({
40
+ column,
41
+ onSelect,
42
+ onClose,
43
+ hasLogo,
44
+ }: NavItemTypePickerProps) {
45
+ const ref = useRef<HTMLDivElement>(null);
46
+
47
+ // Close on Escape
48
+ useEffect(() => {
49
+ const handleKey = (e: KeyboardEvent) => {
50
+ if (e.key === "Escape") onClose();
51
+ };
52
+ window.addEventListener("keydown", handleKey);
53
+ return () => window.removeEventListener("keydown", handleKey);
54
+ }, [onClose]);
55
+
56
+ // Focus trap
57
+ useEffect(() => {
58
+ ref.current?.focus();
59
+ }, []);
60
+
61
+ const types = hasLogo
62
+ ? ITEM_TYPES.filter((t) => t.type !== "logo")
63
+ : ITEM_TYPES;
64
+
65
+ return (
66
+ <div
67
+ className="fixed inset-0 z-50 flex items-center justify-center"
68
+ style={{ background: "rgba(0,0,0,0.2)", backdropFilter: "blur(4px)" }}
69
+ onClick={onClose}
70
+ >
71
+ <div
72
+ ref={ref}
73
+ tabIndex={-1}
74
+ className="outline-none bg-white border border-neutral-200 rounded-2xl p-6 shadow-2xl"
75
+ style={{ width: 320 }}
76
+ onClick={(e) => e.stopPropagation()}
77
+ >
78
+ <div className="text-[13px] font-semibold text-neutral-900 mb-1">Add Nav Item</div>
79
+ <div className="text-[11px] text-neutral-500 mb-4">Column {column}</div>
80
+ <div className="flex flex-col gap-2">
81
+ {types.map((t) => (
82
+ <button
83
+ key={t.type}
84
+ onClick={() => onSelect(t.type, t.defaultSpan)}
85
+ className="flex items-center gap-3 p-3 rounded-xl border border-neutral-200 bg-white text-left transition-colors hover:border-[#3580f9] hover:bg-[#3580f9]/[0.02]"
86
+ >
87
+ <div className="w-10 h-10 rounded-lg bg-neutral-50 border border-neutral-100 flex items-center justify-center text-[#3580f9] shrink-0">
88
+ {t.icon}
89
+ </div>
90
+ <div>
91
+ <div className="text-[13px] font-medium text-neutral-900">{t.label}</div>
92
+ <div className="text-[11px] text-neutral-500 mt-0.5">
93
+ {t.description}
94
+ </div>
95
+ </div>
96
+ </button>
97
+ ))}
98
+ </div>
99
+ </div>
100
+ </div>
101
+ );
102
+ }
@@ -10,7 +10,7 @@ const NAV_COLOR_HEX: Record<NavColorVariant, string> = {
10
10
  "yellow-lime": _cfg.palette.accent,
11
11
  yellow: _cfg.palette.accent,
12
12
  "red-coral": _cfg.palette.secondary,
13
- blue: "#076bff",
13
+ blue: "#3580f9",
14
14
  green: _cfg.palette.accent,
15
15
  white: _cfg.palette.text,
16
16
  };
@@ -1,226 +1,226 @@
1
- "use client";
2
-
3
- import type { NavItem, NavDesign, MobileNavDesign, NavColorVariant } from "../../../lib/sanity/types";
4
- import { hexToRgba } from "./nav-builder-utils";
5
- import { getSiteConfig } from "../../../lib/config";
6
-
7
- const _cfg = getSiteConfig();
8
-
9
- const NAV_COLOR_HEX: Record<NavColorVariant, string> = {
10
- "yellow-lime": _cfg.palette.accent,
11
- yellow: _cfg.palette.accent,
12
- "red-coral": _cfg.palette.secondary,
13
- blue: "#076bff",
14
- green: _cfg.palette.accent,
15
- white: _cfg.palette.text,
16
- };
17
-
18
- // ============================================
19
- // NavMobileLivePreview — phone-sized preview of the mobile menu
20
- // Session 158: Mirrors NavLivePreview visual language (dark bg, footer label)
21
- // but renders the mobile hamburger overlay layout inside a phone frame.
22
- // Designed to sit on the right side of NavMobileSettings.
23
- // ============================================
24
-
25
- interface NavMobileLivePreviewProps {
26
- items: NavItem[];
27
- design: NavDesign;
28
- mobileDesign: MobileNavDesign;
29
- }
30
-
31
- export default function NavMobileLivePreview({
32
- items,
33
- design,
34
- mobileDesign,
35
- }: NavMobileLivePreviewProps) {
36
- // Desktop fallback values
37
- const desktopColorKey = design.color || "yellow-lime";
38
- const desktopColor = /^#[0-9a-fA-F]{6}$/.test(desktopColorKey)
39
- ? desktopColorKey
40
- : NAV_COLOR_HEX[desktopColorKey as keyof typeof NAV_COLOR_HEX] || NAV_COLOR_HEX["yellow-lime"];
41
-
42
- // ── Navbar bar (logo + hamburger) ──
43
- const barPaddingH = mobileDesign.padding_h ?? design.padding_h ?? 24;
44
- const barPaddingV = mobileDesign.padding_v ?? design.padding_v ?? 27;
45
- const barBg = mobileDesign.navbar_bg || "";
46
- const barBgOpacity = mobileDesign.navbar_bg_opacity ?? 0;
47
- const barBgColor =
48
- barBg && barBgOpacity > 0 ? hexToRgba(barBg, barBgOpacity / 100) : "transparent";
49
- const hamburgerColor = mobileDesign.hamburger_color || desktopColor;
50
-
51
- // ── Overlay (expanded menu) ──
52
- const overlayBg = mobileDesign.overlay_bg || "#0a0a0a";
53
- const textColor = mobileDesign.text_color || desktopColor;
54
- const fontSize = mobileDesign.font_size ?? 24;
55
- const textTransform = (mobileDesign.text_transform || design.text_transform || "uppercase") as React.CSSProperties["textTransform"];
56
- const itemsGap = mobileDesign.items_gap ?? 32;
57
- const itemsAlign = mobileDesign.items_align || "center";
58
- const alignItems =
59
- itemsAlign === "right" ? "flex-end" : itemsAlign === "left" ? "flex-start" : "center";
60
-
61
- const fontFamily =
62
- design.font_family || `var(--font-family, '${_cfg.typography.defaultFont}', ${_cfg.typography.monoFallback})`;
63
-
64
- const logoLabel =
65
- items.find((i) => i.type === "logo")?.label ||
66
- design.logo_text ||
67
- _cfg.defaults.logoText;
68
- const menuItems = items
69
- .filter((i) => i.type !== "logo" && i.visible !== false)
70
- .sort((a, b) => (a.grid_column || 0) - (b.grid_column || 0));
71
-
72
- return (
73
- <div
74
- style={{
75
- background: "#020202",
76
- height: "100%",
77
- display: "flex",
78
- flexDirection: "column",
79
- }}
80
- >
81
- {/* Phone frame — centered */}
82
- <div
83
- style={{
84
- flex: 1,
85
- display: "flex",
86
- alignItems: "center",
87
- justifyContent: "center",
88
- padding: "20px 24px 12px",
89
- }}
90
- >
91
- <div
92
- style={{
93
- width: "100%",
94
- maxWidth: 280,
95
- borderRadius: 20,
96
- overflow: "hidden",
97
- border: "1px solid rgba(255,255,255,0.08)",
98
- background: overlayBg,
99
- }}
100
- >
101
- {/* Navbar bar — logo + hamburger icon */}
102
- <div
103
- style={{
104
- display: "flex",
105
- alignItems: "center",
106
- justifyContent: "space-between",
107
- paddingLeft: `${Math.min(barPaddingH, 24)}px`,
108
- paddingRight: `${Math.min(barPaddingH, 24)}px`,
109
- paddingTop: `${Math.min(barPaddingV, 20)}px`,
110
- paddingBottom: `${Math.min(barPaddingV, 20)}px`,
111
- background: barBgColor,
112
- }}
113
- >
114
- {/* Logo label */}
115
- <span
116
- style={{
117
- color: hamburgerColor,
118
- fontSize: Math.min(design.font_size ?? 14, 14),
119
- fontWeight: design.font_weight || "400",
120
- fontFamily,
121
- textTransform,
122
- letterSpacing: "0.05em",
123
- overflow: "hidden",
124
- textOverflow: "ellipsis",
125
- whiteSpace: "nowrap",
126
- }}
127
- >
128
- {logoLabel}
129
- </span>
130
-
131
- {/* Hamburger icon */}
132
- <div
133
- style={{
134
- display: "flex",
135
- flexDirection: "column",
136
- gap: 3.5,
137
- flexShrink: 0,
138
- }}
139
- >
140
- <span
141
- style={{
142
- display: "block",
143
- width: 16,
144
- height: 1.5,
145
- borderRadius: 1,
146
- background: hamburgerColor,
147
- }}
148
- />
149
- <span
150
- style={{
151
- display: "block",
152
- width: 16,
153
- height: 1.5,
154
- borderRadius: 1,
155
- background: hamburgerColor,
156
- }}
157
- />
158
- <span
159
- style={{
160
- display: "block",
161
- width: 16,
162
- height: 1.5,
163
- borderRadius: 1,
164
- background: hamburgerColor,
165
- }}
166
- />
167
- </div>
168
- </div>
169
-
170
- {/* Overlay — menu items */}
171
- <div
172
- style={{
173
- display: "flex",
174
- flexDirection: "column",
175
- alignItems,
176
- justifyContent: "center",
177
- gap: `${Math.min(itemsGap, 40)}px`,
178
- paddingLeft: `${Math.min(barPaddingH, 24)}px`,
179
- paddingRight: `${Math.min(barPaddingH, 24)}px`,
180
- paddingTop: 24,
181
- paddingBottom: 32,
182
- minHeight: 180,
183
- }}
184
- >
185
- {menuItems.map((item) => (
186
- <span
187
- key={item._key}
188
- style={{
189
- color: textColor,
190
- fontSize: Math.min(fontSize, 28),
191
- fontWeight: design.font_weight || "400",
192
- fontFamily,
193
- textTransform,
194
- letterSpacing: "0.05em",
195
- whiteSpace: "nowrap",
196
- overflow: "hidden",
197
- textOverflow: "ellipsis",
198
- maxWidth: "100%",
199
- }}
200
- >
201
- {item.label || "Untitled"}
202
- </span>
203
- ))}
204
- {menuItems.length === 0 && (
205
- <span
206
- style={{
207
- color: "rgba(255,255,255,0.2)",
208
- fontSize: 12,
209
- fontStyle: "italic",
210
- }}
211
- >
212
- No menu items
213
- </span>
214
- )}
215
- </div>
216
- </div>
217
- </div>
218
-
219
- {/* Footer — matches NavLivePreview style */}
220
- <div className="flex items-center justify-between px-3 py-1.5 text-[9px] text-neutral-600">
221
- <span>Mobile Preview</span>
222
- <span>{menuItems.length} menu items</span>
223
- </div>
224
- </div>
225
- );
226
- }
1
+ "use client";
2
+
3
+ import type { NavItem, NavDesign, MobileNavDesign, NavColorVariant } from "../../../lib/sanity/types";
4
+ import { hexToRgba } from "./nav-builder-utils";
5
+ import { getSiteConfig } from "../../../lib/config";
6
+
7
+ const _cfg = getSiteConfig();
8
+
9
+ const NAV_COLOR_HEX: Record<NavColorVariant, string> = {
10
+ "yellow-lime": _cfg.palette.accent,
11
+ yellow: _cfg.palette.accent,
12
+ "red-coral": _cfg.palette.secondary,
13
+ blue: "#3580f9",
14
+ green: _cfg.palette.accent,
15
+ white: _cfg.palette.text,
16
+ };
17
+
18
+ // ============================================
19
+ // NavMobileLivePreview — phone-sized preview of the mobile menu
20
+ // Session 158: Mirrors NavLivePreview visual language (dark bg, footer label)
21
+ // but renders the mobile hamburger overlay layout inside a phone frame.
22
+ // Designed to sit on the right side of NavMobileSettings.
23
+ // ============================================
24
+
25
+ interface NavMobileLivePreviewProps {
26
+ items: NavItem[];
27
+ design: NavDesign;
28
+ mobileDesign: MobileNavDesign;
29
+ }
30
+
31
+ export default function NavMobileLivePreview({
32
+ items,
33
+ design,
34
+ mobileDesign,
35
+ }: NavMobileLivePreviewProps) {
36
+ // Desktop fallback values
37
+ const desktopColorKey = design.color || "yellow-lime";
38
+ const desktopColor = /^#[0-9a-fA-F]{6}$/.test(desktopColorKey)
39
+ ? desktopColorKey
40
+ : NAV_COLOR_HEX[desktopColorKey as keyof typeof NAV_COLOR_HEX] || NAV_COLOR_HEX["yellow-lime"];
41
+
42
+ // ── Navbar bar (logo + hamburger) ──
43
+ const barPaddingH = mobileDesign.padding_h ?? design.padding_h ?? 24;
44
+ const barPaddingV = mobileDesign.padding_v ?? design.padding_v ?? 27;
45
+ const barBg = mobileDesign.navbar_bg || "";
46
+ const barBgOpacity = mobileDesign.navbar_bg_opacity ?? 0;
47
+ const barBgColor =
48
+ barBg && barBgOpacity > 0 ? hexToRgba(barBg, barBgOpacity / 100) : "transparent";
49
+ const hamburgerColor = mobileDesign.hamburger_color || desktopColor;
50
+
51
+ // ── Overlay (expanded menu) ──
52
+ const overlayBg = mobileDesign.overlay_bg || "#0a0a0a";
53
+ const textColor = mobileDesign.text_color || desktopColor;
54
+ const fontSize = mobileDesign.font_size ?? 24;
55
+ const textTransform = (mobileDesign.text_transform || design.text_transform || "uppercase") as React.CSSProperties["textTransform"];
56
+ const itemsGap = mobileDesign.items_gap ?? 32;
57
+ const itemsAlign = mobileDesign.items_align || "center";
58
+ const alignItems =
59
+ itemsAlign === "right" ? "flex-end" : itemsAlign === "left" ? "flex-start" : "center";
60
+
61
+ const fontFamily =
62
+ design.font_family || `var(--font-family, '${_cfg.typography.defaultFont}', ${_cfg.typography.monoFallback})`;
63
+
64
+ const logoLabel =
65
+ items.find((i) => i.type === "logo")?.label ||
66
+ design.logo_text ||
67
+ _cfg.defaults.logoText;
68
+ const menuItems = items
69
+ .filter((i) => i.type !== "logo" && i.visible !== false)
70
+ .sort((a, b) => (a.grid_column || 0) - (b.grid_column || 0));
71
+
72
+ return (
73
+ <div
74
+ style={{
75
+ background: "#020202",
76
+ height: "100%",
77
+ display: "flex",
78
+ flexDirection: "column",
79
+ }}
80
+ >
81
+ {/* Phone frame — centered */}
82
+ <div
83
+ style={{
84
+ flex: 1,
85
+ display: "flex",
86
+ alignItems: "center",
87
+ justifyContent: "center",
88
+ padding: "20px 24px 12px",
89
+ }}
90
+ >
91
+ <div
92
+ style={{
93
+ width: "100%",
94
+ maxWidth: 280,
95
+ borderRadius: 20,
96
+ overflow: "hidden",
97
+ border: "1px solid rgba(255,255,255,0.08)",
98
+ background: overlayBg,
99
+ }}
100
+ >
101
+ {/* Navbar bar — logo + hamburger icon */}
102
+ <div
103
+ style={{
104
+ display: "flex",
105
+ alignItems: "center",
106
+ justifyContent: "space-between",
107
+ paddingLeft: `${Math.min(barPaddingH, 24)}px`,
108
+ paddingRight: `${Math.min(barPaddingH, 24)}px`,
109
+ paddingTop: `${Math.min(barPaddingV, 20)}px`,
110
+ paddingBottom: `${Math.min(barPaddingV, 20)}px`,
111
+ background: barBgColor,
112
+ }}
113
+ >
114
+ {/* Logo label */}
115
+ <span
116
+ style={{
117
+ color: hamburgerColor,
118
+ fontSize: Math.min(design.font_size ?? 14, 14),
119
+ fontWeight: design.font_weight || "400",
120
+ fontFamily,
121
+ textTransform,
122
+ letterSpacing: "0.05em",
123
+ overflow: "hidden",
124
+ textOverflow: "ellipsis",
125
+ whiteSpace: "nowrap",
126
+ }}
127
+ >
128
+ {logoLabel}
129
+ </span>
130
+
131
+ {/* Hamburger icon */}
132
+ <div
133
+ style={{
134
+ display: "flex",
135
+ flexDirection: "column",
136
+ gap: 3.5,
137
+ flexShrink: 0,
138
+ }}
139
+ >
140
+ <span
141
+ style={{
142
+ display: "block",
143
+ width: 16,
144
+ height: 1.5,
145
+ borderRadius: 1,
146
+ background: hamburgerColor,
147
+ }}
148
+ />
149
+ <span
150
+ style={{
151
+ display: "block",
152
+ width: 16,
153
+ height: 1.5,
154
+ borderRadius: 1,
155
+ background: hamburgerColor,
156
+ }}
157
+ />
158
+ <span
159
+ style={{
160
+ display: "block",
161
+ width: 16,
162
+ height: 1.5,
163
+ borderRadius: 1,
164
+ background: hamburgerColor,
165
+ }}
166
+ />
167
+ </div>
168
+ </div>
169
+
170
+ {/* Overlay — menu items */}
171
+ <div
172
+ style={{
173
+ display: "flex",
174
+ flexDirection: "column",
175
+ alignItems,
176
+ justifyContent: "center",
177
+ gap: `${Math.min(itemsGap, 40)}px`,
178
+ paddingLeft: `${Math.min(barPaddingH, 24)}px`,
179
+ paddingRight: `${Math.min(barPaddingH, 24)}px`,
180
+ paddingTop: 24,
181
+ paddingBottom: 32,
182
+ minHeight: 180,
183
+ }}
184
+ >
185
+ {menuItems.map((item) => (
186
+ <span
187
+ key={item._key}
188
+ style={{
189
+ color: textColor,
190
+ fontSize: Math.min(fontSize, 28),
191
+ fontWeight: design.font_weight || "400",
192
+ fontFamily,
193
+ textTransform,
194
+ letterSpacing: "0.05em",
195
+ whiteSpace: "nowrap",
196
+ overflow: "hidden",
197
+ textOverflow: "ellipsis",
198
+ maxWidth: "100%",
199
+ }}
200
+ >
201
+ {item.label || "Untitled"}
202
+ </span>
203
+ ))}
204
+ {menuItems.length === 0 && (
205
+ <span
206
+ style={{
207
+ color: "rgba(255,255,255,0.2)",
208
+ fontSize: 12,
209
+ fontStyle: "italic",
210
+ }}
211
+ >
212
+ No menu items
213
+ </span>
214
+ )}
215
+ </div>
216
+ </div>
217
+ </div>
218
+
219
+ {/* Footer — matches NavLivePreview style */}
220
+ <div className="flex items-center justify-between px-3 py-1.5 text-[9px] text-neutral-600">
221
+ <span>Mobile Preview</span>
222
+ <span>{menuItems.length} menu items</span>
223
+ </div>
224
+ </div>
225
+ );
226
+ }