@morphika/andami 0.5.1 → 0.5.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (147) hide show
  1. package/README.md +27 -2
  2. package/app/admin/assets/page.tsx +6 -6
  3. package/app/admin/database/page.tsx +302 -302
  4. package/app/admin/error.tsx +53 -53
  5. package/app/admin/layout.tsx +332 -320
  6. package/app/admin/navigation/page.tsx +255 -255
  7. package/app/admin/pages/[slug]/page.tsx +44 -27
  8. package/app/admin/pages/page.tsx +24 -19
  9. package/app/admin/projects/page.tsx +30 -21
  10. package/app/admin/setup/page.tsx +1 -1
  11. package/app/admin/styles/page.tsx +1 -1
  12. package/app/api/admin/assets/register/route.ts +51 -14
  13. package/app/api/admin/assets/registry/route.ts +4 -1
  14. package/app/api/admin/assets/relink/confirm/route.ts +4 -1
  15. package/app/api/admin/assets/relink/route.ts +4 -1
  16. package/app/api/admin/assets/scan/route.ts +4 -1
  17. package/app/api/admin/backups/restore-data/route.ts +4 -1
  18. package/app/api/admin/r2/connect/route.ts +4 -1
  19. package/app/api/admin/r2/delete/route.ts +4 -1
  20. package/app/api/admin/r2/rename/route.ts +4 -1
  21. package/app/api/admin/r2/upload-url/route.ts +4 -1
  22. package/app/api/admin/revalidate/route.ts +4 -1
  23. package/app/api/admin/storage/switch/route.ts +4 -1
  24. package/app/api/custom-sections/[id]/route.ts +5 -6
  25. package/components/admin/MetadataEditor.tsx +6 -6
  26. package/components/admin/PublishToggle.tsx +2 -2
  27. package/components/admin/nav-builder/NavBuilder.tsx +1 -1
  28. package/components/admin/nav-builder/NavBuilderGrid.tsx +3 -3
  29. package/components/admin/nav-builder/NavGridCell.tsx +48 -48
  30. package/components/admin/nav-builder/NavGridItem.tsx +8 -6
  31. package/components/admin/nav-builder/NavItemSettings.tsx +331 -331
  32. package/components/admin/nav-builder/NavItemTypePicker.tsx +102 -102
  33. package/components/admin/nav-builder/NavLivePreview.tsx +1 -1
  34. package/components/admin/nav-builder/NavMobileLivePreview.tsx +226 -226
  35. package/components/admin/nav-builder/NavMobileSettings.tsx +242 -242
  36. package/components/admin/nav-builder/NavSettingsFields.tsx +518 -514
  37. package/components/admin/setup-wizard/BrandingStep.tsx +3 -3
  38. package/components/admin/setup-wizard/DatabaseStep.tsx +2 -2
  39. package/components/admin/setup-wizard/DoneStep.tsx +1 -1
  40. package/components/admin/setup-wizard/SetupWizard.tsx +4 -4
  41. package/components/admin/setup-wizard/StorageStep.tsx +2 -2
  42. package/components/admin/setup-wizard/WelcomeStep.tsx +2 -2
  43. package/components/admin/styles/ColorsEditor.tsx +9 -8
  44. package/components/admin/styles/FontsEditor.tsx +9 -7
  45. package/components/admin/styles/GridLayoutEditor.tsx +9 -9
  46. package/components/admin/styles/LinksButtonsEditor.tsx +5 -5
  47. package/components/admin/styles/TypographyEditor.tsx +6 -6
  48. package/components/admin/styles/shared.tsx +68 -68
  49. package/components/blocks/AudioBlockRenderer.tsx +286 -286
  50. package/components/blocks/CoverSectionRenderer.tsx +7 -1
  51. package/components/blocks/MarqueeBlockRenderer.tsx +316 -0
  52. package/components/blocks/ProjectCarouselBlockRenderer.tsx +1 -1
  53. package/components/blocks/SectionV2Renderer.tsx +8 -1
  54. package/components/builder/BlockCardIcons.tsx +316 -316
  55. package/components/builder/BlockTypePicker.tsx +1 -1
  56. package/components/builder/BubbleIcons.tsx +104 -0
  57. package/components/builder/BuilderCanvas.tsx +2 -0
  58. package/components/builder/CanvasMinimap.tsx +66 -49
  59. package/components/builder/CanvasToolbar.tsx +31 -41
  60. package/components/builder/CoverSectionCanvas.tsx +363 -363
  61. package/components/builder/DeviceFrame.tsx +1 -1
  62. package/components/builder/DndWrapper.tsx +3 -3
  63. package/components/builder/InsertionLines.tsx +1 -1
  64. package/components/builder/SectionCardIcons.tsx +421 -320
  65. package/components/builder/SectionEditorBar.tsx +5 -3
  66. package/components/builder/SectionTypePicker.tsx +7 -5
  67. package/components/builder/SectionV2Canvas.tsx +1 -1
  68. package/components/builder/SectionV2Column.tsx +82 -68
  69. package/components/builder/SettingsPanel.tsx +21 -17
  70. package/components/builder/SortableBlock.tsx +93 -73
  71. package/components/builder/SortableRow.tsx +33 -35
  72. package/components/builder/VirtualAssetGrid.tsx +10 -4
  73. package/components/builder/asset-browser/R2BrowserContent.tsx +18 -14
  74. package/components/builder/blockStyles.tsx +192 -185
  75. package/components/builder/color-picker/AlphaSlider.tsx +141 -141
  76. package/components/builder/color-picker/ColorInputs.tsx +105 -105
  77. package/components/builder/color-picker/EyedropperButton.tsx +75 -74
  78. package/components/builder/color-picker/HueSlider.tsx +124 -124
  79. package/components/builder/color-picker/SaturationCanvas.tsx +142 -142
  80. package/components/builder/color-picker/SwatchBar.tsx +98 -93
  81. package/components/builder/color-picker/UnifiedColorPicker.tsx +11 -6
  82. package/components/builder/editors/AudioBlockEditor.tsx +242 -242
  83. package/components/builder/editors/BeforeAfterBlockEditor.tsx +360 -360
  84. package/components/builder/editors/ButtonBlockEditor.tsx +4 -4
  85. package/components/builder/editors/EnterAnimationPicker.tsx +2 -2
  86. package/components/builder/editors/HoverEffectPicker.tsx +2 -2
  87. package/components/builder/editors/ImageBlockEditor.tsx +2 -2
  88. package/components/builder/editors/ImageGridBlockEditor.tsx +8 -6
  89. package/components/builder/editors/MarqueeBlockEditor.tsx +622 -0
  90. package/components/builder/editors/ProjectCarouselBlockEditor.tsx +443 -443
  91. package/components/builder/editors/ProjectGridEditor.tsx +21 -16
  92. package/components/builder/editors/SpacerBlockEditor.tsx +29 -27
  93. package/components/builder/editors/StaggerSettings.tsx +109 -109
  94. package/components/builder/editors/TextBlockEditor.tsx +22 -17
  95. package/components/builder/editors/TextStylePicker.tsx +1 -1
  96. package/components/builder/editors/VideoBlockEditor.tsx +2 -2
  97. package/components/builder/editors/index.ts +11 -10
  98. package/components/builder/editors/shared.tsx +10 -8
  99. package/components/builder/live-preview/LiveAudioPreview.tsx +120 -120
  100. package/components/builder/live-preview/LiveBeforeAfterPreview.tsx +1 -1
  101. package/components/builder/live-preview/LiveImageGridPreview.tsx +10 -2
  102. package/components/builder/live-preview/LiveImagePreview.tsx +4 -2
  103. package/components/builder/live-preview/LiveMarqueePreview.tsx +39 -0
  104. package/components/builder/live-preview/LiveProjectCarouselPreview.tsx +1 -1
  105. package/components/builder/live-preview/LiveVideoPreview.tsx +1 -1
  106. package/components/builder/live-preview/ProjectCardWrapper.tsx +293 -291
  107. package/components/builder/live-preview/RichTextBubbleMenu.tsx +10 -6
  108. package/components/builder/live-preview/shared.tsx +5 -2
  109. package/components/builder/settings-panel/AnimationTab.tsx +138 -138
  110. package/components/builder/settings-panel/BlockLayoutTab.tsx +11 -9
  111. package/components/builder/settings-panel/CardEntranceSection.tsx +114 -114
  112. package/components/builder/settings-panel/ColumnV2LayoutTab.tsx +242 -0
  113. package/components/builder/settings-panel/ColumnV2Settings.tsx +5 -5
  114. package/components/builder/settings-panel/CoverSectionLayoutTab.tsx +71 -71
  115. package/components/builder/settings-panel/CoverSectionSettings.tsx +337 -335
  116. package/components/builder/settings-panel/PageSettings.tsx +3 -3
  117. package/components/builder/settings-panel/ParallaxSlideSettings.tsx +2 -2
  118. package/components/builder/settings-panel/SectionV2AnimationTab.tsx +4 -4
  119. package/components/builder/settings-panel/SectionV2LayoutTab.tsx +356 -356
  120. package/components/builder/settings-panel/SectionV2Settings.tsx +25 -20
  121. package/components/builder/settings-panel/TRBLInputs.tsx +1 -1
  122. package/components/builder/settings-panel/index.ts +1 -0
  123. package/lib/animation/enter-types.ts +1 -0
  124. package/lib/animation/hover-effect-presets.ts +210 -210
  125. package/lib/animation/hover-effect-types.ts +1 -0
  126. package/lib/builder/block-registrations.ts +468 -417
  127. package/lib/builder/constants.ts +111 -111
  128. package/lib/builder/serializer/normalizers.ts +14 -0
  129. package/lib/builder/serializer/serializers.ts +27 -0
  130. package/lib/builder/store-sections.ts +23 -2
  131. package/lib/builder/types-slices.ts +428 -414
  132. package/lib/builder/types.ts +4 -1
  133. package/lib/config/index.ts +27 -27
  134. package/lib/sanity/queries.ts +48 -0
  135. package/lib/sanity/types.ts +112 -1
  136. package/lib/version.ts +1 -1
  137. package/package.json +7 -5
  138. package/sanity/schemas/blocks/audioBlock.ts +69 -69
  139. package/sanity/schemas/blocks/index.ts +12 -11
  140. package/sanity/schemas/blocks/marqueeBlock.ts +292 -0
  141. package/sanity/schemas/index.ts +120 -117
  142. package/sanity/schemas/objects/coverSection.ts +32 -0
  143. package/sanity/schemas/objects/parallaxSlide.ts +32 -0
  144. package/sanity/schemas/pageSectionV2.ts +32 -0
  145. package/styles/admin.css +85 -85
  146. package/styles/animations.css +237 -237
  147. package/styles/base.css +114 -114
@@ -0,0 +1,242 @@
1
+ "use client";
2
+
3
+ /**
4
+ * ColumnV2LayoutTab — Layout tab for a selected V2/cover/parallax-slide column.
5
+ *
6
+ * Desktop-only for now. Viewport-aware column layout would require extending
7
+ * ColumnOverride (currently position-only) — not yet implemented.
8
+ *
9
+ * Sections: Background (color/opacity/image), Border (color/width/style/sides/radius).
10
+ * Columns intentionally have no Spacing or Offset — use the parent section's
11
+ * row_gap/col_gap, or block-level padding.
12
+ */
13
+
14
+ import { useBuilderStore } from "../../../lib/builder/store";
15
+ import type { PageSectionV2, SectionColumn } from "../../../lib/sanity/types";
16
+ import {
17
+ SettingsField,
18
+ SettingsSection,
19
+ SELECT_CLASS,
20
+ AssetPathInput,
21
+ } from "../editors/shared";
22
+ import ColorSwatchPicker, { usePaletteSwatches } from "../ColorSwatchPicker";
23
+ import { serializeColorField, parseColorField, isGradient } from "../../../lib/color-utils";
24
+ import { BackgroundIcon, BorderIcon } from "../editors/section-icons";
25
+
26
+ type LayoutField =
27
+ | "background_color" | "background_opacity" | "background_image"
28
+ | "background_size" | "background_position" | "background_repeat"
29
+ | "border_color" | "border_width" | "border_style" | "border_sides" | "border_radius";
30
+
31
+ export function ColumnV2LayoutTab({
32
+ section,
33
+ column,
34
+ }: {
35
+ section: PageSectionV2;
36
+ column: SectionColumn;
37
+ }) {
38
+ const store = useBuilderStore();
39
+ const paletteSwatches = usePaletteSwatches();
40
+ const activeViewport = store.activeViewport;
41
+
42
+ const isResponsive = activeViewport !== "desktop";
43
+
44
+ const update = (field: LayoutField, value: unknown) => {
45
+ store._pushSnapshot();
46
+ store.updateColumnV2Layout(section._key, column._key, {
47
+ [field]: value,
48
+ } as Partial<SectionColumn>);
49
+ };
50
+
51
+ const getValue = <T,>(field: LayoutField, fallback: T): T => {
52
+ const val = (column as unknown as Record<string, unknown>)[field];
53
+ return (val !== undefined && val !== null ? val : fallback) as T;
54
+ };
55
+
56
+ const handleBgPreview = (val: import("../../../lib/sanity/types").ColorField) => {
57
+ store.setColorPickerPreview({ sectionKey: section._key, field: "column_background_color", value: val });
58
+ };
59
+
60
+ const bgIsGradient = isGradient(parseColorField(getValue<string>("background_color", "")));
61
+
62
+ return (
63
+ <>
64
+ {isResponsive && (
65
+ <div className="px-4 pt-3">
66
+ <div className="flex items-center gap-1.5 px-3 py-1.5 rounded-lg bg-neutral-100 border border-neutral-200">
67
+ <span className="text-[11px] font-medium text-neutral-500">
68
+ Column layout is desktop-only for now
69
+ </span>
70
+ </div>
71
+ </div>
72
+ )}
73
+
74
+ {/* Background */}
75
+ <SettingsSection title="Background" defaultOpen icon={<BackgroundIcon />}>
76
+ <SettingsField label="Color">
77
+ <ColorSwatchPicker
78
+ value={parseColorField(getValue<string>("background_color", ""))}
79
+ onChange={(val) => {
80
+ store.clearColorPickerPreview();
81
+ update("background_color", serializeColorField(val));
82
+ }}
83
+ swatches={paletteSwatches}
84
+ allowGradients
85
+ onPreview={handleBgPreview}
86
+ />
87
+ </SettingsField>
88
+
89
+ <SettingsField label="Opacity">
90
+ <div className="flex items-center gap-2">
91
+ <input
92
+ type="range"
93
+ min={0}
94
+ max={100}
95
+ value={getValue<number>("background_opacity", 100)}
96
+ onChange={(e) => update("background_opacity", parseInt(e.target.value))}
97
+ className={`flex-1 accent-[#3580f9] ${bgIsGradient ? "opacity-40 pointer-events-none" : ""}`}
98
+ disabled={bgIsGradient}
99
+ />
100
+ <span className="text-xs text-neutral-900 w-10 text-right">
101
+ {getValue<number>("background_opacity", 100)}%
102
+ </span>
103
+ </div>
104
+ {bgIsGradient && (
105
+ <p className="text-[9px] text-neutral-400 italic mt-1">
106
+ Opacity is controlled per stop in gradient mode
107
+ </p>
108
+ )}
109
+ </SettingsField>
110
+
111
+ <SettingsField label="Image">
112
+ <AssetPathInput
113
+ value={getValue<string>("background_image", "")}
114
+ onFocus={() => store._pushSnapshot()}
115
+ onChange={(v) => update("background_image", v)}
116
+ placeholder="path/to/image.jpg"
117
+ filterType="image"
118
+ />
119
+ </SettingsField>
120
+
121
+ {getValue<string>("background_image", "") && (
122
+ <>
123
+ <SettingsField label="Size">
124
+ <select
125
+ value={getValue<string>("background_size", "cover")}
126
+ onChange={(e) => update("background_size", e.target.value)}
127
+ className={SELECT_CLASS}
128
+ >
129
+ <option value="cover">Cover</option>
130
+ <option value="contain">Contain</option>
131
+ <option value="auto">Auto</option>
132
+ </select>
133
+ </SettingsField>
134
+
135
+ <SettingsField label="Position">
136
+ <select
137
+ value={getValue<string>("background_position", "center center")}
138
+ onChange={(e) => update("background_position", e.target.value)}
139
+ className={SELECT_CLASS}
140
+ >
141
+ <option value="center center">Center</option>
142
+ <option value="top center">Top</option>
143
+ <option value="bottom center">Bottom</option>
144
+ <option value="left center">Left</option>
145
+ <option value="right center">Right</option>
146
+ </select>
147
+ </SettingsField>
148
+
149
+ <SettingsField label="Repeat">
150
+ <select
151
+ value={getValue<string>("background_repeat", "no-repeat")}
152
+ onChange={(e) => update("background_repeat", e.target.value)}
153
+ className={SELECT_CLASS}
154
+ >
155
+ <option value="no-repeat">No Repeat</option>
156
+ <option value="repeat">Repeat</option>
157
+ <option value="repeat-x">Repeat X</option>
158
+ <option value="repeat-y">Repeat Y</option>
159
+ </select>
160
+ </SettingsField>
161
+ </>
162
+ )}
163
+ </SettingsSection>
164
+
165
+ {/* Border */}
166
+ <SettingsSection title="Border" icon={<BorderIcon />}>
167
+ <SettingsField label="Color">
168
+ <ColorSwatchPicker
169
+ value={parseColorField(getValue<string>("border_color", ""))}
170
+ onChange={(val) => {
171
+ store.clearColorPickerPreview();
172
+ update("border_color", serializeColorField(val));
173
+ }}
174
+ swatches={paletteSwatches}
175
+ allowGradients
176
+ />
177
+ </SettingsField>
178
+
179
+ <SettingsField label="Width">
180
+ <div className="flex items-center gap-2">
181
+ <input
182
+ type="range"
183
+ min={0}
184
+ max={20}
185
+ value={parseInt(getValue<string>("border_width", "0"))}
186
+ onChange={(e) => update("border_width", e.target.value)}
187
+ className="flex-1 accent-[#3580f9]"
188
+ />
189
+ <span className="text-xs text-neutral-900 w-10 text-right">
190
+ {getValue<string>("border_width", "0")}px
191
+ </span>
192
+ </div>
193
+ </SettingsField>
194
+
195
+ <SettingsField label="Style">
196
+ <select
197
+ value={getValue<string>("border_style", "none")}
198
+ onChange={(e) => update("border_style", e.target.value)}
199
+ className={SELECT_CLASS}
200
+ >
201
+ <option value="none">None</option>
202
+ <option value="solid">Solid</option>
203
+ <option value="dashed">Dashed</option>
204
+ <option value="dotted">Dotted</option>
205
+ </select>
206
+ </SettingsField>
207
+
208
+ <SettingsField label="Sides">
209
+ <select
210
+ value={getValue<string>("border_sides", "all")}
211
+ onChange={(e) => update("border_sides", e.target.value)}
212
+ className={SELECT_CLASS}
213
+ >
214
+ <option value="all">All</option>
215
+ <option value="top">Top</option>
216
+ <option value="right">Right</option>
217
+ <option value="bottom">Bottom</option>
218
+ <option value="left">Left</option>
219
+ <option value="top-bottom">Top & Bottom</option>
220
+ <option value="left-right">Left & Right</option>
221
+ </select>
222
+ </SettingsField>
223
+
224
+ <SettingsField label="Radius">
225
+ <div className="flex items-center gap-2">
226
+ <input
227
+ type="range"
228
+ min={0}
229
+ max={50}
230
+ value={parseInt(getValue<string>("border_radius", "0"))}
231
+ onChange={(e) => update("border_radius", e.target.value)}
232
+ className="flex-1 accent-[#3580f9]"
233
+ />
234
+ <span className="text-xs text-neutral-900 w-10 text-right">
235
+ {getValue<string>("border_radius", "0")}px
236
+ </span>
237
+ </div>
238
+ </SettingsField>
239
+ </SettingsSection>
240
+ </>
241
+ );
242
+ }
@@ -85,8 +85,8 @@ export default function ColumnV2Settings({
85
85
  <>
86
86
  {isResponsive && (
87
87
  <div className="px-4 pt-3">
88
- <div className="flex items-center gap-1.5 px-3 py-1.5 rounded-lg bg-[#4794e2]/8 border border-[#4794e2]/15">
89
- <span className="text-[11px] font-medium text-[#4794e2]">
88
+ <div className="flex items-center gap-1.5 px-3 py-1.5 rounded-lg bg-[#3580f9]/8 border border-[#3580f9]/15">
89
+ <span className="text-[11px] font-medium text-[#3580f9]">
90
90
  Editing {activeViewport === "tablet" ? "Tablet" : "Phone"} overrides
91
91
  </span>
92
92
  </div>
@@ -98,7 +98,7 @@ export default function ColumnV2Settings({
98
98
  <span>
99
99
  Span
100
100
  {hasSpanOverride && (
101
- <span className="ml-1 text-[9px] text-[#4794e2]">overridden</span>
101
+ <span className="ml-1 text-[9px] text-[#3580f9]">overridden</span>
102
102
  )}
103
103
  </span>
104
104
  }>
@@ -109,7 +109,7 @@ export default function ColumnV2Settings({
109
109
  max={gridColumns}
110
110
  value={effectiveSpan}
111
111
  onChange={(e) => handleSpanChange(parseInt(e.target.value))}
112
- className="flex-1 accent-[#4794e2]"
112
+ className="flex-1 accent-[#3580f9]"
113
113
  />
114
114
  <span className="text-xs text-neutral-900 w-12 text-right font-medium">
115
115
  {effectiveSpan}/{gridColumns}
@@ -133,7 +133,7 @@ export default function ColumnV2Settings({
133
133
  <div
134
134
  key={i}
135
135
  className={`h-1.5 flex-1 rounded-full transition-colors ${
136
- isActive ? "bg-[#4794e2]" : "bg-neutral-200"
136
+ isActive ? "bg-[#3580f9]" : "bg-neutral-200"
137
137
  }`}
138
138
  />
139
139
  );
@@ -1,71 +1,71 @@
1
- "use client";
2
-
3
- import { useBuilderStore } from "../../../lib/builder/store";
4
- import type { CoverSection } from "../../../lib/sanity/types";
5
- import {
6
- SpacingIcon,
7
- OffsetIcon,
8
- BorderIcon,
9
- } from "../editors/section-icons";
10
- import { SettingsField, SettingsSection } from "../editors/shared";
11
- import { TRBLInputs } from "./TRBLInputs";
12
-
13
- interface CoverSectionLayoutTabProps {
14
- section: CoverSection;
15
- }
16
-
17
- export default function CoverSectionLayoutTab({ section }: CoverSectionLayoutTabProps) {
18
- const store = useBuilderStore();
19
- const s = section.settings;
20
-
21
- const update = (fields: Record<string, string | undefined>) => {
22
- store.updateCoverSettings(section._key, fields as Partial<typeof s>);
23
- };
24
-
25
- return (
26
- <>
27
- {/* Spacing (padding) */}
28
- <SettingsSection title="Spacing" defaultOpen icon={<SpacingIcon />}>
29
- <TRBLInputs
30
- top={s.spacing_top || ""}
31
- right={s.spacing_right || ""}
32
- bottom={s.spacing_bottom || ""}
33
- left={s.spacing_left || ""}
34
- onChange={(field, value) => update({ [`spacing_${field}`]: value || undefined })}
35
- />
36
- </SettingsSection>
37
-
38
- {/* Offset (margin) */}
39
- <SettingsSection title="Offset" defaultOpen={false} icon={<OffsetIcon />}>
40
- <TRBLInputs
41
- top={s.offset_top || ""}
42
- right={s.offset_right || ""}
43
- bottom={s.offset_bottom || ""}
44
- left={s.offset_left || ""}
45
- onChange={(field, value) => update({ [`offset_${field}`]: value || undefined })}
46
- />
47
- </SettingsSection>
48
-
49
- {/* Border Radius */}
50
- <SettingsSection title="Border" defaultOpen={false} icon={<BorderIcon />}>
51
- <SettingsField label="Radius">
52
- <div className="flex items-center gap-2">
53
- <input
54
- type="range"
55
- min={0}
56
- max={50}
57
- step={1}
58
- value={parseInt(s.border_radius || "0", 10) || 0}
59
- onMouseDown={() => store._pushSnapshot()}
60
- onChange={(e) => update({ border_radius: e.target.value === "0" ? undefined : e.target.value })}
61
- className="flex-1 accent-[#076bff]"
62
- />
63
- <span className="text-xs text-neutral-900 w-10 text-right tabular-nums">
64
- {parseInt(s.border_radius || "0", 10) || 0}px
65
- </span>
66
- </div>
67
- </SettingsField>
68
- </SettingsSection>
69
- </>
70
- );
71
- }
1
+ "use client";
2
+
3
+ import { useBuilderStore } from "../../../lib/builder/store";
4
+ import type { CoverSection } from "../../../lib/sanity/types";
5
+ import {
6
+ SpacingIcon,
7
+ OffsetIcon,
8
+ BorderIcon,
9
+ } from "../editors/section-icons";
10
+ import { SettingsField, SettingsSection } from "../editors/shared";
11
+ import { TRBLInputs } from "./TRBLInputs";
12
+
13
+ interface CoverSectionLayoutTabProps {
14
+ section: CoverSection;
15
+ }
16
+
17
+ export default function CoverSectionLayoutTab({ section }: CoverSectionLayoutTabProps) {
18
+ const store = useBuilderStore();
19
+ const s = section.settings;
20
+
21
+ const update = (fields: Record<string, string | undefined>) => {
22
+ store.updateCoverSettings(section._key, fields as Partial<typeof s>);
23
+ };
24
+
25
+ return (
26
+ <>
27
+ {/* Spacing (padding) */}
28
+ <SettingsSection title="Spacing" defaultOpen icon={<SpacingIcon />}>
29
+ <TRBLInputs
30
+ top={s.spacing_top || ""}
31
+ right={s.spacing_right || ""}
32
+ bottom={s.spacing_bottom || ""}
33
+ left={s.spacing_left || ""}
34
+ onChange={(field, value) => update({ [`spacing_${field}`]: value || undefined })}
35
+ />
36
+ </SettingsSection>
37
+
38
+ {/* Offset (margin) */}
39
+ <SettingsSection title="Offset" defaultOpen={false} icon={<OffsetIcon />}>
40
+ <TRBLInputs
41
+ top={s.offset_top || ""}
42
+ right={s.offset_right || ""}
43
+ bottom={s.offset_bottom || ""}
44
+ left={s.offset_left || ""}
45
+ onChange={(field, value) => update({ [`offset_${field}`]: value || undefined })}
46
+ />
47
+ </SettingsSection>
48
+
49
+ {/* Border Radius */}
50
+ <SettingsSection title="Border" defaultOpen={false} icon={<BorderIcon />}>
51
+ <SettingsField label="Radius">
52
+ <div className="flex items-center gap-2">
53
+ <input
54
+ type="range"
55
+ min={0}
56
+ max={50}
57
+ step={1}
58
+ value={parseInt(s.border_radius || "0", 10) || 0}
59
+ onMouseDown={() => store._pushSnapshot()}
60
+ onChange={(e) => update({ border_radius: e.target.value === "0" ? undefined : e.target.value })}
61
+ className="flex-1 accent-[#3580f9]"
62
+ />
63
+ <span className="text-xs text-neutral-900 w-10 text-right tabular-nums">
64
+ {parseInt(s.border_radius || "0", 10) || 0}px
65
+ </span>
66
+ </div>
67
+ </SettingsField>
68
+ </SettingsSection>
69
+ </>
70
+ );
71
+ }