@morphika/andami 0.5.2 → 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 (65) hide show
  1. package/README.md +27 -2
  2. package/app/admin/layout.tsx +26 -14
  3. package/app/admin/pages/[slug]/page.tsx +39 -22
  4. package/app/admin/pages/page.tsx +13 -8
  5. package/app/admin/projects/page.tsx +17 -8
  6. package/app/api/admin/assets/register/route.ts +51 -14
  7. package/app/api/admin/assets/registry/route.ts +4 -1
  8. package/app/api/admin/assets/relink/confirm/route.ts +4 -1
  9. package/app/api/admin/assets/relink/route.ts +4 -1
  10. package/app/api/admin/assets/scan/route.ts +4 -1
  11. package/app/api/admin/backups/restore-data/route.ts +4 -1
  12. package/app/api/admin/r2/connect/route.ts +4 -1
  13. package/app/api/admin/r2/delete/route.ts +4 -1
  14. package/app/api/admin/r2/rename/route.ts +4 -1
  15. package/app/api/admin/r2/upload-url/route.ts +4 -1
  16. package/app/api/admin/revalidate/route.ts +4 -1
  17. package/app/api/admin/storage/switch/route.ts +4 -1
  18. package/app/api/custom-sections/[id]/route.ts +5 -6
  19. package/components/admin/PublishToggle.tsx +2 -2
  20. package/components/admin/nav-builder/NavGridItem.tsx +4 -2
  21. package/components/admin/nav-builder/NavSettingsFields.tsx +10 -6
  22. package/components/admin/styles/ColorsEditor.tsx +7 -6
  23. package/components/admin/styles/FontsEditor.tsx +3 -1
  24. package/components/blocks/CoverSectionRenderer.tsx +7 -1
  25. package/components/blocks/SectionV2Renderer.tsx +8 -1
  26. package/components/builder/BubbleIcons.tsx +14 -0
  27. package/components/builder/CanvasMinimap.tsx +66 -49
  28. package/components/builder/CanvasToolbar.tsx +31 -41
  29. package/components/builder/SectionEditorBar.tsx +4 -2
  30. package/components/builder/SectionTypePicker.tsx +4 -2
  31. package/components/builder/SectionV2Column.tsx +13 -1
  32. package/components/builder/SettingsPanel.tsx +21 -17
  33. package/components/builder/SortableBlock.tsx +2 -2
  34. package/components/builder/SortableRow.tsx +6 -9
  35. package/components/builder/VirtualAssetGrid.tsx +8 -2
  36. package/components/builder/asset-browser/R2BrowserContent.tsx +8 -4
  37. package/components/builder/color-picker/EyedropperButton.tsx +7 -6
  38. package/components/builder/color-picker/SwatchBar.tsx +11 -6
  39. package/components/builder/color-picker/UnifiedColorPicker.tsx +11 -6
  40. package/components/builder/editors/ImageGridBlockEditor.tsx +4 -2
  41. package/components/builder/editors/MarqueeBlockEditor.tsx +3 -2
  42. package/components/builder/editors/ProjectGridEditor.tsx +12 -7
  43. package/components/builder/editors/SpacerBlockEditor.tsx +25 -23
  44. package/components/builder/editors/TextBlockEditor.tsx +19 -14
  45. package/components/builder/editors/shared.tsx +4 -2
  46. package/components/builder/live-preview/LiveImagePreview.tsx +3 -1
  47. package/components/builder/live-preview/ProjectCardWrapper.tsx +3 -1
  48. package/components/builder/live-preview/RichTextBubbleMenu.tsx +10 -6
  49. package/components/builder/live-preview/shared.tsx +5 -2
  50. package/components/builder/settings-panel/BlockLayoutTab.tsx +4 -2
  51. package/components/builder/settings-panel/ColumnV2LayoutTab.tsx +242 -0
  52. package/components/builder/settings-panel/CoverSectionSettings.tsx +4 -2
  53. package/components/builder/settings-panel/SectionV2Settings.tsx +13 -8
  54. package/components/builder/settings-panel/index.ts +1 -0
  55. package/lib/builder/serializer/normalizers.ts +14 -0
  56. package/lib/builder/serializer/serializers.ts +27 -0
  57. package/lib/builder/store-sections.ts +21 -0
  58. package/lib/builder/types-slices.ts +14 -0
  59. package/lib/sanity/queries.ts +48 -0
  60. package/lib/sanity/types.ts +14 -0
  61. package/lib/version.ts +1 -1
  62. package/package.json +7 -5
  63. package/sanity/schemas/objects/coverSection.ts +32 -0
  64. package/sanity/schemas/objects/parallaxSlide.ts +32 -0
  65. package/sanity/schemas/pageSectionV2.ts +32 -0
@@ -31,6 +31,19 @@ export function normalizePageSectionV2(section: Partial<PageSectionV2> & { _key:
31
31
  // Migrate column-level enter_animation (new field, no old equivalent for columns)
32
32
  const colEnterAnimation = colRecord.enter_animation as EnterAnimationConfig | undefined;
33
33
 
34
+ // Column-level background + border — carry through from Sanity (desktop-only).
35
+ const colLayoutFields: Partial<SectionColumn> = {};
36
+ for (const field of [
37
+ "background_color", "background_opacity", "background_image",
38
+ "background_size", "background_position", "background_repeat",
39
+ "border_color", "border_width", "border_style", "border_sides", "border_radius",
40
+ ] as const) {
41
+ const val = colRecord[field];
42
+ if (val !== undefined && val !== null) {
43
+ (colLayoutFields as Record<string, unknown>)[field] = val;
44
+ }
45
+ }
46
+
34
47
  return {
35
48
  _key: col._key || generateKey(),
36
49
  grid_column: col.grid_column || 1,
@@ -55,6 +68,7 @@ export function normalizePageSectionV2(section: Partial<PageSectionV2> & { _key:
55
68
  return b as ContentBlock;
56
69
  }),
57
70
  ...(colEnterAnimation ? { enter_animation: colEnterAnimation } : {}),
71
+ ...colLayoutFields,
58
72
  };
59
73
  });
60
74
 
@@ -134,6 +134,21 @@ function serializePageSectionV2(section: PageSectionV2): Record<string, unknown>
134
134
  if (col.enter_animation && col.enter_animation.preset && col.enter_animation.preset !== "none") {
135
135
  colData.enter_animation = col.enter_animation;
136
136
  }
137
+ // Column-level background + border (desktop-only).
138
+ const colLayout = stripUndefined({
139
+ background_color: col.background_color,
140
+ background_opacity: col.background_opacity,
141
+ background_image: col.background_image,
142
+ background_size: col.background_size,
143
+ background_position: col.background_position,
144
+ background_repeat: col.background_repeat,
145
+ border_color: col.border_color,
146
+ border_width: col.border_width,
147
+ border_style: col.border_style,
148
+ border_sides: col.border_sides,
149
+ border_radius: col.border_radius,
150
+ });
151
+ if (colLayout) Object.assign(colData, colLayout);
137
152
  return colData;
138
153
  }),
139
154
  settings: s ? stripUndefined({
@@ -223,6 +238,18 @@ function serializeCoverSection(section: CoverSection): Record<string, unknown> {
223
238
  span: col.span,
224
239
  blocks: (col.blocks || []).map((b) => serializeBlock(b)),
225
240
  enter_animation: col.enter_animation,
241
+ // Column-level background + border (desktop-only).
242
+ background_color: col.background_color,
243
+ background_opacity: col.background_opacity,
244
+ background_image: col.background_image,
245
+ background_size: col.background_size,
246
+ background_position: col.background_position,
247
+ background_repeat: col.background_repeat,
248
+ border_color: col.border_color,
249
+ border_width: col.border_width,
250
+ border_style: col.border_style,
251
+ border_sides: col.border_sides,
252
+ border_radius: col.border_radius,
226
253
  } as Record<string, unknown>));
227
254
 
228
255
  return stripUndefined({
@@ -541,6 +541,27 @@ export function createSectionActions(set: StoreSet, get: StoreGet): SectionSlice
541
541
  });
542
542
  },
543
543
 
544
+ // Update desktop-level column layout fields (spacing/background/border).
545
+ // Undefined values in `updates` clear that field.
546
+ updateColumnV2Layout: (sectionKey, colKey, updates) => {
547
+ set((state) => {
548
+ const path = findSectionPath(state.rows, sectionKey);
549
+ if (!path) return state;
550
+ const rows = updateSectionAtPath(state.rows, path, (section) => ({
551
+ ...section,
552
+ columns: section.columns.map((col) => {
553
+ if (col._key !== colKey) return col;
554
+ const merged = { ...col, ...updates };
555
+ for (const [k, v] of Object.entries(updates)) {
556
+ if (v === undefined) delete (merged as Record<string, unknown>)[k];
557
+ }
558
+ return merged;
559
+ }),
560
+ }));
561
+ return { rows, isDirty: true };
562
+ });
563
+ },
564
+
544
565
  // ---- Custom Section Instance operations (Session 108) ----
545
566
 
546
567
  addCustomSectionInstance: (
@@ -31,6 +31,7 @@ import type {
31
31
  PageSectionV2,
32
32
  SectionV2Settings,
33
33
  SectionV2Preset,
34
+ SectionColumn,
34
35
  PageMetadata,
35
36
  ColorField,
36
37
  CoverSection,
@@ -168,6 +169,19 @@ export interface SectionSliceActions {
168
169
  colKey: string,
169
170
  config: import("../../lib/animation/enter-types").EnterAnimationConfig | undefined,
170
171
  ) => void;
172
+ /** Update desktop-level layout fields on a V2/cover/parallax-slide column.
173
+ * Columns only support background + border — spacing belongs to the
174
+ * section (row_gap/col_gap) or to block-level padding. */
175
+ updateColumnV2Layout: (
176
+ sectionKey: string,
177
+ colKey: string,
178
+ updates: Partial<Pick<
179
+ SectionColumn,
180
+ | "background_color" | "background_opacity" | "background_image"
181
+ | "background_size" | "background_position" | "background_repeat"
182
+ | "border_color" | "border_width" | "border_style" | "border_sides" | "border_radius"
183
+ >>,
184
+ ) => void;
171
185
 
172
186
  /** Select a V2 column — sets selectedRowKey + selectedColumnKey. */
173
187
  selectColumnV2: (sectionKey: string | null, columnKey: string | null) => void;
@@ -20,6 +20,18 @@ const blockExpansion = `
20
20
  grid_row,
21
21
  span,
22
22
  enter_animation,
23
+ // Column-level background + border (Session 184)
24
+ background_color,
25
+ background_opacity,
26
+ background_image,
27
+ background_size,
28
+ background_position,
29
+ background_repeat,
30
+ border_color,
31
+ border_width,
32
+ border_style,
33
+ border_sides,
34
+ border_radius,
23
35
  blocks[] {
24
36
  _type,
25
37
  _key,
@@ -60,6 +72,18 @@ const blockExpansion = `
60
72
  grid_column,
61
73
  grid_row,
62
74
  span,
75
+ // Column-level background + border (Session 184)
76
+ background_color,
77
+ background_opacity,
78
+ background_image,
79
+ background_size,
80
+ background_position,
81
+ background_repeat,
82
+ border_color,
83
+ border_width,
84
+ border_style,
85
+ border_sides,
86
+ border_radius,
63
87
  blocks[] {
64
88
  _type,
65
89
  _key,
@@ -429,6 +453,18 @@ export const customSectionBySlugQuery = groq`
429
453
  grid_column,
430
454
  grid_row,
431
455
  span,
456
+ enter_animation,
457
+ background_color,
458
+ background_opacity,
459
+ background_image,
460
+ background_size,
461
+ background_position,
462
+ background_repeat,
463
+ border_color,
464
+ border_width,
465
+ border_style,
466
+ border_sides,
467
+ border_radius,
432
468
  blocks[] {
433
469
  _type,
434
470
  _key,
@@ -456,6 +492,18 @@ export const customSectionByIdQuery = groq`
456
492
  grid_column,
457
493
  grid_row,
458
494
  span,
495
+ enter_animation,
496
+ background_color,
497
+ background_opacity,
498
+ background_image,
499
+ background_size,
500
+ background_position,
501
+ background_repeat,
502
+ border_color,
503
+ border_width,
504
+ border_style,
505
+ border_sides,
506
+ border_radius,
459
507
  blocks[] {
460
508
  _type,
461
509
  _key,
@@ -559,6 +559,20 @@ export interface SectionColumn {
559
559
  blocks: ContentBlock[]; // same block types as today
560
560
  // NEW (Session 116) — column-level enter animation for 4-level cascade
561
561
  enter_animation?: import("../../lib/animation/enter-types").EnterAnimationConfig;
562
+ // Column-level layout — desktop-only for now. Background + border only
563
+ // (no spacing — section row_gap/col_gap + block padding cover that).
564
+ // Viewport overrides are TBD: the current ColumnOverride type is position-only.
565
+ background_color?: string;
566
+ background_opacity?: number;
567
+ background_image?: string;
568
+ background_size?: string;
569
+ background_position?: string;
570
+ background_repeat?: string;
571
+ border_color?: string;
572
+ border_width?: string;
573
+ border_style?: string;
574
+ border_sides?: string;
575
+ border_radius?: string;
562
576
  }
563
577
 
564
578
  export interface ColumnOverride {
package/lib/version.ts CHANGED
@@ -6,4 +6,4 @@
6
6
  * Exposed as a plain constant so it can be imported without reading
7
7
  * package.json at runtime.
8
8
  */
9
- export const ANDAMI_VERSION = "0.5.2";
9
+ export const ANDAMI_VERSION = "0.5.3";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@morphika/andami",
3
- "version": "0.5.2",
3
+ "version": "0.5.3",
4
4
  "description": "Visual Page Builder — core library. A reusable website builder with visual editing, CMS integration, and asset management.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -193,7 +193,6 @@
193
193
  "react-dom": ">=19.0.0"
194
194
  },
195
195
  "dependencies": {
196
- "archiver": "^7.0.1",
197
196
  "@aws-sdk/client-s3": "^3.1021.0",
198
197
  "@aws-sdk/s3-request-presigner": "^3.1021.0",
199
198
  "@dnd-kit/core": "^6.3.1",
@@ -210,10 +209,11 @@
210
209
  "@tiptap/starter-kit": "^2.12.0",
211
210
  "@types/archiver": "^6.0.3",
212
211
  "@types/unzipper": "^0.10.10",
212
+ "archiver": "^7.0.1",
213
213
  "jszip": "^3.10.1",
214
- "next-sanity": "^12.1.5",
214
+ "next-sanity": "^12.3.0",
215
215
  "ogl": "^1.0.8",
216
- "sanity": "^5.17.1",
216
+ "sanity": "^5.21.0",
217
217
  "unzipper": "^0.12.3",
218
218
  "zustand": "^5.0.12"
219
219
  },
@@ -228,10 +228,12 @@
228
228
  "@types/react-dom": "^19",
229
229
  "eslint": "^9",
230
230
  "eslint-config-next": "16.2.1",
231
+ "framer-motion": "^12.38.0",
231
232
  "jsdom": "^26.1.0",
232
- "next": "16.2.1",
233
+ "next": "^16.2.4",
233
234
  "react": "19.2.4",
234
235
  "react-dom": "19.2.4",
236
+ "styled-components": "^6.4.0",
235
237
  "tailwindcss": "^4",
236
238
  "typescript": "^5",
237
239
  "vitest": "^4.1.2"
@@ -50,6 +50,38 @@ const columnFields = [
50
50
  title: "Enter Animation",
51
51
  type: "enterAnimationConfig",
52
52
  }),
53
+ // Column-level layout — desktop-only, background + border only.
54
+ defineField({ name: "background_color", title: "Background Color", type: "string" }),
55
+ defineField({ name: "background_opacity", title: "Background Opacity", type: "number" }),
56
+ defineField({ name: "background_image", title: "Background Image", type: "string" }),
57
+ defineField({
58
+ name: "background_size",
59
+ title: "Background Size",
60
+ type: "string",
61
+ options: { list: ["cover", "contain", "auto"] },
62
+ }),
63
+ defineField({ name: "background_position", title: "Background Position", type: "string" }),
64
+ defineField({
65
+ name: "background_repeat",
66
+ title: "Background Repeat",
67
+ type: "string",
68
+ options: { list: ["no-repeat", "repeat", "repeat-x", "repeat-y"] },
69
+ }),
70
+ defineField({ name: "border_color", title: "Border Color", type: "string" }),
71
+ defineField({ name: "border_width", title: "Border Width", type: "string" }),
72
+ defineField({
73
+ name: "border_style",
74
+ title: "Border Style",
75
+ type: "string",
76
+ options: { list: ["none", "solid", "dashed", "dotted"] },
77
+ }),
78
+ defineField({
79
+ name: "border_sides",
80
+ title: "Border Sides",
81
+ type: "string",
82
+ options: { list: ["all", "top", "right", "bottom", "left", "top-bottom", "left-right"] },
83
+ }),
84
+ defineField({ name: "border_radius", title: "Border Radius", type: "string" }),
53
85
  ];
54
86
 
55
87
  const responsiveColumnOverrideFields = [
@@ -115,6 +115,38 @@ export default defineType({
115
115
  title: "Enter Animation",
116
116
  type: "enterAnimationConfig",
117
117
  }),
118
+ // Column-level layout — desktop-only, background + border only.
119
+ defineField({ name: "background_color", title: "Background Color", type: "string" }),
120
+ defineField({ name: "background_opacity", title: "Background Opacity", type: "number" }),
121
+ defineField({ name: "background_image", title: "Background Image", type: "string" }),
122
+ defineField({
123
+ name: "background_size",
124
+ title: "Background Size",
125
+ type: "string",
126
+ options: { list: ["cover", "contain", "auto"] },
127
+ }),
128
+ defineField({ name: "background_position", title: "Background Position", type: "string" }),
129
+ defineField({
130
+ name: "background_repeat",
131
+ title: "Background Repeat",
132
+ type: "string",
133
+ options: { list: ["no-repeat", "repeat", "repeat-x", "repeat-y"] },
134
+ }),
135
+ defineField({ name: "border_color", title: "Border Color", type: "string" }),
136
+ defineField({ name: "border_width", title: "Border Width", type: "string" }),
137
+ defineField({
138
+ name: "border_style",
139
+ title: "Border Style",
140
+ type: "string",
141
+ options: { list: ["none", "solid", "dashed", "dotted"] },
142
+ }),
143
+ defineField({
144
+ name: "border_sides",
145
+ title: "Border Sides",
146
+ type: "string",
147
+ options: { list: ["all", "top", "right", "bottom", "left", "top-bottom", "left-right"] },
148
+ }),
149
+ defineField({ name: "border_radius", title: "Border Radius", type: "string" }),
118
150
  ],
119
151
  },
120
152
  ],
@@ -76,6 +76,38 @@ export default defineType({
76
76
  title: "Enter Animation",
77
77
  type: "enterAnimationConfig",
78
78
  }),
79
+ // Column-level layout — desktop-only, background + border only.
80
+ defineField({ name: "background_color", title: "Background Color", type: "string" }),
81
+ defineField({ name: "background_opacity", title: "Background Opacity", type: "number" }),
82
+ defineField({ name: "background_image", title: "Background Image", type: "string" }),
83
+ defineField({
84
+ name: "background_size",
85
+ title: "Background Size",
86
+ type: "string",
87
+ options: { list: ["cover", "contain", "auto"] },
88
+ }),
89
+ defineField({ name: "background_position", title: "Background Position", type: "string" }),
90
+ defineField({
91
+ name: "background_repeat",
92
+ title: "Background Repeat",
93
+ type: "string",
94
+ options: { list: ["no-repeat", "repeat", "repeat-x", "repeat-y"] },
95
+ }),
96
+ defineField({ name: "border_color", title: "Border Color", type: "string" }),
97
+ defineField({ name: "border_width", title: "Border Width", type: "string" }),
98
+ defineField({
99
+ name: "border_style",
100
+ title: "Border Style",
101
+ type: "string",
102
+ options: { list: ["none", "solid", "dashed", "dotted"] },
103
+ }),
104
+ defineField({
105
+ name: "border_sides",
106
+ title: "Border Sides",
107
+ type: "string",
108
+ options: { list: ["all", "top", "right", "bottom", "left", "top-bottom", "left-right"] },
109
+ }),
110
+ defineField({ name: "border_radius", title: "Border Radius", type: "string" }),
79
111
  ],
80
112
  },
81
113
  ],