@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.
Files changed (37) hide show
  1. package/config/jest.js +3 -3
  2. package/package.json +3 -3
  3. package/src/components/Button/Button.tsx +2 -0
  4. package/src/components/Field/Field.tsx +19 -14
  5. package/src/components/Icon/Icon.tsx +29 -24
  6. package/src/components/Incrementer/Incrementer.css +6 -0
  7. package/src/components/Incrementer/Incrementer.stories.tsx +18 -0
  8. package/src/components/Incrementer/Incrementer.tsx +66 -59
  9. package/src/components/MultiSelect/MultiSelect.css +273 -0
  10. package/src/components/MultiSelect/MultiSelect.stories.tsx +459 -0
  11. package/src/components/MultiSelect/MultiSelect.tsx +763 -0
  12. package/src/components/MultiSelect/index.ts +11 -0
  13. package/src/components/Pagination/Pagination.css +14 -5
  14. package/src/components/Pagination/Pagination.stories.tsx +32 -3
  15. package/src/components/Pagination/Pagination.tsx +28 -22
  16. package/src/components/Pagination/PaginationButton.tsx +46 -0
  17. package/src/components/Price/Price.tsx +8 -41
  18. package/src/components/Table/Table.css +183 -0
  19. package/src/components/Table/Table.stories.tsx +1024 -0
  20. package/src/components/Table/Table.tsx +253 -0
  21. package/src/components/Table/index.ts +11 -0
  22. package/src/components/ToggleButton/ToggleButton.css +13 -1
  23. package/src/components/ToggleButton/ToggleButton.stories.tsx +13 -6
  24. package/src/components/ToggleButton/ToggleButton.tsx +4 -0
  25. package/src/components/index.ts +5 -3
  26. package/src/docs/slots.mdx +2 -0
  27. package/src/i18n/en_US.json +38 -0
  28. package/src/icons/Business.svg +3 -0
  29. package/src/icons/List.svg +3 -0
  30. package/src/icons/Quote.svg +3 -0
  31. package/src/icons/Structure.svg +8 -0
  32. package/src/icons/Team.svg +5 -0
  33. package/src/icons/index.ts +29 -24
  34. package/src/lib/aem/configs.ts +10 -4
  35. package/src/lib/get-price-formatter.ts +69 -0
  36. package/src/lib/index.ts +4 -3
  37. 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