@morphika/andami 0.1.10 → 0.2.1
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/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/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/index.ts +0 -1
- package/components/builder/settings-panel/responsive-helpers.ts +2 -50
- package/components/builder/settings-panel/useSettingsPanelSelection.ts +1 -16
- 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 +2 -70
- package/package.json +2 -2
- 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 +7 -5
- package/components/blocks/SectionRenderer.tsx +0 -171
- package/components/builder/settings-panel/LayoutTab.tsx +0 -346
- package/sanity/schemas/pageSection.ts +0 -157
|
@@ -2,11 +2,8 @@ import type { BuilderStore, BuilderState, BlockType } from "./types";
|
|
|
2
2
|
import type {
|
|
3
3
|
ContentBlock,
|
|
4
4
|
ContentItem,
|
|
5
|
-
PageSection,
|
|
6
5
|
PageSectionV2,
|
|
7
6
|
SectionV2Settings,
|
|
8
|
-
SectionSettings,
|
|
9
|
-
SectionBlock,
|
|
10
7
|
CustomSectionInstance,
|
|
11
8
|
ParallaxGroup,
|
|
12
9
|
ParallaxSlideV2,
|
|
@@ -14,7 +11,6 @@ import type {
|
|
|
14
11
|
EnterAnimationConfig,
|
|
15
12
|
} from "../../lib/sanity/types";
|
|
16
13
|
import {
|
|
17
|
-
isPageSection,
|
|
18
14
|
isPageSectionV2,
|
|
19
15
|
isCustomSectionInstance,
|
|
20
16
|
isParallaxGroup,
|
|
@@ -52,20 +48,36 @@ export function createSectionActions(set: StoreSet, get: StoreGet) {
|
|
|
52
48
|
// ---- Section operations ----
|
|
53
49
|
|
|
54
50
|
/**
|
|
55
|
-
* Add a
|
|
56
|
-
*
|
|
57
|
-
*
|
|
51
|
+
* Add a Project Grid section — creates a PageSectionV2 with a single
|
|
52
|
+
* full-width column (span 12) containing a pre-populated projectGridBlock.
|
|
53
|
+
*
|
|
54
|
+
* Session 164: Migrated from V1 (PageSection) to V2 (PageSectionV2).
|
|
55
|
+
* Existing V1 pages still work — V1 update/delete actions are kept until
|
|
56
|
+
* the data migration in Session 165.
|
|
58
57
|
*/
|
|
59
58
|
addSection: (blockType: "projectGridBlock", afterRowKey?: string | null): void => {
|
|
60
59
|
get()._pushSnapshot();
|
|
61
|
-
const block = createDefaultBlock(blockType)
|
|
62
|
-
const
|
|
63
|
-
|
|
60
|
+
const block = createDefaultBlock(blockType);
|
|
61
|
+
const gridColumns = 12;
|
|
62
|
+
|
|
63
|
+
const newSection: PageSectionV2 = {
|
|
64
|
+
_type: "pageSectionV2",
|
|
64
65
|
_key: generateKey(),
|
|
65
|
-
section_type: "
|
|
66
|
-
|
|
67
|
-
|
|
66
|
+
section_type: "empty-v2",
|
|
67
|
+
columns: [
|
|
68
|
+
{
|
|
69
|
+
_key: generateKey(),
|
|
70
|
+
grid_column: 1,
|
|
71
|
+
grid_row: 1,
|
|
72
|
+
span: gridColumns,
|
|
73
|
+
blocks: [block],
|
|
74
|
+
},
|
|
75
|
+
],
|
|
68
76
|
settings: {
|
|
77
|
+
preset: "full",
|
|
78
|
+
grid_columns: gridColumns,
|
|
79
|
+
col_gap: 20,
|
|
80
|
+
row_gap: 20,
|
|
69
81
|
spacing_top: "32",
|
|
70
82
|
spacing_bottom: "32",
|
|
71
83
|
},
|
|
@@ -97,50 +109,6 @@ export function createSectionActions(set: StoreSet, get: StoreGet) {
|
|
|
97
109
|
});
|
|
98
110
|
},
|
|
99
111
|
|
|
100
|
-
updateSectionSettings: (
|
|
101
|
-
sectionKey: string,
|
|
102
|
-
settings: Partial<SectionSettings>
|
|
103
|
-
): void => {
|
|
104
|
-
set((state) => ({
|
|
105
|
-
rows: state.rows.map((item) =>
|
|
106
|
-
item._key === sectionKey && isPageSection(item)
|
|
107
|
-
? { ...item, settings: { ...item.settings, ...settings } }
|
|
108
|
-
: item
|
|
109
|
-
),
|
|
110
|
-
isDirty: true,
|
|
111
|
-
}));
|
|
112
|
-
},
|
|
113
|
-
|
|
114
|
-
/** BUG-013 fix: Update responsive overrides for a PageSection */
|
|
115
|
-
updateSectionResponsive: (
|
|
116
|
-
sectionKey: string,
|
|
117
|
-
responsive: PageSection["responsive"]
|
|
118
|
-
): void => {
|
|
119
|
-
set((state) => ({
|
|
120
|
-
rows: state.rows.map((item) =>
|
|
121
|
-
item._key === sectionKey && isPageSection(item)
|
|
122
|
-
? { ...item, responsive }
|
|
123
|
-
: item
|
|
124
|
-
),
|
|
125
|
-
isDirty: true,
|
|
126
|
-
}));
|
|
127
|
-
},
|
|
128
|
-
|
|
129
|
-
updateSectionBlock: (sectionKey: string, updates: Partial<ContentBlock>): void => {
|
|
130
|
-
set((state) => ({
|
|
131
|
-
rows: state.rows.map((item) => {
|
|
132
|
-
if (item._key !== sectionKey || !isPageSection(item)) return item;
|
|
133
|
-
const block = item.block[0];
|
|
134
|
-
if (!block) return item;
|
|
135
|
-
return {
|
|
136
|
-
...item,
|
|
137
|
-
block: [{ ...block, ...updates } as SectionBlock],
|
|
138
|
-
};
|
|
139
|
-
}),
|
|
140
|
-
isDirty: true,
|
|
141
|
-
}));
|
|
142
|
-
},
|
|
143
|
-
|
|
144
112
|
deleteSection: (sectionKey: string): void => {
|
|
145
113
|
get()._pushSnapshot();
|
|
146
114
|
set((state) => ({
|
|
@@ -165,13 +133,7 @@ export function createSectionActions(set: StoreSet, get: StoreGet) {
|
|
|
165
133
|
const clone = JSON.parse(JSON.stringify(original)) as ContentItem;
|
|
166
134
|
clone._key = generateKey();
|
|
167
135
|
// Re-key internal structures
|
|
168
|
-
if (
|
|
169
|
-
if (clone.block.length > 0) {
|
|
170
|
-
clone.block = [
|
|
171
|
-
{ ...clone.block[0], _key: generateKey() },
|
|
172
|
-
] as [SectionBlock];
|
|
173
|
-
}
|
|
174
|
-
} else if (isPageSectionV2(clone)) {
|
|
136
|
+
if (isPageSectionV2(clone)) {
|
|
175
137
|
(clone as PageSectionV2).columns = (clone as PageSectionV2).columns.map(
|
|
176
138
|
(col) => ({
|
|
177
139
|
...col,
|
package/lib/builder/store.ts
CHANGED
|
@@ -3,8 +3,8 @@ import type { BuilderStore, BuilderState, BlockType, PageSettings, CanvasTool, D
|
|
|
3
3
|
import { DEFAULT_PAGE_SETTINGS, DEFAULT_GRID_SETTINGS, DEVICE_WIDTHS } from "./types";
|
|
4
4
|
import { stateToDocument, documentToState } from "./serializer";
|
|
5
5
|
import { generateKey } from "./utils";
|
|
6
|
-
import type { ContentBlock, ContentItem,
|
|
7
|
-
import {
|
|
6
|
+
import type { ContentBlock, ContentItem, PageSectionV2, SectionV2Settings, PageMetadata, CustomSectionInstance, ParallaxGroup, ParallaxSlideV2 } from "../../lib/sanity/types";
|
|
7
|
+
import { isPageSectionV2, isCustomSectionInstance, isParallaxGroup } from "../../lib/sanity/types";
|
|
8
8
|
import { createDefaultBlock, createDefaultParallaxSlide } from "./defaults";
|
|
9
9
|
import { MAX_HISTORY, pushSnapshot } from "./history";
|
|
10
10
|
import {
|
|
@@ -48,10 +48,7 @@ function getBlockParentCache(rows: ContentItem[]): Map<string, BlockParent> {
|
|
|
48
48
|
|
|
49
49
|
const cache = new Map<string, BlockParent>();
|
|
50
50
|
for (const item of rows) {
|
|
51
|
-
if (
|
|
52
|
-
const sBlock = Array.isArray(item.block) ? item.block[0] : undefined;
|
|
53
|
-
if (sBlock) cache.set(sBlock._key, { rowKey: item._key, colKey: null });
|
|
54
|
-
} else if (isPageSectionV2(item)) {
|
|
51
|
+
if (isPageSectionV2(item)) {
|
|
55
52
|
const v2 = item as PageSectionV2;
|
|
56
53
|
for (const col of v2.columns) {
|
|
57
54
|
for (const b of col.blocks) {
|
package/lib/builder/templates.ts
CHANGED
|
@@ -4,10 +4,9 @@
|
|
|
4
4
|
// Each template defines a set of pre-built rows/columns/blocks
|
|
5
5
|
// that get injected as content_rows when creating a new page.
|
|
6
6
|
//
|
|
7
|
-
//
|
|
8
|
-
// objects for section blocks instead of the old matryoshka format.
|
|
7
|
+
// All templates use PageSectionV2 (V1 PageSection removed in cleanup).
|
|
9
8
|
|
|
10
|
-
import type { ContentBlock, ContentItem,
|
|
9
|
+
import type { ContentBlock, ContentItem, PageSectionV2, SectionColumn, ParallaxGroup, ParallaxSlideV2 } from "../../lib/sanity/types";
|
|
11
10
|
import { generateKey } from "./utils";
|
|
12
11
|
|
|
13
12
|
// ============================================
|
|
@@ -84,43 +83,20 @@ function makeV2Section(blocks: ContentBlock[], settings?: Partial<PageSectionV2[
|
|
|
84
83
|
};
|
|
85
84
|
}
|
|
86
85
|
|
|
87
|
-
/**
|
|
88
|
-
* Create a first-class PageSection with a section block.
|
|
89
|
-
* BUG-004 fix: replaces the old pattern of wrapping section blocks in makeRow().
|
|
90
|
-
*/
|
|
91
|
-
function makeSection(
|
|
92
|
-
blockType: "projectGridBlock",
|
|
93
|
-
blockDefaults: Record<string, unknown>,
|
|
94
|
-
): PageSection {
|
|
95
|
-
const block = makeBlock({ _type: blockType, ...blockDefaults }) as SectionBlock;
|
|
96
|
-
return {
|
|
97
|
-
_type: "pageSection",
|
|
98
|
-
_key: generateKey(),
|
|
99
|
-
section_type: "projectGrid" as import("../../lib/sanity/types").PageSectionType,
|
|
100
|
-
block: [block] as [SectionBlock],
|
|
101
|
-
settings: {
|
|
102
|
-
spacing_top: "32",
|
|
103
|
-
spacing_bottom: "32",
|
|
104
|
-
},
|
|
105
|
-
};
|
|
106
|
-
}
|
|
107
|
-
|
|
108
86
|
// ============================================
|
|
109
|
-
// Template:
|
|
87
|
+
// Template: Portfolio (content showcase)
|
|
110
88
|
// ============================================
|
|
111
89
|
|
|
112
|
-
const
|
|
113
|
-
id: "
|
|
114
|
-
label: "
|
|
115
|
-
description: "
|
|
90
|
+
const portfolioTemplate: PageTemplate = {
|
|
91
|
+
id: "portfolio",
|
|
92
|
+
label: "Portfolio",
|
|
93
|
+
description: "Portfolio showcase with flexible layout sections",
|
|
116
94
|
icon: "⬡",
|
|
117
95
|
preview: [
|
|
118
96
|
{ type: "spacer", height: 0.5 },
|
|
119
97
|
{ type: "text", height: 0.5 },
|
|
120
98
|
{ type: "spacer", height: 0.3 },
|
|
121
|
-
{ type: "cover", height: 2 },
|
|
122
99
|
{ type: "columns", height: 1.5 },
|
|
123
|
-
{ type: "cover", height: 2 },
|
|
124
100
|
{ type: "columns", height: 1.5 },
|
|
125
101
|
],
|
|
126
102
|
buildRows: () => [
|
|
@@ -136,19 +112,7 @@ const projectGridTemplate: PageTemplate = {
|
|
|
136
112
|
]),
|
|
137
113
|
// Section 3: Spacer
|
|
138
114
|
makeV2Section([makeBlock({ _type: "spacerBlock", height: "medium" })]),
|
|
139
|
-
// Section 4:
|
|
140
|
-
makeSection("projectGridBlock", {
|
|
141
|
-
columns: 3,
|
|
142
|
-
aspect_ratios: ["16/9"],
|
|
143
|
-
gap_v: 16,
|
|
144
|
-
gap_h: 16,
|
|
145
|
-
hover_effect: "scale",
|
|
146
|
-
show_subtitle: true,
|
|
147
|
-
border_radius: 0,
|
|
148
|
-
video_mode: "off",
|
|
149
|
-
projects: [],
|
|
150
|
-
}),
|
|
151
|
-
// Section 5: Bottom spacer
|
|
115
|
+
// Section 4: Bottom spacer
|
|
152
116
|
makeV2Section([makeBlock({ _type: "spacerBlock", height: "xlarge" })]),
|
|
153
117
|
],
|
|
154
118
|
};
|
|
@@ -285,7 +249,7 @@ const parallaxHomeTemplate: PageTemplate = {
|
|
|
285
249
|
|
|
286
250
|
export const PAGE_TEMPLATES: PageTemplate[] = [
|
|
287
251
|
blankTemplate,
|
|
288
|
-
|
|
252
|
+
portfolioTemplate,
|
|
289
253
|
parallaxHomeTemplate,
|
|
290
254
|
];
|
|
291
255
|
|
package/lib/builder/types.ts
CHANGED
|
@@ -9,9 +9,7 @@ import type {
|
|
|
9
9
|
PageType,
|
|
10
10
|
ContentBlock,
|
|
11
11
|
ContentItem,
|
|
12
|
-
PageSection,
|
|
13
12
|
PageSectionV2,
|
|
14
|
-
SectionSettings,
|
|
15
13
|
SectionV2Settings,
|
|
16
14
|
SectionV2Preset,
|
|
17
15
|
SectionColumn,
|
|
@@ -178,7 +176,7 @@ export interface BuilderState {
|
|
|
178
176
|
publishedAt: string | null;
|
|
179
177
|
draftMode: boolean;
|
|
180
178
|
|
|
181
|
-
// Content —
|
|
179
|
+
// Content — V2 sections, custom sections, parallax groups
|
|
182
180
|
rows: ContentItem[];
|
|
183
181
|
|
|
184
182
|
// Selection
|
|
@@ -251,17 +249,12 @@ export interface BuilderActions {
|
|
|
251
249
|
unpublishPage: () => void;
|
|
252
250
|
|
|
253
251
|
// Section operations
|
|
252
|
+
/** Add a Project Grid section as a V2 section with a full-width column (Session 164) */
|
|
254
253
|
addSection: (blockType: "projectGridBlock", afterRowKey?: string | null) => void;
|
|
255
254
|
reorderRows: (fromIndex: number, toIndex: number) => void;
|
|
256
|
-
/**
|
|
257
|
-
updateSectionSettings: (sectionKey: string, settings: Partial<SectionSettings>) => void;
|
|
258
|
-
/** BUG-013 fix: Update responsive overrides for a PageSection */
|
|
259
|
-
updateSectionResponsive: (sectionKey: string, responsive: PageSection["responsive"]) => void;
|
|
260
|
-
/** Update the block content inside a PageSection */
|
|
261
|
-
updateSectionBlock: (sectionKey: string, updates: Partial<import("../../lib/sanity/types").ContentBlock>) => void;
|
|
262
|
-
/** Delete a section (PageSection or PageSectionV2) by key */
|
|
255
|
+
/** Delete a section by key */
|
|
263
256
|
deleteSection: (sectionKey: string) => void;
|
|
264
|
-
/** Duplicate a section
|
|
257
|
+
/** Duplicate a section, inserting copy after the original */
|
|
265
258
|
duplicateSection: (sectionKey: string) => void;
|
|
266
259
|
|
|
267
260
|
// Block operations
|
package/lib/sanity/queries.ts
CHANGED
|
@@ -4,45 +4,22 @@ import { groq } from "next-sanity";
|
|
|
4
4
|
// BLOCK EXPANSION (shared fragment)
|
|
5
5
|
// ============================================
|
|
6
6
|
|
|
7
|
-
// Deep expansion of
|
|
8
|
-
// Handles
|
|
9
|
-
//
|
|
10
|
-
// ParallaxGroup objects (_type: "parallaxGroup") with nested slides.
|
|
11
|
-
// GROQ uses conditional projections to handle all types.
|
|
7
|
+
// Deep expansion of content_rows.
|
|
8
|
+
// Handles PageSectionV2, CustomSectionInstance, and ParallaxGroup.
|
|
9
|
+
// GROQ projections are additive — fields that don't exist on an object are omitted.
|
|
12
10
|
const blockExpansion = `
|
|
13
11
|
content_rows[] {
|
|
14
12
|
_key,
|
|
15
13
|
_type,
|
|
16
|
-
// ── PageSection fields (only present when _type == "pageSection") ──
|
|
17
14
|
section_type,
|
|
18
|
-
|
|
19
|
-
_type,
|
|
20
|
-
_key,
|
|
21
|
-
...,
|
|
22
|
-
"project_ref": project_ref-> {
|
|
23
|
-
_id,
|
|
24
|
-
title,
|
|
25
|
-
slug,
|
|
26
|
-
page_type
|
|
27
|
-
}
|
|
28
|
-
},
|
|
29
|
-
// ── Row fields (columns only present on row objects) ──
|
|
30
|
-
// Note: V2 sections also have "columns" but with different fields.
|
|
31
|
-
// GROQ projections are additive — fields that don't exist on an object are omitted.
|
|
32
|
-
// We include BOTH Row column fields (width, settings, responsive) and V2 column
|
|
33
|
-
// fields (grid_column, grid_row, span) so both types are fully hydrated.
|
|
15
|
+
// ── V2 section columns ──
|
|
34
16
|
columns[] {
|
|
35
17
|
_key,
|
|
36
18
|
_type,
|
|
37
|
-
// Row column fields
|
|
38
|
-
width,
|
|
39
|
-
settings,
|
|
40
|
-
responsive,
|
|
41
|
-
// V2 column fields (grid positions)
|
|
42
19
|
grid_column,
|
|
43
20
|
grid_row,
|
|
44
21
|
span,
|
|
45
|
-
|
|
22
|
+
enter_animation,
|
|
46
23
|
blocks[] {
|
|
47
24
|
_type,
|
|
48
25
|
_key,
|
|
@@ -96,7 +73,7 @@ const blockExpansion = `
|
|
|
96
73
|
}
|
|
97
74
|
}
|
|
98
75
|
},
|
|
99
|
-
// ── Settings (
|
|
76
|
+
// ── Settings (V2 section settings) ──
|
|
100
77
|
settings,
|
|
101
78
|
// ── Responsive overrides (V2 sections store responsive at section level) ──
|
|
102
79
|
responsive
|
package/lib/sanity/types.ts
CHANGED
|
@@ -346,68 +346,6 @@ export interface ParallaxGroup {
|
|
|
346
346
|
parallax_intensity: number; // 0.2–0.6, default 0.4 (hidden from UI, hardcoded)
|
|
347
347
|
}
|
|
348
348
|
|
|
349
|
-
// ============================================
|
|
350
|
-
// Page Section — first-class section type (Session 76)
|
|
351
|
-
// ============================================
|
|
352
|
-
// Replaces the old "matryoshka" approach where section blocks were
|
|
353
|
-
// wrapped in row → column → block. Sections are direct entries in
|
|
354
|
-
// content_rows alongside regular rows.
|
|
355
|
-
|
|
356
|
-
export type PageSectionType = "projectGrid";
|
|
357
|
-
|
|
358
|
-
export interface SectionSettings {
|
|
359
|
-
// Background
|
|
360
|
-
background_color?: string;
|
|
361
|
-
background_opacity?: number;
|
|
362
|
-
background_image?: string;
|
|
363
|
-
background_size?: "cover" | "contain" | "auto";
|
|
364
|
-
background_position?: string;
|
|
365
|
-
background_repeat?: "no-repeat" | "repeat" | "repeat-x" | "repeat-y";
|
|
366
|
-
// Spacing (padding TRBL)
|
|
367
|
-
spacing_top?: string;
|
|
368
|
-
spacing_right?: string;
|
|
369
|
-
spacing_bottom?: string;
|
|
370
|
-
spacing_left?: string;
|
|
371
|
-
// Offset (margin TRBL)
|
|
372
|
-
offset_top?: string;
|
|
373
|
-
offset_right?: string;
|
|
374
|
-
offset_bottom?: string;
|
|
375
|
-
offset_left?: string;
|
|
376
|
-
// Border
|
|
377
|
-
border_color?: string;
|
|
378
|
-
border_width?: string;
|
|
379
|
-
border_style?: "none" | "solid" | "dashed" | "dotted";
|
|
380
|
-
border_sides?: "all" | "top" | "right" | "bottom" | "left" | "top-bottom" | "left-right";
|
|
381
|
-
border_radius?: string;
|
|
382
|
-
// Animation
|
|
383
|
-
enter_animation?: import("../../lib/animation/enter-types").EnterAnimationConfig;
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
/** Section block types that can appear inside a PageSection */
|
|
387
|
-
export type SectionBlock = ProjectGridBlock;
|
|
388
|
-
|
|
389
|
-
/** Section settings that can be overridden per-viewport */
|
|
390
|
-
export type SectionSettingsOverridable = Pick<SectionSettings,
|
|
391
|
-
"background_color" | "background_opacity" | "background_image" | "background_size" | "background_position" | "background_repeat" |
|
|
392
|
-
"spacing_top" | "spacing_right" | "spacing_bottom" | "spacing_left" |
|
|
393
|
-
"offset_top" | "offset_right" | "offset_bottom" | "offset_left" |
|
|
394
|
-
"border_color" | "border_width" | "border_style" | "border_sides" | "border_radius"
|
|
395
|
-
>;
|
|
396
|
-
|
|
397
|
-
export interface PageSection {
|
|
398
|
-
_type: "pageSection";
|
|
399
|
-
_key: string;
|
|
400
|
-
section_type: PageSectionType;
|
|
401
|
-
/** The actual section content — single block */
|
|
402
|
-
block: [SectionBlock];
|
|
403
|
-
settings?: SectionSettings;
|
|
404
|
-
/** BUG-013 fix: Per-viewport responsive overrides for section settings */
|
|
405
|
-
responsive?: {
|
|
406
|
-
tablet?: Partial<SectionSettingsOverridable>;
|
|
407
|
-
phone?: Partial<SectionSettingsOverridable>;
|
|
408
|
-
};
|
|
409
|
-
}
|
|
410
|
-
|
|
411
349
|
// ============================================
|
|
412
350
|
// Page Section V2 — V2 Grid System (Session 83)
|
|
413
351
|
// ============================================
|
|
@@ -540,16 +478,10 @@ export interface CustomSectionInstance {
|
|
|
540
478
|
}
|
|
541
479
|
|
|
542
480
|
// ============================================
|
|
543
|
-
// Content Item — union of
|
|
481
|
+
// Content Item — union of section types in content_rows
|
|
544
482
|
// ============================================
|
|
545
|
-
// Used in content_rows and the builder store.
|
|
546
483
|
|
|
547
|
-
export type ContentItem =
|
|
548
|
-
|
|
549
|
-
/** Type guard: check if a content item is a PageSection (V1) */
|
|
550
|
-
export function isPageSection(item: ContentItem): item is PageSection {
|
|
551
|
-
return (item as PageSection)._type === "pageSection";
|
|
552
|
-
}
|
|
484
|
+
export type ContentItem = PageSectionV2 | CustomSectionInstance | ParallaxGroup;
|
|
553
485
|
|
|
554
486
|
/** Type guard: check if a content item is a PageSectionV2 */
|
|
555
487
|
export function isPageSectionV2(item: ContentItem): item is PageSectionV2 {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@morphika/andami",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
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",
|
|
@@ -209,4 +209,4 @@
|
|
|
209
209
|
"typescript": "^5",
|
|
210
210
|
"vitest": "^4.1.2"
|
|
211
211
|
}
|
|
212
|
-
}
|
|
212
|
+
}
|
package/sanity/schemas/index.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import page from "./page";
|
|
2
|
-
import pageSection from "./pageSection";
|
|
3
2
|
import pageSectionV2 from "./pageSectionV2";
|
|
4
3
|
import customSection from "./customSection";
|
|
5
4
|
import customSectionInstance from "./customSectionInstance";
|
|
@@ -35,9 +34,6 @@ export {
|
|
|
35
34
|
export {
|
|
36
35
|
default as assetRegistry,
|
|
37
36
|
} from "./assetRegistry";
|
|
38
|
-
export {
|
|
39
|
-
default as pageSection,
|
|
40
|
-
} from "./pageSection";
|
|
41
37
|
export {
|
|
42
38
|
default as pageSectionV2,
|
|
43
39
|
} from "./pageSectionV2";
|
|
@@ -81,7 +77,6 @@ export const schemaTypes = [
|
|
|
81
77
|
assetRegistry,
|
|
82
78
|
|
|
83
79
|
// Structural objects
|
|
84
|
-
pageSection,
|
|
85
80
|
pageSectionV2,
|
|
86
81
|
customSection,
|
|
87
82
|
customSectionInstance,
|
|
@@ -3,8 +3,8 @@ import { defineField, defineType } from "sanity";
|
|
|
3
3
|
/**
|
|
4
4
|
* parallaxGroup — A group of full-screen parallax slides.
|
|
5
5
|
*
|
|
6
|
-
* Lives inline in a page's content_rows alongside
|
|
7
|
-
*
|
|
6
|
+
* Lives inline in a page's content_rows alongside PageSectionV2
|
|
7
|
+
* and CustomSectionInstance. Each slide is a
|
|
8
8
|
* full V2 section with background image/video and 100vh height.
|
|
9
9
|
*
|
|
10
10
|
* Session 123: Parallax V2 rewrite.
|
package/sanity/schemas/page.ts
CHANGED
|
@@ -42,7 +42,7 @@ export default defineType({
|
|
|
42
42
|
name: "content_rows",
|
|
43
43
|
title: "Content Rows",
|
|
44
44
|
type: "array",
|
|
45
|
-
of: [{ type: "
|
|
45
|
+
of: [{ type: "pageSectionV2" }, { type: "parallaxGroup" }, { type: "customSectionInstance" }],
|
|
46
46
|
}),
|
|
47
47
|
defineField({
|
|
48
48
|
name: "metadata",
|
|
@@ -210,6 +210,48 @@ export default defineType({
|
|
|
210
210
|
defineField({ name: "entrance_delay", title: "Entrance Delay (ms)", type: "number", description: "Delay before animation starts. Default: 0" }),
|
|
211
211
|
defineField({ name: "entrance_stagger", title: "Stagger Items", type: "boolean", description: "Animate items one by one", initialValue: false }),
|
|
212
212
|
defineField({ name: "entrance_stagger_delay", title: "Stagger Delay (ms)", type: "number", description: "Delay between items. Default: 80" }),
|
|
213
|
+
// ── Responsive overrides (tablet / phone) ──
|
|
214
|
+
defineField({
|
|
215
|
+
name: "responsive",
|
|
216
|
+
title: "Responsive Overrides",
|
|
217
|
+
type: "object",
|
|
218
|
+
fields: [
|
|
219
|
+
defineField({
|
|
220
|
+
name: "tablet",
|
|
221
|
+
title: "Tablet Overrides",
|
|
222
|
+
type: "object",
|
|
223
|
+
fields: [
|
|
224
|
+
defineField({ name: "font_size", title: "Font Size", type: "number" }),
|
|
225
|
+
defineField({ name: "font_weight", title: "Font Weight", type: "string" }),
|
|
226
|
+
defineField({ name: "text_align", title: "Text Align", type: "string" }),
|
|
227
|
+
defineField({ name: "vertical_align", title: "Vertical Align", type: "string" }),
|
|
228
|
+
defineField({ name: "text_transform", title: "Text Transform", type: "string" }),
|
|
229
|
+
defineField({ name: "padding_h", title: "Padding H", type: "number" }),
|
|
230
|
+
defineField({ name: "padding_v", title: "Padding V", type: "number" }),
|
|
231
|
+
defineField({ name: "margin_h", title: "Margin H", type: "number" }),
|
|
232
|
+
defineField({ name: "margin_v", title: "Margin V", type: "number" }),
|
|
233
|
+
defineField({ name: "items_gap", title: "Items Gap", type: "number" }),
|
|
234
|
+
],
|
|
235
|
+
}),
|
|
236
|
+
defineField({
|
|
237
|
+
name: "phone",
|
|
238
|
+
title: "Phone Overrides",
|
|
239
|
+
type: "object",
|
|
240
|
+
fields: [
|
|
241
|
+
defineField({ name: "font_size", title: "Font Size", type: "number" }),
|
|
242
|
+
defineField({ name: "font_weight", title: "Font Weight", type: "string" }),
|
|
243
|
+
defineField({ name: "text_align", title: "Text Align", type: "string" }),
|
|
244
|
+
defineField({ name: "vertical_align", title: "Vertical Align", type: "string" }),
|
|
245
|
+
defineField({ name: "text_transform", title: "Text Transform", type: "string" }),
|
|
246
|
+
defineField({ name: "padding_h", title: "Padding H", type: "number" }),
|
|
247
|
+
defineField({ name: "padding_v", title: "Padding V", type: "number" }),
|
|
248
|
+
defineField({ name: "margin_h", title: "Margin H", type: "number" }),
|
|
249
|
+
defineField({ name: "margin_v", title: "Margin V", type: "number" }),
|
|
250
|
+
defineField({ name: "items_gap", title: "Items Gap", type: "number" }),
|
|
251
|
+
],
|
|
252
|
+
}),
|
|
253
|
+
],
|
|
254
|
+
}),
|
|
213
255
|
],
|
|
214
256
|
}),
|
|
215
257
|
|
package/styles/base.css
CHANGED
|
@@ -58,17 +58,19 @@ body {
|
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
/* Prevent text overflow in grid columns on narrow viewports.
|
|
61
|
-
* overflow-wrap:
|
|
62
|
-
* cannot fit on a fresh line.
|
|
63
|
-
*
|
|
64
|
-
*
|
|
61
|
+
* overflow-wrap: break-word — breaks mid-word ONLY when the entire word
|
|
62
|
+
* cannot fit on a fresh line. Does NOT affect min-content intrinsic sizing,
|
|
63
|
+
* so CSS Grid columns keep their proper width (unlike 'anywhere' which
|
|
64
|
+
* collapses min-content to a single character width).
|
|
65
|
+
* word-break: normal — wraps at natural word boundaries first, preventing
|
|
66
|
+
* ugly mid-word splits like "Collecti" + "on". */
|
|
65
67
|
[data-site] p,
|
|
66
68
|
[data-site] h1,
|
|
67
69
|
[data-site] h2,
|
|
68
70
|
[data-site] h3,
|
|
69
71
|
[data-site] h4,
|
|
70
72
|
[data-site] span {
|
|
71
|
-
overflow-wrap:
|
|
73
|
+
overflow-wrap: break-word;
|
|
72
74
|
word-break: normal;
|
|
73
75
|
}
|
|
74
76
|
|