@adobe-commerce/elsie 1.1.0-beta1 → 1.2.0-beta1
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/bin/builders/build/index.js +1 -2
- package/bin/builders/lint/index.js +1 -1
- package/bin/builders/storybook/index.js +1 -1
- package/bin/builders/test/index.js +1 -1
- package/bin/lib/cli.js +16 -1
- package/package.json +2 -2
- package/src/components/Header/Header.tsx +2 -2
- package/src/components/ImageSwatch/ImageSwatch.css +10 -0
- package/src/components/ImageSwatch/ImageSwatch.stories.tsx +53 -4
- package/src/components/ImageSwatch/ImageSwatch.tsx +44 -9
- package/src/components/Incrementer/Incrementer.tsx +10 -1
- package/src/components/Tag/Tag.stories.tsx +1 -0
- package/src/lib/slot.tsx +11 -2
|
@@ -3,8 +3,7 @@ const path = require('path');
|
|
|
3
3
|
module.exports = async function generateResourceBuilder({ argv }) {
|
|
4
4
|
const { build } = await import('vite');
|
|
5
5
|
|
|
6
|
-
const configFile =
|
|
7
|
-
argv?.config ?? path.resolve(__dirname, '../../../config/vite.mjs');
|
|
6
|
+
const configFile = argv?.config ?? path.resolve(__dirname, '../../../config/vite.mjs');
|
|
8
7
|
|
|
9
8
|
const outDir = argv?.outDir ?? 'dist';
|
|
10
9
|
|
package/bin/lib/cli.js
CHANGED
|
@@ -4,5 +4,20 @@ module.exports = function cli(command) {
|
|
|
4
4
|
let cmd = command;
|
|
5
5
|
const argvs = process.argv.slice(3).join(' ');
|
|
6
6
|
if (argvs) cmd += ` ${argvs}`;
|
|
7
|
-
|
|
7
|
+
|
|
8
|
+
return new Promise((resolve, reject) => {
|
|
9
|
+
const child = spawn(cmd, { shell: true, stdio: 'inherit' });
|
|
10
|
+
|
|
11
|
+
child.on('close', (code) => {
|
|
12
|
+
if (code !== 0) {
|
|
13
|
+
reject(new Error(`Command failed with exit code ${code}`));
|
|
14
|
+
} else {
|
|
15
|
+
resolve(child);
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
child.on('error', (err) => {
|
|
20
|
+
reject(err);
|
|
21
|
+
});
|
|
22
|
+
});
|
|
8
23
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adobe-commerce/elsie",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0-beta1",
|
|
4
4
|
"license": "SEE LICENSE IN LICENSE.md",
|
|
5
5
|
"description": "Domain Package SDK",
|
|
6
6
|
"engines": {
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"dev": "concurrently 'yarn storybook' 'yarn serve'",
|
|
16
16
|
"storybook": "elsie storybook",
|
|
17
17
|
"serve": "elsie serve --config vite.config.mjs",
|
|
18
|
-
"lint": "elsie lint
|
|
18
|
+
"lint": "elsie lint",
|
|
19
19
|
"test": "elsie test",
|
|
20
20
|
"test:ci": "jest --config jest.config.js --passWithNoTests --coverage",
|
|
21
21
|
"build": "elsie build --config vite.config.mjs",
|
|
@@ -7,13 +7,13 @@
|
|
|
7
7
|
* accompanying it.
|
|
8
8
|
*******************************************************************/
|
|
9
9
|
|
|
10
|
-
import { ComponentChildren, FunctionComponent, VNode } from 'preact';
|
|
10
|
+
import { ComponentChildren, FunctionComponent, VNode, JSX } from 'preact';
|
|
11
11
|
import { HTMLAttributes } from 'preact/compat';
|
|
12
12
|
import { classes, VComponent } from '@adobe-commerce/elsie/lib';
|
|
13
13
|
import { Divider } from '@adobe-commerce/elsie/components';
|
|
14
14
|
import '@adobe-commerce/elsie/components/Header/Header.css';
|
|
15
15
|
|
|
16
|
-
export interface HeaderProps extends HTMLAttributes<HTMLDivElement> {
|
|
16
|
+
export interface HeaderProps extends Omit<HTMLAttributes<HTMLDivElement>, 'size'> {
|
|
17
17
|
title: string;
|
|
18
18
|
size?: 'medium' | 'large';
|
|
19
19
|
divider?: boolean;
|
|
@@ -33,6 +33,7 @@
|
|
|
33
33
|
overflow: hidden;
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
+
.dropin-image-swatch__span img,
|
|
36
37
|
.dropin-image-swatch__content {
|
|
37
38
|
width: inherit;
|
|
38
39
|
position: absolute;
|
|
@@ -121,10 +122,19 @@
|
|
|
121
122
|
}
|
|
122
123
|
|
|
123
124
|
.dropin-image-swatch__span--out-of-stock > .dropin-image-swatch__content,
|
|
125
|
+
.dropin-image-swatch__span--out-of-stock img,
|
|
126
|
+
.dropin-image-swatch__container
|
|
127
|
+
input[type='radio']:disabled
|
|
128
|
+
~ .dropin-image-swatch__span
|
|
129
|
+
img,
|
|
124
130
|
.dropin-image-swatch__container
|
|
125
131
|
input[type='radio']:disabled
|
|
126
132
|
~ .dropin-image-swatch__span
|
|
127
133
|
> .dropin-image-swatch__content,
|
|
134
|
+
.dropin-image-swatch__container
|
|
135
|
+
input[type='checkbox']:disabled
|
|
136
|
+
~ .dropin-image-swatch__span
|
|
137
|
+
img,
|
|
128
138
|
.dropin-image-swatch__container
|
|
129
139
|
input[type='checkbox']:disabled
|
|
130
140
|
~ .dropin-image-swatch__span
|
|
@@ -260,11 +260,11 @@ export const MultiImageSwatch: Story = {
|
|
|
260
260
|
),
|
|
261
261
|
};
|
|
262
262
|
|
|
263
|
-
export const
|
|
263
|
+
export const CustomImageNodeVNodeSwatch: Story = {
|
|
264
264
|
args: {
|
|
265
265
|
name: 'customImageSwatch',
|
|
266
266
|
id: 'customImageSwatch1',
|
|
267
|
-
label: 'Custom Image Node Example',
|
|
267
|
+
label: 'Custom Image Node VNode Example',
|
|
268
268
|
groupAriaLabel: 'Custom Image Swatches',
|
|
269
269
|
value: 'customImageNode',
|
|
270
270
|
src: `https://picsum.photos/${defaultWidth}/${defaultHeight}`, // fallback, not used with imageNode
|
|
@@ -277,7 +277,7 @@ export const CustomImageNodeSwatch: Story = {
|
|
|
277
277
|
<div style="position: relative; width: 100%; height: 100%;">
|
|
278
278
|
<img
|
|
279
279
|
src={`https://picsum.photos/${defaultWidth}/${defaultHeight}?grayscale`}
|
|
280
|
-
alt="Custom grayscale image"
|
|
280
|
+
alt="Custom grayscale image - VNode"
|
|
281
281
|
style="width: 100%; height: 100%; object-fit: cover;"
|
|
282
282
|
/>
|
|
283
283
|
<div style="position: absolute; top: 0; left: 0; background: rgba(255,255,255,0.7); padding: 4px 8px; border-radius: 0 0 8px 0;">
|
|
@@ -295,7 +295,56 @@ export const CustomImageNodeSwatch: Story = {
|
|
|
295
295
|
'div[style*="position: relative"]'
|
|
296
296
|
);
|
|
297
297
|
const customImage = canvasElement.querySelector(
|
|
298
|
-
'img[alt="Custom grayscale image"]'
|
|
298
|
+
'img[alt="Custom grayscale image - VNode"]'
|
|
299
|
+
);
|
|
300
|
+
const customLabel = canvasElement.querySelector(
|
|
301
|
+
'span[style*="font-weight: bold"]'
|
|
302
|
+
);
|
|
303
|
+
|
|
304
|
+
expect(imageSwatch).toBeInTheDocument();
|
|
305
|
+
expect(customImageContainer).toBeInTheDocument();
|
|
306
|
+
expect(customImage).toBeInTheDocument();
|
|
307
|
+
expect(customLabel).toBeInTheDocument();
|
|
308
|
+
expect(customLabel?.textContent).toBe('Custom');
|
|
309
|
+
},
|
|
310
|
+
};
|
|
311
|
+
|
|
312
|
+
export const CustomImageNodeRenderFunctionSwatch: Story = {
|
|
313
|
+
args: {
|
|
314
|
+
name: 'customImageSwatch',
|
|
315
|
+
id: 'customImageSwatch2',
|
|
316
|
+
label: 'Custom Image Node Render Function Example',
|
|
317
|
+
groupAriaLabel: 'Custom Image Swatches',
|
|
318
|
+
value: 'customImageNode',
|
|
319
|
+
src: `https://picsum.photos/${defaultWidth}/${defaultHeight}`, // fallback, not used with imageNode
|
|
320
|
+
alt: 'Custom Image Node',
|
|
321
|
+
selected: false,
|
|
322
|
+
disabled: false,
|
|
323
|
+
outOfStock: false,
|
|
324
|
+
onValue: action('onValue'),
|
|
325
|
+
imageNode: () => (
|
|
326
|
+
<div style="position: relative; width: 100%; height: 100%;">
|
|
327
|
+
<img
|
|
328
|
+
src={`https://picsum.photos/${defaultWidth}/${defaultHeight}?grayscale`}
|
|
329
|
+
alt="Custom grayscale image - Render Function"
|
|
330
|
+
style="width: 100%; height: 100%; object-fit: cover;"
|
|
331
|
+
/>
|
|
332
|
+
<div style="position: absolute; top: 0; left: 0; background: rgba(255,255,255,0.7); padding: 4px 8px; border-radius: 0 0 8px 0;">
|
|
333
|
+
<span style="font-size: 12px; font-weight: bold; color: #333;">
|
|
334
|
+
Custom
|
|
335
|
+
</span>
|
|
336
|
+
</div>
|
|
337
|
+
</div>
|
|
338
|
+
),
|
|
339
|
+
},
|
|
340
|
+
play: async ({ canvasElement }) => {
|
|
341
|
+
const canvas = within(canvasElement);
|
|
342
|
+
const imageSwatch = await canvas.findByRole('radio');
|
|
343
|
+
const customImageContainer = canvasElement.querySelector(
|
|
344
|
+
'div[style*="position: relative"]'
|
|
345
|
+
);
|
|
346
|
+
const customImage = canvasElement.querySelector(
|
|
347
|
+
'img[alt="Custom grayscale image - Render Function"]'
|
|
299
348
|
);
|
|
300
349
|
const customLabel = canvasElement.querySelector(
|
|
301
350
|
'span[style*="font-weight: bold"]'
|
|
@@ -8,11 +8,26 @@
|
|
|
8
8
|
*******************************************************************/
|
|
9
9
|
|
|
10
10
|
import { FunctionComponent, VNode } from 'preact';
|
|
11
|
-
import { HTMLAttributes, useCallback } from 'preact/compat';
|
|
11
|
+
import { HTMLAttributes, useCallback, JSX, useMemo } from 'preact/compat';
|
|
12
12
|
import { classes } from '@adobe-commerce/elsie/lib';
|
|
13
13
|
import '@adobe-commerce/elsie/components/ImageSwatch/ImageSwatch.css';
|
|
14
|
-
import { Image } from '@adobe-commerce/elsie/components/Image';
|
|
14
|
+
import { Image, ImageProps } from '@adobe-commerce/elsie/components/Image';
|
|
15
15
|
import { useText } from '@adobe-commerce/elsie/i18n';
|
|
16
|
+
|
|
17
|
+
export interface ImageNodeRenderProps extends ImageProps {
|
|
18
|
+
imageSwatchContext: {
|
|
19
|
+
disabled?: boolean;
|
|
20
|
+
outOfStock?: boolean;
|
|
21
|
+
multi?: boolean;
|
|
22
|
+
selected?: boolean;
|
|
23
|
+
value?: string;
|
|
24
|
+
label?: string;
|
|
25
|
+
groupAriaLabel?: string;
|
|
26
|
+
name?: string;
|
|
27
|
+
id?: string;
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
16
31
|
export interface ImageSwatchProps
|
|
17
32
|
extends Omit<HTMLAttributes<HTMLInputElement>, 'label'> {
|
|
18
33
|
name?: string;
|
|
@@ -26,7 +41,7 @@ export interface ImageSwatchProps
|
|
|
26
41
|
selected?: boolean;
|
|
27
42
|
outOfStock?: boolean;
|
|
28
43
|
multi?: boolean;
|
|
29
|
-
imageNode?: VNode;
|
|
44
|
+
imageNode?: VNode | ((props: ImageNodeRenderProps) => JSX.Element);
|
|
30
45
|
onValue?: (value: any) => void;
|
|
31
46
|
onUpdateError?: (error: Error) => void;
|
|
32
47
|
}
|
|
@@ -80,6 +95,16 @@ export const ImageSwatch: FunctionComponent<ImageSwatchProps> = ({
|
|
|
80
95
|
return `${groupAriaLabel}: ${label} ${swatchLabel}`;
|
|
81
96
|
};
|
|
82
97
|
|
|
98
|
+
const imageProps: ImageProps = useMemo(() => {
|
|
99
|
+
return {
|
|
100
|
+
src,
|
|
101
|
+
alt,
|
|
102
|
+
loading: 'lazy',
|
|
103
|
+
params: { width: 100, fit: 'bounds', crop: true },
|
|
104
|
+
onError: (e: any) => (e.target.style.display = 'none'),
|
|
105
|
+
};
|
|
106
|
+
}, [src, alt]);
|
|
107
|
+
|
|
83
108
|
return (
|
|
84
109
|
<label className={classes(['dropin-image-swatch__container', className])}>
|
|
85
110
|
<input
|
|
@@ -107,14 +132,24 @@ export const ImageSwatch: FunctionComponent<ImageSwatchProps> = ({
|
|
|
107
132
|
className,
|
|
108
133
|
])}
|
|
109
134
|
>
|
|
110
|
-
{imageNode
|
|
135
|
+
{typeof imageNode === 'function' ? (
|
|
136
|
+
imageNode({
|
|
137
|
+
...imageProps,
|
|
138
|
+
imageSwatchContext: {
|
|
139
|
+
disabled,
|
|
140
|
+
outOfStock,
|
|
141
|
+
selected,
|
|
142
|
+
value,
|
|
143
|
+
label,
|
|
144
|
+
groupAriaLabel,
|
|
145
|
+
name,
|
|
146
|
+
id,
|
|
147
|
+
},
|
|
148
|
+
})
|
|
149
|
+
) : imageNode || (
|
|
111
150
|
<Image
|
|
112
|
-
|
|
151
|
+
{...imageProps}
|
|
113
152
|
className={classes(['dropin-image-swatch__content'])}
|
|
114
|
-
params={{ width: 100, fit: 'bounds', crop: true }}
|
|
115
|
-
alt={alt}
|
|
116
|
-
loading={'lazy'}
|
|
117
|
-
onError={(e: any) => (e.target.style.display = 'none')}
|
|
118
153
|
/>
|
|
119
154
|
)}
|
|
120
155
|
</span>
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*******************************************************************/
|
|
9
9
|
|
|
10
10
|
import { FunctionComponent } from 'preact';
|
|
11
|
-
import { useState, useCallback } from 'preact/hooks';
|
|
11
|
+
import { useState, useCallback, useEffect } from 'preact/hooks';
|
|
12
12
|
import { HTMLAttributes } from 'preact/compat';
|
|
13
13
|
import { classes, debounce } from '@adobe-commerce/elsie/lib';
|
|
14
14
|
import { Add, Minus } from '@adobe-commerce/elsie/icons';
|
|
@@ -56,6 +56,15 @@ export const Incrementer: FunctionComponent<IncrementerProps> = ({
|
|
|
56
56
|
? 'Dropin.Incrementer.maxQuantityMessage'
|
|
57
57
|
: 'Dropin.Incrementer.errorMessage';
|
|
58
58
|
|
|
59
|
+
// Add this effect to synchronize internal state with external value prop
|
|
60
|
+
useEffect(() => {
|
|
61
|
+
const propValue = Number(value);
|
|
62
|
+
if (propValue !== currentValue) {
|
|
63
|
+
setCurrentValue(propValue);
|
|
64
|
+
}
|
|
65
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
66
|
+
}, [value]);
|
|
67
|
+
|
|
59
68
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
60
69
|
const debouncedOnValueHandler = useCallback(
|
|
61
70
|
debounce(async (newValue: any) => {
|
|
@@ -48,6 +48,7 @@ const Template: StoryObj<TagProps> = {
|
|
|
48
48
|
<Tag {...args}>
|
|
49
49
|
{/* This workaround allows children to be edited as plain text in Storybook */}
|
|
50
50
|
{args.children && typeof args.children === 'string' ? (
|
|
51
|
+
// eslint-disable-next-line react/no-danger
|
|
51
52
|
<span dangerouslySetInnerHTML={{ __html: args.children }} />
|
|
52
53
|
) : undefined}
|
|
53
54
|
</Tag>
|
package/src/lib/slot.tsx
CHANGED
|
@@ -25,6 +25,7 @@ import '@adobe-commerce/elsie/components/UIProvider/debugger.css';
|
|
|
25
25
|
|
|
26
26
|
type MutateElement = (elem: HTMLElement) => void;
|
|
27
27
|
|
|
28
|
+
|
|
28
29
|
interface State {
|
|
29
30
|
get: (key: string) => void;
|
|
30
31
|
set: (key: string, value: any) => void;
|
|
@@ -42,6 +43,7 @@ interface PrivateContext<T> {
|
|
|
42
43
|
_registerMethod: (
|
|
43
44
|
cb: (next: T & DefaultSlotContext<T>, state: State) => void
|
|
44
45
|
) => void;
|
|
46
|
+
// eslint-disable-next-line no-undef
|
|
45
47
|
_htmlElementToVNode: (element: HTMLElement, tag: keyof HTMLElementTagNameMap) => VNode;
|
|
46
48
|
}
|
|
47
49
|
|
|
@@ -76,6 +78,7 @@ export function useSlot<K, V extends HTMLElement>(
|
|
|
76
78
|
callback?: SlotProps<K>,
|
|
77
79
|
children?: ComponentChildren,
|
|
78
80
|
render?: Function,
|
|
81
|
+
// eslint-disable-next-line no-undef
|
|
79
82
|
contentTag: keyof HTMLElementTagNameMap = 'div'
|
|
80
83
|
): [RefObject<V>, Record<string, any>] {
|
|
81
84
|
const slotsQueue = useContext(SlotQueueContext);
|
|
@@ -319,7 +322,7 @@ export function useSlot<K, V extends HTMLElement>(
|
|
|
319
322
|
status.current = 'loading';
|
|
320
323
|
|
|
321
324
|
log(`🟩 "${name}" Slot Initialized`);
|
|
322
|
-
await callback(context as K & DefaultSlotContext<K>, elementRef.current);
|
|
325
|
+
await callback(context as K & DefaultSlotContext<K>, elementRef.current as HTMLDivElement | null);
|
|
323
326
|
} catch (error) {
|
|
324
327
|
console.error(`Error in "${callback.name}" Slot callback`, error);
|
|
325
328
|
} finally {
|
|
@@ -359,7 +362,9 @@ interface SlotPropsComponent<T>
|
|
|
359
362
|
slot?: SlotProps<T>;
|
|
360
363
|
context?: Context<T>;
|
|
361
364
|
render?: (props: Record<string, any>) => VNode | VNode[];
|
|
365
|
+
// eslint-disable-next-line no-undef
|
|
362
366
|
slotTag?: keyof HTMLElementTagNameMap; // The tag for the slot wrapper itself
|
|
367
|
+
// eslint-disable-next-line no-undef
|
|
363
368
|
contentTag?: keyof HTMLElementTagNameMap; // The tag for dynamically inserted content
|
|
364
369
|
children?: ComponentChildren;
|
|
365
370
|
}
|
|
@@ -373,7 +378,11 @@ export function Slot<T>({
|
|
|
373
378
|
slotTag = 'div',
|
|
374
379
|
contentTag = 'div',
|
|
375
380
|
...props
|
|
376
|
-
}: Readonly<SlotPropsComponent<T>>) {
|
|
381
|
+
}: Readonly<SlotPropsComponent<T>>): VNode<{
|
|
382
|
+
ref: RefObject<HTMLElement>;
|
|
383
|
+
'data-slot': string;
|
|
384
|
+
[key: string]: any;
|
|
385
|
+
}> {
|
|
377
386
|
const slotsQueue = useContext(SlotQueueContext);
|
|
378
387
|
|
|
379
388
|
const [elementRef, slotProps] = useSlot<T, HTMLElement>(
|