@morphika/andami 0.5.1 → 0.5.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +27 -2
- package/app/admin/assets/page.tsx +6 -6
- package/app/admin/database/page.tsx +302 -302
- package/app/admin/error.tsx +53 -53
- package/app/admin/layout.tsx +332 -320
- package/app/admin/navigation/page.tsx +255 -255
- package/app/admin/pages/[slug]/page.tsx +44 -27
- package/app/admin/pages/page.tsx +24 -19
- package/app/admin/projects/page.tsx +30 -21
- package/app/admin/setup/page.tsx +1 -1
- package/app/admin/styles/page.tsx +1 -1
- package/app/api/admin/assets/register/route.ts +51 -14
- package/app/api/admin/assets/registry/route.ts +4 -1
- package/app/api/admin/assets/relink/confirm/route.ts +4 -1
- package/app/api/admin/assets/relink/route.ts +4 -1
- package/app/api/admin/assets/scan/route.ts +4 -1
- package/app/api/admin/backups/restore-data/route.ts +4 -1
- package/app/api/admin/r2/connect/route.ts +4 -1
- package/app/api/admin/r2/delete/route.ts +4 -1
- package/app/api/admin/r2/rename/route.ts +4 -1
- package/app/api/admin/r2/upload-url/route.ts +4 -1
- package/app/api/admin/revalidate/route.ts +4 -1
- package/app/api/admin/storage/switch/route.ts +4 -1
- package/app/api/custom-sections/[id]/route.ts +5 -6
- package/components/admin/MetadataEditor.tsx +6 -6
- package/components/admin/PublishToggle.tsx +2 -2
- package/components/admin/nav-builder/NavBuilder.tsx +1 -1
- package/components/admin/nav-builder/NavBuilderGrid.tsx +3 -3
- package/components/admin/nav-builder/NavGridCell.tsx +48 -48
- package/components/admin/nav-builder/NavGridItem.tsx +8 -6
- package/components/admin/nav-builder/NavItemSettings.tsx +331 -331
- package/components/admin/nav-builder/NavItemTypePicker.tsx +102 -102
- package/components/admin/nav-builder/NavLivePreview.tsx +1 -1
- package/components/admin/nav-builder/NavMobileLivePreview.tsx +226 -226
- package/components/admin/nav-builder/NavMobileSettings.tsx +242 -242
- package/components/admin/nav-builder/NavSettingsFields.tsx +518 -514
- package/components/admin/setup-wizard/BrandingStep.tsx +3 -3
- package/components/admin/setup-wizard/DatabaseStep.tsx +2 -2
- package/components/admin/setup-wizard/DoneStep.tsx +1 -1
- package/components/admin/setup-wizard/SetupWizard.tsx +4 -4
- package/components/admin/setup-wizard/StorageStep.tsx +2 -2
- package/components/admin/setup-wizard/WelcomeStep.tsx +2 -2
- package/components/admin/styles/ColorsEditor.tsx +9 -8
- package/components/admin/styles/FontsEditor.tsx +9 -7
- package/components/admin/styles/GridLayoutEditor.tsx +9 -9
- package/components/admin/styles/LinksButtonsEditor.tsx +5 -5
- package/components/admin/styles/TypographyEditor.tsx +6 -6
- package/components/admin/styles/shared.tsx +68 -68
- package/components/blocks/AudioBlockRenderer.tsx +286 -286
- package/components/blocks/CoverSectionRenderer.tsx +7 -1
- package/components/blocks/MarqueeBlockRenderer.tsx +316 -0
- package/components/blocks/ProjectCarouselBlockRenderer.tsx +1 -1
- package/components/blocks/SectionV2Renderer.tsx +8 -1
- package/components/builder/BlockCardIcons.tsx +316 -316
- package/components/builder/BlockTypePicker.tsx +1 -1
- package/components/builder/BubbleIcons.tsx +104 -0
- package/components/builder/BuilderCanvas.tsx +2 -0
- package/components/builder/CanvasMinimap.tsx +66 -49
- package/components/builder/CanvasToolbar.tsx +31 -41
- package/components/builder/CoverSectionCanvas.tsx +363 -363
- package/components/builder/DeviceFrame.tsx +1 -1
- package/components/builder/DndWrapper.tsx +3 -3
- package/components/builder/InsertionLines.tsx +1 -1
- package/components/builder/SectionCardIcons.tsx +421 -320
- package/components/builder/SectionEditorBar.tsx +5 -3
- package/components/builder/SectionTypePicker.tsx +7 -5
- package/components/builder/SectionV2Canvas.tsx +1 -1
- package/components/builder/SectionV2Column.tsx +82 -68
- package/components/builder/SettingsPanel.tsx +21 -17
- package/components/builder/SortableBlock.tsx +93 -73
- package/components/builder/SortableRow.tsx +33 -35
- package/components/builder/VirtualAssetGrid.tsx +10 -4
- package/components/builder/asset-browser/R2BrowserContent.tsx +18 -14
- package/components/builder/blockStyles.tsx +192 -185
- package/components/builder/color-picker/AlphaSlider.tsx +141 -141
- package/components/builder/color-picker/ColorInputs.tsx +105 -105
- package/components/builder/color-picker/EyedropperButton.tsx +75 -74
- package/components/builder/color-picker/HueSlider.tsx +124 -124
- package/components/builder/color-picker/SaturationCanvas.tsx +142 -142
- package/components/builder/color-picker/SwatchBar.tsx +98 -93
- package/components/builder/color-picker/UnifiedColorPicker.tsx +11 -6
- package/components/builder/editors/AudioBlockEditor.tsx +242 -242
- package/components/builder/editors/BeforeAfterBlockEditor.tsx +360 -360
- package/components/builder/editors/ButtonBlockEditor.tsx +4 -4
- package/components/builder/editors/EnterAnimationPicker.tsx +2 -2
- package/components/builder/editors/HoverEffectPicker.tsx +2 -2
- package/components/builder/editors/ImageBlockEditor.tsx +2 -2
- package/components/builder/editors/ImageGridBlockEditor.tsx +8 -6
- package/components/builder/editors/MarqueeBlockEditor.tsx +622 -0
- package/components/builder/editors/ProjectCarouselBlockEditor.tsx +443 -443
- package/components/builder/editors/ProjectGridEditor.tsx +21 -16
- package/components/builder/editors/SpacerBlockEditor.tsx +29 -27
- package/components/builder/editors/StaggerSettings.tsx +109 -109
- package/components/builder/editors/TextBlockEditor.tsx +22 -17
- package/components/builder/editors/TextStylePicker.tsx +1 -1
- package/components/builder/editors/VideoBlockEditor.tsx +2 -2
- package/components/builder/editors/index.ts +11 -10
- package/components/builder/editors/shared.tsx +10 -8
- package/components/builder/live-preview/LiveAudioPreview.tsx +120 -120
- package/components/builder/live-preview/LiveBeforeAfterPreview.tsx +1 -1
- package/components/builder/live-preview/LiveImageGridPreview.tsx +10 -2
- package/components/builder/live-preview/LiveImagePreview.tsx +4 -2
- package/components/builder/live-preview/LiveMarqueePreview.tsx +39 -0
- package/components/builder/live-preview/LiveProjectCarouselPreview.tsx +1 -1
- package/components/builder/live-preview/LiveVideoPreview.tsx +1 -1
- package/components/builder/live-preview/ProjectCardWrapper.tsx +293 -291
- package/components/builder/live-preview/RichTextBubbleMenu.tsx +10 -6
- package/components/builder/live-preview/shared.tsx +5 -2
- package/components/builder/settings-panel/AnimationTab.tsx +138 -138
- package/components/builder/settings-panel/BlockLayoutTab.tsx +11 -9
- package/components/builder/settings-panel/CardEntranceSection.tsx +114 -114
- package/components/builder/settings-panel/ColumnV2LayoutTab.tsx +242 -0
- package/components/builder/settings-panel/ColumnV2Settings.tsx +5 -5
- package/components/builder/settings-panel/CoverSectionLayoutTab.tsx +71 -71
- package/components/builder/settings-panel/CoverSectionSettings.tsx +337 -335
- package/components/builder/settings-panel/PageSettings.tsx +3 -3
- package/components/builder/settings-panel/ParallaxSlideSettings.tsx +2 -2
- package/components/builder/settings-panel/SectionV2AnimationTab.tsx +4 -4
- package/components/builder/settings-panel/SectionV2LayoutTab.tsx +356 -356
- package/components/builder/settings-panel/SectionV2Settings.tsx +25 -20
- package/components/builder/settings-panel/TRBLInputs.tsx +1 -1
- package/components/builder/settings-panel/index.ts +1 -0
- package/lib/animation/enter-types.ts +1 -0
- package/lib/animation/hover-effect-presets.ts +210 -210
- package/lib/animation/hover-effect-types.ts +1 -0
- package/lib/builder/block-registrations.ts +468 -417
- package/lib/builder/constants.ts +111 -111
- package/lib/builder/serializer/normalizers.ts +14 -0
- package/lib/builder/serializer/serializers.ts +27 -0
- package/lib/builder/store-sections.ts +23 -2
- package/lib/builder/types-slices.ts +428 -414
- package/lib/builder/types.ts +4 -1
- package/lib/config/index.ts +27 -27
- package/lib/sanity/queries.ts +48 -0
- package/lib/sanity/types.ts +112 -1
- package/lib/version.ts +1 -1
- package/package.json +7 -5
- package/sanity/schemas/blocks/audioBlock.ts +69 -69
- package/sanity/schemas/blocks/index.ts +12 -11
- package/sanity/schemas/blocks/marqueeBlock.ts +292 -0
- package/sanity/schemas/index.ts +120 -117
- package/sanity/schemas/objects/coverSection.ts +32 -0
- package/sanity/schemas/objects/parallaxSlide.ts +32 -0
- package/sanity/schemas/pageSectionV2.ts +32 -0
- package/styles/admin.css +85 -85
- package/styles/animations.css +237 -237
- package/styles/base.css +114 -114
|
@@ -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-[#
|
|
86
|
-
>
|
|
87
|
-
<div className="w-10 h-10 rounded-lg bg-neutral-50 border border-neutral-100 flex items-center justify-center text-[#
|
|
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: "#
|
|
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: "#
|
|
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
|
+
}
|