Skip to content

feat(textfield, textarea): migrate to spectrum 2 #2856

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
d6dd813
feat(textfield): migrate to s2
rise-erpelding Feb 19, 2025
d1a2f9c
test(textfield, textarea): testapalooza
rise-erpelding Feb 24, 2025
4de0c71
docs(textfield, textarea): required label docs
rise-erpelding Feb 21, 2025
695de22
feat(textfield,textarea): s2 icons
rise-erpelding Feb 19, 2025
d5196de
fix(textfield): disabled readonly text color
rise-erpelding Feb 20, 2025
26b4a15
fix(textfield): remove char count custom props to use textfield custo…
rise-erpelding Feb 20, 2025
9d744a5
fix(textfield): bring back props lost in rebase
rise-erpelding Feb 21, 2025
d19dd53
fix(textfield): remove nonexistent ::after styles
rise-erpelding Feb 24, 2025
5d4bc61
fix(textfield): use min-width multiplier correctly
rise-erpelding Feb 24, 2025
daa11c8
fix(textfield): correct focus hover borders
rise-erpelding Feb 25, 2025
d16fcb4
fix(textfield,textarea): small PR fixes
rise-erpelding Mar 5, 2025
c2ef1b7
chore: regenerate metadata
rise-erpelding Mar 13, 2025
5a940a9
docs: update jsdoc comments
rise-erpelding Mar 14, 2025
9dca22d
fix: remove :focus-visible selectors, see #2306
rise-erpelding Mar 14, 2025
cfb0d54
docs: invalid stories separated, update story formatting
rise-erpelding Mar 14, 2025
c7eefb5
docs: remove isLoading from controls
rise-erpelding Mar 14, 2025
0e15d15
docs: add control descriptions
rise-erpelding Mar 14, 2025
3338255
test: add disabled & hover
rise-erpelding Mar 14, 2025
710cd4b
fix: scope generic selectors to .spectrum-Textfield
rise-erpelding Mar 24, 2025
a4fafb3
fix: add WHCM readonly border, disabled bg color
rise-erpelding Mar 24, 2025
9976179
fix: adjust WHCM placeholder text to match value
rise-erpelding Mar 24, 2025
647ddd9
fix: adjust WHCM keyfocus border color to match spec
rise-erpelding Mar 24, 2025
16406fa
fix: adjust high contrast focus hover colors
rise-erpelding Mar 25, 2025
5d0c8e0
fix: prevent WHCM orange flash
rise-erpelding Mar 28, 2025
e3ba0dd
fix: readonly & disabled selector
rise-erpelding Mar 28, 2025
dcc9e1a
fix: remove invalid border colors
rise-erpelding Apr 1, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions .changeset/healthy-chicken-clap.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
---
"@spectrum-css/textfield": major
---

Migrate to Spectrum 2

- Default styles are used for medium
- Removal of quiet variant
- Style adjustments to match design spec:
- Spacing start/end edge to value for XL component should use `component-edge-to-text-300`, not `-200`
- 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`)
- 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`)
- Border thickness has been increased to 2px, and border colors have been updated to match spec
- Adjust side label inline spacing to field, should be `spacing-200` for all tshirt sizes
- Change disabled background color to `gray-25`
- Change disabled-border-color from transparent to `disabled-border-color`
- Use new and updated tokens:
- use tokens for textfield width (`field-default-width-small`, `-medium`, `-large`, `-extra-large`)
- use updated corner radius tokens
- use different component-bottom-to-text tokens for character-count-spacing-block
- update tokens for alert icon block spacing
- update tokens for alert and invalid inline-start spacing
- update font tokens

Adds mods:

- `--mod-textfield-character-count-color`
- `--mod-textfield-character-count-font-style`
- `--mod-textfield-character-count-line-height`
- `--mod-textfield-character-count-line-height-cjk`
- `--mod-textfield-font-style`
- `--mod-textfield-line-height` (used for multiline only)
- `--mod-textfield-line-height-cjk`

Removes mods:

- `--mod-text-area-min-block-size-quiet`
- `--mod-textfield-character-count-spacing-block-quiet`
- `--mod-textfield-icon-spacing-inline-end-quiet-invalid`
- `--mod-textfield-icon-spacing-inline-end-quiet-valid`
- `--mod-textfield-label-spacing-block-quiet`
- `--mod-textfield-spacing-block-quiet`
- `--mod-textfield-spacing-inline-quiet`

Changed mods:

- `--mod-texfield-animation-duration` --> `--mod-textfield-animation-duration`
- `--mod-texfield-placeholder-font-size` --> `--mod-texfield-placeholder-font-size`
178 changes: 85 additions & 93 deletions components/textfield/dist/metadata.json

Large diffs are not rendered by default.

433 changes: 196 additions & 237 deletions components/textfield/index.css

Large diffs are not rendered by default.

83 changes: 42 additions & 41 deletions components/textfield/stories/template.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ import "../index.css";
* @property {boolean} [isValid=false]
* @property {boolean} [multiline=false]
* @property {boolean} [grows=false]
* @property {boolean} [isQuiet=false]
* @property {boolean} [isFocused=false]
* @property {boolean} [isDisabled=false]
* @property {boolean} [isRequired=false]
Expand Down Expand Up @@ -63,14 +62,15 @@ export const Template = ({
isValid = false,
multiline = false,
grows = false,
isQuiet = false,
isFocused = false,
isDisabled = false,
isHovered = false,
isRequired = false,
isRequiredWithoutAsterisk = false,
isReadOnly = false,
isKeyboardFocused = false,
isLoading = false,
displayLabel = false,
displayLabel = true,
labelPosition = "top",
labelText,
characterCount,
Expand All @@ -91,12 +91,17 @@ export const Template = ({

// Override icon name and set if the field is invalid or valid
if (isInvalid) {
iconName = "Alert";
iconName = "AlertTriangle";
iconSet = "workflow";
}
else if (isValid) {
iconName = "Checkmark";
iconSet = "ui";
iconName = "Checkmark" + ({
s: "75",
m: "100",
l: "200",
xl: "300",
}[size] || "100");
}

return html`
Expand All @@ -107,10 +112,10 @@ export const Template = ({
typeof size !== "undefined",
[`${rootClass}--multiline`]: multiline,
[`${rootClass}--grows`]: grows,
[`${rootClass}--quiet`]: isQuiet,
[`${rootClass}--sideLabel`]: labelPosition === "side",
"is-invalid": isInvalid,
"is-valid": isValid,
"is-hover": isHovered,
"is-focused": isFocused,
"is-keyboardFocused": isKeyboardFocused,
"is-disabled": isDisabled,
Expand Down Expand Up @@ -149,6 +154,7 @@ export const Template = ({
size,
label: labelText,
isDisabled,
isRequired: isRequired && !isRequiredWithoutAsterisk,
}, context))}
${when(typeof characterCount !== "undefined", () => html`
<span class="${rootClass}-characterCount">${characterCount}</span>`)}
Expand Down Expand Up @@ -215,7 +221,7 @@ export const Template = ({
};

export const HelpTextOptions = (args, context) => Container({
direction: "column",
direction: "row",
withBorder: false,
withHeading: false,
content: html`
Expand All @@ -235,67 +241,62 @@ export const HelpTextOptions = (args, context) => Container({
export const TextFieldOptions = (args, context) => Container({
direction: "row",
withBorder: false,
wrapperStyles: {
rowGap: "12px",
},
content: html`
${Container({
withBorder: false,
containerStyles: {
"gap": "8px",
},
heading: "Default",
content: Template(args, context)
}, context)}
${Container({
withBorder: false,
containerStyles: {
"gap": "8px",
},
heading: "Invalid",
content: Template({...args, isInvalid: true}, context)
heading: "Focused",
content: Template({...args, isFocused: true}, context)
}, context)}
${Container({
withBorder: false,
containerStyles: {
"gap": "8px",
},
heading: "Focused",
content: Template({...args, isFocused: true}, context)
heading: "Keyboard focused",
content: Template({...args, isKeyboardFocused: true}, context)
}, context)}
`
}, context);

export const InvalidOptions = (args, context) => Container({
direction: "row",
withBorder: false,
withHeading: false,
content: html`
${Container({
withBorder: false,
heading: "Invalid",
content: Template({...args, isInvalid: true}, context)
}, context)}
${Container({
withBorder: false,
containerStyles: {
"gap": "8px",
},
heading: "Invalid, focused",
content: Template({...args, isInvalid: true, isFocused: true}, context)
}, context)}
`
}, context);

export const KeyboardFocusTemplate = (args, context) => Container({
direction: "column",
export const RequiredOptions = (args, context) => Container({
direction: "row",
withBorder: false,
wrapperStyles: {
rowGap: "12px",
},
withHeading: false,
content: html`
${Container({
withBorder: false,
containerStyles: {
"gap": "8px",
},
heading: "Default",
content: Template({...args, isKeyboardFocused: true}, context)
heading: "Required with (required) label",
content: Template({...args, isRequired: true, isRequiredWithoutAsterisk: true, labelText: "Email address (required)", value: "abc@adobe.com", helpText: "Email address is required"}, context),
}, context)}
${Container({
withBorder: false,
heading: "Required with asterisk",
content: Template({...args, isRequired: true, labelText: "Email address", value: "abc@adobe.com", helpText: "Email address is required"}, context),
}, context)}
${Container({
withBorder: false,
containerStyles: {
"gap": "8px",
},
heading: "Quiet",
content: Template({...args, isKeyboardFocused: true, isQuiet: true}, context)
heading: "Required with asterisk, side label",
content: Template({...args, isRequired: true, labelPosition: "side", labelText: "Email address", value: "abc@adobe.com", helpText: "Email address is required"}, context),
}, context)}
`
}, context);
74 changes: 46 additions & 28 deletions components/textfield/stories/textarea.stories.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,15 @@ import { Sizes } from "@spectrum-css/preview/decorators";
import { disableDefaultModes } from "@spectrum-css/preview/modes";
import metadata from "../dist/metadata.json";
import packageJson from "../package.json";
import { HelpTextOptionsTextArea, KeyboardFocusTemplate, Template, TextAreaOptions } from "./textarea.template.js";
import { HelpTextOptionsTextArea, InvalidOptionsTextArea, RequiredOptionsTextArea, Template, TextAreaOptions } from "./textarea.template.js";
import { TextAreaGroup } from "./textarea.test.js";
import { default as Textfield } from "./textfield.stories.js";

/**
* 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.
* 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.
*
* For single-line text input, see the [text field](/docs/components-textfield--docs) component.
*/
export default {
title: "Text area",
Expand Down Expand Up @@ -35,7 +38,15 @@ Default.args = {};
Default.tags = ["!autodocs"];

// ********* DOCS ONLY ********* //

/**
* 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
* 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
* "aria-labelledby").
*
* When the text area is focused using the keyboard (e.g. with the tab key), the implementation must add the `is-keyboardFocused` class, which
* displays the focus indicator. This indicator should not appear on focus from a click or tap. The keyboard focused example below has this class
* applied on first load for demonstration purposes.
*/
export const Standard = TextAreaOptions.bind({});
Standard.tags = ["!dev"];
Standard.storyName = "Default";
Expand All @@ -55,7 +66,8 @@ CharacterCount.parameters = {
};

/**
* 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.
* 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.
*/
export const Disabled = Template.bind({});
Disabled.tags = ["!dev"];
Expand All @@ -66,28 +78,48 @@ Disabled.parameters = {
chromatic: { disableSnapshot: true }
};

export const Invalid = InvalidOptionsTextArea.bind({});
Invalid.tags = ["!dev"];
Invalid.args = {
isInvalid: true
};
Invalid.parameters = {
chromatic: { disableSnapshot: true }
};

/**
* 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.
* 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.
*
* 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.
* 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.
*/
export const HelpText = HelpTextOptionsTextArea.bind({});
HelpText.tags = ["!dev"];
HelpText.parameters = {
chromatic: { disableSnapshot: true }
};

export const Quiet = TextAreaOptions.bind({});
Quiet.tags = ["!dev"];
Quiet.args = {
isQuiet: true,
/**
* Text areas can be marked as optional or required, depending on the situation. For required text areas, there are two styling options: a
* "(required)" label or an asterisk. If you use an asterisk, be sure to include hint text to explain what the asterisk means. Optional text
* fields are either denoted with text added to the end of the label — "(optional)" — or have no indication at all.
*/
export const Required = RequiredOptionsTextArea.bind({});
Required.tags = ["!dev"];
Required.args = {
isRequired: true,
};
Quiet.parameters = {
Required.parameters = {
chromatic: { disableSnapshot: true }
};

/**
* 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.
* 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.
*/
export const Readonly = Template.bind({});
Readonly.tags = ["!dev"];
Expand Down Expand Up @@ -117,7 +149,8 @@ SideLabel.parameters = {
};

/**
* 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.
* 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.
*/
export const Validation = Template.bind({});
Validation.tags = ["!dev"];
Expand All @@ -144,22 +177,7 @@ Sizing.parameters = {
chromatic: { disableSnapshot: true }
};

/**
* When the text area was focused using the keyboard (e.g. with the tab key), the implementation must add the `is-keyboardFocused` class, which
* displays the focus indicator. This indicator should not appear on focus from a click or tap.
* The example below has this class applied on first load for demonstration purposes.
*/
export const KeyboardFocus = KeyboardFocusTemplate.bind({});
KeyboardFocus.tags = ["!dev"];
KeyboardFocus.args = {
isKeyboardFocused: true,
};
KeyboardFocus.parameters = {
chromatic: { disableSnapshot: true }
};

// ********* VRT ONLY ********* //
// @todo should this show text field and text area in the same snapshot?
export const WithForcedColors = TextAreaGroup.bind({});
WithForcedColors.args = Default.args;
WithForcedColors.tags = ["!autodocs", "!dev"];
Expand Down
Loading
Loading