Skip to content

Commit 19ca328

Browse files
feat(textfield, textarea): migrate to spectrum 2 (#2856)
1 parent 68b85ca commit 19ca328

File tree

9 files changed

+631
-522
lines changed

9 files changed

+631
-522
lines changed

.changeset/healthy-chicken-clap.md

+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
---
2+
"@spectrum-css/textfield": major
3+
---
4+
5+
Migrate to Spectrum 2
6+
7+
- Default styles are used for medium
8+
- Removal of quiet variant
9+
- Style adjustments to match design spec:
10+
- Spacing start/end edge to value for XL component should use `component-edge-to-text-300`, not `-200`
11+
- Spacing top edge to value should change with size, now uses different spacing tokens for each component tshirt size according to spec (`component-top-to-text-75`, `-100`, `-200`, and `-300`)
12+
- Spacing bottom edge to value should change with size, now uses different spacing tokens for each component tshirt size according to spec (`component-bottom-to-text-75`, `-100`, `-200`, and `-300`)
13+
- Border thickness has been increased to 2px, and border colors have been updated to match spec
14+
- Adjust side label inline spacing to field, should be `spacing-200` for all tshirt sizes
15+
- Change disabled background color to `gray-25`
16+
- Change disabled-border-color from transparent to `disabled-border-color`
17+
- Use new and updated tokens:
18+
- use tokens for textfield width (`field-default-width-small`, `-medium`, `-large`, `-extra-large`)
19+
- use updated corner radius tokens
20+
- use different component-bottom-to-text tokens for character-count-spacing-block
21+
- update tokens for alert icon block spacing
22+
- update tokens for alert and invalid inline-start spacing
23+
- update font tokens
24+
25+
Adds mods:
26+
27+
- `--mod-textfield-character-count-color`
28+
- `--mod-textfield-character-count-font-style`
29+
- `--mod-textfield-character-count-line-height`
30+
- `--mod-textfield-character-count-line-height-cjk`
31+
- `--mod-textfield-font-style`
32+
- `--mod-textfield-line-height` (used for multiline only)
33+
- `--mod-textfield-line-height-cjk`
34+
35+
Removes mods:
36+
37+
- `--mod-text-area-min-block-size-quiet`
38+
- `--mod-textfield-character-count-spacing-block-quiet`
39+
- `--mod-textfield-icon-spacing-inline-end-quiet-invalid`
40+
- `--mod-textfield-icon-spacing-inline-end-quiet-valid`
41+
- `--mod-textfield-label-spacing-block-quiet`
42+
- `--mod-textfield-spacing-block-quiet`
43+
- `--mod-textfield-spacing-inline-quiet`
44+
45+
Changed mods:
46+
47+
- `--mod-texfield-animation-duration` --> `--mod-textfield-animation-duration`
48+
- `--mod-texfield-placeholder-font-size` --> `--mod-texfield-placeholder-font-size`

components/textfield/dist/metadata.json

+85-93
Large diffs are not rendered by default.

components/textfield/index.css

+196-237
Large diffs are not rendered by default.

components/textfield/stories/template.js

+42-41
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ import "../index.css";
2424
* @property {boolean} [isValid=false]
2525
* @property {boolean} [multiline=false]
2626
* @property {boolean} [grows=false]
27-
* @property {boolean} [isQuiet=false]
2827
* @property {boolean} [isFocused=false]
2928
* @property {boolean} [isDisabled=false]
3029
* @property {boolean} [isRequired=false]
@@ -63,14 +62,15 @@ export const Template = ({
6362
isValid = false,
6463
multiline = false,
6564
grows = false,
66-
isQuiet = false,
6765
isFocused = false,
6866
isDisabled = false,
67+
isHovered = false,
6968
isRequired = false,
69+
isRequiredWithoutAsterisk = false,
7070
isReadOnly = false,
7171
isKeyboardFocused = false,
7272
isLoading = false,
73-
displayLabel = false,
73+
displayLabel = true,
7474
labelPosition = "top",
7575
labelText,
7676
characterCount,
@@ -91,12 +91,17 @@ export const Template = ({
9191

9292
// Override icon name and set if the field is invalid or valid
9393
if (isInvalid) {
94-
iconName = "Alert";
94+
iconName = "AlertTriangle";
9595
iconSet = "workflow";
9696
}
9797
else if (isValid) {
98-
iconName = "Checkmark";
9998
iconSet = "ui";
99+
iconName = "Checkmark" + ({
100+
s: "75",
101+
m: "100",
102+
l: "200",
103+
xl: "300",
104+
}[size] || "100");
100105
}
101106

102107
return html`
@@ -107,10 +112,10 @@ export const Template = ({
107112
typeof size !== "undefined",
108113
[`${rootClass}--multiline`]: multiline,
109114
[`${rootClass}--grows`]: grows,
110-
[`${rootClass}--quiet`]: isQuiet,
111115
[`${rootClass}--sideLabel`]: labelPosition === "side",
112116
"is-invalid": isInvalid,
113117
"is-valid": isValid,
118+
"is-hover": isHovered,
114119
"is-focused": isFocused,
115120
"is-keyboardFocused": isKeyboardFocused,
116121
"is-disabled": isDisabled,
@@ -149,6 +154,7 @@ export const Template = ({
149154
size,
150155
label: labelText,
151156
isDisabled,
157+
isRequired: isRequired && !isRequiredWithoutAsterisk,
152158
}, context))}
153159
${when(typeof characterCount !== "undefined", () => html`
154160
<span class="${rootClass}-characterCount">${characterCount}</span>`)}
@@ -215,7 +221,7 @@ export const Template = ({
215221
};
216222

217223
export const HelpTextOptions = (args, context) => Container({
218-
direction: "column",
224+
direction: "row",
219225
withBorder: false,
220226
withHeading: false,
221227
content: html`
@@ -235,67 +241,62 @@ export const HelpTextOptions = (args, context) => Container({
235241
export const TextFieldOptions = (args, context) => Container({
236242
direction: "row",
237243
withBorder: false,
238-
wrapperStyles: {
239-
rowGap: "12px",
240-
},
241244
content: html`
242245
${Container({
243246
withBorder: false,
244-
containerStyles: {
245-
"gap": "8px",
246-
},
247247
heading: "Default",
248248
content: Template(args, context)
249249
}, context)}
250250
${Container({
251251
withBorder: false,
252-
containerStyles: {
253-
"gap": "8px",
254-
},
255-
heading: "Invalid",
256-
content: Template({...args, isInvalid: true}, context)
252+
heading: "Focused",
253+
content: Template({...args, isFocused: true}, context)
257254
}, context)}
258255
${Container({
259256
withBorder: false,
260-
containerStyles: {
261-
"gap": "8px",
262-
},
263-
heading: "Focused",
264-
content: Template({...args, isFocused: true}, context)
257+
heading: "Keyboard focused",
258+
content: Template({...args, isKeyboardFocused: true}, context)
259+
}, context)}
260+
`
261+
}, context);
262+
263+
export const InvalidOptions = (args, context) => Container({
264+
direction: "row",
265+
withBorder: false,
266+
withHeading: false,
267+
content: html`
268+
${Container({
269+
withBorder: false,
270+
heading: "Invalid",
271+
content: Template({...args, isInvalid: true}, context)
265272
}, context)}
266273
${Container({
267274
withBorder: false,
268-
containerStyles: {
269-
"gap": "8px",
270-
},
271275
heading: "Invalid, focused",
272276
content: Template({...args, isInvalid: true, isFocused: true}, context)
273277
}, context)}
274278
`
275279
}, context);
276280

277-
export const KeyboardFocusTemplate = (args, context) => Container({
278-
direction: "column",
281+
export const RequiredOptions = (args, context) => Container({
282+
direction: "row",
279283
withBorder: false,
280-
wrapperStyles: {
281-
rowGap: "12px",
282-
},
284+
withHeading: false,
283285
content: html`
284286
${Container({
285287
withBorder: false,
286-
containerStyles: {
287-
"gap": "8px",
288-
},
289-
heading: "Default",
290-
content: Template({...args, isKeyboardFocused: true}, context)
288+
heading: "Required with (required) label",
289+
content: Template({...args, isRequired: true, isRequiredWithoutAsterisk: true, labelText: "Email address (required)", value: "[email protected]", helpText: "Email address is required"}, context),
290+
}, context)}
291+
${Container({
292+
withBorder: false,
293+
heading: "Required with asterisk",
294+
content: Template({...args, isRequired: true, labelText: "Email address", value: "[email protected]", helpText: "Email address is required"}, context),
291295
}, context)}
292296
${Container({
293297
withBorder: false,
294-
containerStyles: {
295-
"gap": "8px",
296-
},
297-
heading: "Quiet",
298-
content: Template({...args, isKeyboardFocused: true, isQuiet: true}, context)
298+
heading: "Required with asterisk, side label",
299+
content: Template({...args, isRequired: true, labelPosition: "side", labelText: "Email address", value: "[email protected]", helpText: "Email address is required"}, context),
299300
}, context)}
300301
`
301302
}, context);

components/textfield/stories/textarea.stories.js

+46-28
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,15 @@ import { Sizes } from "@spectrum-css/preview/decorators";
22
import { disableDefaultModes } from "@spectrum-css/preview/modes";
33
import metadata from "../dist/metadata.json";
44
import packageJson from "../package.json";
5-
import { HelpTextOptionsTextArea, KeyboardFocusTemplate, Template, TextAreaOptions } from "./textarea.template.js";
5+
import { HelpTextOptionsTextArea, InvalidOptionsTextArea, RequiredOptionsTextArea, Template, TextAreaOptions } from "./textarea.template.js";
66
import { TextAreaGroup } from "./textarea.test.js";
77
import { default as Textfield } from "./textfield.stories.js";
88

99
/**
10-
* A text area is multi-line text field using the `<textarea>` element that lets a user input a longer amount of text than a standard text field. It can include all of the standard validation options supported by the text field component.
10+
* A text area is multi-line text field using the `<textarea>` element that lets a user input a longer amount of text than a standard text field. It
11+
* can include all of the standard validation options supported by the text field component.
12+
*
13+
* For single-line text input, see the [text field](/docs/components-textfield--docs) component.
1114
*/
1215
export default {
1316
title: "Text area",
@@ -35,7 +38,15 @@ Default.args = {};
3538
Default.tags = ["!autodocs"];
3639

3740
// ********* DOCS ONLY ********* //
38-
41+
/**
42+
* A text area should always have a label. In rare cases where context is sufficient and an accessibility expert has reviewed the design, the label
43+
* could be undefined. A text area without a visible label should still include an aria-label in HTML (depending on the context, "aria-label" or
44+
* "aria-labelledby").
45+
*
46+
* When the text area is focused using the keyboard (e.g. with the tab key), the implementation must add the `is-keyboardFocused` class, which
47+
* displays the focus indicator. This indicator should not appear on focus from a click or tap. The keyboard focused example below has this class
48+
* applied on first load for demonstration purposes.
49+
*/
3950
export const Standard = TextAreaOptions.bind({});
4051
Standard.tags = ["!dev"];
4152
Standard.storyName = "Default";
@@ -55,7 +66,8 @@ CharacterCount.parameters = {
5566
};
5667

5768
/**
58-
* A text area in a disabled state shows that an input field exists, but is not available in that circumstance. This can be used to maintain layout continuity and communicate that a field may become available later.
69+
* A text area in a disabled state shows that an input field exists, but is not available in that circumstance. This can be used to maintain layout
70+
* continuity and communicate that a field may become available later.
5971
*/
6072
export const Disabled = Template.bind({});
6173
Disabled.tags = ["!dev"];
@@ -66,28 +78,48 @@ Disabled.parameters = {
6678
chromatic: { disableSnapshot: true }
6779
};
6880

81+
export const Invalid = InvalidOptionsTextArea.bind({});
82+
Invalid.tags = ["!dev"];
83+
Invalid.args = {
84+
isInvalid: true
85+
};
86+
Invalid.parameters = {
87+
chromatic: { disableSnapshot: true }
88+
};
89+
6990
/**
70-
* A text area can have [help text](/docs/components-help-text--docs) below the field to give extra context or instruction about what a user should input in the field. The help text area has two options: a description and an error message. The description communicates a hint or helpful information, such as specific requirements for correctly filling out the field. The error message communicates an error for when the field requirements aren’t met, prompting a user to adjust what they had originally input.
91+
* A text area can have [help text](/docs/components-help-text--docs) below the field to give extra context or instruction about what a user should
92+
* input in the field. The help text area has two options: a description and an error message. The description communicates a hint or helpful
93+
* information, such as specific requirements for correctly filling out the field. The error message communicates an error for when the field
94+
* requirements aren't met, prompting a user to adjust what they had originally input.
7195
*
72-
* Instead of placeholder text, use the help text description to convey requirements or to show any formatting examples that would help user comprehension. Putting instructions for how to complete an input, requirements, or any other essential information into placeholder text is not accessible.
96+
* Instead of placeholder text, use the help text description to convey requirements or to show any formatting examples that would help user
97+
* comprehension. Putting instructions for how to complete an input, requirements, or any other essential information into placeholder text is not
98+
* accessible.
7399
*/
74100
export const HelpText = HelpTextOptionsTextArea.bind({});
75101
HelpText.tags = ["!dev"];
76102
HelpText.parameters = {
77103
chromatic: { disableSnapshot: true }
78104
};
79105

80-
export const Quiet = TextAreaOptions.bind({});
81-
Quiet.tags = ["!dev"];
82-
Quiet.args = {
83-
isQuiet: true,
106+
/**
107+
* Text areas can be marked as optional or required, depending on the situation. For required text areas, there are two styling options: a
108+
* "(required)" label or an asterisk. If you use an asterisk, be sure to include hint text to explain what the asterisk means. Optional text
109+
* fields are either denoted with text added to the end of the label — "(optional)" — or have no indication at all.
110+
*/
111+
export const Required = RequiredOptionsTextArea.bind({});
112+
Required.tags = ["!dev"];
113+
Required.args = {
114+
isRequired: true,
84115
};
85-
Quiet.parameters = {
116+
Required.parameters = {
86117
chromatic: { disableSnapshot: true }
87118
};
88119

89120
/**
90-
* Text area has a read-only option for when content in the disabled state still needs to be shown. This allows for content to be copied, but not interacted with or changed.
121+
* Text area has a read-only option for when content in the disabled state still needs to be shown. This allows for content to be copied, but not
122+
* interacted with or changed.
91123
*/
92124
export const Readonly = Template.bind({});
93125
Readonly.tags = ["!dev"];
@@ -117,7 +149,8 @@ SideLabel.parameters = {
117149
};
118150

119151
/**
120-
* Text area can display a validation icon when the text entry is expected to conform to a specific format (e.g., email address, credit card number, password creation requirements, etc.). The icon appears as soon as a user types a valid entry in the field.
152+
* Text area can display a validation icon when the text entry is expected to conform to a specific format (e.g., email address, credit card number,
153+
* password creation requirements, etc.). The icon appears as soon as a user types a valid entry in the field.
121154
*/
122155
export const Validation = Template.bind({});
123156
Validation.tags = ["!dev"];
@@ -144,22 +177,7 @@ Sizing.parameters = {
144177
chromatic: { disableSnapshot: true }
145178
};
146179

147-
/**
148-
* When the text area was focused using the keyboard (e.g. with the tab key), the implementation must add the `is-keyboardFocused` class, which
149-
* displays the focus indicator. This indicator should not appear on focus from a click or tap.
150-
* The example below has this class applied on first load for demonstration purposes.
151-
*/
152-
export const KeyboardFocus = KeyboardFocusTemplate.bind({});
153-
KeyboardFocus.tags = ["!dev"];
154-
KeyboardFocus.args = {
155-
isKeyboardFocused: true,
156-
};
157-
KeyboardFocus.parameters = {
158-
chromatic: { disableSnapshot: true }
159-
};
160-
161180
// ********* VRT ONLY ********* //
162-
// @todo should this show text field and text area in the same snapshot?
163181
export const WithForcedColors = TextAreaGroup.bind({});
164182
WithForcedColors.args = Default.args;
165183
WithForcedColors.tags = ["!autodocs", "!dev"];

0 commit comments

Comments
 (0)