@morphika/andami 0.3.1 → 0.4.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.
@@ -0,0 +1,218 @@
1
+ import { defineField, defineType } from "sanity";
2
+ import { blockLayoutField, blockAnimationFields } from "./blockLayout";
3
+
4
+ /**
5
+ * projectCarouselBlock — horizontal carousel of projects.
6
+ *
7
+ * Intended use: "keep browsing" row at the end of a project page, but usable
8
+ * on any page. Projects are pulled automatically (latest / random) — no
9
+ * manual selection list. When placed on /work/[slug], the current project
10
+ * can be filtered out automatically via `exclude_current`.
11
+ *
12
+ * Treated as a **section-level block** (same semantics as projectGridBlock):
13
+ * it lives in a full-width column of a pageSectionV2 and is added via the
14
+ * "+ Add Section" modal, not the "+ Add Block" picker.
15
+ *
16
+ * Intentionally independent from projectGridBlock — the two share visual
17
+ * primitives (thumbnail resolution, project data shape) but no coupled
18
+ * code, so changes on one side never break the other.
19
+ */
20
+ export const projectCarouselBlock = defineType({
21
+ name: "projectCarouselBlock",
22
+ title: "Project Carousel",
23
+ type: "object",
24
+ fields: [
25
+ // ─── Source ───
26
+ defineField({
27
+ name: "source_mode",
28
+ title: "Source",
29
+ type: "string",
30
+ description: "How projects are selected for the carousel",
31
+ options: {
32
+ list: [
33
+ { title: "Latest projects", value: "auto_latest" },
34
+ { title: "Random projects", value: "auto_random" },
35
+ ],
36
+ },
37
+ initialValue: "auto_latest",
38
+ }),
39
+ defineField({
40
+ name: "max_projects",
41
+ title: "Number of projects",
42
+ type: "number",
43
+ description: "How many projects to show (2–20)",
44
+ initialValue: 8,
45
+ validation: (Rule) => Rule.min(2).max(20).integer(),
46
+ }),
47
+ defineField({
48
+ name: "exclude_current",
49
+ title: "Exclude current project",
50
+ type: "boolean",
51
+ description:
52
+ "When the carousel is on a /work/[slug] page, automatically filter out the project currently being viewed",
53
+ initialValue: true,
54
+ }),
55
+
56
+ // ─── Layout ───
57
+ defineField({
58
+ name: "cards_per_view_desktop",
59
+ title: "Cards per view (desktop)",
60
+ type: "number",
61
+ description: "Supports fractional values — e.g. 3.5 shows half of the next card to hint 'more content'",
62
+ initialValue: 3.5,
63
+ validation: (Rule) => Rule.min(1).max(6),
64
+ }),
65
+ defineField({
66
+ name: "cards_per_view_tablet",
67
+ title: "Cards per view (tablet)",
68
+ type: "number",
69
+ initialValue: 2.2,
70
+ validation: (Rule) => Rule.min(1).max(4),
71
+ }),
72
+ defineField({
73
+ name: "cards_per_view_phone",
74
+ title: "Cards per view (phone)",
75
+ type: "number",
76
+ initialValue: 1.2,
77
+ validation: (Rule) => Rule.min(1).max(3),
78
+ }),
79
+ defineField({
80
+ name: "gap",
81
+ title: "Gap between cards (px)",
82
+ type: "number",
83
+ initialValue: 16,
84
+ validation: (Rule) => Rule.min(0).max(80),
85
+ }),
86
+
87
+ // ─── Card Display ───
88
+ defineField({
89
+ name: "aspect_ratio",
90
+ title: "Card aspect ratio",
91
+ type: "string",
92
+ options: {
93
+ list: [
94
+ { title: "Landscape (16:9)", value: "16/9" },
95
+ { title: "Standard (4:3)", value: "4/3" },
96
+ { title: "Square (1:1)", value: "1/1" },
97
+ { title: "Portrait (3:4)", value: "3/4" },
98
+ { title: "Tall (9:16)", value: "9/16" },
99
+ ],
100
+ },
101
+ initialValue: "4/3",
102
+ }),
103
+ defineField({
104
+ name: "show_title",
105
+ title: "Show title",
106
+ type: "boolean",
107
+ initialValue: true,
108
+ }),
109
+ defineField({
110
+ name: "show_subtitle",
111
+ title: "Show subtitle",
112
+ type: "boolean",
113
+ initialValue: false,
114
+ }),
115
+ defineField({
116
+ name: "border_radius",
117
+ title: "Border radius (px)",
118
+ type: "number",
119
+ initialValue: 0,
120
+ }),
121
+ defineField({
122
+ name: "hover_effect",
123
+ title: "Hover effect",
124
+ type: "string",
125
+ options: {
126
+ list: [
127
+ { title: "Scale", value: "scale" },
128
+ { title: "None", value: "none" },
129
+ ],
130
+ },
131
+ initialValue: "scale",
132
+ }),
133
+
134
+ // ─── Video ───
135
+ defineField({
136
+ name: "video_mode",
137
+ title: "Video mode",
138
+ type: "string",
139
+ options: {
140
+ list: [
141
+ { title: "Off", value: "off" },
142
+ { title: "Hover", value: "hover" },
143
+ { title: "Autoloop", value: "autoloop" },
144
+ ],
145
+ },
146
+ initialValue: "off",
147
+ }),
148
+
149
+ // ─── Controls ───
150
+ defineField({
151
+ name: "show_arrows",
152
+ title: "Show prev / next arrows",
153
+ type: "boolean",
154
+ initialValue: true,
155
+ }),
156
+ defineField({
157
+ name: "show_dots",
158
+ title: "Show pagination dots",
159
+ type: "boolean",
160
+ initialValue: false,
161
+ }),
162
+ defineField({
163
+ name: "snap_scroll",
164
+ title: "Snap scrolling",
165
+ type: "boolean",
166
+ description: "Cards snap into place as the user scrolls / swipes",
167
+ initialValue: true,
168
+ }),
169
+
170
+ // ─── Card entrance animation ───
171
+ defineField({
172
+ name: "card_entrance",
173
+ title: "Card Entrance Animation",
174
+ type: "object",
175
+ fields: [
176
+ defineField({ name: "enabled", title: "Enabled", type: "boolean", initialValue: false }),
177
+ defineField({
178
+ name: "preset",
179
+ title: "Preset",
180
+ type: "string",
181
+ options: {
182
+ list: [
183
+ { title: "Fade", value: "fade" },
184
+ { title: "Slide Up", value: "slide-up" },
185
+ { title: "Scale", value: "scale" },
186
+ ],
187
+ },
188
+ initialValue: "slide-up",
189
+ }),
190
+ defineField({
191
+ name: "stagger_delay",
192
+ title: "Stagger Delay",
193
+ type: "number",
194
+ description: "Delay between cards (ms)",
195
+ initialValue: 80,
196
+ }),
197
+ defineField({
198
+ name: "duration",
199
+ title: "Duration",
200
+ type: "number",
201
+ description: "Animation duration (ms)",
202
+ initialValue: 500,
203
+ }),
204
+ ],
205
+ }),
206
+
207
+ // ─── Standard block fields ───
208
+ ...blockAnimationFields,
209
+ blockLayoutField,
210
+ ],
211
+ preview: {
212
+ select: { mode: "source_mode", n: "max_projects" },
213
+ prepare({ mode, n }: { mode?: string; n?: number }) {
214
+ const modeLabel = mode === "auto_random" ? "Random" : "Latest";
215
+ return { title: `Project Carousel (${modeLabel} · ${n ?? 8})` };
216
+ },
217
+ },
218
+ });
@@ -19,6 +19,7 @@ import {
19
19
  spacerBlock,
20
20
  buttonBlock,
21
21
  projectGridBlock,
22
+ projectCarouselBlock,
22
23
  } from "./blocks";
23
24
 
24
25
  // Re-export individual schemas for granular use by instances
@@ -69,6 +70,7 @@ export {
69
70
  spacerBlock,
70
71
  buttonBlock,
71
72
  projectGridBlock,
73
+ projectCarouselBlock,
72
74
  } from "./blocks";
73
75
 
74
76
  export const schemaTypes = [
@@ -91,7 +93,7 @@ export const schemaTypes = [
91
93
  parallaxGroup, // Parallax V2 group (Session 123)
92
94
  coverSection, // Cover Section — proportional rows (Session 176)
93
95
 
94
- // Blocks (8)
96
+ // Blocks (9)
95
97
  textBlock,
96
98
  imageBlock,
97
99
  imageGridBlock,
@@ -99,6 +101,7 @@ export const schemaTypes = [
99
101
  spacerBlock,
100
102
  buttonBlock,
101
103
  projectGridBlock,
104
+ projectCarouselBlock,
102
105
  ];
103
106
 
104
107
  /**
@@ -67,6 +67,7 @@ export default defineType({
67
67
  { type: "spacerBlock" },
68
68
  { type: "buttonBlock" },
69
69
  { type: "projectGridBlock" },
70
+ { type: "projectCarouselBlock" },
70
71
  ],
71
72
  }),
72
73
  // Column-level enter animation (Session 117)