@morphika/andami 0.1.9 → 0.2.0
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/app/admin/pages/[slug]/page.tsx +3 -7
- package/app/api/admin/pages/[slug]/route.ts +2 -28
- package/app/api/admin/settings/route.ts +30 -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/EnterAnimationWrapper.tsx +19 -4
- package/components/blocks/PageRenderer.tsx +2 -15
- package/components/blocks/ProjectGridBlockRenderer.tsx +34 -36
- package/components/blocks/TextBlockRenderer.tsx +1 -1
- package/components/builder/DndWrapper.tsx +2 -24
- package/components/builder/InsertionLines.tsx +5 -5
- package/components/builder/ReadOnlyFrame.tsx +5 -49
- package/components/builder/SectionV2Canvas.tsx +2 -2
- package/components/builder/SectionV2Column.tsx +5 -5
- package/components/builder/SettingsPanel.tsx +0 -12
- package/components/builder/SortableBlock.tsx +3 -3
- package/components/builder/SortableRow.tsx +6 -27
- 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/LiveTextEditor.tsx +1 -1
- package/components/builder/live-preview/ProjectCardWrapper.tsx +3 -3
- package/components/builder/live-preview/drag-utils.tsx +2 -2
- package/components/builder/settings-panel/AnimationTab.tsx +2 -16
- package/components/builder/settings-panel/BlockLayoutTab.tsx +13 -58
- package/components/builder/settings-panel/ColumnV2Settings.tsx +4 -1
- 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 +0 -1
- package/components/builder/settings-panel/responsive-helpers.ts +2 -50
- package/components/builder/settings-panel/useSettingsPanelSelection.ts +1 -16
- package/components/ui/Navbar.tsx +151 -30
- package/lib/builder/constants.ts +5 -4
- package/lib/builder/serializer/normalizers.ts +2 -40
- package/lib/builder/serializer/serializers.ts +3 -74
- package/lib/builder/store-blocks.ts +3 -19
- package/lib/builder/store-helpers.ts +2 -2
- package/lib/builder/store-sections.ts +26 -64
- package/lib/builder/store.ts +3 -6
- package/lib/builder/templates.ts +9 -45
- package/lib/builder/types.ts +4 -11
- package/lib/sanity/queries.ts +6 -29
- package/lib/sanity/types.ts +24 -70
- package/package.json +4 -1
- package/sanity/schemas/index.ts +0 -5
- package/sanity/schemas/objects/parallaxGroup.ts +2 -2
- package/sanity/schemas/page.ts +1 -1
- package/sanity/schemas/pageSectionV2.ts +1 -0
- package/sanity/schemas/siteSettings.ts +42 -0
- package/styles/base.css +8 -2
- package/components/blocks/SectionRenderer.tsx +0 -171
- package/components/builder/settings-panel/LayoutTab.tsx +0 -382
- package/sanity/schemas/pageSection.ts +0 -157
|
@@ -1,382 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* LayoutTab — Page Section styling (spacing, offset, background, border).
|
|
5
|
-
* Viewport-aware with responsive override support.
|
|
6
|
-
*
|
|
7
|
-
* Session 64: Extracted from SettingsPanel.tsx.
|
|
8
|
-
* Session 65: Split out RowLayoutPresetPicker, TRBLInputs, BlockLayoutTab
|
|
9
|
-
* into separate modules. This file now contains only LayoutTab.
|
|
10
|
-
* Session 158: Added section title icons matching BlockLayoutTab/SectionV2LayoutTab.
|
|
11
|
-
* Reordered sections: Spacing → Offset → Background → Border.
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
import { useBuilderStore } from "../../../lib/builder/store";
|
|
15
|
-
import { resolveEffectiveSpacing } from "../../../lib/builder/layout-styles";
|
|
16
|
-
import type { PageSection } from "../../../lib/sanity/types";
|
|
17
|
-
import {
|
|
18
|
-
SettingsField,
|
|
19
|
-
SettingsSection,
|
|
20
|
-
SELECT_CLASS,
|
|
21
|
-
AssetPathInput,
|
|
22
|
-
} from "../editors/shared";
|
|
23
|
-
import ColorSwatchPicker, { usePaletteSwatches } from "../ColorSwatchPicker";
|
|
24
|
-
import { serializeColorField, parseColorField, isGradient } from "../../../lib/color-utils";
|
|
25
|
-
import {
|
|
26
|
-
getRowSettingValue,
|
|
27
|
-
hasRowSettingOverride,
|
|
28
|
-
setRowResponsiveOverride,
|
|
29
|
-
} from "./responsive-helpers";
|
|
30
|
-
import { TRBLInputs } from "./TRBLInputs";
|
|
31
|
-
|
|
32
|
-
// ── Section title icons (matching BlockLayoutTab / SectionV2LayoutTab) ──
|
|
33
|
-
|
|
34
|
-
function SpacingSectionIcon() {
|
|
35
|
-
return (
|
|
36
|
-
<svg width={14} height={14} viewBox="0 0 14 14" fill="none">
|
|
37
|
-
<rect x="4" y="4" width="6" height="6" rx="1" stroke="currentColor" strokeWidth="1" fill="none" opacity="0.5" />
|
|
38
|
-
<path d="M7 1 L7 3.5" stroke="currentColor" strokeWidth="0.8" opacity="0.7" />
|
|
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
|
-
}
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* BUG-007 fix: LayoutTab now handles PageSection styling (spacing, background, border).
|
|
78
|
-
* BUG-013 fix: Sections support responsive overrides (tablet/phone).
|
|
79
|
-
*/
|
|
80
|
-
export function LayoutTab({ section, sectionKey }: { section: PageSection; sectionKey: string }) {
|
|
81
|
-
const store = useBuilderStore();
|
|
82
|
-
const paletteSwatches = usePaletteSwatches();
|
|
83
|
-
const settings = section.settings || {};
|
|
84
|
-
const activeViewport = store.activeViewport;
|
|
85
|
-
|
|
86
|
-
// Live preview callbacks (Phase 4)
|
|
87
|
-
const handleBgPreview = (val: import("../../../lib/sanity/types").ColorField) => {
|
|
88
|
-
store.setColorPickerPreview({ sectionKey, field: "background_color", value: val });
|
|
89
|
-
};
|
|
90
|
-
const handleBorderPreview = (val: import("../../../lib/sanity/types").ColorField) => {
|
|
91
|
-
store.setColorPickerPreview({ sectionKey, field: "border_color", value: val });
|
|
92
|
-
};
|
|
93
|
-
|
|
94
|
-
const updateSetting = (updates: Partial<NonNullable<PageSection["settings"]>>) => {
|
|
95
|
-
store.updateSectionSettings(sectionKey, updates);
|
|
96
|
-
};
|
|
97
|
-
|
|
98
|
-
/** Update a setting, viewport-aware. Supports sections with responsive overrides. */
|
|
99
|
-
const updateSettingResponsive = (property: string, value: unknown) => {
|
|
100
|
-
// BUG-013 fix: Sections now support responsive overrides
|
|
101
|
-
if (activeViewport === "desktop") {
|
|
102
|
-
store.updateSectionSettings(sectionKey, { [property]: value });
|
|
103
|
-
} else {
|
|
104
|
-
// Build responsive override for section
|
|
105
|
-
const existing = section.responsive || {};
|
|
106
|
-
const vp = activeViewport as "tablet" | "phone";
|
|
107
|
-
const vpOverrides = { ...(existing[vp] || {}), [property]: value };
|
|
108
|
-
if (value === undefined) delete (vpOverrides as Record<string, unknown>)[property];
|
|
109
|
-
const responsive = { ...existing, [vp]: vpOverrides };
|
|
110
|
-
if (Object.keys(vpOverrides).length === 0) delete responsive[vp];
|
|
111
|
-
store.updateSectionResponsive(sectionKey, Object.keys(responsive).length ? responsive : undefined);
|
|
112
|
-
}
|
|
113
|
-
};
|
|
114
|
-
|
|
115
|
-
// Resolve effective spacing — shows real values even when legacy enum is active
|
|
116
|
-
const effective = resolveEffectiveSpacing(settings);
|
|
117
|
-
|
|
118
|
-
// Viewport-aware spacing values
|
|
119
|
-
const effectiveSpacingTop = getRowSettingValue<string>(section, activeViewport, "spacing_top", effective.top);
|
|
120
|
-
const effectiveSpacingRight = getRowSettingValue<string>(section, activeViewport, "spacing_right", effective.right);
|
|
121
|
-
const effectiveSpacingBottom = getRowSettingValue<string>(section, activeViewport, "spacing_bottom", effective.bottom);
|
|
122
|
-
const effectiveSpacingLeft = getRowSettingValue<string>(section, activeViewport, "spacing_left", effective.left);
|
|
123
|
-
|
|
124
|
-
// Parse background color + opacity to display
|
|
125
|
-
const bgOpacity = getRowSettingValue<number>(section, activeViewport, "background_opacity", settings.background_opacity ?? 100);
|
|
126
|
-
|
|
127
|
-
const viewportLabel = activeViewport !== "desktop"
|
|
128
|
-
? activeViewport === "tablet" ? "Tablet" : "Phone"
|
|
129
|
-
: null;
|
|
130
|
-
|
|
131
|
-
return (
|
|
132
|
-
<>
|
|
133
|
-
{viewportLabel && (
|
|
134
|
-
<div className="px-4 pt-3">
|
|
135
|
-
<div className="flex items-center gap-1.5 px-3 py-1.5 rounded-lg bg-[#076bff]/8 border border-[#076bff]/15">
|
|
136
|
-
<span className="text-[11px] font-medium text-[#076bff]">
|
|
137
|
-
Editing {viewportLabel} overrides
|
|
138
|
-
</span>
|
|
139
|
-
</div>
|
|
140
|
-
</div>
|
|
141
|
-
)}
|
|
142
|
-
|
|
143
|
-
{/* Spacing (Padding) */}
|
|
144
|
-
<SettingsSection title="Spacing" defaultOpen icon={<SpacingSectionIcon />}>
|
|
145
|
-
<TRBLInputs
|
|
146
|
-
top={effectiveSpacingTop}
|
|
147
|
-
right={effectiveSpacingRight}
|
|
148
|
-
bottom={effectiveSpacingBottom}
|
|
149
|
-
left={effectiveSpacingLeft}
|
|
150
|
-
onChange={(field, value) => {
|
|
151
|
-
if (activeViewport === "desktop") {
|
|
152
|
-
// When user edits TRBL, set explicit TRBL values
|
|
153
|
-
const base = resolveEffectiveSpacing(settings);
|
|
154
|
-
updateSetting({
|
|
155
|
-
spacing_top: field === "top" ? value : (settings.spacing_top ?? base.top),
|
|
156
|
-
spacing_right: field === "right" ? value : (settings.spacing_right ?? base.right),
|
|
157
|
-
spacing_bottom: field === "bottom" ? value : (settings.spacing_bottom ?? base.bottom),
|
|
158
|
-
spacing_left: field === "left" ? value : (settings.spacing_left ?? base.left),
|
|
159
|
-
});
|
|
160
|
-
} else {
|
|
161
|
-
updateSettingResponsive(`spacing_${field}`, value);
|
|
162
|
-
}
|
|
163
|
-
}}
|
|
164
|
-
/>
|
|
165
|
-
{activeViewport !== "desktop" && (
|
|
166
|
-
["spacing_top", "spacing_right", "spacing_bottom", "spacing_left"].some(
|
|
167
|
-
(p) => hasRowSettingOverride(section, activeViewport, p)
|
|
168
|
-
) ? (
|
|
169
|
-
<div className="flex items-center gap-2 mt-1">
|
|
170
|
-
<span className="text-[9px] text-[#076bff]">overridden</span>
|
|
171
|
-
<button
|
|
172
|
-
onClick={() => {
|
|
173
|
-
// BUG-021 fix: use proper store action for responsive reset
|
|
174
|
-
let updated = section;
|
|
175
|
-
["spacing_top", "spacing_right", "spacing_bottom", "spacing_left"].forEach((p) => {
|
|
176
|
-
const updates = setRowResponsiveOverride(updated, activeViewport, p, undefined);
|
|
177
|
-
if (updates.responsive !== undefined) {
|
|
178
|
-
updated = { ...updated, responsive: updates.responsive };
|
|
179
|
-
}
|
|
180
|
-
});
|
|
181
|
-
store.updateSectionResponsive(sectionKey, (updated as PageSection).responsive);
|
|
182
|
-
}}
|
|
183
|
-
className="text-[10px] text-neutral-400 hover:text-[var(--admin-error)] transition-colors"
|
|
184
|
-
>
|
|
185
|
-
Reset
|
|
186
|
-
</button>
|
|
187
|
-
</div>
|
|
188
|
-
) : (
|
|
189
|
-
<p className="text-[9px] text-neutral-300 italic mt-1">inherited</p>
|
|
190
|
-
)
|
|
191
|
-
)}
|
|
192
|
-
</SettingsSection>
|
|
193
|
-
|
|
194
|
-
{/* Offset (Margin) */}
|
|
195
|
-
<SettingsSection title="Offset" icon={<OffsetSectionIcon />}>
|
|
196
|
-
<TRBLInputs
|
|
197
|
-
top={getRowSettingValue<string>(section, activeViewport, "offset_top", "0")}
|
|
198
|
-
right={getRowSettingValue<string>(section, activeViewport, "offset_right", "0")}
|
|
199
|
-
bottom={getRowSettingValue<string>(section, activeViewport, "offset_bottom", "0")}
|
|
200
|
-
left={getRowSettingValue<string>(section, activeViewport, "offset_left", "0")}
|
|
201
|
-
onChange={(field, value) => {
|
|
202
|
-
updateSettingResponsive(`offset_${field}`, value);
|
|
203
|
-
}}
|
|
204
|
-
/>
|
|
205
|
-
</SettingsSection>
|
|
206
|
-
|
|
207
|
-
{/* Background */}
|
|
208
|
-
<SettingsSection title="Background" defaultOpen icon={<BackgroundSectionIcon />}>
|
|
209
|
-
<SettingsField label="Color">
|
|
210
|
-
<ColorSwatchPicker
|
|
211
|
-
value={parseColorField(getRowSettingValue<string>(section, activeViewport, "background_color", ""))}
|
|
212
|
-
onChange={(val) => { store.clearColorPickerPreview(); updateSettingResponsive("background_color", serializeColorField(val)); }}
|
|
213
|
-
swatches={paletteSwatches}
|
|
214
|
-
allowGradients
|
|
215
|
-
onPreview={handleBgPreview}
|
|
216
|
-
/>
|
|
217
|
-
</SettingsField>
|
|
218
|
-
|
|
219
|
-
<SettingsField label="Opacity">
|
|
220
|
-
{(() => {
|
|
221
|
-
const bgIsGrad = isGradient(parseColorField(getRowSettingValue<string>(section, activeViewport, "background_color", "")));
|
|
222
|
-
return (
|
|
223
|
-
<>
|
|
224
|
-
<div className="flex items-center gap-2">
|
|
225
|
-
<input
|
|
226
|
-
type="range"
|
|
227
|
-
min={0}
|
|
228
|
-
max={100}
|
|
229
|
-
value={bgOpacity}
|
|
230
|
-
onChange={(e) => updateSettingResponsive("background_opacity", parseInt(e.target.value))}
|
|
231
|
-
className={`flex-1 accent-[#076bff] ${bgIsGrad ? "opacity-40 pointer-events-none" : ""}`}
|
|
232
|
-
disabled={bgIsGrad}
|
|
233
|
-
/>
|
|
234
|
-
<span className="text-xs text-neutral-900 w-10 text-right">
|
|
235
|
-
{bgOpacity}%
|
|
236
|
-
</span>
|
|
237
|
-
</div>
|
|
238
|
-
{bgIsGrad && (
|
|
239
|
-
<p className="text-[9px] text-neutral-400 italic mt-1">
|
|
240
|
-
Opacity is controlled per stop in gradient mode
|
|
241
|
-
</p>
|
|
242
|
-
)}
|
|
243
|
-
</>
|
|
244
|
-
);
|
|
245
|
-
})()}
|
|
246
|
-
</SettingsField>
|
|
247
|
-
|
|
248
|
-
<SettingsField label="Image">
|
|
249
|
-
<AssetPathInput
|
|
250
|
-
value={getRowSettingValue<string>(section, activeViewport, "background_image", "")}
|
|
251
|
-
onFocus={() => store._pushSnapshot()}
|
|
252
|
-
onChange={(v) => updateSettingResponsive("background_image", v)}
|
|
253
|
-
placeholder="path/to/image.jpg"
|
|
254
|
-
filterType="image"
|
|
255
|
-
/>
|
|
256
|
-
</SettingsField>
|
|
257
|
-
|
|
258
|
-
{getRowSettingValue<string>(section, activeViewport, "background_image", "") && (
|
|
259
|
-
<>
|
|
260
|
-
<SettingsField label="Size">
|
|
261
|
-
<select
|
|
262
|
-
value={getRowSettingValue<string>(section, activeViewport, "background_size", "cover")}
|
|
263
|
-
onChange={(e) => updateSettingResponsive("background_size", e.target.value)}
|
|
264
|
-
className={SELECT_CLASS}
|
|
265
|
-
>
|
|
266
|
-
<option value="cover">Cover</option>
|
|
267
|
-
<option value="contain">Contain</option>
|
|
268
|
-
<option value="auto">Auto</option>
|
|
269
|
-
</select>
|
|
270
|
-
</SettingsField>
|
|
271
|
-
|
|
272
|
-
<SettingsField label="Position">
|
|
273
|
-
<select
|
|
274
|
-
value={getRowSettingValue<string>(section, activeViewport, "background_position", "center center")}
|
|
275
|
-
onFocus={() => store._pushSnapshot()}
|
|
276
|
-
onChange={(e) => updateSettingResponsive("background_position", e.target.value)}
|
|
277
|
-
className={SELECT_CLASS}
|
|
278
|
-
>
|
|
279
|
-
<option value="center center">Center</option>
|
|
280
|
-
<option value="top center">Top</option>
|
|
281
|
-
<option value="bottom center">Bottom</option>
|
|
282
|
-
<option value="left center">Left</option>
|
|
283
|
-
<option value="right center">Right</option>
|
|
284
|
-
<option value="top left">Top Left</option>
|
|
285
|
-
<option value="top right">Top Right</option>
|
|
286
|
-
<option value="bottom left">Bottom Left</option>
|
|
287
|
-
<option value="bottom right">Bottom Right</option>
|
|
288
|
-
</select>
|
|
289
|
-
</SettingsField>
|
|
290
|
-
|
|
291
|
-
<SettingsField label="Repeat">
|
|
292
|
-
<select
|
|
293
|
-
value={getRowSettingValue<string>(section, activeViewport, "background_repeat", "no-repeat")}
|
|
294
|
-
onChange={(e) => updateSettingResponsive("background_repeat", e.target.value)}
|
|
295
|
-
className={SELECT_CLASS}
|
|
296
|
-
>
|
|
297
|
-
<option value="no-repeat">No Repeat</option>
|
|
298
|
-
<option value="repeat">Repeat</option>
|
|
299
|
-
<option value="repeat-x">Repeat X</option>
|
|
300
|
-
<option value="repeat-y">Repeat Y</option>
|
|
301
|
-
</select>
|
|
302
|
-
</SettingsField>
|
|
303
|
-
</>
|
|
304
|
-
)}
|
|
305
|
-
</SettingsSection>
|
|
306
|
-
|
|
307
|
-
{/* Border */}
|
|
308
|
-
<SettingsSection title="Border" icon={<BorderSectionIcon />}>
|
|
309
|
-
<SettingsField label="Color">
|
|
310
|
-
<ColorSwatchPicker
|
|
311
|
-
value={parseColorField(getRowSettingValue<string>(section, activeViewport, "border_color", ""))}
|
|
312
|
-
onChange={(val) => { store.clearColorPickerPreview(); updateSettingResponsive("border_color", serializeColorField(val)); }}
|
|
313
|
-
swatches={paletteSwatches}
|
|
314
|
-
allowGradients
|
|
315
|
-
onPreview={handleBorderPreview}
|
|
316
|
-
/>
|
|
317
|
-
</SettingsField>
|
|
318
|
-
|
|
319
|
-
<SettingsField label="Width">
|
|
320
|
-
<div className="flex items-center gap-2">
|
|
321
|
-
<input
|
|
322
|
-
type="range"
|
|
323
|
-
min={0}
|
|
324
|
-
max={20}
|
|
325
|
-
value={parseInt(getRowSettingValue<string>(section, activeViewport, "border_width", "0"))}
|
|
326
|
-
onChange={(e) => updateSettingResponsive("border_width", e.target.value)}
|
|
327
|
-
className="flex-1 accent-[#076bff]"
|
|
328
|
-
/>
|
|
329
|
-
<span className="text-xs text-neutral-900 w-10 text-right">
|
|
330
|
-
{getRowSettingValue<string>(section, activeViewport, "border_width", "0")}px
|
|
331
|
-
</span>
|
|
332
|
-
</div>
|
|
333
|
-
</SettingsField>
|
|
334
|
-
|
|
335
|
-
<SettingsField label="Style">
|
|
336
|
-
<select
|
|
337
|
-
value={getRowSettingValue<string>(section, activeViewport, "border_style", "none")}
|
|
338
|
-
onChange={(e) => updateSettingResponsive("border_style", e.target.value)}
|
|
339
|
-
className={SELECT_CLASS}
|
|
340
|
-
>
|
|
341
|
-
<option value="none">None</option>
|
|
342
|
-
<option value="solid">Solid</option>
|
|
343
|
-
<option value="dashed">Dashed</option>
|
|
344
|
-
<option value="dotted">Dotted</option>
|
|
345
|
-
</select>
|
|
346
|
-
</SettingsField>
|
|
347
|
-
|
|
348
|
-
<SettingsField label="Sides">
|
|
349
|
-
<select
|
|
350
|
-
value={getRowSettingValue<string>(section, activeViewport, "border_sides", "all")}
|
|
351
|
-
onChange={(e) => updateSettingResponsive("border_sides", e.target.value)}
|
|
352
|
-
className={SELECT_CLASS}
|
|
353
|
-
>
|
|
354
|
-
<option value="all">All</option>
|
|
355
|
-
<option value="top">Top</option>
|
|
356
|
-
<option value="right">Right</option>
|
|
357
|
-
<option value="bottom">Bottom</option>
|
|
358
|
-
<option value="left">Left</option>
|
|
359
|
-
<option value="top-bottom">Top & Bottom</option>
|
|
360
|
-
<option value="left-right">Left & Right</option>
|
|
361
|
-
</select>
|
|
362
|
-
</SettingsField>
|
|
363
|
-
|
|
364
|
-
<SettingsField label="Radius">
|
|
365
|
-
<div className="flex items-center gap-2">
|
|
366
|
-
<input
|
|
367
|
-
type="range"
|
|
368
|
-
min={0}
|
|
369
|
-
max={50}
|
|
370
|
-
value={parseInt(getRowSettingValue<string>(section, activeViewport, "border_radius", "0"))}
|
|
371
|
-
onChange={(e) => updateSettingResponsive("border_radius", e.target.value)}
|
|
372
|
-
className="flex-1 accent-[#076bff]"
|
|
373
|
-
/>
|
|
374
|
-
<span className="text-xs text-neutral-900 w-10 text-right">
|
|
375
|
-
{getRowSettingValue<string>(section, activeViewport, "border_radius", "0")}px
|
|
376
|
-
</span>
|
|
377
|
-
</div>
|
|
378
|
-
</SettingsField>
|
|
379
|
-
</SettingsSection>
|
|
380
|
-
</>
|
|
381
|
-
);
|
|
382
|
-
}
|
|
@@ -1,157 +0,0 @@
|
|
|
1
|
-
import { defineField, defineType } from "sanity";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* pageSection — First-class page section type.
|
|
5
|
-
*
|
|
6
|
-
* Unlike regular rows (which contain columns → blocks), page sections are
|
|
7
|
-
* direct, flat entities in the content_rows array. Each section wraps a single
|
|
8
|
-
* section-level block (projectGridBlock) with its own
|
|
9
|
-
* layout settings — no row/column matryoshka.
|
|
10
|
-
*
|
|
11
|
-
* Session 76: Refactored from the old "section row" approach where section
|
|
12
|
-
* blocks were wrapped in row → column → block.
|
|
13
|
-
*/
|
|
14
|
-
export default defineType({
|
|
15
|
-
name: "pageSection",
|
|
16
|
-
title: "Page Section",
|
|
17
|
-
type: "object",
|
|
18
|
-
fields: [
|
|
19
|
-
defineField({
|
|
20
|
-
name: "section_type",
|
|
21
|
-
title: "Section Type",
|
|
22
|
-
type: "string",
|
|
23
|
-
options: {
|
|
24
|
-
list: [
|
|
25
|
-
{ title: "Project Grid", value: "projectGrid" },
|
|
26
|
-
],
|
|
27
|
-
},
|
|
28
|
-
validation: (Rule) => Rule.required(),
|
|
29
|
-
}),
|
|
30
|
-
defineField({
|
|
31
|
-
name: "block",
|
|
32
|
-
title: "Section Content",
|
|
33
|
-
type: "array",
|
|
34
|
-
of: [{ type: "projectGridBlock" }],
|
|
35
|
-
validation: (Rule) => Rule.max(1).required(),
|
|
36
|
-
description: "The section block content (one block per section)",
|
|
37
|
-
}),
|
|
38
|
-
defineField({
|
|
39
|
-
name: "settings",
|
|
40
|
-
title: "Section Settings",
|
|
41
|
-
type: "object",
|
|
42
|
-
fields: [
|
|
43
|
-
// Background
|
|
44
|
-
defineField({ name: "background_color", title: "Background Color", type: "string" }),
|
|
45
|
-
defineField({ name: "background_opacity", title: "Background Opacity", type: "number" }),
|
|
46
|
-
defineField({ name: "background_image", title: "Background Image", type: "string" }),
|
|
47
|
-
defineField({
|
|
48
|
-
name: "background_size",
|
|
49
|
-
title: "Background Size",
|
|
50
|
-
type: "string",
|
|
51
|
-
options: { list: ["cover", "contain", "auto"] },
|
|
52
|
-
}),
|
|
53
|
-
defineField({ name: "background_position", title: "Background Position", type: "string" }),
|
|
54
|
-
defineField({
|
|
55
|
-
name: "background_repeat",
|
|
56
|
-
title: "Background Repeat",
|
|
57
|
-
type: "string",
|
|
58
|
-
options: { list: ["no-repeat", "repeat", "repeat-x", "repeat-y"] },
|
|
59
|
-
}),
|
|
60
|
-
// Spacing (padding TRBL)
|
|
61
|
-
defineField({ name: "spacing_top", title: "Spacing Top", type: "string" }),
|
|
62
|
-
defineField({ name: "spacing_right", title: "Spacing Right", type: "string" }),
|
|
63
|
-
defineField({ name: "spacing_bottom", title: "Spacing Bottom", type: "string" }),
|
|
64
|
-
defineField({ name: "spacing_left", title: "Spacing Left", type: "string" }),
|
|
65
|
-
// Offset (margin TRBL)
|
|
66
|
-
defineField({ name: "offset_top", title: "Offset Top", type: "string" }),
|
|
67
|
-
defineField({ name: "offset_right", title: "Offset Right", type: "string" }),
|
|
68
|
-
defineField({ name: "offset_bottom", title: "Offset Bottom", type: "string" }),
|
|
69
|
-
defineField({ name: "offset_left", title: "Offset Left", type: "string" }),
|
|
70
|
-
// Border
|
|
71
|
-
defineField({ name: "border_color", title: "Border Color", type: "string" }),
|
|
72
|
-
defineField({ name: "border_width", title: "Border Width", type: "string" }),
|
|
73
|
-
defineField({
|
|
74
|
-
name: "border_style",
|
|
75
|
-
title: "Border Style",
|
|
76
|
-
type: "string",
|
|
77
|
-
options: { list: ["none", "solid", "dashed", "dotted"] },
|
|
78
|
-
}),
|
|
79
|
-
defineField({
|
|
80
|
-
name: "border_sides",
|
|
81
|
-
title: "Border Sides",
|
|
82
|
-
type: "string",
|
|
83
|
-
options: { list: ["all", "top", "right", "bottom", "left", "top-bottom", "left-right"] },
|
|
84
|
-
}),
|
|
85
|
-
defineField({ name: "border_radius", title: "Border Radius", type: "string" }),
|
|
86
|
-
// Animation
|
|
87
|
-
defineField({
|
|
88
|
-
name: "enter_animation",
|
|
89
|
-
title: "Enter Animation",
|
|
90
|
-
type: "enterAnimationConfig",
|
|
91
|
-
}),
|
|
92
|
-
],
|
|
93
|
-
}),
|
|
94
|
-
// BUG-013 fix: Per-viewport responsive overrides for section settings
|
|
95
|
-
defineField({
|
|
96
|
-
name: "responsive",
|
|
97
|
-
title: "Responsive Overrides",
|
|
98
|
-
type: "object",
|
|
99
|
-
hidden: true, // Managed by the visual builder
|
|
100
|
-
fields: [
|
|
101
|
-
defineField({
|
|
102
|
-
name: "tablet",
|
|
103
|
-
title: "Tablet",
|
|
104
|
-
type: "object",
|
|
105
|
-
fields: [
|
|
106
|
-
defineField({ name: "background_color", type: "string", title: "Background Color" }),
|
|
107
|
-
defineField({ name: "background_opacity", type: "number", title: "Background Opacity" }),
|
|
108
|
-
defineField({ name: "spacing_top", type: "string", title: "Spacing Top" }),
|
|
109
|
-
defineField({ name: "spacing_right", type: "string", title: "Spacing Right" }),
|
|
110
|
-
defineField({ name: "spacing_bottom", type: "string", title: "Spacing Bottom" }),
|
|
111
|
-
defineField({ name: "spacing_left", type: "string", title: "Spacing Left" }),
|
|
112
|
-
defineField({ name: "offset_top", type: "string", title: "Offset Top" }),
|
|
113
|
-
defineField({ name: "offset_right", type: "string", title: "Offset Right" }),
|
|
114
|
-
defineField({ name: "offset_bottom", type: "string", title: "Offset Bottom" }),
|
|
115
|
-
defineField({ name: "offset_left", type: "string", title: "Offset Left" }),
|
|
116
|
-
defineField({ name: "border_color", type: "string", title: "Border Color" }),
|
|
117
|
-
defineField({ name: "border_width", type: "string", title: "Border Width" }),
|
|
118
|
-
defineField({ name: "border_style", type: "string", title: "Border Style" }),
|
|
119
|
-
defineField({ name: "border_sides", type: "string", title: "Border Sides" }),
|
|
120
|
-
defineField({ name: "border_radius", type: "string", title: "Border Radius" }),
|
|
121
|
-
],
|
|
122
|
-
}),
|
|
123
|
-
defineField({
|
|
124
|
-
name: "phone",
|
|
125
|
-
title: "Phone",
|
|
126
|
-
type: "object",
|
|
127
|
-
fields: [
|
|
128
|
-
defineField({ name: "background_color", type: "string", title: "Background Color" }),
|
|
129
|
-
defineField({ name: "background_opacity", type: "number", title: "Background Opacity" }),
|
|
130
|
-
defineField({ name: "spacing_top", type: "string", title: "Spacing Top" }),
|
|
131
|
-
defineField({ name: "spacing_right", type: "string", title: "Spacing Right" }),
|
|
132
|
-
defineField({ name: "spacing_bottom", type: "string", title: "Spacing Bottom" }),
|
|
133
|
-
defineField({ name: "spacing_left", type: "string", title: "Spacing Left" }),
|
|
134
|
-
defineField({ name: "offset_top", type: "string", title: "Offset Top" }),
|
|
135
|
-
defineField({ name: "offset_right", type: "string", title: "Offset Right" }),
|
|
136
|
-
defineField({ name: "offset_bottom", type: "string", title: "Offset Bottom" }),
|
|
137
|
-
defineField({ name: "offset_left", type: "string", title: "Offset Left" }),
|
|
138
|
-
defineField({ name: "border_color", type: "string", title: "Border Color" }),
|
|
139
|
-
defineField({ name: "border_width", type: "string", title: "Border Width" }),
|
|
140
|
-
defineField({ name: "border_style", type: "string", title: "Border Style" }),
|
|
141
|
-
defineField({ name: "border_sides", type: "string", title: "Border Sides" }),
|
|
142
|
-
defineField({ name: "border_radius", type: "string", title: "Border Radius" }),
|
|
143
|
-
],
|
|
144
|
-
}),
|
|
145
|
-
],
|
|
146
|
-
}),
|
|
147
|
-
],
|
|
148
|
-
preview: {
|
|
149
|
-
select: { section_type: "section_type" },
|
|
150
|
-
prepare({ section_type }) {
|
|
151
|
-
const labels: Record<string, string> = {
|
|
152
|
-
projectGrid: "Project Grid",
|
|
153
|
-
};
|
|
154
|
-
return { title: labels[section_type] || "Section" };
|
|
155
|
-
},
|
|
156
|
-
},
|
|
157
|
-
});
|