Skip to content
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

[wb-1797] Address cell color contrast a11y issues #2435

Open
wants to merge 28 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
1538fcf
[wb-1797] Add CompactCell variant stories
beaesguerra Jan 20, 2025
f2e611a
[wb-1797] use semantic icon token for right accessory
beaesguerra Jan 20, 2025
20fc419
[wb-1797] use new gray value for subtitle
beaesguerra Jan 20, 2025
5149ed0
[wb-1797] Add :before pseudo selector to aphrodite type
beaesguerra Jan 20, 2025
7b6efb0
[wb-1797] set up active state
beaesguerra Jan 20, 2025
142f546
[wb-1797] update terminology in variants story
beaesguerra Jan 20, 2025
20d256d
[wb-1797] update text color when it is active
beaesguerra Jan 20, 2025
0d21574
[wb-1797] use light blue for hover and press states
beaesguerra Jan 20, 2025
a66356b
[wb-1797] pressed indicator
beaesguerra Jan 20, 2025
e9b73bc
[wb-1797] use disabled border color
beaesguerra Jan 20, 2025
ce980f3
[wb-1797] Add CompactCell variant stories
beaesguerra Jan 20, 2025
bebbdf4
[wb-1797-cell-color-contrast] Merge branch 'wb-1797' into wb-1797-cel…
beaesguerra Jan 20, 2025
27c2941
[wb-1797-cell-color-contrast] linting
beaesguerra Jan 20, 2025
962fe64
[wb-1797-cell-color-contrast] docs(changeset): DetailCell and Compact…
beaesguerra Jan 20, 2025
6d0b1d5
[wb-1797-cell-color-contrast] exploring other potential fadedOffBlack…
beaesguerra Jan 21, 2025
dc57eec
[wb-1797-cell-color-contrast] Address overflow of left bar indicator
beaesguerra Jan 21, 2025
8cd75b8
Revert "[wb-1797-cell-color-contrast] exploring other potential faded…
beaesguerra Jan 22, 2025
072e006
[wb-1797-cell-color-contrast] Add fadedOffBlack72 color token. Update…
beaesguerra Jan 22, 2025
c3e0408
[wb-1797-cell-color-contrast] use semantic text secondary token for d…
beaesguerra Jan 22, 2025
e3bc9b8
[wb-1797-cell-color-contrast] docs(changeset): Adds `fadedOffBlack72`…
beaesguerra Jan 22, 2025
f5291ee
[wb-1797-cell-color-contrast] Merge branch 'main' into wb-1797-cell-c…
beaesguerra Jan 22, 2025
01a6add
[wb-1797-cell-color-contrast] update token usage for active color wit…
beaesguerra Jan 22, 2025
31a2073
[wb-1797-cell-color-contrast] update changeset
beaesguerra Jan 22, 2025
018a3b9
[wb-1797-cell-color-contrast] rework styling for the left bar indicat…
beaesguerra Jan 22, 2025
862e3c3
[wb-1797-cell-color-contrast] Set inner wrapper styles using a class …
beaesguerra Jan 23, 2025
44f5ae0
[wb-1797-cell-color-contrast] Make sure inner wrapper has full height
beaesguerra Jan 29, 2025
2d6d5ef
[wb-1797-cell-color-contrast] Merge branch 'main' into wb-1797-cell-c…
beaesguerra Jan 29, 2025
da9b159
[wb-1797-cell-color-contrast] put back decorator styles
beaesguerra Jan 29, 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
5 changes: 5 additions & 0 deletions .changeset/shiny-chicken-speak.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@khanacademy/wonder-blocks-tokens": minor
---

Adds `fadedOffBlack72` color primitive token and sets the `semanticColor.text.secondary` token to this primitive. The slightly darker gray has better color contrast on a variety of backgrounds, including the fadedBlue8 background
20 changes: 20 additions & 0 deletions .changeset/wise-actors-peel.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
---
"@khanacademy/wonder-blocks-cell": patch
---

DetailCell and CompactCell: update styling to address accessibility issues (color contrast and using color as the only visual indicator). Updated styles include:

- General:
- Changing the grey used for subtitles
- Using `icon.primary` for the right accessory
- Press state:
- Changing the background to `fadedBlue8`
- Adding a thin left border when clickable cells are pressed
- Hover state:
- Changing the background to `fadedBlue8`
- Disabled state:
- Changing the focus outline to `action.disabled.default`
- Selected state (cells with `active=true`):
- Adding a thick left border
- Changing the text color to `activeBlue`
- The styling no longer changes when a selected cell is hovered or pressed on
120 changes: 120 additions & 0 deletions __docs__/wonder-blocks-cell/compact-cell-variants.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import * as React from "react";
import {StyleSheet} from "aphrodite";
import type {Meta, StoryObj} from "@storybook/react";

import {PropsFor, View} from "@khanacademy/wonder-blocks-core";
import {spacing} from "@khanacademy/wonder-blocks-tokens";
import {CompactCell} from "@khanacademy/wonder-blocks-cell";
import {PhosphorIcon} from "@khanacademy/wonder-blocks-icon";
import {IconMappings} from "../wonder-blocks-icon/phosphor-icon.argtypes";
import {LabelSmall} from "@khanacademy/wonder-blocks-typography";

/**
* The following stories are used to generate the pseudo states for the
* CompactCell component. This is only used for visual testing in Chromatic.
*/
export default {
title: "Packages / Cell / CompactCell / All Variants",
parameters: {
docs: {
autodocs: false,
},
backgrounds: {
default: "offWhite",
},
},
} as Meta;

type StoryComponentType = StoryObj<typeof CompactCell>;

const states = [
{
label: "Default",
props: {},
},
{
label: "Disabled",
props: {disabled: true},
},
{
label: "Selected using active: true",
props: {active: true},
},
];

const defaultProps = {
title: "Title for article item",
leftAccessory: (
<PhosphorIcon icon={IconMappings.playCircle} size="medium" />
),
rightAccessory: <PhosphorIcon icon={IconMappings.caretRight} />,
};

const States = (props: {label: string} & PropsFor<typeof CompactCell>) => {
return (
<View>
<View style={[styles.scenarios]}>
{states.map((scenario) => {
return (
<View style={styles.scenario} key={scenario.label}>
<LabelSmall>
{props.label} ({scenario.label})
</LabelSmall>
<CompactCell {...props} {...scenario.props} />
</View>
);
})}
</View>
</View>
);
};

const AllVariants = () => (
<View style={{gap: spacing.large_24}}>
<States label="Default" {...defaultProps} />
<States label="Clickable" {...defaultProps} onClick={() => {}} />
<States label="Link" {...defaultProps} href="/" />
</View>
);

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

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}},
};

export const Active: StoryComponentType = {
render: AllVariants,
parameters: {pseudo: {active: true}},
};

const styles = StyleSheet.create({
statesContainer: {
padding: spacing.medium_16,
},
scenarios: {
display: "flex",
flexDirection: "row",
alignItems: "center",
gap: spacing.xxxLarge_64,
flexWrap: "wrap",
},
scenario: {
gap: spacing.small_12,
overflow: "hidden",
},
});
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ const states = [
props: {disabled: true},
},
{
label: "Active",
label: "Selected using active: true",
props: {active: true},
},
];
Expand Down
85 changes: 83 additions & 2 deletions __docs__/wonder-blocks-cell/detail-cell.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,13 @@ import {StyleSheet} from "aphrodite";
import {MemoryRouter, Route, Switch} from "react-router-dom";
import type {Meta, StoryObj} from "@storybook/react";

import {View} from "@khanacademy/wonder-blocks-core";
import {color, spacing} from "@khanacademy/wonder-blocks-tokens";
import {PropsFor, View} from "@khanacademy/wonder-blocks-core";
import {
border,
color,
semanticColor,
spacing,
} from "@khanacademy/wonder-blocks-tokens";
import {PhosphorIcon} from "@khanacademy/wonder-blocks-icon";

import {DetailCell} from "@khanacademy/wonder-blocks-cell";
Expand Down Expand Up @@ -469,6 +474,82 @@ export const Scenarios = () => {
);
};

/**
* Custom styling can be applied to the component using the `rootStyle` or
* `style` props.
*/
export const CustomRootStyle = {
args: {
title: "Title for article item",
subtitle1: "Subtitle for article item",
subtitle2: "Subtitle for article item",
leftAccessory: (
<PhosphorIcon icon={IconMappings.playCircle} size="medium" />
),
},
render(args: PropsFor<typeof DetailCell>) {
return (
<View style={{gap: spacing.large_24}}>
Active (with rootStyle prop):
<DetailCell
{...args}
rootStyle={{borderRadius: border.radius.xLarge_12}}
active={true}
/>
Pressed (with rootStyle prop):
<DetailCell
{...args}
rootStyle={{borderRadius: border.radius.xLarge_12}}
/>
Different content heights (with style prop)
<View
style={{
display: "grid",
gridTemplateColumns: "1fr 1fr 1fr",
gap: "16px",
}}
>
<DetailCell
title="Title"
subtitle1="Subtitle 1"
subtitle2="Subtitle2"
onClick={() => {}}
style={[
{
border: `1px solid ${semanticColor.border.primary}`,
},
]}
horizontalRule={"none"}
/>
<DetailCell
title="Title"
onClick={() => {}}
style={[
args.rootStyle,
{
border: `1px solid ${semanticColor.border.primary}`,
},
]}
horizontalRule={"none"}
/>
<DetailCell
title="Title"
onClick={() => {}}
style={[
args.rootStyle,
{
border: `1px solid ${semanticColor.border.primary}`,
},
]}
horizontalRule={"none"}
/>
</View>
</View>
);
},
parameters: {pseudo: {active: true}},
};

const styles = StyleSheet.create({
example: {
backgroundColor: color.offWhite,
Expand Down
4 changes: 2 additions & 2 deletions packages/wonder-blocks-cell/src/components/detail-cell.tsx
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One thing I noticed about DetailCell is that it is used by OptionItem. The updated styles aren't propagated to the OptionItems in the dropdown component because OptionItem overrides a lot of the styles. Is it expected that DetailCell and OptionItem have different styling?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm good question... I would say that it's better to have different styling as OptionItems tend to be closer to "action" styles (like the button ones). My recommendation for now would be to keep it as it is for now. We could ask Design in case they want to revisit the styling for dropdowns if we want to be more consistent of course :).

Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import * as React from "react";
import {StyleSheet} from "aphrodite";

import {Strut} from "@khanacademy/wonder-blocks-layout";
import {color, spacing} from "@khanacademy/wonder-blocks-tokens";
import {semanticColor, spacing} from "@khanacademy/wonder-blocks-tokens";
import {LabelSmall, LabelMedium} from "@khanacademy/wonder-blocks-typography";

import CellCore from "./internal/cell-core";
Expand Down Expand Up @@ -94,7 +94,7 @@ const DetailCell = function (props: DetailCellProps): React.ReactElement {

const styles = StyleSheet.create({
subtitle: {
color: color.offBlack64,
color: semanticColor.text.secondary,
},

// This is to override the default padding of the CellCore innerWrapper.
Expand Down
65 changes: 58 additions & 7 deletions packages/wonder-blocks-cell/src/components/internal/cell-core.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,12 @@ import type {StyleType} from "@khanacademy/wonder-blocks-core";
import Clickable from "@khanacademy/wonder-blocks-clickable";
import {View} from "@khanacademy/wonder-blocks-core";
import {Strut} from "@khanacademy/wonder-blocks-layout";
import {color, spacing} from "@khanacademy/wonder-blocks-tokens";
import {
border,
color,
semanticColor,
spacing,
} from "@khanacademy/wonder-blocks-tokens";

import {CellMeasurements, getHorizontalRuleStyles} from "./common";

Expand Down Expand Up @@ -118,7 +123,11 @@ function CellInner(props: CellCoreProps): React.ReactElement {
// custom styles
style,
horizontalRuleStyles,
active && styles.activeInnerWrapper,
]}
// Set className so we can set styles on the inner wrapper directly
// when the clickable element is pressed
className="inner-wrapper"
Comment on lines +128 to +130
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

praise: Thanks for adding this comment explaining the why we do this for this very specific case.

>
{/* Left accessory */}
<LeftAccessory
Expand Down Expand Up @@ -235,6 +244,12 @@ const styles = StyleSheet.create({
padding: `${CellMeasurements.cellPadding.paddingVertical}px ${CellMeasurements.cellPadding.paddingHorizontal}px`,
flexDirection: "row",
flex: 1,
borderRadius: "inherit",
// Hide overflow so that if custom styling applies a border radius, the
// left visual indicator for press/active states does not overflow
overflow: "hidden",
// Make sure inner wrapper is always the same height as parent
height: "100%",

// Reduce the padding of the innerWrapper when the focus ring is
// visible.
Expand All @@ -244,6 +259,18 @@ const styles = StyleSheet.create({
}px`,
},
},
activeInnerWrapper: {
position: "relative",
":before": {
content: "''",
position: "absolute",
top: 0,
left: 0,
bottom: 0,
width: border.width.thick,
backgroundColor: semanticColor.surface.emphasis,
},
},

content: {
alignSelf: "center",
Expand All @@ -264,7 +291,7 @@ const styles = StyleSheet.create({
accessoryRight: {
// The right accessory will have this color by default. Unless the
// accessory element overrides that color internally.
color: color.offBlack64,
color: semanticColor.icon.primary,
},

/**
Expand Down Expand Up @@ -309,28 +336,52 @@ const styles = StyleSheet.create({
border: `${spacing.xxxxSmall_2}px solid ${color.blue}`,
borderRadius: spacing.xxxSmall_4,
},
[":focus-visible[aria-disabled=true]:after" as any]: {
borderColor: semanticColor.action.disabled.default,
},

// hover + enabled
[":hover[aria-disabled=false]" as any]: {
background: color.offBlack8,
background: color.fadedBlue8,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: You could probably use semanticColor.status.notice.background here (and in all the similar cases in this file).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll switch to using semantic tokens in a follow up PR and include this! Thanks for the suggestion!

},

// pressed + enabled
[":active[aria-disabled=false]" as any]: {
background: color.offBlack16,
background: color.fadedBlue8,
},
// press + enabled + not currently selected (active prop: false)
// We apply the left bar indicator styles on the inner-wrapper element
// instead of the clickable element directly because we need to hide the
// left bar overflow when custom cell styles apply a border-radius. We
// have overflow: hidden on the inner wrapper instead of the clickable element
// because setting it on the clickable element causes issues with existing
// cases.
[":active[aria-disabled=false]:not([aria-current=true]) .inner-wrapper" as any]:
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Continuing #2435 (comment) : Discussed with @jandrade , we can use a class selector on the inner wrapper element directly (instead of using :first-child) and remove the > to reduce specificity so it is less brittle if the structure changes!

{
position: "relative",
":before": {
content: "''",
position: "absolute",
top: 0,
left: 0,
bottom: 0,
width: border.width.thin,
backgroundColor: semanticColor.surface.emphasis,
},
},
},

active: {
background: color.fadedBlue8,
color: color.blue,
color: color.activeBlue,
cursor: "default",

[":hover[aria-disabled=false]" as any]: {
background: color.fadedBlue16,
background: color.fadedBlue8,
},

[":active[aria-disabled=false]" as any]: {
background: color.fadedBlue24,
background: color.fadedBlue8,
},
},

Expand Down
Loading