@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,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/MultiSelect/MultiSelect';
11
+ export { MultiSelect as default } from '@adobe-commerce/elsie/components/MultiSelect/MultiSelect';
@@ -64,7 +64,8 @@
64
64
  align-items: center;
65
65
  }
66
66
 
67
- .dropin-pagination_list-item button {
67
+ .dropin-pagination_list-item button,
68
+ .dropin-pagination_list-item a {
68
69
  cursor: pointer;
69
70
  margin: 0;
70
71
  padding: 0;
@@ -72,24 +73,32 @@
72
73
  border: none;
73
74
  font: var(--type-details-caption-1-font);
74
75
  letter-spacing: var(--type-details-caption-1-letter-spacing);
76
+ text-decoration: none;
75
77
  }
76
78
 
77
- .dropin-pagination_list-item--active button {
79
+ .dropin-pagination_list-item--active button,
80
+ .dropin-pagination_list-item--active a {
78
81
  cursor: default;
79
82
  }
80
83
 
81
84
  .dropin-pagination_list-item--active button:disabled,
82
85
  .dropin-pagination_list-item--ellipsis button,
86
+ .dropin-pagination_list-item--ellipsis a,
83
87
  .dropin-pagination-arrow--backward:disabled,
84
- .dropin-pagination-arrow--forward:disabled {
88
+ .dropin-pagination-arrow--forward:disabled,
89
+ .dropin-pagination-arrow--disabled {
85
90
  cursor: default;
91
+ opacity: 0.5;
92
+ pointer-events: none;
86
93
  }
87
94
 
88
- .dropin-pagination button:not(:disabled) {
95
+ .dropin-pagination button:not(:disabled),
96
+ .dropin-pagination a:not([aria-disabled="true"]) {
89
97
  color: var(--color-neutral-800);
90
98
  }
91
99
 
92
- .dropin-pagination button {
100
+ .dropin-pagination button,
101
+ .dropin-pagination a {
93
102
  appearance: none;
94
103
  -webkit-appearance: none;
95
104
  }
@@ -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
  // https://storybook.js.org/docs/7.0/preact/writing-stories/introduction
@@ -16,6 +16,8 @@ import {
16
16
  } from '@adobe-commerce/elsie/components/Pagination';
17
17
  import { expect, userEvent, within } from '@storybook/test';
18
18
  import { useState } from 'preact/hooks';
19
+ import { action } from '@storybook/addon-actions';
20
+
19
21
 
20
22
  /**
21
23
  * A component that divides large content sets into multiple pages, allowing users to navigate through the data with controls like "Next," "Previous," or page numbers, enhancing performance and usability by limiting the number of items displayed per page.
@@ -40,6 +42,10 @@ const meta: Meta<PaginationProps> = {
40
42
  description:
41
43
  'Called when the page number is changed, and it takes the resulting page number.',
42
44
  },
45
+ routePage: {
46
+ description:
47
+ 'Optional function that generates hrefs for each page. When provided, pagination will render anchor tags instead of buttons, enabling proper SEO and link behavior.',
48
+ },
43
49
  },
44
50
  };
45
51
 
@@ -115,3 +121,26 @@ export const LongPagination: Story = {
115
121
  );
116
122
  },
117
123
  };
124
+
125
+ export const WithAnchors: Story = {
126
+ name: 'As anchor tags',
127
+ render: () => {
128
+ const [currentPage, setCurrentPage] = useState(1);
129
+ const totalPages = 10;
130
+
131
+ const handlePageChange = (newPage: number, e?: Event) => {
132
+ e?.preventDefault();
133
+ setCurrentPage(newPage);
134
+ action('pageChanged')(newPage, e);
135
+ };
136
+
137
+ return (
138
+ <Pagination
139
+ totalPages={totalPages}
140
+ currentPage={currentPage}
141
+ onChange={handlePageChange}
142
+ routePage={(page) => `?page=${page}`}
143
+ />
144
+ );
145
+ },
146
+ };
@@ -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
  import { FunctionComponent } from 'preact';
@@ -14,12 +14,14 @@ import { ChevronDown } from '@adobe-commerce/elsie/icons';
14
14
  import { Icon } from '@adobe-commerce/elsie/components/Icon';
15
15
  import { useText } from '@adobe-commerce/elsie/i18n';
16
16
  import '@adobe-commerce/elsie/components/Pagination/Pagination.css';
17
+ import { PaginationButton } from './PaginationButton';
17
18
 
18
19
  export interface PaginationProps {
19
20
  className?: string;
20
21
  currentPage?: number;
21
22
  totalPages?: number;
22
- onChange?: (currentPage: number) => void;
23
+ onChange?: (currentPage: number, e?: Event) => void;
24
+ routePage?: (page: number) => string;
23
25
  }
24
26
 
25
27
  export type PaginationList = {
@@ -32,6 +34,7 @@ export const Pagination: FunctionComponent<PaginationProps> = ({
32
34
  totalPages = 10,
33
35
  currentPage = 1,
34
36
  onChange,
37
+ routePage,
35
38
  className,
36
39
  ...props
37
40
  }) => {
@@ -40,22 +43,22 @@ export const Pagination: FunctionComponent<PaginationProps> = ({
40
43
  forwardButton: 'Dropin.Pagination.forwardButton.ariaLabel',
41
44
  });
42
45
 
43
- const handleForward = useCallback(() => {
46
+ const handleForward = useCallback((e?: Event) => {
44
47
  const nextPage = Math.min(currentPage + 1, totalPages);
45
48
 
46
- onChange?.(nextPage);
49
+ onChange?.(nextPage, e);
47
50
  }, [currentPage, onChange, totalPages]);
48
51
 
49
- const handleBackward = useCallback(() => {
52
+ const handleBackward = useCallback((e?: Event) => {
50
53
  const prevPage = Math.max(currentPage - 1, 1);
51
54
 
52
- onChange?.(prevPage);
55
+ onChange?.(prevPage, e);
53
56
  }, [currentPage, onChange]);
54
57
 
55
58
  const handleSetPage = useCallback(
56
- (currentPage: number | string) => {
59
+ (currentPage: number | string, e?: Event) => {
57
60
  if (isNumber(currentPage)) {
58
- onChange?.(currentPage as number);
61
+ onChange?.(currentPage as number, e);
59
62
  }
60
63
  },
61
64
  [onChange]
@@ -97,19 +100,20 @@ export const Pagination: FunctionComponent<PaginationProps> = ({
97
100
 
98
101
  return (
99
102
  <div {...props} className={classes(['dropin-pagination', className])}>
100
- <button
101
- type="button"
103
+ <PaginationButton
102
104
  data-testid="prev-button"
103
105
  aria-label={translations.backwardButton}
104
106
  disabled={currentPage === 1}
105
- onClick={handleBackward}
107
+ onClick={(e: Event) => handleBackward(e)}
108
+ href={routePage?.(currentPage) ?? undefined}
106
109
  className={classes([
107
110
  'dropin-pagination-arrow',
108
111
  'dropin-pagination-arrow--backward',
112
+ ['dropin-pagination-arrow--disabled', currentPage === 1],
109
113
  ])}
110
114
  >
111
115
  <Icon size="24" source={ChevronDown} />
112
- </button>
116
+ </PaginationButton>
113
117
  <ul className="dropin-pagination_list">
114
118
  {(paginationList as PaginationList[]).map((item, index) => (
115
119
  <li
@@ -121,29 +125,31 @@ export const Pagination: FunctionComponent<PaginationProps> = ({
121
125
  [`dropin-pagination_list-item--active`, item.isActive],
122
126
  ])}
123
127
  >
124
- <button
125
- type="button"
128
+ <PaginationButton
126
129
  data-testid={`set-page-button-${item.page}`}
127
- onClick={() => handleSetPage(item.page)}
130
+ onClick={(e: Event) => handleSetPage(item.page, e)}
131
+ href={routePage?.(item.page as number) ?? undefined}
132
+
128
133
  >
129
134
  {item.label}
130
- </button>
135
+ </PaginationButton>
131
136
  </li>
132
137
  ))}
133
138
  </ul>
134
- <button
135
- type="button"
139
+ <PaginationButton
136
140
  data-testid="next-button"
137
141
  aria-label={translations.forwardButton}
138
142
  disabled={currentPage === totalPages}
139
- onClick={handleForward}
143
+ onClick={(e: Event) => handleForward(e)}
144
+ href={routePage?.(currentPage) ?? undefined}
140
145
  className={classes([
141
146
  'dropin-pagination-arrow',
142
147
  'dropin-pagination-arrow--forward',
148
+ ['dropin-pagination-arrow--disabled', currentPage === totalPages],
143
149
  ])}
144
150
  >
145
151
  <Icon size="24" source={ChevronDown} />
146
- </button>
152
+ </PaginationButton>
147
153
  </div>
148
154
  );
149
155
  };
@@ -0,0 +1,46 @@
1
+ /********************************************************************
2
+ * Copyright 2024 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, JSX } from 'preact';
11
+
12
+ type BaseProps = {
13
+ href?: string;
14
+ type?: 'button';
15
+ disabled?: boolean;
16
+ };
17
+
18
+ export type PaginationButtonProps = BaseProps &
19
+ (
20
+ | Omit<JSX.HTMLAttributes<HTMLAnchorElement>, 'type'>
21
+ | Omit<JSX.HTMLAttributes<HTMLButtonElement>, 'href'>
22
+ );
23
+
24
+ export const PaginationButton: FunctionComponent<PaginationButtonProps> = (
25
+ props
26
+ ) => {
27
+ if (props.href) {
28
+
29
+ const { href, disabled, ...anchorProps } = props;
30
+ return (
31
+ <a
32
+ href={href}
33
+ aria-disabled={disabled}
34
+ {...(anchorProps as JSX.HTMLAttributes<HTMLAnchorElement>)}
35
+ />
36
+ );
37
+ }
38
+
39
+ const { type = 'button', ...buttonProps } = props;
40
+ return (
41
+ <button
42
+ type={type}
43
+ {...(buttonProps as JSX.HTMLAttributes<HTMLButtonElement>)}
44
+ />
45
+ );
46
+ };
@@ -2,14 +2,14 @@
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
  import { FunctionComponent } from 'preact';
11
11
  import { HTMLAttributes, useMemo } from 'preact/compat';
12
- import { classes, getGlobalLocale } from '@adobe-commerce/elsie/lib';
12
+ import { classes, getPriceFormatter } from '@adobe-commerce/elsie/lib';
13
13
  import '@adobe-commerce/elsie/components/Price/Price.css';
14
14
 
15
15
  export interface PriceProps
@@ -39,43 +39,10 @@ export const Price: FunctionComponent<PriceProps> = ({
39
39
  size = 'small',
40
40
  ...props
41
41
  }) => {
42
- // Determine the locale to use: prop locale > global locale > browser locale
43
- const effectiveLocale = useMemo(() => {
44
- if (locale) {
45
- return locale;
46
- }
47
- const globalLocale = getGlobalLocale();
48
- if (globalLocale) {
49
- return globalLocale;
50
- }
51
- // Fallback to browser locale or default
52
- return process.env.LOCALE && process.env.LOCALE !== 'undefined' ? process.env.LOCALE : 'en-US';
53
- }, [locale]);
54
-
55
- const formatter = useMemo(
56
- () => {
57
- const params: Intl.NumberFormatOptions = {
58
- style: 'currency',
59
- currency: currency || 'USD',
60
- // These options are needed to round to whole numbers if that's what you want.
61
- minimumFractionDigits: 2, // (this suffices for whole numbers, but will print 2500.10 as $2,500.1)
62
- maximumFractionDigits: 2, // (causes 2500.99 to be printed as $2,501)
63
- ...formatOptions,
64
- }
65
- try {
66
- return new Intl.NumberFormat(effectiveLocale, params);
67
- } catch (error) {
68
- console.error(`Error creating Intl.NumberFormat instance for locale ${effectiveLocale}. Falling back to en-US.`, error);
69
- return new Intl.NumberFormat('en-US', params);
70
- }
71
- },
72
- [effectiveLocale, currency, formatOptions]
73
- );
74
-
75
- const formattedAmount = useMemo(
76
- () => formatter.format(amount),
77
- [amount, formatter]
78
- );
42
+ const formattedAmount = useMemo(() => {
43
+ const formatter = getPriceFormatter({ currency, locale, formatOptions });
44
+ return formatter.format(amount);
45
+ }, [amount, currency, locale, formatOptions]);
79
46
 
80
47
  return (
81
48
  <span
@@ -0,0 +1,183 @@
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-table {
13
+ container-type: inline-size;
14
+ overflow-x: auto;
15
+ font: var(--type-body-1-default-font);
16
+ letter-spacing: var(--type-body-1-default-letter-spacing);
17
+ }
18
+
19
+ .dropin-table__table {
20
+ border: var(--shape-border-width-1) solid var(--color-neutral-400);
21
+ border-collapse: collapse;
22
+ width: 100%;
23
+ }
24
+
25
+ .dropin-table__caption {
26
+ font: var(--type-details-caption-1-font);
27
+ letter-spacing: var(--type-details-caption-1-letter-spacing);
28
+ text-align: left;
29
+ margin-bottom: var(--spacing-small);
30
+ caption-side: top;
31
+ }
32
+
33
+ .dropin-table__header__cell {
34
+ font: var(--type-body-1-strong-font);
35
+ letter-spacing: var(--type-body-1-strong-letter-spacing);
36
+ }
37
+
38
+ .dropin-table__header__cell {
39
+ color: var(--color-neutral-800);
40
+ text-align: left;
41
+ white-space: nowrap;
42
+ }
43
+
44
+ .dropin-table__header__cell--sortable {
45
+ cursor: pointer;
46
+ }
47
+
48
+ .dropin-table__header__row {
49
+ border-bottom: var(--shape-border-width-1) solid var(--color-neutral-400);
50
+ }
51
+
52
+ .dropin-table__header__row th {
53
+ padding: var(--spacing-small) var(--spacing-medium);
54
+ }
55
+
56
+ .dropin-table__body__row:nth-child(even) {
57
+ background-color: var(--color-neutral-100);
58
+ }
59
+
60
+ .dropin-table__body__row.dropin-table__body__row--expanded {
61
+ background-color: var(--color-neutral-200);
62
+ }
63
+
64
+ .dropin-table__body__cell {
65
+ padding: var(--spacing-small) var(--spacing-medium);
66
+ }
67
+
68
+ .dropin-table__header__sort-button {
69
+ vertical-align: middle;
70
+ }
71
+
72
+ .dropin-table__row-details {
73
+ display: none;
74
+ }
75
+
76
+ .dropin-table__row-details--expanded {
77
+ display: table-row;
78
+ }
79
+
80
+ .dropin-table__row-details__cell {
81
+ padding: var(--spacing-small) var(--spacing-medium);
82
+ background-color: var(--color-neutral-200);
83
+ }
84
+
85
+ .dropin-table__row-details--expanded .dropin-table__row-details__cell > *:last-child {
86
+ margin: 0;
87
+ }
88
+
89
+ .dropin-table__row-details__cell h1,
90
+ .dropin-table__row-details__cell h2,
91
+ .dropin-table__row-details__cell h3,
92
+ .dropin-table__row-details__cell h4,
93
+ .dropin-table__row-details__cell h5,
94
+ .dropin-table__row-details__cell h6 {
95
+ font: var(--type-body-1-strong-font);
96
+ letter-spacing: var(--type-body-1-strong-letter-spacing);
97
+ margin: 0;
98
+ }
99
+
100
+ .dropin-table__row-details__cell p,
101
+ .dropin-table__row-details__cell span {
102
+ font: var(--type-body-1-default-font);
103
+ letter-spacing: var(--type-body-1-default-letter-spacing);
104
+ }
105
+
106
+ /* Container query for mobile layout */
107
+ @container (max-width: 600px) {
108
+ /* Mobile layout Stacked */
109
+ .dropin-table--mobile-layout-stacked .dropin-table__header {
110
+ display: none;
111
+ }
112
+
113
+ .dropin-table--mobile-layout-stacked .dropin-table__body__row {
114
+ padding: var(--spacing-medium);
115
+ display: block;
116
+ }
117
+
118
+ .dropin-table--mobile-layout-stacked .dropin-table__body__cell {
119
+ display: block;
120
+ font: var(--type-body-2-default-font);
121
+ letter-spacing: var(--type-body-2-default-letter-spacing);
122
+ padding: var(--spacing-xsmall) 0;
123
+ }
124
+
125
+ .dropin-table--mobile-layout-stacked .dropin-table__body__cell:first-child {
126
+ padding-top: 0;
127
+ }
128
+
129
+ .dropin-table--mobile-layout-stacked .dropin-table__body__cell:last-child {
130
+ padding-bottom: 0;
131
+ }
132
+
133
+ .dropin-table--mobile-layout-stacked .dropin-table__body__cell::before {
134
+ content: attr(data-label);
135
+ font: var(--type-body-2-strong-font);
136
+ letter-spacing: var(--type-body-2-strong-letter-spacing);
137
+ display: block;
138
+ margin-bottom: var(--spacing-xxsmall);
139
+ }
140
+
141
+ .dropin-table--mobile-layout-stacked .dropin-table__row-details__cell {
142
+ display: block;
143
+ padding: var(--spacing-medium);
144
+ }
145
+
146
+ .dropin-table--mobile-layout-stacked .dropin-table__row-details__cell > *:first-child {
147
+ border-top: var(--shape-border-width-1) solid var(--color-neutral-400);
148
+ padding-top: var(--spacing-medium);
149
+ margin-top: calc(var(--spacing-medium) * -1);
150
+ }
151
+
152
+ .dropin-table--mobile-layout-stacked .dropin-table__row-details--expanded .dropin-table__row-details__cell > *:last-child {
153
+ margin: 0;
154
+ }
155
+
156
+ .dropin-table--mobile-layout-stacked .dropin-table__row-details__cell h1,
157
+ .dropin-table--mobile-layout-stacked .dropin-table__row-details__cell h2,
158
+ .dropin-table--mobile-layout-stacked .dropin-table__row-details__cell h3,
159
+ .dropin-table--mobile-layout-stacked .dropin-table__row-details__cell h4,
160
+ .dropin-table--mobile-layout-stacked .dropin-table__row-details__cell h5,
161
+ .dropin-table--mobile-layout-stacked .dropin-table__row-details__cell h6 {
162
+ font: var(--type-body-2-strong-font);
163
+ letter-spacing: var(--type-body-2-strong-letter-spacing);
164
+ }
165
+
166
+ .dropin-table--mobile-layout-stacked .dropin-table__row-details__cell p,
167
+ .dropin-table--mobile-layout-stacked .dropin-table__row-details__cell span {
168
+ font: var(--type-body-2-default-font);
169
+ letter-spacing: var(--type-body-2-default-letter-spacing);
170
+ }
171
+ }
172
+
173
+ /* Medium (portrait tablets and large phones, 768px and up) */
174
+ /* @media only screen and (min-width: 768px) { } */
175
+
176
+ /* Large (landscape tablets, 1024px and up) */
177
+ /* @media only screen and (min-width: 1024px) { } */
178
+
179
+ /* XLarge (laptops/desktops, 1366px and up) */
180
+ /* @media only screen and (min-width: 1366px) { } */
181
+
182
+ /* XXlarge (large laptops and desktops, 1920px and up) */
183
+ /* @media only screen and (min-width: 1920px) { } */