@adobe-commerce/elsie 1.6.0-alpha999 → 1.6.0-beta2
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/jest.js +3 -3
- package/package.json +3 -3
- package/src/components/Button/Button.tsx +2 -0
- package/src/components/Field/Field.tsx +19 -14
- package/src/components/Icon/Icon.tsx +29 -24
- package/src/components/Incrementer/Incrementer.css +6 -0
- package/src/components/Incrementer/Incrementer.stories.tsx +18 -0
- package/src/components/Incrementer/Incrementer.tsx +66 -59
- package/src/components/MultiSelect/MultiSelect.css +273 -0
- package/src/components/MultiSelect/MultiSelect.stories.tsx +459 -0
- package/src/components/MultiSelect/MultiSelect.tsx +763 -0
- package/src/components/MultiSelect/index.ts +11 -0
- package/src/components/Pagination/Pagination.css +14 -5
- package/src/components/Pagination/Pagination.stories.tsx +32 -3
- package/src/components/Pagination/Pagination.tsx +28 -22
- package/src/components/Pagination/PaginationButton.tsx +46 -0
- package/src/components/Price/Price.tsx +8 -41
- package/src/components/Table/Table.css +183 -0
- package/src/components/Table/Table.stories.tsx +1024 -0
- package/src/components/Table/Table.tsx +253 -0
- package/src/components/Table/index.ts +11 -0
- package/src/components/ToggleButton/ToggleButton.css +13 -1
- package/src/components/ToggleButton/ToggleButton.stories.tsx +13 -6
- package/src/components/ToggleButton/ToggleButton.tsx +4 -0
- package/src/components/index.ts +5 -3
- package/src/docs/slots.mdx +2 -0
- package/src/i18n/en_US.json +38 -0
- package/src/icons/Business.svg +3 -0
- package/src/icons/List.svg +3 -0
- package/src/icons/Quote.svg +3 -0
- package/src/icons/Structure.svg +8 -0
- package/src/icons/Team.svg +5 -0
- package/src/icons/index.ts +29 -24
- package/src/lib/aem/configs.ts +10 -4
- package/src/lib/get-price-formatter.ts +69 -0
- package/src/lib/index.ts +4 -3
- package/src/lib/slot.tsx +61 -5
|
@@ -0,0 +1,69 @@
|
|
|
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 { getGlobalLocale } from '@adobe-commerce/elsie/lib';
|
|
11
|
+
|
|
12
|
+
export interface PriceFormatterOptions {
|
|
13
|
+
currency?: string | null;
|
|
14
|
+
locale?: string;
|
|
15
|
+
formatOptions?: Intl.NumberFormatOptions;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Determines the effective locale to use for price formatting
|
|
20
|
+
* Priority: prop locale > global locale > browser locale > default 'en-US'
|
|
21
|
+
*/
|
|
22
|
+
export function getEffectiveLocale(locale?: string): string {
|
|
23
|
+
if (locale) {
|
|
24
|
+
return locale;
|
|
25
|
+
}
|
|
26
|
+
const globalLocale = getGlobalLocale();
|
|
27
|
+
if (globalLocale) {
|
|
28
|
+
return globalLocale;
|
|
29
|
+
}
|
|
30
|
+
// Fallback to browser locale or default
|
|
31
|
+
return process.env.LOCALE && process.env.LOCALE !== 'undefined' ? process.env.LOCALE : 'en-US';
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Gets an Intl.NumberFormat instance for price formatting
|
|
36
|
+
* Uses getEffectiveLocale internally to determine the best locale
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* // Single price formatting
|
|
40
|
+
* const formatter = getPriceFormatter({ currency: 'USD', locale: 'en-US' });
|
|
41
|
+
* const price = formatter.format(10.99); // "$10.99"
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* // Bulk price formatting (more efficient)
|
|
45
|
+
* const formatter = getPriceFormatter({ currency: 'EUR', locale: 'fr-FR' });
|
|
46
|
+
* const prices = [10.99, 25.50, 99.99].map(amount => formatter.format(amount));
|
|
47
|
+
*/
|
|
48
|
+
export function getPriceFormatter(
|
|
49
|
+
options: PriceFormatterOptions = {}
|
|
50
|
+
): Intl.NumberFormat {
|
|
51
|
+
const { currency, locale, formatOptions = {} } = options;
|
|
52
|
+
const effectiveLocale = getEffectiveLocale(locale);
|
|
53
|
+
|
|
54
|
+
const params: Intl.NumberFormatOptions = {
|
|
55
|
+
style: 'currency',
|
|
56
|
+
currency: currency || 'USD',
|
|
57
|
+
// These options are needed to round to whole numbers if that's what you want.
|
|
58
|
+
minimumFractionDigits: 2, // (this suffices for whole numbers, but will print 2500.10 as $2,500.1)
|
|
59
|
+
maximumFractionDigits: 2, // (causes 2500.99 to be printed as $2,501)
|
|
60
|
+
...formatOptions,
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
try {
|
|
64
|
+
return new Intl.NumberFormat(effectiveLocale, params);
|
|
65
|
+
} catch (error) {
|
|
66
|
+
console.error(`Error creating Intl.NumberFormat instance for locale ${effectiveLocale}. Falling back to en-US.`, error);
|
|
67
|
+
return new Intl.NumberFormat('en-US', params);
|
|
68
|
+
}
|
|
69
|
+
}
|
package/src/lib/index.ts
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
* Copyright 2024 Adobe
|
|
3
3
|
* All Rights Reserved.
|
|
4
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.
|
|
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
8
|
*******************************************************************/
|
|
9
9
|
|
|
10
10
|
export * from '@adobe-commerce/elsie/lib/form-values';
|
|
@@ -25,3 +25,4 @@ export * from '@adobe-commerce/elsie/lib/is-number';
|
|
|
25
25
|
export * from '@adobe-commerce/elsie/lib/deviceUtils';
|
|
26
26
|
export * from '@adobe-commerce/elsie/lib/get-path-value';
|
|
27
27
|
export * from '@adobe-commerce/elsie/lib/get-cookie';
|
|
28
|
+
export * from '@adobe-commerce/elsie/lib/get-price-formatter';
|
package/src/lib/slot.tsx
CHANGED
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
RefObject,
|
|
16
16
|
VNode,
|
|
17
17
|
} from 'preact';
|
|
18
|
-
import { HTMLAttributes } from 'preact/compat';
|
|
18
|
+
import { Children, HTMLAttributes, isValidElement } from 'preact/compat';
|
|
19
19
|
import {
|
|
20
20
|
StateUpdater,
|
|
21
21
|
useCallback,
|
|
@@ -41,6 +41,7 @@ interface SlotElement {
|
|
|
41
41
|
prependChild: MutateElement;
|
|
42
42
|
appendSibling: MutateElement;
|
|
43
43
|
prependSibling: MutateElement;
|
|
44
|
+
remove: () => void;
|
|
44
45
|
}
|
|
45
46
|
|
|
46
47
|
interface PrivateContext<T> {
|
|
@@ -60,6 +61,7 @@ interface DefaultSlotContext<T> extends PrivateContext<T> {
|
|
|
60
61
|
prependChild: MutateElement;
|
|
61
62
|
appendSibling: MutateElement;
|
|
62
63
|
prependSibling: MutateElement;
|
|
64
|
+
remove: () => void;
|
|
63
65
|
onRender: (cb: (next: T & DefaultSlotContext<T>) => void) => void;
|
|
64
66
|
onChange: (cb: (next: T & DefaultSlotContext<T>) => void) => void;
|
|
65
67
|
}
|
|
@@ -85,7 +87,7 @@ export function useSlot<K, V extends HTMLElement>(
|
|
|
85
87
|
render?: Function,
|
|
86
88
|
// eslint-disable-next-line no-undef
|
|
87
89
|
contentTag: keyof HTMLElementTagNameMap = 'div'
|
|
88
|
-
): [RefObject<V>, Record<string, any
|
|
90
|
+
): [RefObject<V>, Record<string, any>, 'loading' | 'pending' | 'ready'] {
|
|
89
91
|
const slotsQueue = useContext(SlotQueueContext);
|
|
90
92
|
|
|
91
93
|
// HTML Element
|
|
@@ -207,6 +209,10 @@ export function useSlot<K, V extends HTMLElement>(
|
|
|
207
209
|
const parent = element.parentNode;
|
|
208
210
|
parent?.insertBefore(elem, element);
|
|
209
211
|
},
|
|
212
|
+
|
|
213
|
+
remove: () => {
|
|
214
|
+
element.remove();
|
|
215
|
+
},
|
|
210
216
|
};
|
|
211
217
|
},
|
|
212
218
|
[name]
|
|
@@ -301,6 +307,14 @@ export function useSlot<K, V extends HTMLElement>(
|
|
|
301
307
|
[_registerMethod]
|
|
302
308
|
);
|
|
303
309
|
|
|
310
|
+
// @ts-ignore
|
|
311
|
+
context.remove = useCallback(() => {
|
|
312
|
+
// @ts-ignore
|
|
313
|
+
_registerMethod(() => {
|
|
314
|
+
elementRef.current?.remove();
|
|
315
|
+
});
|
|
316
|
+
}, [_registerMethod]);
|
|
317
|
+
|
|
304
318
|
const handleLifeCycleRender = useCallback(async () => {
|
|
305
319
|
if (status.current === 'loading') return;
|
|
306
320
|
|
|
@@ -363,9 +377,51 @@ export function useSlot<K, V extends HTMLElement>(
|
|
|
363
377
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
364
378
|
}, [JSON.stringify(context), JSON.stringify(_state)]);
|
|
365
379
|
|
|
366
|
-
return [elementRef, props];
|
|
380
|
+
return [elementRef, props, status.current];
|
|
367
381
|
}
|
|
368
382
|
|
|
383
|
+
/**
|
|
384
|
+
* Recursively processes children elements to conditionally prevent image loading.
|
|
385
|
+
*
|
|
386
|
+
* This function traverses the children tree and modifies img elements to prevent
|
|
387
|
+
* premature loading during slot initialization. When isReady is false, img src
|
|
388
|
+
* attributes are set to empty string to prevent network requests, while preserving
|
|
389
|
+
* the original src in a data attribute for debugging purposes.
|
|
390
|
+
*
|
|
391
|
+
* @param children - The children elements to process (can be any React/Preact children)
|
|
392
|
+
* @param isReady - Whether the slot is ready to load images (true) or should prevent loading (false)
|
|
393
|
+
* @returns Processed children with conditional image src attributes
|
|
394
|
+
*/
|
|
395
|
+
const processChildren = (children: any, isReady: boolean): any => {
|
|
396
|
+
return Children.map(children, child => {
|
|
397
|
+
// Handle text nodes, numbers, etc.
|
|
398
|
+
if (!isValidElement(child)) {
|
|
399
|
+
return child;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// Handle img elements - conditionally set src
|
|
403
|
+
if (child.props.src) {
|
|
404
|
+
return cloneElement(child, {
|
|
405
|
+
...child.props,
|
|
406
|
+
src: isReady ? child.props.src : "",
|
|
407
|
+
// Optionally preserve original src in data attribute for debugging
|
|
408
|
+
'data-original-src': child.props.src,
|
|
409
|
+
});
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// Handle elements with children - recursively process them
|
|
413
|
+
if (child.props && child.props.children) {
|
|
414
|
+
return cloneElement(child, {
|
|
415
|
+
...child.props,
|
|
416
|
+
children: processChildren(child.props.children, isReady),
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// Return other elements as-is
|
|
421
|
+
return child;
|
|
422
|
+
});
|
|
423
|
+
};
|
|
424
|
+
|
|
369
425
|
// Slot Component
|
|
370
426
|
interface SlotPropsComponent<T>
|
|
371
427
|
extends Omit<HTMLAttributes<HTMLElement>, 'slot'> {
|
|
@@ -398,7 +454,7 @@ export function Slot<T>({
|
|
|
398
454
|
}> {
|
|
399
455
|
const slotsQueue = useContext(SlotQueueContext);
|
|
400
456
|
|
|
401
|
-
const [elementRef, slotProps] = useSlot<T, HTMLElement>(
|
|
457
|
+
const [elementRef, slotProps, status] = useSlot<T, HTMLElement>(
|
|
402
458
|
name,
|
|
403
459
|
context,
|
|
404
460
|
slot,
|
|
@@ -426,7 +482,7 @@ export function Slot<T>({
|
|
|
426
482
|
ref: elementRef,
|
|
427
483
|
'data-slot': name,
|
|
428
484
|
},
|
|
429
|
-
slotProps.children
|
|
485
|
+
processChildren(slotProps.children, status === 'ready')
|
|
430
486
|
);
|
|
431
487
|
}
|
|
432
488
|
|