@adobe-commerce/elsie 1.6.0 → 1.7.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/package.json +1 -2
- package/src/components/CartItem/CartItem.css +22 -3
- package/src/components/CartItem/CartItem.stories.tsx +102 -57
- package/src/components/CartItem/CartItem.tsx +10 -0
- package/src/components/CartList/CartList.stories.tsx +66 -3
- package/src/components/Field/Field.css +3 -3
- package/src/components/Field/Field.stories.tsx +45 -3
- package/src/components/Field/Field.tsx +22 -20
- package/src/components/Input/Input.css +3 -3
- package/src/components/Input/Input.stories.tsx +42 -3
- package/src/components/Input/Input.tsx +14 -6
- package/src/components/Picker/Picker.css +3 -3
- package/src/components/Picker/Picker.stories.tsx +74 -3
- package/src/components/Picker/Picker.tsx +8 -7
- package/src/components/RadioButton/RadioButton.css +8 -3
- package/src/components/RadioButton/RadioButton.stories.tsx +37 -3
- package/src/components/RadioButton/RadioButton.tsx +18 -4
- package/src/components/RadioButton/index.ts +3 -3
- package/src/components/TextArea/TextArea.css +4 -4
- package/src/components/TextArea/TextArea.stories.tsx +54 -7
- package/src/components/TextArea/TextArea.tsx +5 -5
- package/src/components/UIProvider/normalize.css +8 -3
- package/src/lib/debounce.ts +5 -1
- package/src/lib/get-price-formatter.ts +1 -1
- package/src/lib/index.ts +1 -0
- package/src/lib/wrap-required-asterisk.tsx +70 -0
|
@@ -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
|
|
@@ -153,3 +153,42 @@ export const Secondary: Story = {
|
|
|
153
153
|
await expect(await canvas.findByDisplayValue('')).toBeTruthy();
|
|
154
154
|
},
|
|
155
155
|
};
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Input with a floating label that includes a required asterisk.
|
|
159
|
+
* The asterisk is automatically wrapped in a `<span class="dropin-label-required">` for consistent styling.
|
|
160
|
+
*
|
|
161
|
+
* ```ts
|
|
162
|
+
* import { Input } from '@adobe-commerce/elsie/components/Input';
|
|
163
|
+
* ```
|
|
164
|
+
*
|
|
165
|
+
* This example demonstrates:
|
|
166
|
+
* - Automatic asterisk wrapping in floating labels
|
|
167
|
+
* - The asterisk can be styled independently using `.dropin-label-required`
|
|
168
|
+
* - The label floats above the input when it has a value
|
|
169
|
+
*/
|
|
170
|
+
export const WithRequiredAsterisk: Story = {
|
|
171
|
+
args: {
|
|
172
|
+
name: 'emailField',
|
|
173
|
+
value: 'user@example.com',
|
|
174
|
+
variant: 'primary',
|
|
175
|
+
size: 'medium',
|
|
176
|
+
floatingLabel: 'Email Address',
|
|
177
|
+
required: true,
|
|
178
|
+
disabled: false,
|
|
179
|
+
error: false,
|
|
180
|
+
success: false,
|
|
181
|
+
onValue: action('onValue'),
|
|
182
|
+
},
|
|
183
|
+
play: async ({ canvasElement }) => {
|
|
184
|
+
// Verify the floating label is rendered
|
|
185
|
+
const label = canvasElement.querySelector('.dropin-input__label--floating');
|
|
186
|
+
await expect(label).toBeTruthy();
|
|
187
|
+
await expect(label?.textContent).toBe('Email Address *');
|
|
188
|
+
|
|
189
|
+
// Verify the asterisk is wrapped in a span with required class
|
|
190
|
+
const asteriskSpan = label?.querySelector('.dropin-label-required');
|
|
191
|
+
await expect(asteriskSpan).toBeTruthy();
|
|
192
|
+
await expect(asteriskSpan?.textContent).toBe('*');
|
|
193
|
+
},
|
|
194
|
+
};
|
|
@@ -2,15 +2,23 @@
|
|
|
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 { Icon } from '@adobe-commerce/elsie/components/Icon';
|
|
11
11
|
import '@adobe-commerce/elsie/components/Input/Input.css';
|
|
12
|
-
import {
|
|
13
|
-
|
|
12
|
+
import {
|
|
13
|
+
CheckWithCircle,
|
|
14
|
+
WarningWithCircle,
|
|
15
|
+
} from '@adobe-commerce/elsie/icons';
|
|
16
|
+
import {
|
|
17
|
+
VComponent,
|
|
18
|
+
classes,
|
|
19
|
+
debounce,
|
|
20
|
+
wrapRequiredAsterisk,
|
|
21
|
+
} from '@adobe-commerce/elsie/lib';
|
|
14
22
|
import { FunctionComponent, VNode } from 'preact';
|
|
15
23
|
import { HTMLAttributes } from 'preact/compat';
|
|
16
24
|
import { useCallback } from 'preact/hooks';
|
|
@@ -123,7 +131,7 @@ export const Input: FunctionComponent<InputProps> = ({
|
|
|
123
131
|
[`dropin-input__label--floating--error`, !!error],
|
|
124
132
|
])}
|
|
125
133
|
>
|
|
126
|
-
{floatingLabel}
|
|
134
|
+
{wrapRequiredAsterisk(floatingLabel, !!props.required)}
|
|
127
135
|
</label>
|
|
128
136
|
)}
|
|
129
137
|
</div>
|
|
@@ -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://cssguidelin.es/#bem-like-naming */
|
|
@@ -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
|
|
@@ -316,3 +316,74 @@ export const MandatoryFieldFloatingLabelWithValue: Story = {
|
|
|
316
316
|
],
|
|
317
317
|
},
|
|
318
318
|
};
|
|
319
|
+
|
|
320
|
+
export const SingleOption: Story = {
|
|
321
|
+
name: 'Single option',
|
|
322
|
+
args: {
|
|
323
|
+
name: 'pickerField',
|
|
324
|
+
variant: 'primary',
|
|
325
|
+
defaultOption: {
|
|
326
|
+
value: 'option1',
|
|
327
|
+
text: 'Only Option',
|
|
328
|
+
},
|
|
329
|
+
options: [
|
|
330
|
+
{
|
|
331
|
+
value: 'option1',
|
|
332
|
+
text: 'Only Option',
|
|
333
|
+
},
|
|
334
|
+
],
|
|
335
|
+
},
|
|
336
|
+
};
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Picker with a floating label that includes a required asterisk.
|
|
340
|
+
* The asterisk is automatically wrapped in a `<span class="dropin-label-required">` for consistent styling.
|
|
341
|
+
*
|
|
342
|
+
* ```ts
|
|
343
|
+
* import { Picker } from '@adobe-commerce/elsie/components/Picker';
|
|
344
|
+
* ```
|
|
345
|
+
*
|
|
346
|
+
* This example demonstrates:
|
|
347
|
+
* - Automatic asterisk wrapping in floating labels
|
|
348
|
+
* - The asterisk can be styled independently using `.dropin-label-required`
|
|
349
|
+
* - The label floats above the picker when an option is selected
|
|
350
|
+
*/
|
|
351
|
+
export const WithRequiredAsterisk: Story = {
|
|
352
|
+
name: 'With required asterisk',
|
|
353
|
+
args: {
|
|
354
|
+
name: 'countryPicker',
|
|
355
|
+
variant: 'primary',
|
|
356
|
+
floatingLabel: 'Country',
|
|
357
|
+
required: true,
|
|
358
|
+
value: 'us',
|
|
359
|
+
options: [
|
|
360
|
+
{
|
|
361
|
+
value: 'us',
|
|
362
|
+
text: 'United States',
|
|
363
|
+
},
|
|
364
|
+
{
|
|
365
|
+
value: 'ca',
|
|
366
|
+
text: 'Canada',
|
|
367
|
+
},
|
|
368
|
+
{
|
|
369
|
+
value: 'mx',
|
|
370
|
+
text: 'Mexico',
|
|
371
|
+
},
|
|
372
|
+
{
|
|
373
|
+
value: 'uk',
|
|
374
|
+
text: 'United Kingdom',
|
|
375
|
+
},
|
|
376
|
+
],
|
|
377
|
+
},
|
|
378
|
+
play: async ({ canvasElement }) => {
|
|
379
|
+
// Verify the floating label is rendered
|
|
380
|
+
const label = canvasElement.querySelector('.dropin-picker__floatingLabel');
|
|
381
|
+
await expect(label).toBeTruthy();
|
|
382
|
+
await expect(label?.textContent).toBe('Country *');
|
|
383
|
+
|
|
384
|
+
// Verify the asterisk is wrapped in a span with required class
|
|
385
|
+
const asteriskSpan = label?.querySelector('.dropin-label-required');
|
|
386
|
+
await expect(asteriskSpan).toBeTruthy();
|
|
387
|
+
await expect(asteriskSpan?.textContent).toBe('*');
|
|
388
|
+
},
|
|
389
|
+
};
|
|
@@ -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 { Icon } from '@adobe-commerce/elsie/components';
|
|
11
11
|
import { ChevronDown } from '@adobe-commerce/elsie/icons';
|
|
12
|
-
import { classes } from '@adobe-commerce/elsie/lib';
|
|
12
|
+
import { classes, wrapRequiredAsterisk } from '@adobe-commerce/elsie/lib';
|
|
13
13
|
import { FunctionComponent, VNode } from 'preact';
|
|
14
14
|
import { HTMLAttributes, useEffect, useState } from 'preact/compat';
|
|
15
15
|
|
|
@@ -74,6 +74,7 @@ export const Picker: FunctionComponent<PickerProps> = ({
|
|
|
74
74
|
}) => {
|
|
75
75
|
const uniqueId = id ?? name ?? `dropin-picker-${Math.random().toString(36)}`;
|
|
76
76
|
const isRequired = !!props?.required;
|
|
77
|
+
const isDisabled = disabled || options?.length === 1;
|
|
77
78
|
|
|
78
79
|
// find the first option that is not disabled
|
|
79
80
|
const firstAvailableOption = options?.find((option) => !option.disabled);
|
|
@@ -143,7 +144,7 @@ export const Picker: FunctionComponent<PickerProps> = ({
|
|
|
143
144
|
['dropin-picker__floating', !!floatingLabel],
|
|
144
145
|
['dropin-picker__selected', isSelected],
|
|
145
146
|
['dropin-picker__error', error],
|
|
146
|
-
['dropin-picker__disabled',
|
|
147
|
+
['dropin-picker__disabled', isDisabled],
|
|
147
148
|
['dropin-picker__icon', icon],
|
|
148
149
|
])}
|
|
149
150
|
>
|
|
@@ -165,7 +166,7 @@ export const Picker: FunctionComponent<PickerProps> = ({
|
|
|
165
166
|
])}
|
|
166
167
|
name={name}
|
|
167
168
|
aria-label={name}
|
|
168
|
-
disabled={
|
|
169
|
+
disabled={isDisabled}
|
|
169
170
|
onChange={handleOptionClick}
|
|
170
171
|
{...props}
|
|
171
172
|
>
|
|
@@ -197,7 +198,7 @@ export const Picker: FunctionComponent<PickerProps> = ({
|
|
|
197
198
|
htmlFor={id}
|
|
198
199
|
className={classes(['dropin-picker__floatingLabel', !!floatingLabel])}
|
|
199
200
|
>
|
|
200
|
-
{floatingLabel}
|
|
201
|
+
{wrapRequiredAsterisk(floatingLabel, isRequired)}
|
|
201
202
|
</label>
|
|
202
203
|
)}
|
|
203
204
|
</div>
|
|
@@ -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://cssguidelin.es/#bem-like-naming */
|
|
@@ -64,6 +64,11 @@
|
|
|
64
64
|
box-shadow: 0 0 0 var(--shape-icon-stroke-4) var(--color-neutral-400);
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
+
.dropin-radio-button__icon {
|
|
68
|
+
margin-right: var(--spacing-xsmall);
|
|
69
|
+
flex-shrink: 0;
|
|
70
|
+
}
|
|
71
|
+
|
|
67
72
|
.dropin-radio-button__description {
|
|
68
73
|
clear: both;
|
|
69
74
|
color: var(--color-neutral-700);
|
|
@@ -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
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
RadioButtonProps,
|
|
15
15
|
} from '@adobe-commerce/elsie/components/RadioButton';
|
|
16
16
|
import { expect, userEvent, within } from '@storybook/test';
|
|
17
|
+
import { IconsList } from '@adobe-commerce/elsie/components/Icon/Icon.stories.helpers';
|
|
17
18
|
|
|
18
19
|
/**
|
|
19
20
|
* Use Radio Buttons to let users select one option from a set of mutually exclusive choices.
|
|
@@ -91,6 +92,15 @@ const meta: Meta<RadioButtonProps> = {
|
|
|
91
92
|
name: 'boolean',
|
|
92
93
|
},
|
|
93
94
|
},
|
|
95
|
+
icon: {
|
|
96
|
+
description:
|
|
97
|
+
'Optional icon to display before the label (SVG or img element)',
|
|
98
|
+
options: Object.keys(IconsList),
|
|
99
|
+
mapping: IconsList,
|
|
100
|
+
control: {
|
|
101
|
+
type: 'select',
|
|
102
|
+
},
|
|
103
|
+
},
|
|
94
104
|
},
|
|
95
105
|
};
|
|
96
106
|
|
|
@@ -124,3 +134,27 @@ export const RadioButtonStory: Story = {
|
|
|
124
134
|
await expect(radioButton).toBeChecked();
|
|
125
135
|
},
|
|
126
136
|
};
|
|
137
|
+
|
|
138
|
+
export const RadioButtonWithIcon: Story = {
|
|
139
|
+
name: 'Radio button with icon',
|
|
140
|
+
args: {
|
|
141
|
+
name: 'shipping',
|
|
142
|
+
label: 'Free Shipping',
|
|
143
|
+
value: 'free-shipping',
|
|
144
|
+
description: 'Delivery in 5-7 business days',
|
|
145
|
+
size: 'medium',
|
|
146
|
+
disabled: false,
|
|
147
|
+
error: false,
|
|
148
|
+
// @ts-ignore - icon is mapped from IconsList
|
|
149
|
+
icon: 'Delivery',
|
|
150
|
+
},
|
|
151
|
+
play: async ({ canvasElement }) => {
|
|
152
|
+
const canvas = within(canvasElement);
|
|
153
|
+
const radioButton = await canvas.findByRole('radio');
|
|
154
|
+
const radioButtonContainer = radioButton.closest('.dropin-radio-button');
|
|
155
|
+
const icon = radioButtonContainer?.querySelector(
|
|
156
|
+
'.dropin-radio-button__icon'
|
|
157
|
+
);
|
|
158
|
+
await expect(icon).toBeInTheDocument();
|
|
159
|
+
},
|
|
160
|
+
};
|
|
@@ -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, VNode } from 'preact';
|
|
@@ -13,7 +13,7 @@ import { classes } from '@adobe-commerce/elsie/lib';
|
|
|
13
13
|
import '@adobe-commerce/elsie/components/RadioButton/RadioButton.css';
|
|
14
14
|
|
|
15
15
|
export interface RadioButtonProps
|
|
16
|
-
extends Omit<HTMLAttributes<HTMLInputElement>, 'size' | 'label'> {
|
|
16
|
+
extends Omit<HTMLAttributes<HTMLInputElement>, 'size' | 'label' | 'icon'> {
|
|
17
17
|
label: string | VNode<HTMLAttributes<HTMLElement>>;
|
|
18
18
|
name: string;
|
|
19
19
|
value: string;
|
|
@@ -23,6 +23,9 @@ export interface RadioButtonProps
|
|
|
23
23
|
error?: boolean;
|
|
24
24
|
description?: string;
|
|
25
25
|
busy?: boolean;
|
|
26
|
+
icon?:
|
|
27
|
+
| VNode<HTMLAttributes<SVGSVGElement>>
|
|
28
|
+
| VNode<HTMLAttributes<HTMLImageElement>>;
|
|
26
29
|
}
|
|
27
30
|
|
|
28
31
|
export const RadioButton: FunctionComponent<RadioButtonProps> = ({
|
|
@@ -35,6 +38,7 @@ export const RadioButton: FunctionComponent<RadioButtonProps> = ({
|
|
|
35
38
|
error = false,
|
|
36
39
|
description = '',
|
|
37
40
|
busy = false,
|
|
41
|
+
icon,
|
|
38
42
|
className,
|
|
39
43
|
children,
|
|
40
44
|
...props
|
|
@@ -70,6 +74,16 @@ export const RadioButton: FunctionComponent<RadioButtonProps> = ({
|
|
|
70
74
|
['dropin-radio-button__label--disabled', disabled],
|
|
71
75
|
])}
|
|
72
76
|
>
|
|
77
|
+
{icon && (
|
|
78
|
+
// @ts-ignore
|
|
79
|
+
<icon.type
|
|
80
|
+
{...icon?.props}
|
|
81
|
+
className={classes([
|
|
82
|
+
'dropin-radio-button__icon',
|
|
83
|
+
icon?.props?.className,
|
|
84
|
+
])}
|
|
85
|
+
/>
|
|
86
|
+
)}
|
|
73
87
|
{label}
|
|
74
88
|
</span>
|
|
75
89
|
<span
|
|
@@ -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/components/RadioButton/RadioButton';
|
|
@@ -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
|
.dropin-textarea-container {
|
|
@@ -135,6 +135,6 @@
|
|
|
135
135
|
.dropin-textarea__label--floating--error {
|
|
136
136
|
font: var(--type-details-caption-1-font);
|
|
137
137
|
letter-spacing: var(--type-details-caption-1-letter-spacing);
|
|
138
|
-
color: var(--color-alert-
|
|
138
|
+
color: var(--color-alert-500);
|
|
139
139
|
padding-top: var(--spacing-xsmall);
|
|
140
140
|
}
|
|
@@ -2,14 +2,17 @@
|
|
|
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
|
|
11
11
|
import type { Meta, StoryObj } from '@storybook/preact';
|
|
12
|
-
import {
|
|
12
|
+
import {
|
|
13
|
+
TextArea,
|
|
14
|
+
TextAreaProps,
|
|
15
|
+
} from '@adobe-commerce/elsie/components/TextArea';
|
|
13
16
|
import { expect, userEvent, within } from '@storybook/test';
|
|
14
17
|
import { useState, useCallback } from 'preact/hooks';
|
|
15
18
|
|
|
@@ -75,7 +78,7 @@ const Template: StoryObj<TextAreaProps> = {
|
|
|
75
78
|
render: (args) => {
|
|
76
79
|
const [value, setValue] = useState(args.value);
|
|
77
80
|
|
|
78
|
-
const setTextAreaValue = useCallback((event) => {
|
|
81
|
+
const setTextAreaValue = useCallback((event: any) => {
|
|
79
82
|
setValue(event.target.value);
|
|
80
83
|
}, []);
|
|
81
84
|
|
|
@@ -83,7 +86,7 @@ const Template: StoryObj<TextAreaProps> = {
|
|
|
83
86
|
},
|
|
84
87
|
};
|
|
85
88
|
|
|
86
|
-
export const DefaultWithValue = {
|
|
89
|
+
export const DefaultWithValue: StoryObj<TextAreaProps> = {
|
|
87
90
|
...Template,
|
|
88
91
|
parameters: {
|
|
89
92
|
layout: 'centered',
|
|
@@ -112,7 +115,7 @@ export const DefaultWithValue = {
|
|
|
112
115
|
},
|
|
113
116
|
};
|
|
114
117
|
|
|
115
|
-
export const WithError = {
|
|
118
|
+
export const WithError: StoryObj<TextAreaProps> = {
|
|
116
119
|
...Template,
|
|
117
120
|
parameters: {
|
|
118
121
|
layout: 'centered',
|
|
@@ -128,3 +131,47 @@ export const WithError = {
|
|
|
128
131
|
errorMessage: 'Message cannot be empty',
|
|
129
132
|
},
|
|
130
133
|
};
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* TextArea with a label that includes a required asterisk.
|
|
137
|
+
* The asterisk is automatically wrapped in a `<span class="dropin-label-required">` for consistent styling.
|
|
138
|
+
*
|
|
139
|
+
* ```ts
|
|
140
|
+
* import { TextArea } from '@adobe-commerce/elsie/components/TextArea';
|
|
141
|
+
* ```
|
|
142
|
+
*
|
|
143
|
+
* This example demonstrates:
|
|
144
|
+
* - Automatic asterisk wrapping in textarea labels
|
|
145
|
+
* - The asterisk can be styled independently using `.dropin-label-required`
|
|
146
|
+
* - The label floats above the textarea when it has content
|
|
147
|
+
*/
|
|
148
|
+
export const WithRequiredAsterisk: StoryObj<TextAreaProps> = {
|
|
149
|
+
...Template,
|
|
150
|
+
parameters: {
|
|
151
|
+
layout: 'centered',
|
|
152
|
+
a11y: {
|
|
153
|
+
config: {
|
|
154
|
+
rules: [{ id: 'color-contrast', enabled: false }],
|
|
155
|
+
},
|
|
156
|
+
},
|
|
157
|
+
},
|
|
158
|
+
args: {
|
|
159
|
+
label: 'Comments',
|
|
160
|
+
name: 'comments',
|
|
161
|
+
required: true,
|
|
162
|
+
value: 'Please provide additional details about your request.',
|
|
163
|
+
},
|
|
164
|
+
play: async ({ canvasElement }) => {
|
|
165
|
+
// Verify the floating label is rendered
|
|
166
|
+
const label = canvasElement.querySelector(
|
|
167
|
+
'.dropin-textarea__label--floating'
|
|
168
|
+
);
|
|
169
|
+
await expect(label).toBeTruthy();
|
|
170
|
+
await expect(label?.textContent).toBe('Comments *');
|
|
171
|
+
|
|
172
|
+
// Verify the asterisk is wrapped in a span with required class
|
|
173
|
+
const asteriskSpan = label?.querySelector('.dropin-label-required');
|
|
174
|
+
await expect(asteriskSpan).toBeTruthy();
|
|
175
|
+
await expect(asteriskSpan?.textContent).toBe('*');
|
|
176
|
+
},
|
|
177
|
+
};
|
|
@@ -2,13 +2,13 @@
|
|
|
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
|
-
import { classes } from '@adobe-commerce/elsie/lib';
|
|
11
|
+
import { classes, wrapRequiredAsterisk } from '@adobe-commerce/elsie/lib';
|
|
12
12
|
import { HTMLAttributes } from 'preact/compat';
|
|
13
13
|
import '@adobe-commerce/elsie/components/TextArea/TextArea.css';
|
|
14
14
|
import { useRef, useEffect, useId } from 'preact/hooks';
|
|
@@ -72,7 +72,7 @@ export const TextArea: FunctionComponent<TextAreaProps> = ({
|
|
|
72
72
|
[`dropin-textarea__label--floating--error`, error],
|
|
73
73
|
])}
|
|
74
74
|
>
|
|
75
|
-
{label}
|
|
75
|
+
{wrapRequiredAsterisk(label as string, !!props.required)}
|
|
76
76
|
</label>
|
|
77
77
|
{error ? (
|
|
78
78
|
<div
|
|
@@ -2,14 +2,15 @@
|
|
|
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
|
.dropin-design a {
|
|
11
11
|
--textColor: var(--color-brand-500);
|
|
12
12
|
color: var(--textColor);
|
|
13
|
+
border: var(--shape-border-width-1) solid transparent;
|
|
13
14
|
text-decoration: none;
|
|
14
15
|
}
|
|
15
16
|
|
|
@@ -24,3 +25,7 @@
|
|
|
24
25
|
border: var(--shape-border-width-1) solid var(--color-neutral-800);
|
|
25
26
|
border-radius: var(--shape-border-radius-1);
|
|
26
27
|
}
|
|
28
|
+
|
|
29
|
+
.dropin-design .dropin-label-required {
|
|
30
|
+
color: var(--color-alert-500);
|
|
31
|
+
}
|
package/src/lib/debounce.ts
CHANGED
|
@@ -9,8 +9,12 @@
|
|
|
9
9
|
|
|
10
10
|
export const debounce = (fn: Function, ms: number) => {
|
|
11
11
|
let timeoutId: ReturnType<typeof setTimeout>;
|
|
12
|
-
|
|
12
|
+
const debouncedFn = function (this: any, ...args: any[]) {
|
|
13
13
|
clearTimeout(timeoutId);
|
|
14
14
|
timeoutId = setTimeout(() => fn.apply(this, args), ms);
|
|
15
15
|
};
|
|
16
|
+
debouncedFn.cancel = () => {
|
|
17
|
+
clearTimeout(timeoutId);
|
|
18
|
+
};
|
|
19
|
+
return debouncedFn;
|
|
16
20
|
};
|
|
@@ -53,7 +53,7 @@ export function getPriceFormatter(
|
|
|
53
53
|
|
|
54
54
|
const params: Intl.NumberFormatOptions = {
|
|
55
55
|
style: 'currency',
|
|
56
|
-
currency: currency
|
|
56
|
+
currency: currency && currency !== 'NONE' ? currency : 'USD',
|
|
57
57
|
// These options are needed to round to whole numbers if that's what you want.
|
|
58
58
|
minimumFractionDigits: 2, // (this suffices for whole numbers, but will print 2500.10 as $2,500.1)
|
|
59
59
|
maximumFractionDigits: 2, // (causes 2500.99 to be printed as $2,501)
|
package/src/lib/index.ts
CHANGED
|
@@ -26,3 +26,4 @@ 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
28
|
export * from '@adobe-commerce/elsie/lib/get-price-formatter';
|
|
29
|
+
export * from '@adobe-commerce/elsie/lib/wrap-required-asterisk';
|