@adobe-commerce/elsie 1.4.1-alpha009 → 1.5.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/package.json +2 -2
- package/src/components/CartItem/CartItem.tsx +26 -15
- package/src/components/InputFile/InputFile.css +49 -0
- package/src/components/InputFile/InputFile.stories.tsx +90 -0
- package/src/components/InputFile/InputFile.tsx +59 -0
- package/src/components/InputFile/index.ts +11 -0
- package/src/components/ProductItemCard/ProductItemCardSkeleton.css +1 -14
- package/src/components/ProductItemCard/ProductItemCardSkeleton.tsx +5 -5
- package/src/components/index.ts +1 -0
- package/src/docs/API/event-bus.mdx +234 -17
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adobe-commerce/elsie",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.0-beta1",
|
|
4
4
|
"license": "SEE LICENSE IN LICENSE.md",
|
|
5
5
|
"description": "Domain Package SDK",
|
|
6
6
|
"engines": {
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"cleanup": "rimraf dist"
|
|
27
27
|
},
|
|
28
28
|
"devDependencies": {
|
|
29
|
-
"@adobe-commerce/event-bus": "~1.0.
|
|
29
|
+
"@adobe-commerce/event-bus": "~1.0.1",
|
|
30
30
|
"@adobe-commerce/fetch-graphql": "~1.1.0",
|
|
31
31
|
"@adobe-commerce/recaptcha": "~1.0.1",
|
|
32
32
|
"@adobe-commerce/storefront-design": "~1.0.0",
|
|
@@ -36,6 +36,7 @@ export interface CartItemProps
|
|
|
36
36
|
totalExcludingTax?: VNode;
|
|
37
37
|
sku?: VNode;
|
|
38
38
|
quantity?: number;
|
|
39
|
+
quantityContent?: VNode;
|
|
39
40
|
description?: VNode;
|
|
40
41
|
attributes?: VNode;
|
|
41
42
|
footer?: VNode;
|
|
@@ -45,6 +46,7 @@ export interface CartItemProps
|
|
|
45
46
|
discount?: VNode;
|
|
46
47
|
savings?: VNode;
|
|
47
48
|
actions?: VNode;
|
|
49
|
+
removeContent?: VNode;
|
|
48
50
|
loading?: boolean;
|
|
49
51
|
updating?: boolean;
|
|
50
52
|
onRemove?: () => void;
|
|
@@ -71,7 +73,9 @@ export const CartItem: FunctionComponent<CartItemProps> = ({
|
|
|
71
73
|
discount,
|
|
72
74
|
savings,
|
|
73
75
|
actions,
|
|
76
|
+
removeContent,
|
|
74
77
|
quantity,
|
|
78
|
+
quantityContent,
|
|
75
79
|
description,
|
|
76
80
|
attributes,
|
|
77
81
|
footer,
|
|
@@ -304,18 +308,20 @@ export const CartItem: FunctionComponent<CartItemProps> = ({
|
|
|
304
308
|
['dropin-cart-item__quantity--edit', !!onQuantity],
|
|
305
309
|
])}
|
|
306
310
|
>
|
|
307
|
-
{
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
</
|
|
318
|
-
|
|
311
|
+
{quantityContent ? (
|
|
312
|
+
<VComponent node={quantityContent} />
|
|
313
|
+
) : onQuantity ? (
|
|
314
|
+
quantityComponent
|
|
315
|
+
) : (
|
|
316
|
+
quantity && (
|
|
317
|
+
<span className={classes(['dropin-cart-item__quantity__value'])}>
|
|
318
|
+
{labels.quantity}:{' '}
|
|
319
|
+
<strong className="dropin-cart-item__quantity__number">
|
|
320
|
+
{Number(quantity).toLocaleString(locale)}
|
|
321
|
+
</strong>
|
|
322
|
+
</span>
|
|
323
|
+
)
|
|
324
|
+
)}
|
|
319
325
|
|
|
320
326
|
{/* Warning */}
|
|
321
327
|
{warning && (
|
|
@@ -432,12 +438,17 @@ export const CartItem: FunctionComponent<CartItemProps> = ({
|
|
|
432
438
|
|
|
433
439
|
{/* Footer */}
|
|
434
440
|
{footer && (
|
|
435
|
-
|
|
441
|
+
<VComponent
|
|
442
|
+
node={footer}
|
|
443
|
+
className={classes(['dropin-cart-item__footer'])}
|
|
444
|
+
/>
|
|
436
445
|
)}
|
|
437
446
|
</div>
|
|
438
447
|
|
|
439
448
|
{/* Remove Item */}
|
|
440
|
-
{
|
|
449
|
+
{removeContent ? (
|
|
450
|
+
<VComponent node={removeContent} />
|
|
451
|
+
) : onRemove ? (
|
|
441
452
|
<Button
|
|
442
453
|
data-testid="cart-item-remove-button"
|
|
443
454
|
className={classes(['dropin-cart-item__remove'])}
|
|
@@ -459,7 +470,7 @@ export const CartItem: FunctionComponent<CartItemProps> = ({
|
|
|
459
470
|
}
|
|
460
471
|
disabled={updating}
|
|
461
472
|
/>
|
|
462
|
-
)}
|
|
473
|
+
) : null}
|
|
463
474
|
</div>
|
|
464
475
|
);
|
|
465
476
|
};
|
|
@@ -0,0 +1,49 @@
|
|
|
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-input-file__input {
|
|
13
|
+
display: none;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.dropin-input-file__label {
|
|
17
|
+
border: 0 none;
|
|
18
|
+
cursor: pointer;
|
|
19
|
+
border-radius: var(--shape-border-radius-3);
|
|
20
|
+
padding: var(--spacing-xsmall) var(--spacing-medium);
|
|
21
|
+
display: flex;
|
|
22
|
+
justify-content: center;
|
|
23
|
+
align-items: center;
|
|
24
|
+
background: var(--color-brand-500);
|
|
25
|
+
color: var(--color-neutral-50);
|
|
26
|
+
font: var(--type-button-2-font);
|
|
27
|
+
letter-spacing: var(--type-button-2-letter-spacing);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.dropin-input-file__label:hover {
|
|
31
|
+
background-color: var(--color-button-hover);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.dropin-input-file__icon {
|
|
35
|
+
height: 24px;
|
|
36
|
+
margin-right: var(--spacing-xsmall);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/* Medium (portrait tablets and large phones, 768px and up) */
|
|
40
|
+
/* @media only screen and (min-width: 768px) { } */
|
|
41
|
+
|
|
42
|
+
/* Large (landscape tablets, 1024px and up) */
|
|
43
|
+
/* @media only screen and (min-width: 1024px) { } */
|
|
44
|
+
|
|
45
|
+
/* XLarge (laptops/desktops, 1366px and up) */
|
|
46
|
+
/* @media only screen and (min-width: 1366px) { } */
|
|
47
|
+
|
|
48
|
+
/* XXlarge (large laptops and desktops, 1920px and up) */
|
|
49
|
+
/* @media only screen and (min-width: 1920px) { } */
|
|
@@ -0,0 +1,90 @@
|
|
|
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://storybook.js.org/docs/7.0/preact/writing-stories/introduction
|
|
11
|
+
import type { Meta, StoryObj } from '@storybook/preact';
|
|
12
|
+
import { InputFile, InputFileProps } from '@adobe-commerce/elsie/components/InputFile';
|
|
13
|
+
import { action } from '@storybook/addon-actions';
|
|
14
|
+
import { IconsList } from '@adobe-commerce/elsie/components/Icon/Icon.stories.helpers';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Use InputFile to upload files.
|
|
18
|
+
*/
|
|
19
|
+
const meta: Meta<InputFileProps> = {
|
|
20
|
+
title: 'Components/InputFile',
|
|
21
|
+
component: InputFile,
|
|
22
|
+
argTypes: {
|
|
23
|
+
label: {
|
|
24
|
+
description: 'Label for the input file.',
|
|
25
|
+
type: 'string',
|
|
26
|
+
},
|
|
27
|
+
accept: {
|
|
28
|
+
description: 'Restrict selectable file types',
|
|
29
|
+
type: 'string',
|
|
30
|
+
},
|
|
31
|
+
multiple: {
|
|
32
|
+
description: 'Allow multiple files selection.',
|
|
33
|
+
type: {
|
|
34
|
+
name: 'boolean',
|
|
35
|
+
required: false
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
id: {
|
|
39
|
+
description: 'id',
|
|
40
|
+
type: {
|
|
41
|
+
required: false,
|
|
42
|
+
name: 'string'
|
|
43
|
+
},
|
|
44
|
+
control: 'text',
|
|
45
|
+
},
|
|
46
|
+
onChange: {
|
|
47
|
+
description: 'Handler for when the file selection changes.',
|
|
48
|
+
control: false,
|
|
49
|
+
table: {
|
|
50
|
+
type: {
|
|
51
|
+
summary: 'function'
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
icon: {
|
|
56
|
+
description: 'Optional icon.',
|
|
57
|
+
table: {
|
|
58
|
+
type: { summary: 'FunctionComponent' },
|
|
59
|
+
},
|
|
60
|
+
options: Object.keys(IconsList),
|
|
61
|
+
mapping: IconsList,
|
|
62
|
+
control: 'select',
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export default meta;
|
|
68
|
+
|
|
69
|
+
type Story = StoryObj<InputFileProps>;
|
|
70
|
+
|
|
71
|
+
export const Default: Story = {
|
|
72
|
+
args: {
|
|
73
|
+
label: 'Upload File',
|
|
74
|
+
id: 'single-file-input',
|
|
75
|
+
accept: ".pdf, .doc, .docx, .xls, .xlsx, .ppt, .pptx, .txt, .csv, .jpg, .jpeg, .png, .gif, .bmp, .tiff, .ico, .webp",
|
|
76
|
+
onChange: action('onChange'),
|
|
77
|
+
icon: 'none' as any,
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
export const MultipleFiles: Story = {
|
|
82
|
+
args: {
|
|
83
|
+
label: 'Upload Multiple Files',
|
|
84
|
+
id: 'multiple-files-input',
|
|
85
|
+
accept: ".pdf, .doc, .docx, .xls, .xlsx, .ppt, .pptx, .txt, .csv, .jpg, .jpeg, .png, .gif, .bmp, .tiff, .ico, .webp",
|
|
86
|
+
multiple: true,
|
|
87
|
+
onChange: action('onChange'),
|
|
88
|
+
icon: 'none' as any,
|
|
89
|
+
},
|
|
90
|
+
};
|
|
@@ -0,0 +1,59 @@
|
|
|
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 { useId } from 'preact/hooks';
|
|
12
|
+
import { HTMLAttributes } from 'preact/compat';
|
|
13
|
+
import { classes } from '@adobe-commerce/elsie/lib';
|
|
14
|
+
import '@adobe-commerce/elsie/components/InputFile/InputFile.css';
|
|
15
|
+
|
|
16
|
+
export interface InputFileProps extends Omit<HTMLAttributes<HTMLInputElement>, 'type' | 'icon'> {
|
|
17
|
+
accept?: string;
|
|
18
|
+
onChange?: (event: Event) => void;
|
|
19
|
+
label?: string;
|
|
20
|
+
multiple?: boolean;
|
|
21
|
+
icon?: VNode<HTMLAttributes<SVGSVGElement>>;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export const InputFile: FunctionComponent<InputFileProps> = ({
|
|
25
|
+
accept,
|
|
26
|
+
onChange,
|
|
27
|
+
label = 'Upload Document',
|
|
28
|
+
icon,
|
|
29
|
+
className,
|
|
30
|
+
multiple,
|
|
31
|
+
id: providedId,
|
|
32
|
+
...props
|
|
33
|
+
}) => {
|
|
34
|
+
|
|
35
|
+
const generatedId = useId();
|
|
36
|
+
const id = providedId || generatedId;
|
|
37
|
+
|
|
38
|
+
const handleChange = (e: Event) => {
|
|
39
|
+
onChange?.(e);
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
return (
|
|
43
|
+
<div className={classes(['dropin-input-file', className])}>
|
|
44
|
+
<label htmlFor={id} className="dropin-input-file__label">
|
|
45
|
+
{icon && <span className="dropin-input-file__icon">{icon}</span>}
|
|
46
|
+
{label}
|
|
47
|
+
</label>
|
|
48
|
+
<input
|
|
49
|
+
id={id}
|
|
50
|
+
type="file"
|
|
51
|
+
accept={accept}
|
|
52
|
+
multiple={multiple}
|
|
53
|
+
onChange={handleChange}
|
|
54
|
+
className="dropin-input-file__input"
|
|
55
|
+
{...props}
|
|
56
|
+
/>
|
|
57
|
+
</div>
|
|
58
|
+
);
|
|
59
|
+
};
|
|
@@ -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/InputFile/InputFile';
|
|
11
|
+
export { InputFile as default } from '@adobe-commerce/elsie/components/InputFile/InputFile';
|
|
@@ -9,23 +9,10 @@
|
|
|
9
9
|
|
|
10
10
|
/* https://cssguidelin.es/#bem-like-naming */
|
|
11
11
|
|
|
12
|
-
.dropin-product-item-card__skeleton {
|
|
13
|
-
gap: var(--spacing-small);
|
|
14
|
-
}
|
|
15
|
-
|
|
16
12
|
.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
13
|
width: 100%;
|
|
26
|
-
height:
|
|
14
|
+
height: 370px;
|
|
27
15
|
}
|
|
28
|
-
*/
|
|
29
16
|
|
|
30
17
|
/* Medium (portrait tablets and large phones, 768px and up) */
|
|
31
18
|
/* @media only screen and (min-width: 768px) { } */
|
|
@@ -19,10 +19,11 @@ export const ProductItemCardSkeleton: FunctionComponent = () => {
|
|
|
19
19
|
fullWidth={true}
|
|
20
20
|
className="dropin-product-item-card__skeleton__image"
|
|
21
21
|
/>
|
|
22
|
-
|
|
22
|
+
</Skeleton>
|
|
23
|
+
<Skeleton className="dropin-product-item-card__content dropin-product-item-card__skeleton__content">
|
|
23
24
|
<SkeletonRow
|
|
24
25
|
fullWidth={true}
|
|
25
|
-
size="
|
|
26
|
+
size="xsmall"
|
|
26
27
|
className="dropin-product-item-card__skeleton__item"
|
|
27
28
|
/>
|
|
28
29
|
<SkeletonRow
|
|
@@ -32,11 +33,10 @@ export const ProductItemCardSkeleton: FunctionComponent = () => {
|
|
|
32
33
|
/>
|
|
33
34
|
<SkeletonRow
|
|
34
35
|
fullWidth={true}
|
|
35
|
-
size="
|
|
36
|
+
size="xsmall"
|
|
36
37
|
className="dropin-product-item-card__skeleton__item"
|
|
37
38
|
/>
|
|
38
|
-
</
|
|
39
|
-
</Skeleton>
|
|
39
|
+
</Skeleton>
|
|
40
40
|
</div>
|
|
41
41
|
);
|
|
42
42
|
};
|
package/src/components/index.ts
CHANGED
|
@@ -48,3 +48,4 @@ 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
50
|
export * from '@adobe-commerce/elsie/components/ProductItemCard';
|
|
51
|
+
export * from '@adobe-commerce/elsie/components/InputFile';
|
|
@@ -5,48 +5,265 @@ import { Meta, Unstyled } from '@storybook/blocks';
|
|
|
5
5
|
|
|
6
6
|
# Event Bus
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
The Event Bus provides a communication system for different parts of your application to exchange messages and stay synchronized. It enables event-driven architecture for drop-ins, allowing Containers to react to changes from other Containers and communicate data changes to the storefront.
|
|
9
|
+
|
|
10
|
+
## Import
|
|
11
|
+
|
|
12
|
+
From drop-in project using the SDK
|
|
9
13
|
|
|
10
14
|
```ts
|
|
11
|
-
// from drop-in project (SDK)
|
|
12
15
|
import { events } from '@adobe-commerce/elsie/lib';
|
|
16
|
+
```
|
|
17
|
+
|
|
13
18
|
|
|
14
|
-
|
|
19
|
+
From integration project (storefront)
|
|
20
|
+
|
|
21
|
+
```js
|
|
15
22
|
import { events } from '@dropins/tools/event-bus.js';
|
|
16
23
|
```
|
|
17
24
|
|
|
18
|
-
## Methods
|
|
25
|
+
## Core Methods
|
|
26
|
+
|
|
27
|
+
### Subscribe to Events
|
|
28
|
+
|
|
29
|
+
Subscribe to events and receive notifications when they occur.
|
|
30
|
+
|
|
31
|
+
```ts
|
|
32
|
+
const eventListener = events.on('<event>', (payload) => {
|
|
33
|
+
// Handle the event payload
|
|
34
|
+
console.log('Event received:', payload);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
// Stop listening to the event
|
|
38
|
+
eventListener.off();
|
|
39
|
+
```
|
|
19
40
|
|
|
20
|
-
|
|
41
|
+
**Example:**
|
|
21
42
|
```ts
|
|
22
|
-
|
|
23
|
-
|
|
43
|
+
// Listen for cart updates
|
|
44
|
+
const cartListener = events.on('cart/data', (cartData) => {
|
|
45
|
+
if (cartData) {
|
|
46
|
+
console.log(`Cart has ${cartData.totalQuantity} items`);
|
|
47
|
+
updateCartUI(cartData);
|
|
48
|
+
} else {
|
|
49
|
+
console.log('Cart is empty');
|
|
50
|
+
showEmptyCart();
|
|
51
|
+
}
|
|
24
52
|
});
|
|
25
53
|
|
|
26
|
-
//
|
|
27
|
-
|
|
54
|
+
// Later, when you want to stop listening
|
|
55
|
+
cartListener.off();
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Emit Events
|
|
59
|
+
|
|
60
|
+
Broadcast events to all listeners across your application.
|
|
61
|
+
|
|
62
|
+
```ts
|
|
63
|
+
events.emit('<event>', payload);
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
**Examples:**
|
|
67
|
+
```ts
|
|
68
|
+
// Emit cart data
|
|
69
|
+
const cartData = {
|
|
70
|
+
id: 'cart-123',
|
|
71
|
+
totalQuantity: 2,
|
|
72
|
+
items: [
|
|
73
|
+
{ uid: 'item-1', quantity: 1, sku: 'PROD-001', name: 'Product Name' }
|
|
74
|
+
]
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
events.emit('cart/data', cartData);
|
|
28
78
|
```
|
|
29
79
|
|
|
30
|
-
###
|
|
80
|
+
### Get Last Event Payload
|
|
81
|
+
|
|
82
|
+
Retrieve the most recent payload for a specific event.
|
|
83
|
+
|
|
84
|
+
```ts
|
|
85
|
+
const lastPayload = events.lastPayload('<event>');
|
|
86
|
+
```
|
|
31
87
|
|
|
88
|
+
**Example:**
|
|
32
89
|
```ts
|
|
33
|
-
|
|
90
|
+
// Get the current cart state without waiting for an event
|
|
91
|
+
const currentCart = events.lastPayload('cart/data');
|
|
92
|
+
|
|
93
|
+
if (currentCart) {
|
|
94
|
+
console.log('Current cart total:', currentCart.totalQuantity);
|
|
95
|
+
}
|
|
34
96
|
```
|
|
35
97
|
|
|
36
|
-
### Logging
|
|
98
|
+
### Enable Debug Logging
|
|
99
|
+
|
|
100
|
+
Turn on console logging to debug event flow.
|
|
37
101
|
|
|
38
102
|
```ts
|
|
39
|
-
// Enable logging
|
|
103
|
+
// Enable logging to see all events in console
|
|
40
104
|
events.enableLogger(true);
|
|
105
|
+
```
|
|
41
106
|
|
|
42
|
-
|
|
43
|
-
|
|
107
|
+
## Advanced Features
|
|
108
|
+
|
|
109
|
+
### Eager Loading
|
|
110
|
+
|
|
111
|
+
Execute the event handler immediately with the last known payload when subscribing. This is useful for getting the current state without waiting for the next event.
|
|
112
|
+
|
|
113
|
+
```ts
|
|
114
|
+
// Handler will execute immediately if there's a previous payload
|
|
115
|
+
const listener = events.on('cart/data', (cartData) => {
|
|
116
|
+
console.log('Cart data received:', cartData);
|
|
117
|
+
}, { eager: true });
|
|
44
118
|
```
|
|
45
119
|
|
|
46
|
-
|
|
120
|
+
**Use Cases:**
|
|
121
|
+
- Initialize UI components with current state
|
|
122
|
+
- Avoid waiting for the first event emission
|
|
123
|
+
- Ensure components have the latest data on mount
|
|
124
|
+
|
|
125
|
+
### Event Scoping
|
|
126
|
+
|
|
127
|
+
Create namespaced events to avoid conflicts between different parts of your application.
|
|
128
|
+
|
|
129
|
+
```ts
|
|
130
|
+
// Subscribe to a scoped event
|
|
131
|
+
const scopedListener = events.on('data/update', (data) => {
|
|
132
|
+
console.log('Scoped data received:', data);
|
|
133
|
+
}, { scope: 'feature-a' });
|
|
134
|
+
|
|
135
|
+
// Emit a scoped event
|
|
136
|
+
events.emit('data/update', payload, { scope: 'feature-a' });
|
|
137
|
+
|
|
138
|
+
// Get last payload for a scoped event
|
|
139
|
+
const lastScopedData = events.lastPayload('data/update', { scope: 'feature-a' });
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
**Scoped Event Names:**
|
|
143
|
+
When using scopes, the actual event name becomes `scope/event`. For example:
|
|
144
|
+
- `'feature-a/data/update'` instead of `'data/update'`
|
|
145
|
+
- `'module-b/user/action'` instead of `'user/action'`
|
|
146
|
+
|
|
147
|
+
**Use Cases:**
|
|
148
|
+
- Separate different features or modules
|
|
149
|
+
- Different contexts within the same application
|
|
150
|
+
- Component-specific event handling
|
|
151
|
+
|
|
152
|
+
### Combining Options
|
|
153
|
+
|
|
154
|
+
Use both eager loading and scoping together for powerful event handling.
|
|
47
155
|
|
|
48
156
|
```ts
|
|
49
|
-
|
|
157
|
+
// Subscribe to a scoped event with eager loading
|
|
158
|
+
const listener = events.on('locale', (locale) => {
|
|
159
|
+
console.log('Current locale:', locale);
|
|
160
|
+
}, {
|
|
161
|
+
eager: true,
|
|
162
|
+
scope: 'user-preferences'
|
|
163
|
+
});
|
|
50
164
|
```
|
|
51
165
|
|
|
166
|
+
## Event-Driven Drop-ins
|
|
167
|
+
|
|
168
|
+
The Event Bus enables drop-ins to be truly event-driven, allowing for loose coupling between components and seamless communication across the application.
|
|
169
|
+
|
|
170
|
+
### Container-to-Container Communication
|
|
171
|
+
|
|
172
|
+
Containers can react to changes from other Containers, enabling complex interactions without direct dependencies.
|
|
173
|
+
|
|
174
|
+
```ts
|
|
175
|
+
// Product Container: Emits when a product is added to cart
|
|
176
|
+
function ProductContainer() {
|
|
177
|
+
const handleAddToCart = (product) => {
|
|
178
|
+
// Add to cart logic...
|
|
179
|
+
|
|
180
|
+
// Notify other containers about the cart change
|
|
181
|
+
events.emit('cart/data', updatedCartData);
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
return (
|
|
185
|
+
<button onClick={() => handleAddToCart(product)}>
|
|
186
|
+
Add to Cart
|
|
187
|
+
</button>
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Cart Container: Reacts to cart changes from any source
|
|
192
|
+
function CartContainer() {
|
|
193
|
+
useEffect(() => {
|
|
194
|
+
const cartListener = events.on('cart/data', (cartData) => {
|
|
195
|
+
updateCartDisplay(cartData);
|
|
196
|
+
updateCartBadge(cartData.totalQuantity);
|
|
197
|
+
}, { eager: true });
|
|
198
|
+
|
|
199
|
+
return () => cartListener.off();
|
|
200
|
+
}, []);
|
|
201
|
+
|
|
202
|
+
return <CartDisplay />;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Mini Cart Container: Also reacts to the same cart changes
|
|
206
|
+
function MiniCartContainer() {
|
|
207
|
+
useEffect(() => {
|
|
208
|
+
const cartListener = events.on('cart/data', (cartData) => {
|
|
209
|
+
updateMiniCart(cartData);
|
|
210
|
+
}, { eager: true });
|
|
211
|
+
|
|
212
|
+
return () => cartListener.off();
|
|
213
|
+
}, []);
|
|
214
|
+
|
|
215
|
+
return <MiniCart />;
|
|
216
|
+
}
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
### Storefront Communication
|
|
220
|
+
|
|
221
|
+
Drop-ins can communicate data changes to the storefront, enabling seamless integration with the host application.
|
|
222
|
+
|
|
223
|
+
```ts
|
|
224
|
+
// Authentication Container: Notifies storefront of login/logout
|
|
225
|
+
function AuthContainer() {
|
|
226
|
+
const handleLogin = (userData) => {
|
|
227
|
+
// Login logic...
|
|
228
|
+
|
|
229
|
+
// Notify storefront of authentication change
|
|
230
|
+
events.emit('authenticated', true);
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
const handleLogout = () => {
|
|
234
|
+
// Logout logic...
|
|
235
|
+
|
|
236
|
+
// Notify storefront of authentication change
|
|
237
|
+
events.emit('authenticated', false);
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
return <AuthForm onLogin={handleLogin} onLogout={handleLogout} />;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Storefront can listen for authentication changes
|
|
244
|
+
// This would be in the host application
|
|
245
|
+
const authListener = events.on('authenticated', (isAuthenticated) => {
|
|
246
|
+
if (isAuthenticated) {
|
|
247
|
+
showUserMenu();
|
|
248
|
+
enableCheckout();
|
|
249
|
+
} else {
|
|
250
|
+
hideUserMenu();
|
|
251
|
+
disableCheckout();
|
|
252
|
+
}
|
|
253
|
+
}, { eager: true });
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
## Best Practices
|
|
259
|
+
|
|
260
|
+
1. **Always unsubscribe** from events when components unmount to prevent memory leaks
|
|
261
|
+
2. **Use scopes** to organize events by feature or component
|
|
262
|
+
3. **Enable eager loading** when you need immediate access to current state
|
|
263
|
+
4. **Use descriptive event names** that clearly indicate what data they contain
|
|
264
|
+
5. **Handle null/undefined payloads** gracefully in your event handlers
|
|
265
|
+
6. **Enable logging during development** to debug event flow
|
|
266
|
+
7. **Keep event payloads lightweight** to avoid performance issues
|
|
267
|
+
8. **Document your event contracts** so other developers know what to expect
|
|
268
|
+
|
|
52
269
|
</Unstyled>
|