@adobe-commerce/elsie 1.2.1 → 1.3.0-alpha01
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/config/vite.mjs +39 -5
- package/package.json +1 -1
- package/src/components/ImageSwatch/ImageSwatch.css +6 -0
- package/src/components/ProductItemCard/ProductItemCard.css +87 -0
- package/src/components/ProductItemCard/ProductItemCard.stories.tsx +371 -0
- package/src/components/ProductItemCard/ProductItemCard.tsx +94 -0
- package/src/components/ProductItemCard/ProductItemCardSkeleton.css +40 -0
- package/src/components/ProductItemCard/ProductItemCardSkeleton.tsx +42 -0
- package/src/components/ProductItemCard/index.ts +11 -0
- package/src/components/index.ts +1 -0
- package/src/docs/Utilities/links.mdx +58 -0
- package/src/lib/aem/configs.ts +237 -0
package/config/vite.mjs
CHANGED
|
@@ -25,9 +25,11 @@ import banner from 'vite-plugin-banner';
|
|
|
25
25
|
const env = loadEnv('', process.cwd());
|
|
26
26
|
|
|
27
27
|
// Load Elsie Config
|
|
28
|
-
const elsieConfig = await import(
|
|
29
|
-
|
|
30
|
-
|
|
28
|
+
const elsieConfig = await import(path.resolve(process.cwd(), './.elsie.js')).then((m) => m.default);
|
|
29
|
+
|
|
30
|
+
// Read package.json using createRequire (compatible with Node 20 and 22)
|
|
31
|
+
const require = createRequire(import.meta.url);
|
|
32
|
+
const packageJSON = require(path.resolve(process.cwd(), './package.json'));
|
|
31
33
|
|
|
32
34
|
// Paths
|
|
33
35
|
const paths = {
|
|
@@ -116,7 +118,6 @@ export default {
|
|
|
116
118
|
entryFileNames: '[name].js',
|
|
117
119
|
assetFileNames: '[name].[ext]',
|
|
118
120
|
chunkFileNames: 'chunks/[name].js',
|
|
119
|
-
|
|
120
121
|
manualChunks: (id) => {
|
|
121
122
|
if (id.includes(paths.fragments)) {
|
|
122
123
|
// Fragments file does not accept chunking
|
|
@@ -176,7 +177,7 @@ export default {
|
|
|
176
177
|
modulePreload: false,
|
|
177
178
|
commonjsOptions: { transformMixedEsModules: true },
|
|
178
179
|
minify: !!isProd,
|
|
179
|
-
sourcemap:
|
|
180
|
+
sourcemap: true,
|
|
180
181
|
},
|
|
181
182
|
|
|
182
183
|
optimizeDeps: {},
|
|
@@ -287,6 +288,39 @@ export default {
|
|
|
287
288
|
},
|
|
288
289
|
}),
|
|
289
290
|
|
|
291
|
+
{
|
|
292
|
+
name: 'rewrite-sourcemap-sources',
|
|
293
|
+
generateBundle(options, bundle) {
|
|
294
|
+
for (const fileName in bundle) {
|
|
295
|
+
const chunk = bundle[fileName];
|
|
296
|
+
|
|
297
|
+
// Process both .map files and JS/TS files with sourcemaps
|
|
298
|
+
if ((chunk.type === 'asset' && fileName.endsWith('.map')) ||
|
|
299
|
+
(chunk.type === 'chunk' && chunk.map)) {
|
|
300
|
+
try {
|
|
301
|
+
// Get the sourcemap object - either from the asset source or the chunk's map
|
|
302
|
+
const map = chunk.type === 'asset' ? JSON.parse(chunk.source) : chunk.map;
|
|
303
|
+
|
|
304
|
+
if (map.sources) {
|
|
305
|
+
map.sources = map.sources.map((input) => {
|
|
306
|
+
return input.replace(/(?:\.\.?\/)+src\//, `/${packageJSON.name}/src/`);
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
// Update the sourcemap in the appropriate place
|
|
310
|
+
if (chunk.type === 'asset') {
|
|
311
|
+
chunk.source = JSON.stringify(map);
|
|
312
|
+
} else {
|
|
313
|
+
chunk.map = map;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
} catch (e) {
|
|
317
|
+
console.error('Error transforming sourcemap:', e);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
},
|
|
323
|
+
|
|
290
324
|
process.env.ANALYZE
|
|
291
325
|
? visualizer({
|
|
292
326
|
title: `${elsieConfig.name} Dropin Bundle Analysis`,
|
package/package.json
CHANGED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/********************************************************************
|
|
2
|
+
* Copyright 2025 Adobe
|
|
3
|
+
* All Rights Reserved.
|
|
4
|
+
*
|
|
5
|
+
* NOTICE: Adobe permits you to use, modify, and distribute this
|
|
6
|
+
* file in accordance with the terms of the Adobe license agreement
|
|
7
|
+
* accompanying it.
|
|
8
|
+
*******************************************************************/
|
|
9
|
+
|
|
10
|
+
/* https://cssguidelin.es/#bem-like-naming */
|
|
11
|
+
|
|
12
|
+
.dropin-product-item-card {
|
|
13
|
+
display: grid;
|
|
14
|
+
position: relative;
|
|
15
|
+
grid-auto-flow: row;
|
|
16
|
+
background: var(--color-neutral-50);
|
|
17
|
+
border: 1px solid var(--color-neutral-400);
|
|
18
|
+
color: var(--color-neutral-800);
|
|
19
|
+
font: var(--type-body-1-default-font);
|
|
20
|
+
letter-spacing: var(--type-body-1-default-letter-spacing);
|
|
21
|
+
margin: var(--spacing-small);
|
|
22
|
+
width: 300px;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.dropin-product-item-card__image-container {
|
|
26
|
+
overflow: hidden;
|
|
27
|
+
width: 100%;
|
|
28
|
+
height: auto;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.dropin-product-item-card__image img {
|
|
32
|
+
width: 100%;
|
|
33
|
+
max-height: 375px;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.dropin-product-item-card__content {
|
|
37
|
+
display: grid;
|
|
38
|
+
grid-template-columns: 1fr 1fr;
|
|
39
|
+
padding: var(--spacing-small);
|
|
40
|
+
gap: var(--spacing-xxsmall);
|
|
41
|
+
align-items: center;
|
|
42
|
+
color: var(--color-neutral-800);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.dropin-product-item-card__title,
|
|
46
|
+
.dropin-product-item-card__sku,
|
|
47
|
+
.dropin-product-item-card__price,
|
|
48
|
+
.dropin-product-item-card__swatches,
|
|
49
|
+
.dropin-product-item-card__action {
|
|
50
|
+
grid-column: 1/3;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.dropin-product-item-card__title {
|
|
54
|
+
font: var(--type-body-1-strong-font);
|
|
55
|
+
letter-spacing: var(--type-body-1-strong-letter-spacing);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.dropin-product-item-card__sku {
|
|
59
|
+
font: var(--type-body-1-default-font);
|
|
60
|
+
letter-spacing: var(--type-body-1-default-letter-spacing);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.dropin-product-item-card__price {
|
|
64
|
+
font: var(--type-body-1-default-font);
|
|
65
|
+
letter-spacing: var(--type-body-1-default-letter-spacing);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.dropin-product-item-card__swatches {
|
|
69
|
+
margin-top: var(--spacing-xsmall);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.dropin-product-item-card__action {
|
|
73
|
+
margin-top: var(--spacing-xsmall);
|
|
74
|
+
width: 100%;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/* Medium (portrait tablets and large phones, 768px and up) */
|
|
78
|
+
/* @media only screen and (min-width: 768px) { } */
|
|
79
|
+
|
|
80
|
+
/* Large (landscape tablets, 1024px and up) */
|
|
81
|
+
/* @media only screen and (min-width: 1024px) { } */
|
|
82
|
+
|
|
83
|
+
/* XLarge (laptops/desktops, 1366px and up) */
|
|
84
|
+
/* @media only screen and (min-width: 1366px) { } */
|
|
85
|
+
|
|
86
|
+
/* XXlarge (large laptops and desktops, 1920px and up) */
|
|
87
|
+
/* @media only screen and (min-width: 1920px) { } */
|
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
/********************************************************************
|
|
2
|
+
* ADOBE CONFIDENTIAL
|
|
3
|
+
* __________________
|
|
4
|
+
*
|
|
5
|
+
* Copyright 2025 Adobe
|
|
6
|
+
* All Rights Reserved.
|
|
7
|
+
*
|
|
8
|
+
* NOTICE: All information contained herein is, and remains
|
|
9
|
+
* the property of Adobe and its suppliers, if any. The intellectual
|
|
10
|
+
* and technical concepts contained herein are proprietary to Adobe
|
|
11
|
+
* and its suppliers and are protected by all applicable intellectual
|
|
12
|
+
* property laws, including trade secret and copyright laws.
|
|
13
|
+
* Dissemination of this information or reproduction of this material
|
|
14
|
+
* is strictly forbidden unless prior written permission is obtained
|
|
15
|
+
* from Adobe.
|
|
16
|
+
*******************************************************************/
|
|
17
|
+
|
|
18
|
+
// https://storybook.js.org/docs/7.0/preact/writing-stories/introduction
|
|
19
|
+
import type { Meta, StoryObj } from '@storybook/preact';
|
|
20
|
+
import {
|
|
21
|
+
Button,
|
|
22
|
+
Icon,
|
|
23
|
+
Image,
|
|
24
|
+
Price,
|
|
25
|
+
ProductItemCard as component,
|
|
26
|
+
ProductItemCardProps,
|
|
27
|
+
ColorSwatch,
|
|
28
|
+
} from '@adobe-commerce/elsie/components';
|
|
29
|
+
import { Cart } from '@adobe-commerce/elsie/icons';
|
|
30
|
+
/**
|
|
31
|
+
* Use ProductItemCard to display product recommendations with image, title, price, SKU, and action button.
|
|
32
|
+
*/
|
|
33
|
+
const meta: Meta<ProductItemCardProps> = {
|
|
34
|
+
title: 'Components/ProductItemCard',
|
|
35
|
+
component,
|
|
36
|
+
|
|
37
|
+
argTypes: {
|
|
38
|
+
image: {
|
|
39
|
+
control: {
|
|
40
|
+
type: 'select',
|
|
41
|
+
labels: {
|
|
42
|
+
DefaultImage: 'Default Image',
|
|
43
|
+
Empty: 'No Image',
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
description: 'Product image node.',
|
|
47
|
+
options: ['DefaultImage', 'Empty'],
|
|
48
|
+
mapping: {
|
|
49
|
+
DefaultImage: (
|
|
50
|
+
<Image
|
|
51
|
+
src="https://picsum.photos/300/375"
|
|
52
|
+
width="300"
|
|
53
|
+
height="375"
|
|
54
|
+
alt="Product Image"
|
|
55
|
+
loading="lazy"
|
|
56
|
+
/>
|
|
57
|
+
),
|
|
58
|
+
Empty: null,
|
|
59
|
+
},
|
|
60
|
+
table: { defaultValue: { summary: 'null' } },
|
|
61
|
+
},
|
|
62
|
+
titleNode: {
|
|
63
|
+
control: {
|
|
64
|
+
type: 'select',
|
|
65
|
+
labels: {
|
|
66
|
+
DefaultTitle: 'Default Title',
|
|
67
|
+
LongTitle: 'Long Title',
|
|
68
|
+
Empty: 'No Title',
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
description: 'Product title node.',
|
|
72
|
+
options: ['DefaultTitle', 'LongTitle', 'Empty'],
|
|
73
|
+
mapping: {
|
|
74
|
+
DefaultTitle: <div>Hollister Backyard Sweatshirt</div>,
|
|
75
|
+
LongTitle: (
|
|
76
|
+
<div>
|
|
77
|
+
Hollister Backyard Sweatshirt with Extra Long Product Name That
|
|
78
|
+
Might Wrap
|
|
79
|
+
</div>
|
|
80
|
+
),
|
|
81
|
+
Empty: null,
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
price: {
|
|
85
|
+
control: {
|
|
86
|
+
type: 'select',
|
|
87
|
+
labels: {
|
|
88
|
+
DefaultPrice: 'Default Price',
|
|
89
|
+
SalePrice: 'Sale Price',
|
|
90
|
+
Empty: 'No Price',
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
description: 'Product price node.',
|
|
94
|
+
options: ['DefaultPrice', 'SalePrice', 'Empty'],
|
|
95
|
+
mapping: {
|
|
96
|
+
DefaultPrice: (
|
|
97
|
+
<>
|
|
98
|
+
<Price amount={49.99} />
|
|
99
|
+
</>
|
|
100
|
+
),
|
|
101
|
+
SalePrice: (
|
|
102
|
+
<>
|
|
103
|
+
<Price amount={69.99} variant="strikethrough" />
|
|
104
|
+
<Price amount={49.99} sale />
|
|
105
|
+
</>
|
|
106
|
+
),
|
|
107
|
+
Empty: null,
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
sku: {
|
|
111
|
+
control: {
|
|
112
|
+
type: 'select',
|
|
113
|
+
labels: {
|
|
114
|
+
DefaultSku: 'Default SKU',
|
|
115
|
+
Empty: 'No SKU',
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
description: 'Product SKU node.',
|
|
119
|
+
options: ['DefaultSku', 'Empty'],
|
|
120
|
+
mapping: {
|
|
121
|
+
DefaultSku: <div>SKU: 123456789</div>,
|
|
122
|
+
Empty: null,
|
|
123
|
+
},
|
|
124
|
+
},
|
|
125
|
+
swatches: {
|
|
126
|
+
control: {
|
|
127
|
+
type: 'select',
|
|
128
|
+
labels: {
|
|
129
|
+
DefaultSwatches: 'Default Swatches',
|
|
130
|
+
SelectedSwatches: 'Selected Swatches',
|
|
131
|
+
OutOfStockSwatches: 'Out of Stock Swatches',
|
|
132
|
+
Empty: 'No Swatches',
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
description: 'Product swatches node.',
|
|
136
|
+
options: [
|
|
137
|
+
'DefaultSwatches',
|
|
138
|
+
'SelectedSwatches',
|
|
139
|
+
'OutOfStockSwatches',
|
|
140
|
+
'Empty',
|
|
141
|
+
],
|
|
142
|
+
mapping: {
|
|
143
|
+
DefaultSwatches: (
|
|
144
|
+
<div style={{ display: 'flex', gap: '8px' }}>
|
|
145
|
+
<ColorSwatch
|
|
146
|
+
color="red"
|
|
147
|
+
label="Red"
|
|
148
|
+
groupAriaLabel="Color options"
|
|
149
|
+
value="red"
|
|
150
|
+
/>
|
|
151
|
+
<ColorSwatch
|
|
152
|
+
color="blue"
|
|
153
|
+
label="Blue"
|
|
154
|
+
groupAriaLabel="Color options"
|
|
155
|
+
value="blue"
|
|
156
|
+
/>
|
|
157
|
+
<ColorSwatch
|
|
158
|
+
color="green"
|
|
159
|
+
label="Green"
|
|
160
|
+
groupAriaLabel="Color options"
|
|
161
|
+
value="green"
|
|
162
|
+
/>
|
|
163
|
+
</div>
|
|
164
|
+
),
|
|
165
|
+
SelectedSwatches: (
|
|
166
|
+
<div style={{ display: 'flex', gap: '8px' }}>
|
|
167
|
+
<ColorSwatch
|
|
168
|
+
color="red"
|
|
169
|
+
label="Red"
|
|
170
|
+
groupAriaLabel="Color options"
|
|
171
|
+
value="red"
|
|
172
|
+
selected
|
|
173
|
+
/>
|
|
174
|
+
<ColorSwatch
|
|
175
|
+
color="blue"
|
|
176
|
+
label="Blue"
|
|
177
|
+
groupAriaLabel="Color options"
|
|
178
|
+
value="blue"
|
|
179
|
+
/>
|
|
180
|
+
<ColorSwatch
|
|
181
|
+
color="green"
|
|
182
|
+
label="Green"
|
|
183
|
+
groupAriaLabel="Color options"
|
|
184
|
+
value="green"
|
|
185
|
+
/>
|
|
186
|
+
</div>
|
|
187
|
+
),
|
|
188
|
+
OutOfStockSwatches: (
|
|
189
|
+
<div style={{ display: 'flex', gap: '8px' }}>
|
|
190
|
+
<ColorSwatch
|
|
191
|
+
color="red"
|
|
192
|
+
label="Red"
|
|
193
|
+
groupAriaLabel="Color options"
|
|
194
|
+
value="red"
|
|
195
|
+
outOfStock
|
|
196
|
+
/>
|
|
197
|
+
<ColorSwatch
|
|
198
|
+
color="blue"
|
|
199
|
+
label="Blue"
|
|
200
|
+
groupAriaLabel="Color options"
|
|
201
|
+
value="blue"
|
|
202
|
+
/>
|
|
203
|
+
<ColorSwatch
|
|
204
|
+
color="green"
|
|
205
|
+
label="Green"
|
|
206
|
+
groupAriaLabel="Color options"
|
|
207
|
+
value="green"
|
|
208
|
+
/>
|
|
209
|
+
</div>
|
|
210
|
+
),
|
|
211
|
+
Empty: null,
|
|
212
|
+
},
|
|
213
|
+
table: { defaultValue: { summary: 'null' } },
|
|
214
|
+
},
|
|
215
|
+
actionButton: {
|
|
216
|
+
control: {
|
|
217
|
+
type: 'select',
|
|
218
|
+
labels: {
|
|
219
|
+
DefaultButton: 'Default Button',
|
|
220
|
+
CustomButton: 'Custom Button',
|
|
221
|
+
Empty: 'No Button',
|
|
222
|
+
},
|
|
223
|
+
},
|
|
224
|
+
description: 'Action button node.',
|
|
225
|
+
options: ['DefaultButton', 'CustomButton', 'Empty'],
|
|
226
|
+
mapping: {
|
|
227
|
+
DefaultButton: <Button>Select Options</Button>,
|
|
228
|
+
CustomButton: (
|
|
229
|
+
<Button icon={<Icon source={Cart} size="24" />} variant="primary">
|
|
230
|
+
Add to Cart
|
|
231
|
+
</Button>
|
|
232
|
+
),
|
|
233
|
+
Empty: null,
|
|
234
|
+
},
|
|
235
|
+
table: { defaultValue: { summary: 'null' } },
|
|
236
|
+
},
|
|
237
|
+
},
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
export default meta;
|
|
241
|
+
|
|
242
|
+
type Story = StoryObj<ProductItemCardProps>;
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Default ProductItemCard with all elements
|
|
246
|
+
*/
|
|
247
|
+
export const Default: Story = {
|
|
248
|
+
args: {
|
|
249
|
+
initialized: true,
|
|
250
|
+
image: 'DefaultImage' as any,
|
|
251
|
+
titleNode: 'DefaultTitle' as any,
|
|
252
|
+
price: 'DefaultPrice' as any,
|
|
253
|
+
sku: 'DefaultSku' as any,
|
|
254
|
+
swatches: 'DefaultSwatches' as any,
|
|
255
|
+
actionButton: 'DefaultButton' as any,
|
|
256
|
+
},
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* ProductItemCard with long title
|
|
261
|
+
*/
|
|
262
|
+
export const LongTitle: Story = {
|
|
263
|
+
args: {
|
|
264
|
+
initialized: true,
|
|
265
|
+
image: 'DefaultImage' as any,
|
|
266
|
+
titleNode: 'LongTitle' as any,
|
|
267
|
+
price: 'DefaultPrice' as any,
|
|
268
|
+
sku: 'DefaultSku' as any,
|
|
269
|
+
swatches: 'DefaultSwatches' as any,
|
|
270
|
+
actionButton: 'DefaultButton' as any,
|
|
271
|
+
},
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* ProductItemCard with sale price
|
|
276
|
+
*/
|
|
277
|
+
export const SalePrice: Story = {
|
|
278
|
+
args: {
|
|
279
|
+
initialized: true,
|
|
280
|
+
image: 'DefaultImage' as any,
|
|
281
|
+
titleNode: 'DefaultTitle' as any,
|
|
282
|
+
price: 'SalePrice' as any,
|
|
283
|
+
sku: 'DefaultSku' as any,
|
|
284
|
+
swatches: 'DefaultSwatches' as any,
|
|
285
|
+
actionButton: 'DefaultButton' as any,
|
|
286
|
+
},
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* ProductItemCard without action button
|
|
291
|
+
*/
|
|
292
|
+
export const NoButton: Story = {
|
|
293
|
+
args: {
|
|
294
|
+
initialized: true,
|
|
295
|
+
image: 'DefaultImage' as any,
|
|
296
|
+
titleNode: 'DefaultTitle' as any,
|
|
297
|
+
price: 'DefaultPrice' as any,
|
|
298
|
+
sku: 'DefaultSku' as any,
|
|
299
|
+
swatches: 'DefaultSwatches' as any,
|
|
300
|
+
actionButton: 'Empty' as any,
|
|
301
|
+
},
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* ProductItemCard without image
|
|
306
|
+
*/
|
|
307
|
+
export const NoImage: Story = {
|
|
308
|
+
args: {
|
|
309
|
+
initialized: true,
|
|
310
|
+
image: 'Empty' as any,
|
|
311
|
+
titleNode: 'DefaultTitle' as any,
|
|
312
|
+
price: 'DefaultPrice' as any,
|
|
313
|
+
sku: 'DefaultSku' as any,
|
|
314
|
+
swatches: 'DefaultSwatches' as any,
|
|
315
|
+
actionButton: 'DefaultButton' as any,
|
|
316
|
+
},
|
|
317
|
+
};
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* ProductItemCard with minimal content
|
|
321
|
+
*/
|
|
322
|
+
export const Minimal: Story = {
|
|
323
|
+
args: {
|
|
324
|
+
initialized: true,
|
|
325
|
+
image: 'DefaultImage' as any,
|
|
326
|
+
titleNode: 'DefaultTitle' as any,
|
|
327
|
+
price: 'Empty' as any,
|
|
328
|
+
sku: 'Empty' as any,
|
|
329
|
+
swatches: 'Empty' as any,
|
|
330
|
+
actionButton: 'Empty' as any,
|
|
331
|
+
},
|
|
332
|
+
};
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* ProductItemCard with selected swatch
|
|
336
|
+
*/
|
|
337
|
+
export const SelectedSwatch: Story = {
|
|
338
|
+
args: {
|
|
339
|
+
initialized: true,
|
|
340
|
+
image: 'DefaultImage' as any,
|
|
341
|
+
titleNode: 'DefaultTitle' as any,
|
|
342
|
+
price: 'DefaultPrice' as any,
|
|
343
|
+
sku: 'DefaultSku' as any,
|
|
344
|
+
swatches: 'SelectedSwatches' as any,
|
|
345
|
+
actionButton: 'DefaultButton' as any,
|
|
346
|
+
},
|
|
347
|
+
};
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* ProductItemCard with out of stock swatch
|
|
351
|
+
*/
|
|
352
|
+
export const OutOfStockSwatch: Story = {
|
|
353
|
+
args: {
|
|
354
|
+
initialized: true,
|
|
355
|
+
image: 'DefaultImage' as any,
|
|
356
|
+
titleNode: 'DefaultTitle' as any,
|
|
357
|
+
price: 'DefaultPrice' as any,
|
|
358
|
+
sku: 'DefaultSku' as any,
|
|
359
|
+
swatches: 'OutOfStockSwatches' as any,
|
|
360
|
+
actionButton: 'DefaultButton' as any,
|
|
361
|
+
},
|
|
362
|
+
};
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* ProductItemCard in skeleton loading state
|
|
366
|
+
*/
|
|
367
|
+
export const Skeleton: Story = {
|
|
368
|
+
args: {
|
|
369
|
+
initialized: false,
|
|
370
|
+
},
|
|
371
|
+
};
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/********************************************************************
|
|
2
|
+
* Copyright 2025 Adobe
|
|
3
|
+
* All Rights Reserved.
|
|
4
|
+
*
|
|
5
|
+
* NOTICE: Adobe permits you to use, modify, and distribute this
|
|
6
|
+
* file in accordance with the terms of the Adobe license agreement
|
|
7
|
+
* accompanying it.
|
|
8
|
+
*******************************************************************/
|
|
9
|
+
|
|
10
|
+
import { FunctionComponent, VNode } from 'preact';
|
|
11
|
+
import { HTMLAttributes } from 'preact/compat';
|
|
12
|
+
import { VComponent, classes } from '@adobe-commerce/elsie/lib';
|
|
13
|
+
import { ProductItemCardSkeleton } from '@adobe-commerce/elsie/components/ProductItemCard/ProductItemCardSkeleton';
|
|
14
|
+
import '@adobe-commerce/elsie/components/ProductItemCard/ProductItemCard.css';
|
|
15
|
+
|
|
16
|
+
export interface ProductItemCardProps
|
|
17
|
+
extends Omit<HTMLAttributes<HTMLDivElement>, 'loading'> {
|
|
18
|
+
image?: VNode;
|
|
19
|
+
titleNode?: VNode;
|
|
20
|
+
price?: VNode;
|
|
21
|
+
sku?: VNode;
|
|
22
|
+
actionButton?: VNode;
|
|
23
|
+
swatches?: VNode;
|
|
24
|
+
initialized?: boolean;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export const ProductItemCard: FunctionComponent<ProductItemCardProps> = ({
|
|
28
|
+
className,
|
|
29
|
+
image,
|
|
30
|
+
titleNode,
|
|
31
|
+
price,
|
|
32
|
+
sku,
|
|
33
|
+
actionButton,
|
|
34
|
+
swatches,
|
|
35
|
+
initialized = false,
|
|
36
|
+
...props
|
|
37
|
+
}) => {
|
|
38
|
+
if (!initialized) {
|
|
39
|
+
return <ProductItemCardSkeleton />;
|
|
40
|
+
}
|
|
41
|
+
return (
|
|
42
|
+
<div
|
|
43
|
+
{...props}
|
|
44
|
+
className={classes(['dropin-product-item-card', className])}
|
|
45
|
+
>
|
|
46
|
+
<div className="dropin-product-item-card__image-container">
|
|
47
|
+
{image && (
|
|
48
|
+
<VComponent
|
|
49
|
+
node={image}
|
|
50
|
+
className={classes(['dropin-product-item-card__image'])}
|
|
51
|
+
/>
|
|
52
|
+
)}
|
|
53
|
+
</div>
|
|
54
|
+
<div className="dropin-product-item-card__content">
|
|
55
|
+
{titleNode && (
|
|
56
|
+
<VComponent
|
|
57
|
+
node={titleNode}
|
|
58
|
+
className={classes(['dropin-product-item-card__title'])}
|
|
59
|
+
/>
|
|
60
|
+
)}
|
|
61
|
+
{sku && (
|
|
62
|
+
<VComponent
|
|
63
|
+
node={sku}
|
|
64
|
+
className={classes(['dropin-product-item-card__sku'])}
|
|
65
|
+
/>
|
|
66
|
+
)}
|
|
67
|
+
{price && (
|
|
68
|
+
<div className="dropin-product-item-card__price">
|
|
69
|
+
<VComponent
|
|
70
|
+
node={price}
|
|
71
|
+
className={classes(['dropin-product-item-card__price'])}
|
|
72
|
+
/>
|
|
73
|
+
</div>
|
|
74
|
+
)}
|
|
75
|
+
{swatches && (
|
|
76
|
+
<div className="dropin-product-item-card__swatches">
|
|
77
|
+
<VComponent
|
|
78
|
+
node={swatches}
|
|
79
|
+
className={classes(['dropin-product-item-card__swatches'])}
|
|
80
|
+
/>
|
|
81
|
+
</div>
|
|
82
|
+
)}
|
|
83
|
+
{actionButton && (
|
|
84
|
+
<div className="dropin-product-item-card__action">
|
|
85
|
+
<VComponent
|
|
86
|
+
node={actionButton}
|
|
87
|
+
className={classes(['dropin-product-item-card__action'])}
|
|
88
|
+
/>
|
|
89
|
+
</div>
|
|
90
|
+
)}
|
|
91
|
+
</div>
|
|
92
|
+
</div>
|
|
93
|
+
);
|
|
94
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/********************************************************************
|
|
2
|
+
* Copyright 2025 Adobe
|
|
3
|
+
* All Rights Reserved.
|
|
4
|
+
*
|
|
5
|
+
* NOTICE: Adobe permits you to use, modify, and distribute this
|
|
6
|
+
* file in accordance with the terms of the Adobe license agreement
|
|
7
|
+
* accompanying it.
|
|
8
|
+
*******************************************************************/
|
|
9
|
+
|
|
10
|
+
/* https://cssguidelin.es/#bem-like-naming */
|
|
11
|
+
|
|
12
|
+
.dropin-product-item-card__skeleton {
|
|
13
|
+
gap: var(--spacing-small);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.dropin-product-item-card__skeleton__image {
|
|
17
|
+
height: 375px;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.dropin-product-item-card__skeleton__content {
|
|
21
|
+
grid-column: 1/-1;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/* .dropin-product-item-card__skeleton__image {
|
|
25
|
+
width: 100%;
|
|
26
|
+
height: auto;
|
|
27
|
+
}
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
/* Medium (portrait tablets and large phones, 768px and up) */
|
|
31
|
+
/* @media only screen and (min-width: 768px) { } */
|
|
32
|
+
|
|
33
|
+
/* Large (landscape tablets, 1024px and up) */
|
|
34
|
+
/* @media only screen and (min-width: 1024px) { } */
|
|
35
|
+
|
|
36
|
+
/* XLarge (laptops/desktops, 1366px and up) */
|
|
37
|
+
/* @media only screen and (min-width: 1366px) { } */
|
|
38
|
+
|
|
39
|
+
/* XXlarge (large laptops and desktops, 1920px and up) */
|
|
40
|
+
/* @media only screen and (min-width: 1920px) { } */
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/********************************************************************
|
|
2
|
+
* Copyright 2025 Adobe
|
|
3
|
+
* All Rights Reserved.
|
|
4
|
+
*
|
|
5
|
+
* NOTICE: Adobe permits you to use, modify, and distribute this
|
|
6
|
+
* file in accordance with the terms of the Adobe license agreement
|
|
7
|
+
* accompanying it.
|
|
8
|
+
*******************************************************************/
|
|
9
|
+
|
|
10
|
+
import { FunctionComponent } from 'preact';
|
|
11
|
+
import { Skeleton, SkeletonRow } from '@adobe-commerce/elsie/components';
|
|
12
|
+
import '@adobe-commerce/elsie/components/ProductItemCard/ProductItemCardSkeleton.css';
|
|
13
|
+
|
|
14
|
+
export const ProductItemCardSkeleton: FunctionComponent = () => {
|
|
15
|
+
return (
|
|
16
|
+
<div className="dropin-product-item-card dropin-product-item-card-skeleton">
|
|
17
|
+
<Skeleton className="dropin-product-item-card__skeleton dropin-product-item-card__image-container">
|
|
18
|
+
<SkeletonRow
|
|
19
|
+
fullWidth={true}
|
|
20
|
+
className="dropin-product-item-card__skeleton__image"
|
|
21
|
+
/>
|
|
22
|
+
<div className="dropin-product-item-card__content dropin-product-item-card__skeleton__content">
|
|
23
|
+
<SkeletonRow
|
|
24
|
+
fullWidth={true}
|
|
25
|
+
size="large"
|
|
26
|
+
className="dropin-product-item-card__skeleton__item"
|
|
27
|
+
/>
|
|
28
|
+
<SkeletonRow
|
|
29
|
+
fullWidth={true}
|
|
30
|
+
size="xsmall"
|
|
31
|
+
className="dropin-product-item-card__skeleton__item"
|
|
32
|
+
/>
|
|
33
|
+
<SkeletonRow
|
|
34
|
+
fullWidth={true}
|
|
35
|
+
size="small"
|
|
36
|
+
className="dropin-product-item-card__skeleton__item"
|
|
37
|
+
/>
|
|
38
|
+
</div>{' '}
|
|
39
|
+
</Skeleton>
|
|
40
|
+
</div>
|
|
41
|
+
);
|
|
42
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/********************************************************************
|
|
2
|
+
* Copyright 2025 Adobe
|
|
3
|
+
* All Rights Reserved.
|
|
4
|
+
*
|
|
5
|
+
* NOTICE: Adobe permits you to use, modify, and distribute this
|
|
6
|
+
* file in accordance with the terms of the Adobe license agreement
|
|
7
|
+
* accompanying it.
|
|
8
|
+
*******************************************************************/
|
|
9
|
+
|
|
10
|
+
export * from '@adobe-commerce/elsie/components/ProductItemCard/ProductItemCard';
|
|
11
|
+
export { ProductItemCard as default } from '@adobe-commerce/elsie/components/ProductItemCard/ProductItemCard';
|
package/src/components/index.ts
CHANGED
|
@@ -47,3 +47,4 @@ export * from '@adobe-commerce/elsie/components/Header';
|
|
|
47
47
|
export * from '@adobe-commerce/elsie/components/Tag';
|
|
48
48
|
export * from '@adobe-commerce/elsie/components/ContentGrid';
|
|
49
49
|
export * from '@adobe-commerce/elsie/components/Pagination';
|
|
50
|
+
export * from '@adobe-commerce/elsie/components/ProductItemCard';
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { Meta, Unstyled } from '@storybook/blocks';
|
|
2
|
+
|
|
3
|
+
<Meta title="Utilities/Links" />
|
|
4
|
+
<Unstyled>
|
|
5
|
+
|
|
6
|
+
# Adding Links using the route pattern
|
|
7
|
+
|
|
8
|
+
Whenever possible, avoid placing `onClick` handlers directly on anchor elements (`<a>`) in drop-in components, such as product or category pages, as this results in accessibility issues and broken browser behavior. Problems include:
|
|
9
|
+
|
|
10
|
+
- Right-click > Open in New Tab results in blank pages.
|
|
11
|
+
- Middle-click (open in background tab) won't work as expected.
|
|
12
|
+
- Keyboard navigation and screen readers may not trigger the link correctly.
|
|
13
|
+
|
|
14
|
+
Instead, follow the route pattern to provide composable and accessible navigation.
|
|
15
|
+
|
|
16
|
+
## How it works
|
|
17
|
+
|
|
18
|
+
Components accept a `routeX` function as a prop. The function receives a data model (a product, for example) and returns a URL. Internally, it's used like this:
|
|
19
|
+
|
|
20
|
+
```tsx
|
|
21
|
+
<a href={routeProduct?.(product) ?? '#'}>...</a>
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
This lets developers customize routing logic per storefront while preserving link semantics.
|
|
25
|
+
|
|
26
|
+
## Example — Component-Side
|
|
27
|
+
|
|
28
|
+
In your component (a PLP item, for example):
|
|
29
|
+
|
|
30
|
+
The `routeProduct` prop must be optional and default to a # or an non-functional element like a `div` if not provided.
|
|
31
|
+
|
|
32
|
+
```tsx
|
|
33
|
+
type Props = {
|
|
34
|
+
routeProduct?: (product: ProductModel) => string;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
function ProductCard({ product, routeProduct }: Props) {
|
|
38
|
+
return (
|
|
39
|
+
<a href={routeProduct?.(product) ?? '#'}>
|
|
40
|
+
<div>{product.name}</div>
|
|
41
|
+
</a>
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Example — Storefront-Side
|
|
47
|
+
|
|
48
|
+
In the storefront integration (`commerce-cart.js` or `commerce-plp.js`, for example):
|
|
49
|
+
|
|
50
|
+
```js
|
|
51
|
+
import { rootLink } from '@adobe/commerce-url-utils';
|
|
52
|
+
|
|
53
|
+
provider.render(ProductList, {
|
|
54
|
+
routeProduct: (product) => rootLink(`/products/${product.url.urlKey}/${product.topLevelSku}`),
|
|
55
|
+
});
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
</Unstyled>
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
import { deepmerge } from '../deepmerge';
|
|
2
|
+
|
|
3
|
+
interface ConfigHeaders {
|
|
4
|
+
all?: Record<string, string>;
|
|
5
|
+
[key: string]: Record<string, string> | undefined;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
interface ConfigPublic {
|
|
9
|
+
default: ConfigRoot;
|
|
10
|
+
[key: string]: ConfigRoot;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
interface ConfigRoot {
|
|
14
|
+
headers?: ConfigHeaders;
|
|
15
|
+
[key: string]: any;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
interface Config {
|
|
19
|
+
public: ConfigPublic;
|
|
20
|
+
[key: string]: any;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Private state
|
|
24
|
+
let config: Config | null = null;
|
|
25
|
+
let rootPath: string | null = null;
|
|
26
|
+
let rootConfig: ConfigRoot | null = null;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Reset the config state
|
|
30
|
+
*/
|
|
31
|
+
function resetConfig() {
|
|
32
|
+
config = null;
|
|
33
|
+
rootPath = null;
|
|
34
|
+
rootConfig = null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Builds the URL for the config file.
|
|
39
|
+
*
|
|
40
|
+
* @returns {URL} - The URL for the config file.
|
|
41
|
+
*/
|
|
42
|
+
function buildConfigURL() {
|
|
43
|
+
return new URL(`${window.location.origin}/config.json`);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Retrieves a value from a config object using dot notation.
|
|
48
|
+
*
|
|
49
|
+
* @param {Object} obj - The config object.
|
|
50
|
+
* @param {string} key - The key to retrieve (supports dot notation).
|
|
51
|
+
* @returns {any} - The value of the key.
|
|
52
|
+
*/
|
|
53
|
+
function getValue(obj: Record<string, any>, key: string): any {
|
|
54
|
+
return key.split('.').reduce((current: Record<string, any>, part: string) => {
|
|
55
|
+
if (!Object.prototype.hasOwnProperty.call(current, part)) {
|
|
56
|
+
console.warn(`Property ${key} does not exist in the object`);
|
|
57
|
+
return undefined;
|
|
58
|
+
}
|
|
59
|
+
return current[part];
|
|
60
|
+
}, obj);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Get cookie
|
|
65
|
+
* @param {string} cookieName - The name of the cookie to get
|
|
66
|
+
* @returns {string} - The value of the cookie
|
|
67
|
+
*/
|
|
68
|
+
function getCookie(cookieName: string): string | undefined {
|
|
69
|
+
const cookies = document.cookie.split(';');
|
|
70
|
+
let foundValue;
|
|
71
|
+
|
|
72
|
+
cookies.forEach((cookie) => {
|
|
73
|
+
const [name, value] = cookie.trim().split('=');
|
|
74
|
+
if (name === cookieName) {
|
|
75
|
+
foundValue = decodeURIComponent(value);
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
return foundValue;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Get root path
|
|
84
|
+
* @param {Object} [configObj=config] - The config object.
|
|
85
|
+
* @returns {string} - The root path.
|
|
86
|
+
*/
|
|
87
|
+
function getRootPath(configObj: Config | null = config): string {
|
|
88
|
+
if (!configObj) {
|
|
89
|
+
console.warn('No config found. Please call initializeConfig() first.');
|
|
90
|
+
return '/';
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const value = Object.keys(configObj?.public)
|
|
94
|
+
// Sort by number of non-empty segments to find the deepest path
|
|
95
|
+
.sort((a, b) => {
|
|
96
|
+
const aSegments = a.split('/').filter(Boolean).length;
|
|
97
|
+
const bSegments = b.split('/').filter(Boolean).length;
|
|
98
|
+
return bSegments - aSegments;
|
|
99
|
+
})
|
|
100
|
+
.find(
|
|
101
|
+
(key) =>
|
|
102
|
+
window.location.pathname === key ||
|
|
103
|
+
window.location.pathname.startsWith(key)
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
return value ?? '/';
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Get list of root paths from public config
|
|
111
|
+
* @returns {Array} - The list of root paths.
|
|
112
|
+
*/
|
|
113
|
+
function getListOfRootPaths(): string[] {
|
|
114
|
+
if (!config) {
|
|
115
|
+
console.warn('No config found. Please call initializeConfig() first.');
|
|
116
|
+
return [];
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return Object.keys(config.public).filter((root) => root !== 'default');
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Checks if the public config contains more than "default"
|
|
124
|
+
* @returns {boolean} - true if public config contains more than "default"
|
|
125
|
+
*/
|
|
126
|
+
function isMultistore(): boolean {
|
|
127
|
+
return getListOfRootPaths().length >= 1;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Retrieves headers from config entries like commerce.headers.pdp.my-header, etc and
|
|
132
|
+
* returns as object of all headers like { my-header: value, ... }
|
|
133
|
+
* @param {string} scope - The scope of the headers to retrieve.
|
|
134
|
+
* @returns {Object} - The headers.
|
|
135
|
+
*/
|
|
136
|
+
function getHeaders(scope: string): Record<string, string> {
|
|
137
|
+
if (!rootConfig) {
|
|
138
|
+
throw new Error(
|
|
139
|
+
'Configuration not initialized. Call initializeConfig() first.'
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
const headers = rootConfig.headers ?? {};
|
|
143
|
+
return {
|
|
144
|
+
...(headers.all ?? {}),
|
|
145
|
+
...(headers[scope] ?? {}),
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Applies config overrides from metadata.
|
|
151
|
+
*
|
|
152
|
+
* @param {Object} [configObj=config] - The base config.
|
|
153
|
+
* @param {string} [root=rootPath] - The root path.
|
|
154
|
+
* @returns {Object} - The config with overrides applied.
|
|
155
|
+
*/
|
|
156
|
+
function applyConfigOverrides(
|
|
157
|
+
configObj: Config | null,
|
|
158
|
+
root: string | null
|
|
159
|
+
): ConfigRoot {
|
|
160
|
+
const defaultConfig = configObj!.public?.default;
|
|
161
|
+
|
|
162
|
+
if (root === '/' || !configObj!.public[root as keyof ConfigPublic])
|
|
163
|
+
return defaultConfig;
|
|
164
|
+
|
|
165
|
+
return deepmerge(
|
|
166
|
+
defaultConfig,
|
|
167
|
+
configObj!.public[root as keyof ConfigPublic]
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Fetches config from remote and saves in session, then returns it, otherwise
|
|
173
|
+
* returns if it already exists.
|
|
174
|
+
*
|
|
175
|
+
* @returns {Promise<Object>} - The config JSON from session storage
|
|
176
|
+
*/
|
|
177
|
+
async function getConfigFromSession(): Promise<Config> {
|
|
178
|
+
try {
|
|
179
|
+
const configJSON = window.sessionStorage.getItem('config');
|
|
180
|
+
if (!configJSON) {
|
|
181
|
+
throw new Error('No config in session storage');
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const parsedConfig = JSON.parse(configJSON);
|
|
185
|
+
if (
|
|
186
|
+
!parsedConfig[':expiry'] ||
|
|
187
|
+
parsedConfig[':expiry'] < Math.round(Date.now() / 1000)
|
|
188
|
+
) {
|
|
189
|
+
throw new Error('Config expired');
|
|
190
|
+
}
|
|
191
|
+
return parsedConfig;
|
|
192
|
+
} catch (e) {
|
|
193
|
+
const config = await fetch(buildConfigURL());
|
|
194
|
+
if (!config.ok) throw new Error('Failed to fetch config');
|
|
195
|
+
const configJSON = await config.json();
|
|
196
|
+
configJSON[':expiry'] = Math.round(Date.now() / 1000) + 7200;
|
|
197
|
+
window.sessionStorage.setItem('config', JSON.stringify(configJSON));
|
|
198
|
+
return configJSON;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Initializes the configuration system.
|
|
204
|
+
* @returns {Promise<void>}
|
|
205
|
+
*/
|
|
206
|
+
async function initializeConfig(): Promise<ConfigRoot> {
|
|
207
|
+
config = await getConfigFromSession();
|
|
208
|
+
rootPath = getRootPath(config);
|
|
209
|
+
rootConfig = applyConfigOverrides(config, rootPath);
|
|
210
|
+
return rootConfig;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Retrieves a configuration value.
|
|
215
|
+
*
|
|
216
|
+
* @param {string} configParam - The configuration parameter to retrieve.
|
|
217
|
+
* @returns {string|undefined} - The value of the configuration parameter, or undefined.
|
|
218
|
+
*/
|
|
219
|
+
function getConfigValue(configParam: string): any {
|
|
220
|
+
if (!rootConfig) {
|
|
221
|
+
throw new Error(
|
|
222
|
+
'Configuration not initialized. Call initializeConfig() first.'
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
return getValue(rootConfig, configParam);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
export {
|
|
229
|
+
initializeConfig,
|
|
230
|
+
getCookie,
|
|
231
|
+
getRootPath,
|
|
232
|
+
getListOfRootPaths,
|
|
233
|
+
isMultistore,
|
|
234
|
+
getConfigValue,
|
|
235
|
+
getHeaders,
|
|
236
|
+
resetConfig,
|
|
237
|
+
};
|