@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.
Files changed (68) hide show
  1. package/app/admin/pages/[slug]/page.tsx +3 -7
  2. package/app/api/admin/pages/[slug]/route.ts +2 -28
  3. package/app/api/admin/settings/route.ts +30 -0
  4. package/components/admin/nav-builder/NavBuilder.tsx +90 -14
  5. package/components/admin/nav-builder/NavGeneralSettings.tsx +521 -271
  6. package/components/admin/nav-builder/NavItemSettings.tsx +331 -312
  7. package/components/admin/nav-builder/NavMobileSettings.tsx +159 -140
  8. package/components/admin/nav-builder/NavSettingsFields.tsx +287 -21
  9. package/components/admin/nav-builder/NavSettingsPanel.tsx +137 -127
  10. package/components/blocks/EnterAnimationWrapper.tsx +19 -4
  11. package/components/blocks/PageRenderer.tsx +2 -15
  12. package/components/blocks/ProjectGridBlockRenderer.tsx +34 -36
  13. package/components/blocks/TextBlockRenderer.tsx +1 -1
  14. package/components/builder/DndWrapper.tsx +2 -24
  15. package/components/builder/InsertionLines.tsx +5 -5
  16. package/components/builder/ReadOnlyFrame.tsx +5 -49
  17. package/components/builder/SectionV2Canvas.tsx +2 -2
  18. package/components/builder/SectionV2Column.tsx +5 -5
  19. package/components/builder/SettingsPanel.tsx +0 -12
  20. package/components/builder/SortableBlock.tsx +3 -3
  21. package/components/builder/SortableRow.tsx +6 -27
  22. package/components/builder/editors/ButtonBlockEditor.tsx +8 -3
  23. package/components/builder/editors/CoverBlockEditor.tsx +14 -6
  24. package/components/builder/editors/ImageBlockEditor.tsx +8 -3
  25. package/components/builder/editors/ImageGridBlockEditor.tsx +8 -3
  26. package/components/builder/editors/ProjectGridEditor.tsx +7 -46
  27. package/components/builder/editors/SpacerBlockEditor.tsx +4 -1
  28. package/components/builder/editors/StaggerSettings.tsx +2 -1
  29. package/components/builder/editors/TextBlockEditor.tsx +8 -3
  30. package/components/builder/editors/VideoBlockEditor.tsx +10 -4
  31. package/components/builder/editors/section-icons.tsx +492 -0
  32. package/components/builder/editors/shared.tsx +23 -4
  33. package/components/builder/live-preview/LiveTextEditor.tsx +1 -1
  34. package/components/builder/live-preview/ProjectCardWrapper.tsx +3 -3
  35. package/components/builder/live-preview/drag-utils.tsx +2 -2
  36. package/components/builder/settings-panel/AnimationTab.tsx +2 -16
  37. package/components/builder/settings-panel/BlockLayoutTab.tsx +13 -58
  38. package/components/builder/settings-panel/ColumnV2Settings.tsx +4 -1
  39. package/components/builder/settings-panel/PageSettings.tsx +10 -4
  40. package/components/builder/settings-panel/ParallaxGroupSettings.tsx +6 -2
  41. package/components/builder/settings-panel/ParallaxSlideSettings.tsx +8 -3
  42. package/components/builder/settings-panel/SectionV2LayoutTab.tsx +11 -47
  43. package/components/builder/settings-panel/SectionV2Settings.tsx +6 -27
  44. package/components/builder/settings-panel/index.ts +0 -1
  45. package/components/builder/settings-panel/responsive-helpers.ts +2 -50
  46. package/components/builder/settings-panel/useSettingsPanelSelection.ts +1 -16
  47. package/components/ui/Navbar.tsx +151 -30
  48. package/lib/builder/constants.ts +5 -4
  49. package/lib/builder/serializer/normalizers.ts +2 -40
  50. package/lib/builder/serializer/serializers.ts +3 -74
  51. package/lib/builder/store-blocks.ts +3 -19
  52. package/lib/builder/store-helpers.ts +2 -2
  53. package/lib/builder/store-sections.ts +26 -64
  54. package/lib/builder/store.ts +3 -6
  55. package/lib/builder/templates.ts +9 -45
  56. package/lib/builder/types.ts +4 -11
  57. package/lib/sanity/queries.ts +6 -29
  58. package/lib/sanity/types.ts +24 -70
  59. package/package.json +4 -1
  60. package/sanity/schemas/index.ts +0 -5
  61. package/sanity/schemas/objects/parallaxGroup.ts +2 -2
  62. package/sanity/schemas/page.ts +1 -1
  63. package/sanity/schemas/pageSectionV2.ts +1 -0
  64. package/sanity/schemas/siteSettings.ts +42 -0
  65. package/styles/base.css +8 -2
  66. package/components/blocks/SectionRenderer.tsx +0 -171
  67. package/components/builder/settings-panel/LayoutTab.tsx +0 -382
  68. 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
- });