Skip to content

Commit

Permalink
docs: fix and document use of keyboard focus classes on text fields (#…
Browse files Browse the repository at this point in the history
…3354)

* docs: fix and document use of keyboard focus classes on text fields

Our Storybook was adding the keyboard focus class on click, which isn't
the intended design. Click focus and focused by use of a keyboard have
different styles. This update stops the class from being applied on
click, and documents how the class should be added by the
implementation.

* docs(textfield): add keyboard focused class on tab focus

Example functionality in our Storybook to add the keyboard focused class
to the text field when it was focused with the tab key, similar to what
is documented for implementations.

* docs(textfield): remove some unneeded keyboard focus examples

Remove some examples of keyboard focus from docs stories, that are not
necessary to show now that keyboard focus has its own docs example.
  • Loading branch information
jawinn authored Nov 11, 2024
1 parent f2b82cf commit 0f50c14
Show file tree
Hide file tree
Showing 4 changed files with 95 additions and 38 deletions.
53 changes: 38 additions & 15 deletions components/textfield/stories/template.js
Original file line number Diff line number Diff line change
Expand Up @@ -120,16 +120,28 @@ export const Template = ({
style=${styleMap(customStyles)}
@click=${onclick}
@focusin=${function() {
updateArgs({
updateArgs?.({
isFocused: true,
isKeyboardFocused: true
});
}}
@keyup=${function(e) {
// Tab key was used.
if (e.keyCode === 9) {
// The element that was focused when the key was released is this textfield / input.
if (e.target == this || e.target?.parentNode == this) {
updateArgs?.({ isKeyboardFocused: true });
// Manually add class since updateArgs doesn't always work on the Docs page.
this.classList.add("is-keyboardFocused");
}
}
}}
@focusout=${function() {
updateArgs({
updateArgs?.({
isFocused: false,
isKeyboardFocused: false
isKeyboardFocused: false,
});
// Manually remove class since updateArgs doesn't always work on the Docs page.
this.classList.remove("is-keyboardFocused");
}}
id=${ifDefined(id)}
>
Expand Down Expand Up @@ -189,13 +201,14 @@ export const Template = ({
customClasses: customProgressCircleClasses,
}, context))}
${when(helpText, () =>
HelpText({
text: helpText,
variant: isInvalid ? "negative" : "neutral",
size,
hideIcon: true,
isDisabled
}, context ))}
HelpText({
text: helpText,
variant: isInvalid ? "negative" : "neutral",
size,
hideIcon: true,
isDisabled
}, context)
)}
</div>
`;
};
Expand Down Expand Up @@ -231,7 +244,7 @@ export const TextFieldOptions = (args, context) => Container({
"gap": "8px",
},
heading: "Default",
content: Template({...args, context})
content: Template(args, context)
}, context)}
${Container({
withBorder: false,
Expand All @@ -257,21 +270,31 @@ export const TextFieldOptions = (args, context) => Container({
heading: "Invalid, focused",
content: Template({...args, isInvalid: true, isFocused: true}, context)
}, context)}
`
}, context);

export const KeyboardFocusTemplate = (args, context) => Container({
direction: "column",
withBorder: false,
wrapperStyles: {
rowGap: "12px",
},
content: html`
${Container({
withBorder: false,
containerStyles: {
"gap": "8px",
},
heading: "Keyboard-focused",
heading: "Default",
content: Template({...args, isKeyboardFocused: true}, context)
}, context)}
${Container({
withBorder: false,
containerStyles: {
"gap": "8px",
},
heading: "Invalid, keyboard-focused",
content: Template({...args, isInvalid: true, isKeyboardFocused: true}, context)
heading: "Quiet",
content: Template({...args, isKeyboardFocused: true, isQuiet: true}, context)
}, context)}
`
}, context);
31 changes: 21 additions & 10 deletions components/textfield/stories/textarea.stories.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,13 @@ import { Sizes } from "@spectrum-css/preview/decorators";
import { disableDefaultModes } from "@spectrum-css/preview/modes";
import metadata from "../metadata/metadata.json";
import packageJson from "../package.json";
import { HelpTextOptionsTextArea, Template, TextAreaOptions } from "./textarea.template.js";
import { HelpTextOptionsTextArea, KeyboardFocusTemplate, 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.
*/

*/
export default {
title: "Text area",
component: "TextArea",
Expand Down Expand Up @@ -53,7 +52,7 @@ 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.
*/
*/
export const Disabled = Template.bind({});
Disabled.tags = ["!dev"];
Disabled.args = {
Expand All @@ -67,7 +66,7 @@ Disabled.parameters = {
* 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.
*/
*/
export const HelpText = HelpTextOptionsTextArea.bind({});
HelpText.tags = ["!dev"];
HelpText.parameters = {
Expand All @@ -85,7 +84,7 @@ Quiet.parameters = {

/**
* 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"];
Readonly.args = {
Expand All @@ -99,7 +98,7 @@ Readonly.storyName = "Read-only";

/**
* Side labels are most useful when vertical space is limited.
*/
*/
export const SideLabel = Template.bind({});
SideLabel.tags = ["!dev"];
SideLabel.args = {
Expand All @@ -113,10 +112,9 @@ SideLabel.parameters = {
chromatic: { disableSnapshot: true }
};


/**
* 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"];
Validation.args = {
Expand All @@ -127,7 +125,6 @@ Validation.parameters = {
};
Validation.storyName = "Validation icon";


export const Sizing = (args, context) => Sizes({
Template: Template,
withHeading: false,
Expand All @@ -143,6 +140,20 @@ 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({});
Expand Down
18 changes: 14 additions & 4 deletions components/textfield/stories/textarea.template.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ export const TextAreaOptions = (args, context) => Container({
"gap": "8px",
},
heading: "Default",
content: Template({...args, context})
content: Template(args, context)
}, context)}
${Container({
withBorder: false,
Expand All @@ -75,21 +75,31 @@ export const TextAreaOptions = (args, context) => Container({
heading: "Invalid, focused",
content: Template({...args, isInvalid: true, isFocused: true}, context)
}, context)}
`
}, context);

export const KeyboardFocusTemplate = (args, context) => Container({
direction: "column",
withBorder: false,
wrapperStyles: {
rowGap: "12px",
},
content: html`
${Container({
withBorder: false,
containerStyles: {
"gap": "8px",
},
heading: "Keyboard-focused",
heading: "Default",
content: Template({...args, isKeyboardFocused: true}, context)
}, context)}
${Container({
withBorder: false,
containerStyles: {
"gap": "8px",
},
heading: "Invalid, keyboard-focused",
content: Template({...args, isInvalid: true, isKeyboardFocused: true}, context)
heading: "Quiet",
content: Template({...args, isKeyboardFocused: true, isQuiet: true}, context)
}, context)}
`
}, context);
31 changes: 22 additions & 9 deletions components/textfield/stories/textfield.stories.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { disableDefaultModes } from "@spectrum-css/preview/modes";
import { isDisabled, isFocused, isInvalid, isKeyboardFocused, isLoading, isQuiet, isReadOnly, isRequired, isValid, size } from "@spectrum-css/preview/types";
import metadata from "../metadata/metadata.json";
import packageJson from "../package.json";
import { HelpTextOptions, Template, TextFieldOptions } from "./template.js";
import { HelpTextOptions, KeyboardFocusTemplate, Template, TextFieldOptions } from "./template.js";
import { TextFieldGroup } from "./textfield.test.js";

/**
Expand Down Expand Up @@ -143,7 +143,7 @@ export default {

/**
* Text fields 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. These text fields without a visible label should still include an aria-label in HTML (depending on the context, “aria-label” or “aria-labelledby”).
*/
*/

export const Default = TextFieldGroup.bind({});
Default.tags = ["!autodocs"];
Expand All @@ -160,7 +160,7 @@ Standard.parameters = {

/**
* Text fields can display a character count indicator when the length of the text entry needs to be kept under a predefined value. Character count indicators can be used in conjunction with other indicators (validation icon, “optional” or “required” indicators) when necessary.
*/
*/
export const CharacterCount = Template.bind({});
CharacterCount.tags = ["!dev"];
CharacterCount.args = {
Expand All @@ -174,7 +174,7 @@ CharacterCount.parameters = {

/**
* A text field 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"];
Disabled.args = {
Expand All @@ -188,7 +188,7 @@ Disabled.parameters = {
* A text field 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.
*/
*/
export const HelpText = HelpTextOptions.bind({});
HelpText.tags = ["!dev"];
HelpText.parameters = {
Expand All @@ -197,7 +197,7 @@ HelpText.parameters = {

/**
* Quiet text fields can have no visible background. This style works best when a clear layout (vertical stack, table, grid) makes it easy to parse. Too many quiet components in a small space can be hard to read.
*/
*/
export const Quiet = TextFieldOptions.bind({});
Quiet.tags = ["!dev"];
Quiet.args = {
Expand All @@ -210,7 +210,7 @@ Quiet.parameters = {

/**
* Text fields have 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"];
Readonly.args = {
Expand All @@ -224,7 +224,7 @@ Readonly.storyName = "Read-only";

/**
* Side labels are most useful when vertical space is limited.
*/
*/
export const SideLabel = Template.bind({});
SideLabel.tags = ["!dev"];
SideLabel.args = {
Expand Down Expand Up @@ -254,7 +254,7 @@ Sizing.parameters = {

/**
* Text fields 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"];
Validation.args = {
Expand All @@ -265,6 +265,19 @@ Validation.parameters = {
};
Validation.storyName = "Validation icon";

/**
* When the text field 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?
Expand Down

0 comments on commit 0f50c14

Please sign in to comment.