@morphika/andami 0.1.8 → 0.1.10
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 +3 -0
- package/components/admin/nav-builder/NavBuilder.tsx +90 -14
- package/components/admin/nav-builder/NavGeneralSettings.tsx +521 -271
- package/components/admin/nav-builder/NavItemSettings.tsx +331 -312
- package/components/admin/nav-builder/NavMobileSettings.tsx +159 -140
- package/components/admin/nav-builder/NavSettingsFields.tsx +287 -21
- package/components/admin/nav-builder/NavSettingsPanel.tsx +137 -127
- package/components/blocks/TextBlockRenderer.tsx +1 -1
- package/components/builder/SettingsPanel.tsx +29 -543
- package/components/builder/editors/ButtonBlockEditor.tsx +8 -3
- package/components/builder/editors/CoverBlockEditor.tsx +14 -6
- package/components/builder/editors/ImageBlockEditor.tsx +8 -3
- package/components/builder/editors/ImageGridBlockEditor.tsx +8 -3
- package/components/builder/editors/ProjectGridEditor.tsx +7 -46
- package/components/builder/editors/SpacerBlockEditor.tsx +4 -1
- package/components/builder/editors/StaggerSettings.tsx +2 -1
- package/components/builder/editors/TextBlockEditor.tsx +8 -3
- package/components/builder/editors/VideoBlockEditor.tsx +10 -4
- package/components/builder/editors/section-icons.tsx +492 -0
- package/components/builder/editors/shared.tsx +23 -4
- package/components/builder/live-preview/GhostCard.tsx +84 -0
- package/components/builder/live-preview/LiveProjectGridPreview.tsx +294 -1010
- package/components/builder/live-preview/LiveTextEditor.tsx +1 -1
- package/components/builder/live-preview/ProjectCardWrapper.tsx +291 -0
- package/components/builder/live-preview/drag-utils.tsx +89 -0
- package/components/builder/live-preview/useDragReorder.ts +370 -0
- package/components/builder/settings-panel/AnimationTab.tsx +152 -0
- package/components/builder/settings-panel/BlockLayoutTab.tsx +13 -58
- package/components/builder/settings-panel/CardEntranceSection.tsx +114 -0
- package/components/builder/settings-panel/ColumnV2AnimationTab.tsx +32 -0
- package/components/builder/settings-panel/ColumnV2Settings.tsx +4 -1
- package/components/builder/settings-panel/CustomSectionSettings.tsx +150 -0
- package/components/builder/settings-panel/LayoutTab.tsx +11 -47
- package/components/builder/settings-panel/PageSettings.tsx +10 -4
- package/components/builder/settings-panel/ParallaxGroupSettings.tsx +6 -2
- package/components/builder/settings-panel/ParallaxSlideSettings.tsx +8 -3
- package/components/builder/settings-panel/SectionV2LayoutTab.tsx +11 -47
- package/components/builder/settings-panel/SectionV2Settings.tsx +6 -27
- package/components/builder/settings-panel/index.ts +6 -0
- package/components/builder/settings-panel/useSettingsPanelSelection.ts +184 -0
- package/components/ui/Navbar.tsx +151 -30
- package/lib/builder/serializer/migrations.ts +107 -0
- package/lib/builder/serializer/normalizers.ts +278 -0
- package/lib/builder/serializer/serializers.ts +393 -0
- package/lib/builder/serializer/shared.ts +102 -0
- package/lib/builder/serializer.ts +11 -846
- package/lib/sanity/types.ts +22 -0
- package/package.json +13 -10
- package/styles/base.css +7 -3
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* CardEntranceSection — Toggle + preset/stagger/duration controls for
|
|
5
|
+
* ProjectGrid card entrance animations.
|
|
6
|
+
*
|
|
7
|
+
* Extracted from SettingsPanel.tsx in Session C (refactor split).
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { useBuilderStore } from "../../../lib/builder/store";
|
|
11
|
+
import type { ProjectGridBlock, CardEntranceConfig } from "../../../lib/sanity/types";
|
|
12
|
+
|
|
13
|
+
export const ENTRANCE_PRESETS = [
|
|
14
|
+
{ value: "fade", label: "Fade" },
|
|
15
|
+
{ value: "slide-up", label: "Slide Up" },
|
|
16
|
+
{ value: "scale", label: "Scale" },
|
|
17
|
+
] as const;
|
|
18
|
+
|
|
19
|
+
export const CARD_ENTRANCE_SELECT_CLASS =
|
|
20
|
+
"w-full rounded-lg border border-transparent bg-[#f5f5f5] px-2.5 py-[7px] text-xs text-neutral-900 font-normal outline-none transition-all hover:bg-[#efefef] focus:bg-white focus:border-[#076bff] focus:shadow-[0_0_0_3px_rgba(7,107,255,0.06)]";
|
|
21
|
+
|
|
22
|
+
export const CARD_ENTRANCE_SLIDER_CLASS =
|
|
23
|
+
"w-full h-1.5 rounded-full bg-[#e5e5e5] appearance-none cursor-pointer accent-[#076bff] [&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:w-3.5 [&::-webkit-slider-thumb]:h-3.5 [&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:bg-[#076bff] [&::-webkit-slider-thumb]:border-2 [&::-webkit-slider-thumb]:border-white [&::-webkit-slider-thumb]:shadow-sm";
|
|
24
|
+
|
|
25
|
+
export function CardEntranceSection({ block }: { block: ProjectGridBlock }) {
|
|
26
|
+
const updateBlock = useBuilderStore((s) => s.updateBlock);
|
|
27
|
+
const entrance = block.card_entrance;
|
|
28
|
+
const enabled = entrance?.enabled ?? false;
|
|
29
|
+
|
|
30
|
+
const update = (updates: Partial<CardEntranceConfig>) => {
|
|
31
|
+
updateBlock(block._key, {
|
|
32
|
+
card_entrance: { ...entrance, ...updates },
|
|
33
|
+
} as Partial<ProjectGridBlock>);
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<div className="px-4 py-3">
|
|
38
|
+
<div className="flex items-center justify-between mb-2.5">
|
|
39
|
+
<span className="text-xs font-medium text-neutral-700">Card Entrance</span>
|
|
40
|
+
<button
|
|
41
|
+
type="button"
|
|
42
|
+
onClick={() => update({ enabled: !enabled })}
|
|
43
|
+
className={`relative w-8 h-[18px] rounded-full transition-colors ${
|
|
44
|
+
enabled ? "bg-[#076bff]" : "bg-neutral-300"
|
|
45
|
+
}`}
|
|
46
|
+
>
|
|
47
|
+
<span
|
|
48
|
+
className={`absolute top-[2px] w-[14px] h-[14px] rounded-full bg-white shadow transition-transform ${
|
|
49
|
+
enabled ? "translate-x-[16px]" : "translate-x-[2px]"
|
|
50
|
+
}`}
|
|
51
|
+
/>
|
|
52
|
+
</button>
|
|
53
|
+
</div>
|
|
54
|
+
|
|
55
|
+
{enabled && (
|
|
56
|
+
<div className="space-y-3">
|
|
57
|
+
{/* Preset — dropdown instead of segmented buttons */}
|
|
58
|
+
<div>
|
|
59
|
+
<label className="text-[11px] text-neutral-500 mb-1 block">Preset</label>
|
|
60
|
+
<select
|
|
61
|
+
value={entrance?.preset || "slide-up"}
|
|
62
|
+
onChange={(e) => update({ preset: e.target.value as "fade" | "slide-up" | "scale" })}
|
|
63
|
+
className={CARD_ENTRANCE_SELECT_CLASS}
|
|
64
|
+
>
|
|
65
|
+
{ENTRANCE_PRESETS.map((opt) => (
|
|
66
|
+
<option key={opt.value} value={opt.value}>
|
|
67
|
+
{opt.label}
|
|
68
|
+
</option>
|
|
69
|
+
))}
|
|
70
|
+
</select>
|
|
71
|
+
</div>
|
|
72
|
+
|
|
73
|
+
{/* Stagger delay */}
|
|
74
|
+
<div>
|
|
75
|
+
<div className="flex items-center justify-between mb-1">
|
|
76
|
+
<label className="text-[11px] text-neutral-500">Stagger</label>
|
|
77
|
+
<span className="text-[11px] text-neutral-500 tabular-nums">
|
|
78
|
+
{entrance?.stagger_delay ?? 80}ms
|
|
79
|
+
</span>
|
|
80
|
+
</div>
|
|
81
|
+
<input
|
|
82
|
+
type="range"
|
|
83
|
+
min={0}
|
|
84
|
+
max={5000}
|
|
85
|
+
step={10}
|
|
86
|
+
value={entrance?.stagger_delay ?? 80}
|
|
87
|
+
onChange={(e) => update({ stagger_delay: Number(e.target.value) })}
|
|
88
|
+
className={CARD_ENTRANCE_SLIDER_CLASS}
|
|
89
|
+
/>
|
|
90
|
+
</div>
|
|
91
|
+
|
|
92
|
+
{/* Duration */}
|
|
93
|
+
<div>
|
|
94
|
+
<div className="flex items-center justify-between mb-1">
|
|
95
|
+
<label className="text-[11px] text-neutral-500">Duration</label>
|
|
96
|
+
<span className="text-[11px] text-neutral-500 tabular-nums">
|
|
97
|
+
{entrance?.duration ?? 500}ms
|
|
98
|
+
</span>
|
|
99
|
+
</div>
|
|
100
|
+
<input
|
|
101
|
+
type="range"
|
|
102
|
+
min={200}
|
|
103
|
+
max={5000}
|
|
104
|
+
step={50}
|
|
105
|
+
value={entrance?.duration ?? 500}
|
|
106
|
+
onChange={(e) => update({ duration: Number(e.target.value) })}
|
|
107
|
+
className={CARD_ENTRANCE_SLIDER_CLASS}
|
|
108
|
+
/>
|
|
109
|
+
</div>
|
|
110
|
+
</div>
|
|
111
|
+
)}
|
|
112
|
+
</div>
|
|
113
|
+
);
|
|
114
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* ColumnV2AnimationTab — Enter animation picker for V2 columns,
|
|
5
|
+
* inheriting from parent section config.
|
|
6
|
+
*
|
|
7
|
+
* Extracted from SettingsPanel.tsx in Session C (refactor split).
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { useBuilderStore } from "../../../lib/builder/store";
|
|
11
|
+
import type { PageSectionV2, SectionColumn } from "../../../lib/sanity/types";
|
|
12
|
+
import EnterAnimationPicker from "../editors/EnterAnimationPicker";
|
|
13
|
+
|
|
14
|
+
export function ColumnV2AnimationTab({
|
|
15
|
+
section,
|
|
16
|
+
column,
|
|
17
|
+
}: {
|
|
18
|
+
section: PageSectionV2;
|
|
19
|
+
column: SectionColumn;
|
|
20
|
+
}) {
|
|
21
|
+
const store = useBuilderStore();
|
|
22
|
+
|
|
23
|
+
return (
|
|
24
|
+
<EnterAnimationPicker
|
|
25
|
+
mode={{ level: "column", parentConfig: section.settings.enter_animation }}
|
|
26
|
+
config={column.enter_animation}
|
|
27
|
+
onChange={(cfg) => {
|
|
28
|
+
store.updateColumnEnterAnimation(section._key, column._key, cfg);
|
|
29
|
+
}}
|
|
30
|
+
/>
|
|
31
|
+
);
|
|
32
|
+
}
|
|
@@ -16,6 +16,9 @@
|
|
|
16
16
|
|
|
17
17
|
import { useBuilderStore } from "../../../lib/builder/store";
|
|
18
18
|
import type { PageSectionV2, SectionColumn } from "../../../lib/sanity/types";
|
|
19
|
+
import {
|
|
20
|
+
ColumnSizeIcon,
|
|
21
|
+
} from "../editors/section-icons";
|
|
19
22
|
import {
|
|
20
23
|
SettingsField,
|
|
21
24
|
SettingsSection,
|
|
@@ -90,7 +93,7 @@ export default function ColumnV2Settings({
|
|
|
90
93
|
</div>
|
|
91
94
|
)}
|
|
92
95
|
|
|
93
|
-
<SettingsSection title="Column Size" defaultOpen>
|
|
96
|
+
<SettingsSection title="Column Size" defaultOpen icon={<ColumnSizeIcon />}>
|
|
94
97
|
<SettingsField label={
|
|
95
98
|
<span>
|
|
96
99
|
Span
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* CustomSectionSettings — Edit & Detach actions for custom section instances.
|
|
5
|
+
*
|
|
6
|
+
* Extracted from SettingsPanel.tsx in Session C (refactor split).
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { useState, useEffect, useCallback } from "react";
|
|
10
|
+
import { useBuilderStore } from "../../../lib/builder/store";
|
|
11
|
+
import { BUILDER_VIOLET } from "../../../lib/builder/constants";
|
|
12
|
+
import type { CustomSectionInstance, PageSectionV2 } from "../../../lib/sanity/types";
|
|
13
|
+
|
|
14
|
+
export function CustomSectionSettings({ instance }: { instance: CustomSectionInstance }) {
|
|
15
|
+
const store = useBuilderStore();
|
|
16
|
+
const [showDetachConfirm, setShowDetachConfirm] = useState(false);
|
|
17
|
+
const [sectionData, setSectionData] = useState<PageSectionV2 | null>(null);
|
|
18
|
+
const [loadingEdit, setLoadingEdit] = useState(false);
|
|
19
|
+
|
|
20
|
+
// Fetch section data for detach
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
let cancelled = false;
|
|
23
|
+
fetch(`/api/custom-sections/${instance.custom_section_id}`)
|
|
24
|
+
.then((res) => res.ok ? res.json() : null)
|
|
25
|
+
.then((data) => {
|
|
26
|
+
if (!cancelled && data?.section) setSectionData(data.section);
|
|
27
|
+
})
|
|
28
|
+
.catch(() => {});
|
|
29
|
+
return () => { cancelled = true; };
|
|
30
|
+
}, [instance.custom_section_id]);
|
|
31
|
+
|
|
32
|
+
const handleEdit = useCallback(async () => {
|
|
33
|
+
setLoadingEdit(true);
|
|
34
|
+
try {
|
|
35
|
+
const res = await fetch(`/api/admin/custom-sections/${instance.custom_section_slug}`);
|
|
36
|
+
if (!res.ok) throw new Error("Failed to load section");
|
|
37
|
+
const data = await res.json();
|
|
38
|
+
|
|
39
|
+
const remoteTitle = data.section.title;
|
|
40
|
+
if (remoteTitle && remoteTitle !== instance.custom_section_title) {
|
|
41
|
+
store.updateCustomSectionInstanceTitle(instance._key, remoteTitle);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
store.enterSectionEditor(
|
|
45
|
+
instance.custom_section_slug,
|
|
46
|
+
remoteTitle,
|
|
47
|
+
data.section.section
|
|
48
|
+
);
|
|
49
|
+
} catch {
|
|
50
|
+
if (sectionData) {
|
|
51
|
+
store.enterSectionEditor(
|
|
52
|
+
instance.custom_section_slug,
|
|
53
|
+
instance.custom_section_title,
|
|
54
|
+
sectionData
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
} finally {
|
|
58
|
+
setLoadingEdit(false);
|
|
59
|
+
}
|
|
60
|
+
}, [instance, sectionData, store]);
|
|
61
|
+
|
|
62
|
+
const handleDetach = useCallback(() => {
|
|
63
|
+
if (!sectionData) return;
|
|
64
|
+
store.detachCustomSectionInstance(instance._key, sectionData);
|
|
65
|
+
setShowDetachConfirm(false);
|
|
66
|
+
}, [instance._key, sectionData, store]);
|
|
67
|
+
|
|
68
|
+
return (
|
|
69
|
+
<div className="px-4 py-3 space-y-3">
|
|
70
|
+
{/* Linked badge */}
|
|
71
|
+
<div className="flex items-center gap-2 px-3 py-2 rounded-lg bg-[#f3f0ff] border border-[#8b5cf6]/20">
|
|
72
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="#8b5cf6" strokeWidth="2" strokeLinecap="round" className="shrink-0">
|
|
73
|
+
<path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" />
|
|
74
|
+
<path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" />
|
|
75
|
+
</svg>
|
|
76
|
+
<span className="text-[11px] text-[#8b5cf6] font-medium truncate">
|
|
77
|
+
Linked Section
|
|
78
|
+
</span>
|
|
79
|
+
</div>
|
|
80
|
+
|
|
81
|
+
{/* Edit button */}
|
|
82
|
+
<button
|
|
83
|
+
onClick={handleEdit}
|
|
84
|
+
disabled={loadingEdit}
|
|
85
|
+
className="w-full flex items-center justify-center gap-2 px-3 py-2.5 rounded-lg text-xs font-medium text-white transition-colors disabled:opacity-50"
|
|
86
|
+
style={{ backgroundColor: BUILDER_VIOLET }}
|
|
87
|
+
>
|
|
88
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
|
|
89
|
+
<path d="M17 3a2.85 2.85 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z" />
|
|
90
|
+
<path d="m15 5 4 4" />
|
|
91
|
+
</svg>
|
|
92
|
+
{loadingEdit ? "Loading..." : "Edit Section"}
|
|
93
|
+
</button>
|
|
94
|
+
<p className="text-[10px] text-neutral-400 -mt-1">
|
|
95
|
+
Changes apply to all pages using this section.
|
|
96
|
+
</p>
|
|
97
|
+
|
|
98
|
+
{/* Detach button */}
|
|
99
|
+
<button
|
|
100
|
+
onClick={() => setShowDetachConfirm(true)}
|
|
101
|
+
disabled={!sectionData}
|
|
102
|
+
className="w-full flex items-center justify-center gap-2 px-3 py-2.5 rounded-lg text-xs font-medium text-neutral-600 bg-[#f5f5f5] hover:bg-[#ebebeb] transition-colors disabled:opacity-30"
|
|
103
|
+
>
|
|
104
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
|
|
105
|
+
<path d="M18.84 12.25l1.72-1.71a5 5 0 0 0-7.07-7.07l-3 3a5 5 0 0 0 .54 7.54" />
|
|
106
|
+
<path d="M5.16 11.75l-1.72 1.71a5 5 0 0 0 7.07 7.07l3-3a5 5 0 0 0-.54-7.54" />
|
|
107
|
+
<line x1="2" y1="2" x2="22" y2="22" />
|
|
108
|
+
</svg>
|
|
109
|
+
Detach
|
|
110
|
+
</button>
|
|
111
|
+
<p className="text-[10px] text-neutral-400 -mt-1">
|
|
112
|
+
Convert to an independent inline section on this page.
|
|
113
|
+
</p>
|
|
114
|
+
|
|
115
|
+
{/* Detach confirmation dialog */}
|
|
116
|
+
{showDetachConfirm && (
|
|
117
|
+
<div
|
|
118
|
+
className="fixed inset-0 z-[9999] flex items-center justify-center bg-black/60"
|
|
119
|
+
onClick={(e) => { e.stopPropagation(); setShowDetachConfirm(false); }}
|
|
120
|
+
>
|
|
121
|
+
<div
|
|
122
|
+
className="bg-white rounded-lg border border-[#e5e5e5] p-6 max-w-sm shadow-xl"
|
|
123
|
+
onClick={(e) => e.stopPropagation()}
|
|
124
|
+
>
|
|
125
|
+
<h3 className="text-neutral-900 text-sm font-medium mb-2">Detach section?</h3>
|
|
126
|
+
<p className="text-neutral-500 text-xs mb-4">
|
|
127
|
+
This will create an independent copy of “{instance.custom_section_title}”.
|
|
128
|
+
Future changes to the saved section won't affect this page.
|
|
129
|
+
</p>
|
|
130
|
+
<div className="flex justify-end gap-2">
|
|
131
|
+
<button
|
|
132
|
+
onClick={() => setShowDetachConfirm(false)}
|
|
133
|
+
className="px-3 py-1.5 text-sm text-neutral-500 hover:text-neutral-900 rounded border border-[#e5e5e5] hover:border-[#ccc] transition-colors"
|
|
134
|
+
>
|
|
135
|
+
Cancel
|
|
136
|
+
</button>
|
|
137
|
+
<button
|
|
138
|
+
onClick={handleDetach}
|
|
139
|
+
className="px-3 py-1.5 text-sm text-white rounded font-medium transition-colors"
|
|
140
|
+
style={{ backgroundColor: BUILDER_VIOLET }}
|
|
141
|
+
>
|
|
142
|
+
Detach
|
|
143
|
+
</button>
|
|
144
|
+
</div>
|
|
145
|
+
</div>
|
|
146
|
+
</div>
|
|
147
|
+
)}
|
|
148
|
+
</div>
|
|
149
|
+
);
|
|
150
|
+
}
|
|
@@ -29,49 +29,13 @@ import {
|
|
|
29
29
|
} from "./responsive-helpers";
|
|
30
30
|
import { TRBLInputs } from "./TRBLInputs";
|
|
31
31
|
|
|
32
|
-
// ── Section title icons (
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
<path d="M7 10.5 L7 13" stroke="currentColor" strokeWidth="0.8" opacity="0.7" />
|
|
40
|
-
<path d="M1 7 L3.5 7" stroke="currentColor" strokeWidth="0.8" opacity="0.7" />
|
|
41
|
-
<path d="M10.5 7 L13 7" stroke="currentColor" strokeWidth="0.8" opacity="0.7" />
|
|
42
|
-
</svg>
|
|
43
|
-
);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
function OffsetSectionIcon() {
|
|
47
|
-
return (
|
|
48
|
-
<svg width={14} height={14} viewBox="0 0 14 14" fill="none">
|
|
49
|
-
<rect x="3" y="3" width="8" height="8" rx="1" stroke="currentColor" strokeWidth="0.8" strokeDasharray="2 1" fill="none" opacity="0.35" />
|
|
50
|
-
<rect x="5" y="5" width="6" height="6" rx="1" stroke="currentColor" strokeWidth="1" fill="none" opacity="0.7" />
|
|
51
|
-
<path d="M4 4 L5 5" stroke="currentColor" strokeWidth="0.6" opacity="0.5" />
|
|
52
|
-
</svg>
|
|
53
|
-
);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
function BackgroundSectionIcon() {
|
|
57
|
-
return (
|
|
58
|
-
<svg width={14} height={14} viewBox="0 0 14 14" fill="none">
|
|
59
|
-
<rect x="1.5" y="1.5" width="11" height="11" rx="2" fill="currentColor" opacity="0.15" />
|
|
60
|
-
<rect x="1.5" y="1.5" width="11" height="11" rx="2" stroke="currentColor" strokeWidth="0.8" opacity="0.5" fill="none" />
|
|
61
|
-
<circle cx="5" cy="5" r="1.5" fill="currentColor" opacity="0.5" />
|
|
62
|
-
<path d="M1.5 10 L5 7 L8 9 L10.5 6.5 L12.5 8.5 L12.5 11 C12.5 11.8 11.8 12.5 11 12.5 L3 12.5 C2.2 12.5 1.5 11.8 1.5 11 Z" fill="currentColor" opacity="0.3" />
|
|
63
|
-
</svg>
|
|
64
|
-
);
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
function BorderSectionIcon() {
|
|
68
|
-
return (
|
|
69
|
-
<svg width={14} height={14} viewBox="0 0 14 14" fill="none">
|
|
70
|
-
<rect x="2" y="2" width="10" height="10" rx="2" stroke="currentColor" strokeWidth="1.2" fill="none" opacity="0.6" />
|
|
71
|
-
<rect x="2" y="2" width="10" height="1.2" rx="0.5" fill="currentColor" opacity="0.7" />
|
|
72
|
-
</svg>
|
|
73
|
-
);
|
|
74
|
-
}
|
|
32
|
+
// ── Section title icons (centralized colored icons — Session 163) ──
|
|
33
|
+
import {
|
|
34
|
+
SpacingIcon,
|
|
35
|
+
OffsetIcon,
|
|
36
|
+
BackgroundIcon,
|
|
37
|
+
BorderIcon,
|
|
38
|
+
} from "../editors/section-icons";
|
|
75
39
|
|
|
76
40
|
/**
|
|
77
41
|
* BUG-007 fix: LayoutTab now handles PageSection styling (spacing, background, border).
|
|
@@ -141,7 +105,7 @@ export function LayoutTab({ section, sectionKey }: { section: PageSection; secti
|
|
|
141
105
|
)}
|
|
142
106
|
|
|
143
107
|
{/* Spacing (Padding) */}
|
|
144
|
-
<SettingsSection title="Spacing" defaultOpen icon={<
|
|
108
|
+
<SettingsSection title="Spacing" defaultOpen icon={<SpacingIcon />}>
|
|
145
109
|
<TRBLInputs
|
|
146
110
|
top={effectiveSpacingTop}
|
|
147
111
|
right={effectiveSpacingRight}
|
|
@@ -192,7 +156,7 @@ export function LayoutTab({ section, sectionKey }: { section: PageSection; secti
|
|
|
192
156
|
</SettingsSection>
|
|
193
157
|
|
|
194
158
|
{/* Offset (Margin) */}
|
|
195
|
-
<SettingsSection title="Offset" icon={<
|
|
159
|
+
<SettingsSection title="Offset" icon={<OffsetIcon />}>
|
|
196
160
|
<TRBLInputs
|
|
197
161
|
top={getRowSettingValue<string>(section, activeViewport, "offset_top", "0")}
|
|
198
162
|
right={getRowSettingValue<string>(section, activeViewport, "offset_right", "0")}
|
|
@@ -205,7 +169,7 @@ export function LayoutTab({ section, sectionKey }: { section: PageSection; secti
|
|
|
205
169
|
</SettingsSection>
|
|
206
170
|
|
|
207
171
|
{/* Background */}
|
|
208
|
-
<SettingsSection title="Background" defaultOpen icon={<
|
|
172
|
+
<SettingsSection title="Background" defaultOpen icon={<BackgroundIcon />}>
|
|
209
173
|
<SettingsField label="Color">
|
|
210
174
|
<ColorSwatchPicker
|
|
211
175
|
value={parseColorField(getRowSettingValue<string>(section, activeViewport, "background_color", ""))}
|
|
@@ -305,7 +269,7 @@ export function LayoutTab({ section, sectionKey }: { section: PageSection; secti
|
|
|
305
269
|
</SettingsSection>
|
|
306
270
|
|
|
307
271
|
{/* Border */}
|
|
308
|
-
<SettingsSection title="Border" icon={<
|
|
272
|
+
<SettingsSection title="Border" icon={<BorderIcon />}>
|
|
309
273
|
<SettingsField label="Color">
|
|
310
274
|
<ColorSwatchPicker
|
|
311
275
|
value={parseColorField(getRowSettingValue<string>(section, activeViewport, "border_color", ""))}
|
|
@@ -11,6 +11,12 @@
|
|
|
11
11
|
|
|
12
12
|
import { useState } from "react";
|
|
13
13
|
import { useBuilderStore } from "../../../lib/builder/store";
|
|
14
|
+
import {
|
|
15
|
+
GeneralIcon,
|
|
16
|
+
AppearanceIcon,
|
|
17
|
+
NavigationIcon,
|
|
18
|
+
SEOIcon,
|
|
19
|
+
} from "../editors/section-icons";
|
|
14
20
|
import {
|
|
15
21
|
SettingsField,
|
|
16
22
|
SettingsSection,
|
|
@@ -39,7 +45,7 @@ export default function PageSettings() {
|
|
|
39
45
|
|
|
40
46
|
return (
|
|
41
47
|
<>
|
|
42
|
-
<SettingsSection title="General" defaultOpen>
|
|
48
|
+
<SettingsSection title="General" defaultOpen icon={<GeneralIcon />}>
|
|
43
49
|
<SettingsField label="Title">
|
|
44
50
|
<input
|
|
45
51
|
type="text"
|
|
@@ -73,7 +79,7 @@ export default function PageSettings() {
|
|
|
73
79
|
</SettingsField>
|
|
74
80
|
</SettingsSection>
|
|
75
81
|
|
|
76
|
-
<SettingsSection title="Appearance" defaultOpen>
|
|
82
|
+
<SettingsSection title="Appearance" defaultOpen icon={<AppearanceIcon />}>
|
|
77
83
|
<SettingsField label="Background">
|
|
78
84
|
<ColorSwatchPicker
|
|
79
85
|
value={parseColorField(store.pageSettings.background_color || "")}
|
|
@@ -91,7 +97,7 @@ export default function PageSettings() {
|
|
|
91
97
|
</SettingsField>
|
|
92
98
|
</SettingsSection>
|
|
93
99
|
|
|
94
|
-
<SettingsSection title="Navigation">
|
|
100
|
+
<SettingsSection title="Navigation" icon={<NavigationIcon />}>
|
|
95
101
|
<SettingsField label="Nav Color">
|
|
96
102
|
<ColorSwatchPicker
|
|
97
103
|
value={store.pageSettings.nav_color || ""}
|
|
@@ -164,7 +170,7 @@ export function PageSeoSettings() {
|
|
|
164
170
|
|
|
165
171
|
return (
|
|
166
172
|
<>
|
|
167
|
-
<SettingsSection title="SEO" defaultOpen>
|
|
173
|
+
<SettingsSection title="SEO" defaultOpen icon={<SEOIcon />}>
|
|
168
174
|
<SettingsField label="SEO Title">
|
|
169
175
|
<input
|
|
170
176
|
type="text"
|
|
@@ -14,6 +14,10 @@
|
|
|
14
14
|
|
|
15
15
|
import { useBuilderStore } from "../../../lib/builder/store";
|
|
16
16
|
import type { ParallaxGroup } from "../../../lib/sanity/types";
|
|
17
|
+
import {
|
|
18
|
+
TransitionIcon,
|
|
19
|
+
InfoIcon,
|
|
20
|
+
} from "../editors/section-icons";
|
|
17
21
|
import {
|
|
18
22
|
SettingsSection,
|
|
19
23
|
} from "../editors/shared";
|
|
@@ -56,7 +60,7 @@ export default function ParallaxGroupSettings({
|
|
|
56
60
|
|
|
57
61
|
return (
|
|
58
62
|
<>
|
|
59
|
-
<SettingsSection title="Transition Effect" defaultOpen>
|
|
63
|
+
<SettingsSection title="Transition Effect" defaultOpen icon={<TransitionIcon />}>
|
|
60
64
|
<div className="space-y-1.5">
|
|
61
65
|
{TRANSITION_EFFECTS.map((effect) => {
|
|
62
66
|
const isActive = activeEffect === effect.value;
|
|
@@ -103,7 +107,7 @@ export default function ParallaxGroupSettings({
|
|
|
103
107
|
</SettingsSection>
|
|
104
108
|
|
|
105
109
|
{/* Group info */}
|
|
106
|
-
<SettingsSection title="Info">
|
|
110
|
+
<SettingsSection title="Info" icon={<InfoIcon />}>
|
|
107
111
|
<div className="text-[11px] text-neutral-500 space-y-1">
|
|
108
112
|
<p>
|
|
109
113
|
{group.slides.length} slide{group.slides.length !== 1 ? "s" : ""}
|
|
@@ -19,6 +19,11 @@
|
|
|
19
19
|
|
|
20
20
|
import { useBuilderStore } from "../../../lib/builder/store";
|
|
21
21
|
import type { ParallaxSlideV2, ParallaxGroup, PageSectionV2 } from "../../../lib/sanity/types";
|
|
22
|
+
import {
|
|
23
|
+
BackgroundIcon,
|
|
24
|
+
NavbarColorIcon,
|
|
25
|
+
OverlayIcon,
|
|
26
|
+
} from "../editors/section-icons";
|
|
22
27
|
import {
|
|
23
28
|
SettingsField,
|
|
24
29
|
SettingsSection,
|
|
@@ -70,7 +75,7 @@ export default function ParallaxSlideSettings({
|
|
|
70
75
|
return (
|
|
71
76
|
<>
|
|
72
77
|
{/* Background Type */}
|
|
73
|
-
<SettingsSection title="Background" defaultOpen>
|
|
78
|
+
<SettingsSection title="Background" defaultOpen icon={<BackgroundIcon />}>
|
|
74
79
|
{/* Segmented control: Image / Video */}
|
|
75
80
|
<SettingsField label="Type">
|
|
76
81
|
<div className="flex rounded-lg bg-[#f0f0f0] p-[3px]">
|
|
@@ -123,7 +128,7 @@ export default function ParallaxSlideSettings({
|
|
|
123
128
|
</SettingsSection>
|
|
124
129
|
|
|
125
130
|
{/* Navbar Color Override */}
|
|
126
|
-
<SettingsSection title="Navbar Color" defaultOpen={false}>
|
|
131
|
+
<SettingsSection title="Navbar Color" defaultOpen={false} icon={<NavbarColorIcon />}>
|
|
127
132
|
<SettingsField label="Color">
|
|
128
133
|
<div className="flex items-center gap-2">
|
|
129
134
|
<ColorSwatchPicker
|
|
@@ -147,7 +152,7 @@ export default function ParallaxSlideSettings({
|
|
|
147
152
|
</SettingsSection>
|
|
148
153
|
|
|
149
154
|
{/* Overlay */}
|
|
150
|
-
<SettingsSection title="Overlay" defaultOpen>
|
|
155
|
+
<SettingsSection title="Overlay" defaultOpen icon={<OverlayIcon />}>
|
|
151
156
|
<SettingsField label="Color">
|
|
152
157
|
<ColorSwatchPicker
|
|
153
158
|
value={overlayColor}
|
|
@@ -25,49 +25,13 @@ import ColorSwatchPicker, { usePaletteSwatches } from "../ColorSwatchPicker";
|
|
|
25
25
|
import { serializeColorField, parseColorField, isGradient } from "../../../lib/color-utils";
|
|
26
26
|
import { TRBLInputs } from "./TRBLInputs";
|
|
27
27
|
|
|
28
|
-
// ── Section title icons (
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
<path d="M7 10.5 L7 13" stroke="currentColor" strokeWidth="0.8" opacity="0.7" />
|
|
36
|
-
<path d="M1 7 L3.5 7" stroke="currentColor" strokeWidth="0.8" opacity="0.7" />
|
|
37
|
-
<path d="M10.5 7 L13 7" stroke="currentColor" strokeWidth="0.8" opacity="0.7" />
|
|
38
|
-
</svg>
|
|
39
|
-
);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
function BackgroundSectionIcon() {
|
|
43
|
-
return (
|
|
44
|
-
<svg width={14} height={14} viewBox="0 0 14 14" fill="none">
|
|
45
|
-
<rect x="1.5" y="1.5" width="11" height="11" rx="2" fill="currentColor" opacity="0.15" />
|
|
46
|
-
<rect x="1.5" y="1.5" width="11" height="11" rx="2" stroke="currentColor" strokeWidth="0.8" opacity="0.5" fill="none" />
|
|
47
|
-
<circle cx="5" cy="5" r="1.5" fill="currentColor" opacity="0.5" />
|
|
48
|
-
<path d="M1.5 10 L5 7 L8 9 L10.5 6.5 L12.5 8.5 L12.5 11 C12.5 11.8 11.8 12.5 11 12.5 L3 12.5 C2.2 12.5 1.5 11.8 1.5 11 Z" fill="currentColor" opacity="0.3" />
|
|
49
|
-
</svg>
|
|
50
|
-
);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
function OffsetSectionIcon() {
|
|
54
|
-
return (
|
|
55
|
-
<svg width={14} height={14} viewBox="0 0 14 14" fill="none">
|
|
56
|
-
<rect x="3" y="3" width="8" height="8" rx="1" stroke="currentColor" strokeWidth="0.8" strokeDasharray="2 1" fill="none" opacity="0.35" />
|
|
57
|
-
<rect x="5" y="5" width="6" height="6" rx="1" stroke="currentColor" strokeWidth="1" fill="none" opacity="0.7" />
|
|
58
|
-
<path d="M4 4 L5 5" stroke="currentColor" strokeWidth="0.6" opacity="0.5" />
|
|
59
|
-
</svg>
|
|
60
|
-
);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
function BorderSectionIcon() {
|
|
64
|
-
return (
|
|
65
|
-
<svg width={14} height={14} viewBox="0 0 14 14" fill="none">
|
|
66
|
-
<rect x="2" y="2" width="10" height="10" rx="2" stroke="currentColor" strokeWidth="1.2" fill="none" opacity="0.6" />
|
|
67
|
-
<rect x="2" y="2" width="10" height="1.2" rx="0.5" fill="currentColor" opacity="0.7" />
|
|
68
|
-
</svg>
|
|
69
|
-
);
|
|
70
|
-
}
|
|
28
|
+
// ── Section title icons (centralized colored icons — Session 163) ──
|
|
29
|
+
import {
|
|
30
|
+
SpacingIcon,
|
|
31
|
+
OffsetIcon,
|
|
32
|
+
BackgroundIcon,
|
|
33
|
+
BorderIcon,
|
|
34
|
+
} from "../editors/section-icons";
|
|
71
35
|
|
|
72
36
|
// ── Override indicator badge (matching BlockLayoutTab pattern) ──
|
|
73
37
|
|
|
@@ -179,7 +143,7 @@ export function SectionV2LayoutTab({ section }: { section: PageSectionV2 }) {
|
|
|
179
143
|
)}
|
|
180
144
|
|
|
181
145
|
{/* Spacing (Padding) */}
|
|
182
|
-
<SettingsSection title="Spacing" defaultOpen icon={<
|
|
146
|
+
<SettingsSection title="Spacing" defaultOpen icon={<SpacingIcon />}>
|
|
183
147
|
<TRBLInputs
|
|
184
148
|
top={getSettingValue<string>("spacing_top", "0")}
|
|
185
149
|
right={getSettingValue<string>("spacing_right", "0")}
|
|
@@ -197,7 +161,7 @@ export function SectionV2LayoutTab({ section }: { section: PageSectionV2 }) {
|
|
|
197
161
|
</SettingsSection>
|
|
198
162
|
|
|
199
163
|
{/* Offset (Margin) */}
|
|
200
|
-
<SettingsSection title="Offset" icon={<
|
|
164
|
+
<SettingsSection title="Offset" icon={<OffsetIcon />}>
|
|
201
165
|
<TRBLInputs
|
|
202
166
|
top={getSettingValue<string>("offset_top", "0")}
|
|
203
167
|
right={getSettingValue<string>("offset_right", "0")}
|
|
@@ -215,7 +179,7 @@ export function SectionV2LayoutTab({ section }: { section: PageSectionV2 }) {
|
|
|
215
179
|
</SettingsSection>
|
|
216
180
|
|
|
217
181
|
{/* Background */}
|
|
218
|
-
<SettingsSection title="Background" defaultOpen icon={<
|
|
182
|
+
<SettingsSection title="Background" defaultOpen icon={<BackgroundIcon />}>
|
|
219
183
|
<SettingsField label="Color">
|
|
220
184
|
<ColorSwatchPicker
|
|
221
185
|
value={parseColorField(getSettingValue<string>("background_color", ""))}
|
|
@@ -309,7 +273,7 @@ export function SectionV2LayoutTab({ section }: { section: PageSectionV2 }) {
|
|
|
309
273
|
</SettingsSection>
|
|
310
274
|
|
|
311
275
|
{/* Border */}
|
|
312
|
-
<SettingsSection title="Border" icon={<
|
|
276
|
+
<SettingsSection title="Border" icon={<BorderIcon />}>
|
|
313
277
|
<SettingsField label="Color">
|
|
314
278
|
<ColorSwatchPicker
|
|
315
279
|
value={parseColorField(getSettingValue<string>("border_color", ""))}
|
|
@@ -50,32 +50,11 @@ const PRESETS: PresetOption[] = [
|
|
|
50
50
|
|
|
51
51
|
const CUSTOM_PRESET: PresetOption = { id: "custom", label: "Custom", cols: [], readonly: true };
|
|
52
52
|
|
|
53
|
-
//
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
return (
|
|
59
|
-
<svg width={14} height={14} viewBox="0 0 14 14" fill="none">
|
|
60
|
-
<rect x="1.5" y="1.5" width="11" height="11" rx="1.5" stroke="currentColor" strokeWidth="0.8" fill="none" opacity="0.4" />
|
|
61
|
-
<line x1="6" y1="1.5" x2="6" y2="12.5" stroke="currentColor" strokeWidth="0.8" opacity="0.6" />
|
|
62
|
-
<rect x="1.5" y="1.5" width="4.5" height="11" rx="0" fill="currentColor" opacity="0.12" />
|
|
63
|
-
</svg>
|
|
64
|
-
);
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
function GridGapIcon() {
|
|
68
|
-
return (
|
|
69
|
-
<svg width={14} height={14} viewBox="0 0 14 14" fill="none">
|
|
70
|
-
<rect x="1" y="1" width="5" height="5" rx="1" fill="currentColor" opacity="0.25" />
|
|
71
|
-
<rect x="8" y="1" width="5" height="5" rx="1" fill="currentColor" opacity="0.25" />
|
|
72
|
-
<rect x="1" y="8" width="5" height="5" rx="1" fill="currentColor" opacity="0.25" />
|
|
73
|
-
<rect x="8" y="8" width="5" height="5" rx="1" fill="currentColor" opacity="0.25" />
|
|
74
|
-
<line x1="7" y1="1" x2="7" y2="13" stroke="currentColor" strokeWidth="0.8" strokeDasharray="1.5 1" opacity="0.5" />
|
|
75
|
-
<line x1="1" y1="7" x2="13" y2="7" stroke="currentColor" strokeWidth="0.8" strokeDasharray="1.5 1" opacity="0.5" />
|
|
76
|
-
</svg>
|
|
77
|
-
);
|
|
78
|
-
}
|
|
53
|
+
// ── Section title icons (centralized colored icons — Session 163) ──
|
|
54
|
+
import {
|
|
55
|
+
LayoutPresetIcon,
|
|
56
|
+
GridGapsIcon,
|
|
57
|
+
} from "../editors/section-icons";
|
|
79
58
|
|
|
80
59
|
// ============================================
|
|
81
60
|
// Preset Grid Component
|
|
@@ -278,7 +257,7 @@ export default function SectionV2Settings({ section }: { section: PageSectionV2
|
|
|
278
257
|
)}
|
|
279
258
|
|
|
280
259
|
{/* Gaps */}
|
|
281
|
-
<SettingsSection title="Grid Gaps" defaultOpen icon={<
|
|
260
|
+
<SettingsSection title="Grid Gaps" defaultOpen icon={<GridGapsIcon />}>
|
|
282
261
|
<SettingsField label={
|
|
283
262
|
<span>
|
|
284
263
|
Col Gap
|