@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
@@ -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
+ }