Skip to content

Commit

Permalink
[WB-1814.1] Refactor Checkbox and Radio to use semantic colors (#2439)
Browse files Browse the repository at this point in the history
## Summary:

This PR get us closer to prepare form field components to apply a future Polaris theme.

- Generated `All Variant` stories for Checkbox and Radio components.
- Migrated `color` instances to use `semanticColor` in `Checkbox` and `Radio`.

**NOTE:** Next PR will do the same for the remaining form fields (`TextField`, `TextArea`).

Issue: WB-1814

## Test plan:

Verify that the Checkbox and Radio stories look correct

Author: jandrade

Reviewers: beaesguerra, jandrade

Required Reviewers:

Approved By: beaesguerra

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

Pull Request URL: #2439
  • Loading branch information
jandrade authored Jan 29, 2025
1 parent c162abb commit 8cfaeab
Show file tree
Hide file tree
Showing 16 changed files with 401 additions and 425 deletions.
5 changes: 5 additions & 0 deletions .changeset/eighty-zebras-burn.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@khanacademy/wonder-blocks-tokens": minor
---

Add `icon.disabled` token to semanticColor.
5 changes: 5 additions & 0 deletions .changeset/unlucky-planes-admire.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@khanacademy/wonder-blocks-form": patch
---

Migrate Radio and Checkbox to use semanticColor tokens
92 changes: 92 additions & 0 deletions __docs__/components/all-variants.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import * as React from "react";
import type {StrictArgs} from "@storybook/react";

import {StyleSheet} from "aphrodite";
import {addStyle} from "@khanacademy/wonder-blocks-core";
import {
border,
semanticColor,
spacing,
} from "@khanacademy/wonder-blocks-tokens";

import {LabelLarge} from "@khanacademy/wonder-blocks-typography";

const StyledTable = addStyle("table");
const StyledTh = addStyle("th");
const StyledTd = addStyle("td");

type Variant = {name: string; props: StrictArgs};

type Props = {
/**
* The children as a function that receives the state props used to render
* each variant of the component.
*/
children: (props: any) => React.ReactNode;
/**
* The categories to display in the table as columns.
*/
rows: Array<Variant>;
/**
* The states to display in the table as rows.
*/
columns: Array<Variant>;
};

/**
* A table that displays all possible variants of a component.
*/
export function AllVariants({children, columns, rows}: Props) {
return (
<StyledTable style={styles.table}>
<thead>
<tr>
<StyledTh style={styles.cell}>
<LabelLarge>Category / State</LabelLarge>
</StyledTh>
{columns.map((col, index) => (
<StyledTh key={index} scope="col" style={styles.cell}>
<LabelLarge>{col.name}</LabelLarge>
</StyledTh>
))}
</tr>
</thead>
<tbody>
{rows.map((row, idx) => (
<tr key={idx}>
<StyledTd scope="row" style={styles.cell}>
<LabelLarge>{row.name}</LabelLarge>
</StyledTd>
{columns.map((col) => (
<StyledTd
key={col.name}
style={[
styles.cell,
{
border: `${border.width.hairline}px dashed ${semanticColor.border.primary}`,
},
]}
>
{children({
...row.props,
...col.props,
})}
</StyledTd>
))}
</tr>
))}
</tbody>
</StyledTable>
);
}

const styles = StyleSheet.create({
table: {
borderCollapse: "collapse",
textAlign: "left",
},
cell: {
margin: spacing.medium_16,
padding: spacing.medium_16,
},
});
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,26 @@ import {StyleSheet} from "aphrodite";
import {PropsFor, View} from "@khanacademy/wonder-blocks-core";
import {Checkbox} from "@khanacademy/wonder-blocks-form";
import {LabelSmall} from "@khanacademy/wonder-blocks-typography";
import {color} from "@khanacademy/wonder-blocks-tokens";
import {semanticColor} from "@khanacademy/wonder-blocks-tokens";

type CheckboxProps = PropsFor<typeof Checkbox>;

const ErrorTemplate = (args: CheckboxProps) => {
const [checked, setChecked] = React.useState(false);
const errorId = React.useId();
const errorState = !checked;
return (
<View>
<Checkbox
error={errorState}
aria-describedby={errorState ? "error-message" : undefined}
aria-describedby={errorState ? errorId : undefined}
aria-required={true}
{...args}
checked={checked}
onChange={setChecked}
/>
{errorState && (
<LabelSmall style={styles.error} id="error-message">
<LabelSmall style={styles.error} id={errorId}>
You must agree to the terms to continue
</LabelSmall>
)}
Expand All @@ -45,7 +46,7 @@ const DisabledTemplate = (args: CheckboxProps) => {

const styles = StyleSheet.create({
error: {
color: color.red,
color: semanticColor.status.critical.foreground,
},
});

Expand Down
6 changes: 3 additions & 3 deletions __docs__/wonder-blocks-form/checkbox-group.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ 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 {semanticColor, spacing} from "@khanacademy/wonder-blocks-tokens";
import {LabelLarge, LabelXSmall} from "@khanacademy/wonder-blocks-typography";

import {Choice, CheckboxGroup} from "@khanacademy/wonder-blocks-form";
Expand Down Expand Up @@ -329,7 +329,7 @@ const styles = StyleSheet.create({
},
title: {
paddingBottom: spacing.xSmall_8,
borderBottom: `1px solid ${color.offBlack64}`,
borderBottom: `1px solid ${semanticColor.border.strong}`,
},
// Multiple choice styling
multipleChoice: {
Expand All @@ -339,7 +339,7 @@ const styles = StyleSheet.create({
justifyContent: "center",
},
description: {
color: color.offBlack64,
color: semanticColor.text.secondary,
},
last: {
borderBottom: "solid 1px #CCC",
Expand Down
69 changes: 69 additions & 0 deletions __docs__/wonder-blocks-form/checkbox-variants.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import * as React from "react";
import type {Meta, StoryObj} from "@storybook/react";

import {Checkbox} from "@khanacademy/wonder-blocks-form";

import {AllVariants} from "../components/all-variants";

const rows = [
{name: "Unchecked", props: {checked: false}},
{name: "Checked", props: {checked: true}},
{name: "Indeterminate", props: {checked: null}},
];

const columns = [
{
name: "Default",
props: {},
},
{
name: "Disabled",
props: {disabled: true},
},
{
name: "Error",
props: {error: true},
},
];

type Story = StoryObj<typeof Checkbox>;

/**
* The following stories are used to generate the pseudo states for the Checkbox
* component. This is only used for visual testing in Chromatic.
*/
const meta = {
title: "Packages / Form / Checkbox / Checkbox - All Variants",
component: Checkbox,
render: (args) => (
<AllVariants rows={rows} columns={columns}>
{(props) => <Checkbox {...args} {...props} />}
</AllVariants>
),
args: {
label: "Label",
description: "Description",
},
tags: ["!autodocs"],
} satisfies Meta<typeof Checkbox>;

export default meta;

export const Default: Story = {};

export const Hover: Story = {
parameters: {pseudo: {hover: true}},
};

export const Focus: Story = {
parameters: {pseudo: {focusVisible: true}},
};

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

export const Active: Story = {
parameters: {pseudo: {active: true}},
};
22 changes: 4 additions & 18 deletions __docs__/wonder-blocks-form/checkbox.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ export default {
version={packageConfig.version}
/>
),
chromatic: {
// These stories are being tested in checkbox-variants.stories.tsx
disableSnapshot: true,
},
},
} as Meta<typeof Checkbox>;

Expand All @@ -33,14 +37,6 @@ export const Default: StoryComponentType = {
},
};

Default.parameters = {
chromatic: {
// We already have screenshots of another story that covers
// this and more cases.
disableSnapshot: true,
},
};

export const Controlled: StoryComponentType = () => {
const [checked, setChecked] = React.useState<boolean | null>(null);

Expand All @@ -55,11 +51,6 @@ export const Controlled: StoryComponentType = () => {
};

Controlled.parameters = {
chromatic: {
// Disabling because this doesn't test visuals, its for testing
// that `state` works as expected.
disableSnapshot: true,
},
docs: {
description: {
story: `Use state to keep track of whether the checkbox
Expand Down Expand Up @@ -258,11 +249,6 @@ export const VariantsControlled: StoryComponentType = () => {
};

VariantsControlled.parameters = {
chromatic: {
// Disabling because this doesn't test visuals, its for testing
// that `state` works as expected.
disableSnapshot: true,
},
docs: {
description: {
story: `A demo of the different kinds of checkboxes
Expand Down
70 changes: 70 additions & 0 deletions __docs__/wonder-blocks-form/radio-variants.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import * as React from "react";
import type {Meta, StoryObj} from "@storybook/react";

// NOTE: Radio is an internal component and should not be used directly. Use
// RadioGroup instead. This import is only used for visual testing in Chromatic.
import Radio from "../../packages/wonder-blocks-form/src/components/radio";

import {AllVariants} from "../components/all-variants";

const rows = [
{name: "Unchecked", props: {checked: false}},
{name: "Checked", props: {checked: true}},
];

const columns = [
{
name: "Default",
props: {},
},
{
name: "Disabled",
props: {disabled: true},
},
{
name: "Error",
props: {error: true},
},
];

type Story = StoryObj<typeof Radio>;

/**
* The following stories are used to generate the pseudo states for the Radio
* component. This is only used for visual testing in Chromatic.
*/
const meta = {
title: "Packages / Form / Radio (internal) / Radio - All Variants",
component: Radio,
render: (args) => (
<AllVariants rows={rows} columns={columns}>
{(props) => <Radio {...args} {...props} />}
</AllVariants>
),
args: {
label: "Label",
description: "Description",
},
tags: ["!autodocs"],
} satisfies Meta<typeof Radio>;

export default meta;

export const Default: Story = {};

export const Hover: Story = {
parameters: {pseudo: {hover: true}},
};

export const Focus: Story = {
parameters: {pseudo: {focusVisible: true}},
};

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

export const Active: Story = {
parameters: {pseudo: {active: true}},
};
17 changes: 4 additions & 13 deletions __docs__/wonder-blocks-form/radio.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ export default {
version={packageConfig.version}
/>
),
chromatic: {
// These stories are being tested in radio-variants.stories.tsx
disableSnapshot: true,
},
},
} as Meta<typeof Radio>;

Expand All @@ -32,25 +36,12 @@ export const Default: StoryComponentType = {
},
};

Default.parameters = {
chromatic: {
// We already have screenshots of another story that covers
// this and more cases.
disableSnapshot: true,
},
};

export const Controlled: StoryComponentType = () => {
const [checked, setChecked] = React.useState(false);
return <Radio checked={checked} onChange={setChecked} />;
};

Controlled.parameters = {
chromatic: {
// Disabling because this doesn't test visuals, it tests
// that the `checked` state works as expected.
disableSnapshot: true,
},
docs: {
description: {
story: `Use state to keep track of whether
Expand Down
Loading

0 comments on commit 8cfaeab

Please sign in to comment.