Skip to content

Commit

Permalink
Update TextField styling for consistency with other components (focus…
Browse files Browse the repository at this point in the history
… styling) (#2302)

## Summary:
- Update `TextField` state styling so it is consistent with other components like `TextArea`, `SingleSelect`, `MultiSelect` (especially the focus styling)
- The styling changes use CSS pseudo-classes now so that we can have visual regression tests for the UI states using Storybook's pseudo-states add on.
- These styles align with the [Figma TextField component](https://www.figma.com/design/VbVu3h2BpBhH80niq101MHHE/%F0%9F%92%A0-Main-Components?node-id=13497-10015&t=QZlu8ecGmGU3QceA-4)
- Removed unneeded focused state since we use pseudo classes now
- Added variant stories for Chromatic tests

Note: These styles are based on how TextArea was styled!

Issue: WB-1746

## Test plan:
1. Confirm the focus styling for TextField when (`?path=/docs/packages-form-textfield--docs`):
- it is focused
- it is focused on a dark background with the `light` prop set to `true`
- it is in an error state and is focused
- it is in an error state on a dark background with the `light` prop set to `true`
2. Confirm other states (error, disabled, default)
3. Review the new variant stories (`?path=/docs/packages-form-textfield-all-variants--docs`)

Author: beaesguerra

Reviewers: beaesguerra, jandrade

Required Reviewers:

Approved By: jandrade

Checks: ✅ codecov/project, ✅ Chromatic - Get results on regular PRs (ubuntu-latest, 20.x), ✅ Test (ubuntu-latest, 20.x, 2/2), ✅ Test (ubuntu-latest, 20.x, 1/2), ✅ Lint (ubuntu-latest, 20.x), ✅ Check build sizes (ubuntu-latest, 20.x), ✅ Chromatic - Build on regular PRs / chromatic (ubuntu-latest, 20.x), ✅ Publish npm snapshot (ubuntu-latest, 20.x), ⏭️  Chromatic - Skip on Release PR (changesets), ✅ Prime node_modules cache for primary configuration (ubuntu-latest, 20.x), ✅ Check for .changeset entries for all changed files (ubuntu-latest, 20.x), ✅ gerald, ⏭️  dependabot

Pull Request URL: #2302
  • Loading branch information
beaesguerra authored Aug 26, 2024
1 parent 17f9a33 commit 982f680
Show file tree
Hide file tree
Showing 5 changed files with 258 additions and 70 deletions.
5 changes: 5 additions & 0 deletions .changeset/famous-walls-scream.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@khanacademy/wonder-blocks-form": patch
---

Update `TextField` state styling so that it is consistent with other components like `TextArea`, `SingleSelect`, `MultiSelect` (especially the focus styling). The styling also now uses CSS pseudo-classes for easier testing in Chromatic and debugging in browsers.
168 changes: 168 additions & 0 deletions __docs__/wonder-blocks-form/text-field-variants.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
import * as React from "react";
import {StyleSheet} from "aphrodite";
import type {Meta, StoryObj} from "@storybook/react";

import {View} from "@khanacademy/wonder-blocks-core";
import {color, spacing} from "@khanacademy/wonder-blocks-tokens";
import {LabelLarge, LabelMedium} from "@khanacademy/wonder-blocks-typography";
import {TextField} from "@khanacademy/wonder-blocks-form";

/**
* The following stories are used to generate the pseudo states for the
* TextField component. This is only used for visual testing in Chromatic.
*
* Note: Error state is not shown on initial render if the TextField value is empty.
*/
export default {
title: "Packages / Form / TextField / All Variants",
parameters: {
docs: {
autodocs: false,
},
},
} as Meta;

type StoryComponentType = StoryObj<typeof TextField>;

const longText =
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.";
const longTextWithNoWordBreak =
"Loremipsumdolorsitametconsecteturadipiscingelitseddoeiusmodtemporincididuntutlaboreetdoloremagnaaliqua";

const states = [
{
label: "Default",
props: {},
},
{
label: "Disabled",
props: {disabled: true},
},
{
label: "Error",
props: {validate: () => "Error"},
},
];
const States = (props: {
light: boolean;
label: string;
value?: string;
placeholder?: string;
}) => {
return (
<View
style={[props.light && styles.darkDefault, styles.statesContainer]}
>
<LabelLarge style={props.light && {color: color.white}}>
{props.label}
</LabelLarge>
<View style={[styles.scenarios]}>
{states.map((scenario) => {
return (
<View style={styles.scenario} key={scenario.label}>
<LabelMedium
style={props.light && {color: color.white}}
>
{scenario.label}
</LabelMedium>
<TextField
value=""
onChange={() => {}}
{...props}
{...scenario.props}
/>
</View>
);
})}
</View>
</View>
);
};

const AllVariants = () => (
<View>
{[false, true].map((light) => {
return (
<React.Fragment key={`light-${light}`}>
<States light={light} label="Default" />
<States light={light} label="With Value" value="Text" />
<States
light={light}
label="With Value (long)"
value={longText}
/>
<States
light={light}
label="With Value (long, no word breaks)"
value={longTextWithNoWordBreak}
/>
<States
light={light}
label="With Placeholder"
placeholder="Placeholder text"
/>
<States
light={light}
label="With Placeholder (long)"
placeholder={longText}
/>
<States
light={light}
label="With Placeholder (long, no word breaks)"
placeholder={longTextWithNoWordBreak}
/>
</React.Fragment>
);
})}
</View>
);

export const Default: StoryComponentType = {
render: AllVariants,
};

/**
* There are currently no hover styles.
*/
export const Hover: StoryComponentType = {
render: AllVariants,
parameters: {pseudo: {hover: true}},
};

export const Focus: StoryComponentType = {
render: AllVariants,
parameters: {pseudo: {focusVisible: true}},
};

export const HoverFocus: StoryComponentType = {
name: "Hover + Focus",
render: AllVariants,
parameters: {pseudo: {hover: true, focusVisible: true}},
};

/**
* There are currently no active styles.
*/
export const Active: StoryComponentType = {
render: AllVariants,
parameters: {pseudo: {active: true}},
};

const styles = StyleSheet.create({
darkDefault: {
backgroundColor: color.darkBlue,
},
statesContainer: {
padding: spacing.medium_16,
},
scenarios: {
display: "flex",
flexDirection: "row",
alignItems: "center",
gap: spacing.xxxLarge_64,
flexWrap: "wrap",
},
scenario: {
gap: spacing.small_12,
},
});
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import {render, screen, fireEvent} from "@testing-library/react";
import {userEvent} from "@testing-library/user-event";

import {StyleSheet} from "aphrodite";
import {color} from "@khanacademy/wonder-blocks-tokens";
import LabeledTextField from "../labeled-text-field";

describe("LabeledTextField", () => {
Expand Down Expand Up @@ -382,28 +381,6 @@ describe("LabeledTextField", () => {
expect(input).toBeInTheDocument();
});

it("light prop is passed to textfield", async () => {
// Arrange

// Act
render(
<LabeledTextField
label="Label"
value=""
onChange={() => {}}
light={true}
/>,
);

const textField = await screen.findByRole("textbox");
textField.focus();

// Assert
expect(textField).toHaveStyle({
boxShadow: `0px 0px 0px 1px ${color.blue}, 0px 0px 0px 2px ${color.white}`,
});
});

it("style prop is passed to fieldheading", async () => {
// Arrange
const styles = StyleSheet.create({
Expand Down
6 changes: 3 additions & 3 deletions packages/wonder-blocks-form/src/components/text-area.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as React from "react";
import {CSSProperties, Falsy, StyleSheet} from "aphrodite";
import {StyleSheet} from "aphrodite";

import {
AriaProps,
Expand Down Expand Up @@ -256,7 +256,7 @@ const TextArea = React.forwardRef<HTMLTextAreaElement, TextAreaProps>(
}
});

const getStyles = (): (CSSProperties | Falsy)[] => {
const getStyles = (): StyleType => {
// Base styles are the styles that apply regardless of light mode
const baseStyles = [
styles.textarea,
Expand Down Expand Up @@ -284,7 +284,7 @@ const TextArea = React.forwardRef<HTMLTextAreaElement, TextAreaProps>(
data-testid={testId}
ref={ref}
className={className}
style={[...getStyles(), style]}
style={[getStyles(), style]}
value={value}
onChange={handleChange}
placeholder={placeholder}
Expand Down
Loading

0 comments on commit 982f680

Please sign in to comment.