@adobe-commerce/elsie 1.2.1-alpha1 → 1.2.1-alpha57
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/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>
|