@lightspeed/crane 2.0.2 → 2.0.4

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lightspeed/crane",
3
- "version": "2.0.2",
3
+ "version": "2.0.4",
4
4
  "type": "module",
5
5
  "bin": "bin/crane.js",
6
6
  "main": "./dist/app.mjs",
@@ -64,8 +64,9 @@
64
64
  },
65
65
  "dependencies": {
66
66
  "@jridgewell/sourcemap-codec": "^1.5.4",
67
- "@lightspeed/crane-api": "1.0.2",
67
+ "@lightspeed/crane-api": "1.1.0",
68
68
  "@lightspeed/eslint-config-crane": "1.1.3",
69
+ "@types/micromatch": "^4.0.8",
69
70
  "@types/prompts": "^2.4.2",
70
71
  "@vitejs/plugin-vue": "^6.0.1",
71
72
  "adm-zip": "^0.5.16",
@@ -17,9 +17,6 @@ export default {
17
17
  logoType: 'IMAGE',
18
18
  imageData: {
19
19
  set: {
20
- LOW_RES: {
21
- url: 'lightspeed_logo.png',
22
- },
23
20
  MOBILE_WEBP_LOW_RES: {
24
21
  url: 'lightspeed_logo.png',
25
22
  },
@@ -0,0 +1,6 @@
1
+ export default {
2
+ metadata: {
3
+ title: 'Custom Page',
4
+ },
5
+ sections: [],
6
+ };
@@ -3,9 +3,54 @@ import type { IncomingMessage, ServerResponse } from 'http';
3
3
  import * as path from 'path';
4
4
  import { URL } from 'url';
5
5
 
6
+ import type {
7
+ ContentEditor,
8
+ ContentSettings,
9
+ DeckContentEditor,
10
+ DesignSettings,
11
+ LayoutSettings,
12
+ } from '@lightspeed/crane-api';
13
+
6
14
  import { getShowcaseData } from './preview';
7
15
  import { fetchTiles, updateTilesSection, updateCustomContent } from './utils';
8
16
 
17
+ /**
18
+ * AppBlock type definition for block-config response
19
+ */
20
+ interface AppBlock {
21
+ id: string;
22
+ type: string;
23
+ name: string;
24
+ contentEditors: ContentSettings;
25
+ designEditors: DesignSettings;
26
+ layouts: LayoutSettings[];
27
+ }
28
+
29
+ /**
30
+ * Layout config item for the config.layoutConfigList
31
+ */
32
+ interface LayoutConfigItem {
33
+ name: string;
34
+ contentEditorConfig: {
35
+ // mainEditors contains all fields from each editor definition
36
+ // with type transformed and translations extracted to English
37
+ mainEditors: Record<string, unknown>[];
38
+ buttons: unknown[];
39
+ };
40
+ designEditorConfig: {
41
+ // mainEditors contains all fields from each editor definition
42
+ // with type transformed and translations extracted to English
43
+ mainEditors: Record<string, unknown>[];
44
+ customEditors: unknown[];
45
+ buttons: unknown[];
46
+ designEditorGroups: unknown[];
47
+ };
48
+ defaults: Record<string, unknown>;
49
+ deprecated: boolean;
50
+ svgIcon: string;
51
+ svgIconText: string;
52
+ }
53
+
9
54
 
10
55
  /**
11
56
  * Extract query parameter from URL
@@ -52,6 +97,282 @@ function setNoCacheHeaders(res: ServerResponse): void {
52
97
  res.setHeader('Surrogate-Control', 'no-store');
53
98
  }
54
99
 
100
+ /**
101
+ * Check if an object is a translation object (has 'en' key with string value)
102
+ */
103
+ function isTranslationObject(obj: unknown): obj is Record<string, string> {
104
+ if (typeof obj !== 'object' || obj === null || Array.isArray(obj)) return false;
105
+ const record = obj as Record<string, unknown>;
106
+ return 'en' in record && typeof record.en === 'string';
107
+ }
108
+
109
+ /**
110
+ * Recursively process a value and extract English translations where applicable
111
+ * - If the value is a translation object (has 'en' key), return the English value
112
+ * - If the value is an array, process each element
113
+ * - If the value is an object, process each property
114
+ * - Otherwise, return the value as-is
115
+ */
116
+ function extractEnglishTranslations(value: unknown): unknown {
117
+ if (value === null || value === undefined) return value;
118
+
119
+ // Check if it's a translation object
120
+ if (isTranslationObject(value)) {
121
+ return value.en;
122
+ }
123
+
124
+ // Handle arrays
125
+ if (Array.isArray(value)) {
126
+ return value.map(item => extractEnglishTranslations(item));
127
+ }
128
+
129
+ // Handle objects (but not translation objects, which we already handled)
130
+ if (typeof value === 'object') {
131
+ const result: Record<string, unknown> = {};
132
+ for (const [key, val] of Object.entries(value as Record<string, unknown>)) {
133
+ result[key] = extractEnglishTranslations(val);
134
+ }
135
+ return result;
136
+ }
137
+
138
+ // Return primitives as-is
139
+ return value;
140
+ }
141
+
142
+ /**
143
+ * Transform editor type to the expected format
144
+ * INPUTBOX -> TEXT_EDITOR
145
+ * TEXTAREA -> MULTILINE_TEXT_EDITOR
146
+ * All other types -> {TYPE}_EDITOR (e.g., DECK -> DECK_EDITOR)
147
+ */
148
+ function transformEditorType(type: string): string {
149
+ const upperType = type.toUpperCase();
150
+ switch (upperType) {
151
+ case 'INPUTBOX':
152
+ return 'TEXT_EDITOR';
153
+ case 'TEXTAREA':
154
+ return 'MULTILINE_TEXT_EDITOR';
155
+ case 'BUTTON':
156
+ return 'ACTION_LINK_EDITOR';
157
+ default:
158
+ return `${upperType}_EDITOR`;
159
+ }
160
+ }
161
+
162
+ function processEditorExceptDeck(setting: ContentEditor, fieldName: string) {
163
+ const result: Record<string, unknown> = {
164
+ field: fieldName,
165
+ };
166
+
167
+ for (const [key, value] of Object.entries(setting)) {
168
+ if (key === 'type') {
169
+ result.type = transformEditorType(value as string);
170
+ } else {
171
+ result[key] = extractEnglishTranslations(value);
172
+ }
173
+ }
174
+ return result;
175
+ }
176
+
177
+ /**
178
+ * Process a DECK editor: transform cards.defaultCardContent.settings into editors array
179
+ */
180
+ function processDeckEditor(editor: DeckContentEditor, fieldName: string): Record<string, unknown> {
181
+ const result: Record<string, unknown> = {
182
+ field: fieldName,
183
+ type: 'DECK_EDITOR',
184
+ };
185
+
186
+ // Process cards to extract editors and defaultCard
187
+ const cards = editor.cards as Record<string, unknown> | undefined;
188
+ if (cards && cards.defaultCardContent) {
189
+ const defaultCardContent = cards.defaultCardContent as Record<string, unknown>;
190
+ const settings = defaultCardContent.settings as ContentSettings;
191
+
192
+ // Create defaultCard with label and editors
193
+ const defaultCard: Record<string, unknown> = {
194
+ label: extractEnglishTranslations(defaultCardContent.label),
195
+ };
196
+
197
+ // Create editors array from settings and add to defaultCard
198
+ if (settings) {
199
+ const editors: Record<string, unknown>[] = [];
200
+ for (const [settingFieldName, settingEditor] of Object.entries(settings)) {
201
+ const processedEditor: Record<string, unknown> = processEditorExceptDeck(settingEditor, settingFieldName);
202
+ editors.push(processedEditor);
203
+ }
204
+ defaultCard.editors = editors;
205
+ }
206
+
207
+ result.defaultCard = defaultCard;
208
+ }
209
+
210
+ // Copy other fields (maxCards, addButtonLabel, label, etc.) but skip 'cards' and 'type'
211
+ for (const [key, value] of Object.entries(editor)) {
212
+ if (key === 'type' || key === 'cards') {
213
+ continue;
214
+ }
215
+ result[key] = extractEnglishTranslations(value);
216
+ }
217
+
218
+ return result;
219
+ }
220
+
221
+ /**
222
+ * Process an editor definition: keep all fields, transform type, extract English translations
223
+ * For DECK type, special processing is applied to create editors array from cards.defaultCardContent.settings
224
+ */
225
+ function processEditor(editor: ContentEditor, fieldName: string): Record<string, unknown> {
226
+ const editorType = (editor.type as string || '').toUpperCase();
227
+
228
+ // Special handling for DECK type
229
+ if (editorType === 'DECK') {
230
+ return processDeckEditor(editor as DeckContentEditor, fieldName);
231
+ }
232
+
233
+ return processEditorExceptDeck(editor, fieldName);
234
+ }
235
+
236
+
237
+ /**
238
+ * Build layout config from appBlock data
239
+ * Creates the config.layoutConfigList structure from appBlock's layouts, contentEditors, and designEditors
240
+ */
241
+ function buildLayoutConfig(appBlock: AppBlock): { type: string; layoutConfigList: LayoutConfigItem[] } {
242
+ const { layouts, contentEditors, designEditors } = appBlock;
243
+
244
+ // contentEditors and designEditors are objects with field names as keys
245
+ const contentEditorKeys = Object.keys(contentEditors);
246
+ const designEditorKeys = Object.keys(designEditors);
247
+
248
+ const layoutConfigList = layouts.map((layout, index) => {
249
+ const { layoutId, selectedContentSettings, selectedDesignSettings } = layout;
250
+
251
+ const contentFieldsToInclude = selectedContentSettings.length === 0
252
+ ? contentEditorKeys
253
+ : selectedContentSettings;
254
+
255
+ const contentMainEditors = contentFieldsToInclude
256
+ .filter((fieldName) => {
257
+ const editor = contentEditors[fieldName];
258
+ return editor && editor.type;
259
+ })
260
+ .map((fieldName) => {
261
+ const editor = contentEditors[fieldName];
262
+ return processEditor(editor as ContentEditor, fieldName);
263
+ });
264
+
265
+ // Determine which design fields to include
266
+ const designFieldsToInclude = selectedDesignSettings.length === 0
267
+ ? designEditorKeys
268
+ : selectedDesignSettings.map(s => s.fieldName);
269
+
270
+ // Build design mainEditors - include all design editor types with all their fields
271
+ const designMainEditors = designFieldsToInclude
272
+ .filter((fieldName) => {
273
+ const editor = designEditors[fieldName];
274
+ return editor && editor.type;
275
+ })
276
+ .map((fieldName) => {
277
+ const editor = designEditors[fieldName];
278
+ return processEditor(editor as ContentEditor, fieldName);
279
+ });
280
+
281
+ // Build defaults from design editors (extract English translations)
282
+ const defaults: Record<string, unknown> = {};
283
+ designFieldsToInclude.forEach((fieldName) => {
284
+ const editor = designEditors[fieldName];
285
+ if (editor && editor.defaults) {
286
+ defaults[fieldName] = extractEnglishTranslations(editor.defaults);
287
+ }
288
+ });
289
+
290
+ // Apply layout-specific design overrides from selectedDesignSettings
291
+ selectedDesignSettings.forEach((setting) => {
292
+ if (typeof setting === 'object' && setting.fieldName && setting.defaults) {
293
+ defaults[setting.fieldName] = {
294
+ ...(defaults[setting.fieldName] as Record<string, unknown> || {}),
295
+ ...(extractEnglishTranslations(setting.defaults) as Record<string, unknown>),
296
+ };
297
+ }
298
+ });
299
+
300
+ return {
301
+ name: layoutId,
302
+ contentEditorConfig: {
303
+ mainEditors: contentMainEditors,
304
+ buttons: [],
305
+ },
306
+ designEditorConfig: {
307
+ mainEditors: designMainEditors,
308
+ customEditors: [],
309
+ buttons: [],
310
+ designEditorGroups: [],
311
+ },
312
+ defaults,
313
+ deprecated: false,
314
+ svgIcon: 'CustomAutomaticLayoutIcon',
315
+ svgIconText: String(index + 1),
316
+ };
317
+ });
318
+
319
+ return {
320
+ type: 'CUSTOM',
321
+ layoutConfigList,
322
+ };
323
+ }
324
+
325
+ /**
326
+ * Handle GET /api/v1/block-config-full
327
+ * Fetches appBlock from /api/v1/block-config and adds config.layoutConfigList
328
+ */
329
+ async function handleGetBlockConfigFull(
330
+ req: IncomingMessage,
331
+ res: ServerResponse,
332
+ sectionName: string,
333
+ typeParam: string,
334
+ ): Promise<void> {
335
+ try {
336
+ // Fetch appBlock from the CLI's /api/v1/block-config endpoint
337
+ const host = req.headers.host || 'localhost:5173';
338
+ const protocol = 'http';
339
+ const blockConfigUrl = `${protocol}://${host}/api/v1/block-config?section=${encodeURIComponent(sectionName)}&type=${encodeURIComponent(typeParam)}`;
340
+
341
+ const response = await fetch(blockConfigUrl);
342
+
343
+ if (!response.ok) {
344
+ const errorText = await response.text();
345
+ res.statusCode = response.status;
346
+ res.setHeader('Content-Type', 'application/json');
347
+ setNoCacheHeaders(res);
348
+ res.end(errorText);
349
+ return;
350
+ }
351
+
352
+ const appBlock = await response.json() as AppBlock;
353
+
354
+ // Build the config with layoutConfigList
355
+ const config = buildLayoutConfig(appBlock);
356
+
357
+ // Return appBlock with config added
358
+ res.statusCode = 200;
359
+ res.setHeader('Content-Type', 'application/json');
360
+ setNoCacheHeaders(res);
361
+ res.end(JSON.stringify({
362
+ config,
363
+ }, null, 2));
364
+ } catch (error) {
365
+ console.error('[API Routes] ❌ Error fetching block config:', error);
366
+ res.statusCode = 500;
367
+ res.setHeader('Content-Type', 'application/json');
368
+ setNoCacheHeaders(res);
369
+ res.end(JSON.stringify({
370
+ error: 'Failed to fetch block config',
371
+ message: error instanceof Error ? error.message : String(error),
372
+ }));
373
+ }
374
+ }
375
+
55
376
  /**
56
377
  * Get all available showcases for a section from the dist folder
57
378
  */
@@ -67,15 +388,8 @@ function getAvailableShowcases(sectionName: string, distPath: string): string[]
67
388
  const showcases = fs.readdirSync(showcasesPath, { withFileTypes: true })
68
389
  .filter(dirent => dirent.isFile() && dirent.name.endsWith('.mjs'))
69
390
  .map(dirent => dirent.name.replace('.mjs', ''))
70
- .sort((a, b) => {
71
- // Sort numerically if both are numbers, otherwise alphabetically
72
- const aNum = parseInt(a, 10);
73
- const bNum = parseInt(b, 10);
74
- if (!isNaN(aNum) && !isNaN(bNum)) {
75
- return aNum - bNum;
76
- }
77
- return a.localeCompare(b);
78
- });
391
+ // Filter out non-showcase files like 'translations'
392
+ .filter(name => name !== 'translations');
79
393
 
80
394
  return showcases;
81
395
  } catch (error) {
@@ -124,7 +438,6 @@ async function handleGetTile(
124
438
  });
125
439
 
126
440
  const allShowcaseData = await Promise.all(showcaseDataPromises);
127
-
128
441
  for (const { sectionName, showcaseId, content, design } of allShowcaseData) {
129
442
  responseData = updateTilesSection(responseData, sectionName, showcaseId, content, design);
130
443
  }
@@ -159,7 +472,14 @@ async function handleUpdateTile(
159
472
  ): Promise<void> {
160
473
  const requestBody = await readRequestBody(req);
161
474
  try {
162
- // Read request body containing tiles array
475
+ // If URL matches tile/custom-* pattern, return request body as-is
476
+ if (originalUrl && /\/tile\/custom-[a-zA-Z0-9]+/.test(originalUrl)) {
477
+ res.statusCode = 200;
478
+ res.setHeader('Content-Type', 'application/json');
479
+ setNoCacheHeaders(res);
480
+ res.end(JSON.stringify(requestBody, null, 2));
481
+ return;
482
+ }
163
483
 
164
484
  if (!requestBody || !Array.isArray(requestBody.tiles)) {
165
485
  res.statusCode = 400;
@@ -583,6 +903,12 @@ export function handleApiRequest(
583
903
  return;
584
904
  }
585
905
 
906
+ if (url.startsWith('/api/v1/block-config-full')) {
907
+ const typeParam = getQueryParam(url, 'type') || 'SECTION';
908
+ handleGetBlockConfigFull(req, res, sectionName, typeParam);
909
+ return;
910
+ }
911
+
586
912
  if (url.startsWith('/api/v1/client-js')) {
587
913
  handleGetClientJs(req, res, sectionName);
588
914
  return;
@@ -264,9 +264,12 @@ export function designTransformer(design: Record<string, any>, showCaseDesign: R
264
264
  return overridenDesign;
265
265
  }
266
266
 
267
- function processImage(component: any, sectionName: string, key: string): Record<string, any> {
268
- const assetLocation = `${distFolderPath}/sections/${sectionName}/assets/`;
269
- const set = component.defaults?.set || component.imageData?.set;
267
+ function processImage(component: any, sectionName: string, key: string, isApiRender: boolean = false): Record<string, any> {
268
+ const assetLocation = isApiRender ?
269
+ `http://${distFolderPath}/sections/${sectionName}/assets/` :
270
+ `${distFolderPath}/sections/${sectionName}/assets/`;
271
+
272
+ const set = component.defaults?.imageData?.set || component.imageData?.set;
270
273
  const newSet = {
271
274
  'cropped-webp-100x200': { url: assetLocation + set.MOBILE_WEBP_LOW_RES.url },
272
275
  'cropped-webp-1000x2000': { url: assetLocation + set.MOBILE_WEBP_HI_RES.url },
@@ -282,7 +285,8 @@ function processImage(component: any, sectionName: string, key: string): Record<
282
285
  };
283
286
  }
284
287
 
285
- function processComponent(key: string, component: any, translations: any, sectionName?: string): any {
288
+ function processComponent(key: string, component: any, translations: any, sectionName?: string,
289
+ isApiRender: boolean = false, extraParams?: Record<string, any>): any {
286
290
  if (!component?.type) return {};
287
291
  switch (component.type) {
288
292
  case 'INPUTBOX':
@@ -297,16 +301,16 @@ function processComponent(key: string, component: any, translations: any, sectio
297
301
  if (defaultSettings) {
298
302
  const result: Record<string, any> = {};
299
303
  Object.entries(defaultSettings).forEach(([k, c]) => {
300
- Object.assign(result, processComponent(k, c, translations));
304
+ Object.assign(result, processComponent(k, c, translations, sectionName, isApiRender));
301
305
  });
302
306
  return { [key]: result };
303
307
  } else {
304
- const cards = component.cards.map((card: any) => {
308
+ const cards = component.cards.map((card: any, index: number) => {
305
309
  const cardContent: Record<string, any> = {};
306
310
  Object.entries(card.settings).forEach(([k, c]) => {
307
- Object.assign(cardContent, processComponent(k, c, translations, sectionName));
311
+ Object.assign(cardContent, processComponent(k, c, translations, sectionName, isApiRender));
308
312
  });
309
- return { settings: cardContent };
313
+ return { settings: cardContent, id: index.toString(), title: extraParams?.deckCardLabel};
310
314
  });
311
315
  return { [key]: { cards } };
312
316
  }
@@ -321,12 +325,17 @@ function processComponent(key: string, component: any, translations: any, sectio
321
325
  },
322
326
  };
323
327
  }
324
- case 'TOGGLE':
325
- return { [key]: component.defaults };
328
+ case 'TOGGLE': {
329
+ return {
330
+ [key]: {
331
+ enabled: component.defaults?.enabled ?? component.enabled
332
+ }
333
+ };
334
+ }
326
335
  case 'SELECTBOX':
327
- return { [key]: component.defaults.value };
336
+ return { [key]: component.defaults?.value ?? component.value };
328
337
  case 'IMAGE':
329
- return processImage(component, sectionName!, key);
338
+ return processImage(component, sectionName!, key, isApiRender);
330
339
  case 'INFO':
331
340
  return {
332
341
  [key]: {
@@ -349,13 +358,19 @@ function getContentToRender(
349
358
  contentTranslations: any,
350
359
  showcaseTranslations: any,
351
360
  sectionName: string,
361
+ isApiRender: boolean = false,
352
362
  ): any {
363
+ let deckCardLabel: string | undefined = undefined;
353
364
  const parsedContent = Object.entries(content).reduce((acc, [k, c]) => {
354
- return { ...acc, ...processComponent(k, c, contentTranslations, sectionName) };
365
+ if ((c as any).type === 'DECK') {
366
+ deckCardLabel = contentTranslations[(c as any).cards?.defaultCardContent?.label];
367
+ }
368
+ return { ...acc, ...processComponent(k, c, contentTranslations, sectionName, isApiRender) };
355
369
  }, {});
356
370
 
371
+
357
372
  const parsedShowcase = Object.entries(showcase).reduce((acc, [k, c]) => {
358
- return { ...acc, ...processComponent(k, c, showcaseTranslations, sectionName) };
373
+ return { ...acc, ...processComponent(k, c, showcaseTranslations, sectionName, isApiRender, {deckCardLabel}) };
359
374
  }, {});
360
375
 
361
376
  return overrideSettingsFromShowcase(parsedContent, parsedShowcase);
@@ -471,6 +486,7 @@ export async function getShowcaseData(sectionName: string, showcaseId: string, d
471
486
  (contentTranslations as any).default.en,
472
487
  (showcaseTranslations as any).default.en,
473
488
  sectionName,
489
+ true
474
490
  );
475
491
 
476
492
  return {
@@ -204,7 +204,6 @@ async function updateSectionShowcase(
204
204
  if (!sectionIds || sectionIds.length === 0) {
205
205
  return false;
206
206
  }
207
-
208
207
  // Process each section ID
209
208
  for (const sectionId of sectionIds) {
210
209
  // Find the section by ID in custom content
@@ -98,6 +98,11 @@ export default defineConfig({
98
98
 
99
99
  // Handle API routes
100
100
  if (url.startsWith('/api/v1/')) {
101
+ // Skip block-config route (but not block-config-full) - handled by CLI middleware
102
+ if (url.startsWith('/api/v1/block-config') && !url.startsWith('/api/v1/block-config-full')) {
103
+ return next();
104
+ }
105
+
101
106
  // Extract auth header from request
102
107
  const authHeader = req.headers['authorization'] || '';
103
108
 
@@ -1,4 +1,4 @@
1
- const content: ContentEditor = {
1
+ export default {
2
2
  title: {
3
3
  type: 'INPUTBOX',
4
4
  label: '$label.section_title.label',
@@ -17,15 +17,17 @@ const content: ContentEditor = {
17
17
  type: 'INPUTBOX',
18
18
  label: '$label.stat_value.label',
19
19
  placeholder: '$label.stat_value.placeholder',
20
- text: '$label.stat_value.default.text',
21
- title: '$label.stat_value.default.text',
20
+ defaults: {
21
+ text: '$label.stat_value.default.text',
22
+ },
22
23
  },
23
24
  stat_caption: {
24
25
  type: 'INPUTBOX',
25
26
  label: '$label.stat_caption.label',
26
27
  placeholder: '$label.stat_caption.placeholder',
27
- text: '$label.stat_caption.default.text',
28
- title: '$label.stat_caption.default.text',
28
+ defaults: {
29
+ text: '$label.stat_caption.default.text',
30
+ },
29
31
  },
30
32
  },
31
33
  },
@@ -34,28 +36,33 @@ const content: ContentEditor = {
34
36
  button: {
35
37
  type: 'BUTTON',
36
38
  label: '$label.button_content.label',
37
- title: '$label.button_content.title',
39
+ defaults: {
40
+ title: '$label.button_content.title',
41
+ buttonType: 'HYPER_LINK',
42
+ link: 'https://www.example.com',
43
+ },
38
44
  },
39
45
  image: {
40
46
  type: 'IMAGE',
41
47
  label: '$label.image_content.label',
42
48
  defaults: {
43
- set: {
44
- MOBILE_WEBP_LOW_RES: {
45
- url: 'our_company_in_numbers.jpg',
46
- },
47
- MOBILE_WEBP_HI_RES: {
48
- url: 'our_company_in_numbers.jpg',
49
- },
50
- WEBP_LOW_RES: {
51
- url: 'our_company_in_numbers.jpg',
52
- },
53
- WEBP_HI_2X_RES: {
54
- url: 'our_company_in_numbers.jpg',
49
+ imageData: {
50
+ set: {
51
+ MOBILE_WEBP_LOW_RES: {
52
+ url: 'our_company_in_numbers.jpg',
53
+ },
54
+ MOBILE_WEBP_HI_RES: {
55
+ url: 'our_company_in_numbers.jpg',
56
+ },
57
+ WEBP_LOW_RES: {
58
+ url: 'our_company_in_numbers.jpg',
59
+ },
60
+ WEBP_HI_2X_RES: {
61
+ url: 'our_company_in_numbers.jpg',
62
+ },
55
63
  },
64
+ borderInfo: {},
56
65
  },
57
- borderInfo: {},
58
66
  },
59
67
  },
60
- };
61
- export default content;
68
+ } as const;
@@ -66,7 +66,7 @@ export default {
66
66
  defaults: {
67
67
  appearance: 'SOLID',
68
68
  size: 'MEDIUM',
69
- style: 'pill',
69
+ shape: 'PILL',
70
70
  color: '#000000',
71
71
  visible: true,
72
72
  },
@@ -103,6 +103,7 @@ export default {
103
103
  type: 'BUTTON',
104
104
  title: '$label.showcase_1.button_content.title',
105
105
  buttonType: 'HYPER_LINK',
106
+ link: 'https://www.example.com',
106
107
  },
107
108
  },
108
109
  design: {
@@ -91,6 +91,7 @@ export default {
91
91
  type: 'BUTTON',
92
92
  title: '$label.showcase_2.button_content.title',
93
93
  buttonType: 'HYPER_LINK',
94
+ link: 'https://www.example.com',
94
95
  },
95
96
  },
96
97
  design: {
@@ -17,7 +17,11 @@ export default {
17
17
  section_button: {
18
18
  type: 'BUTTON',
19
19
  label: '$label.section_button.label',
20
- title: '$label.section_button.title',
20
+ defaults: {
21
+ title: '$label.section_button.title',
22
+ buttonType: 'HYPER_LINK',
23
+ link: 'https://www.example.com',
24
+ },
21
25
  },
22
26
  slider: {
23
27
  type: 'DECK',
@@ -48,7 +48,7 @@ export default {
48
48
  defaults: {
49
49
  appearance: 'SOLID',
50
50
  size: 'MEDIUM',
51
- style: 'pill',
51
+ shape: 'PILL',
52
52
  color: '#000000',
53
53
  visible: true,
54
54
  },
@@ -26,6 +26,7 @@ export default {
26
26
  type: 'BUTTON',
27
27
  title: '$label.showcase_1.section_button.title',
28
28
  buttonType: 'HYPER_LINK',
29
+ link: 'https://www.example.com',
29
30
  },
30
31
  slider: {
31
32
  type: 'DECK',