Skip to content

Commit 6d37702

Browse files
authored
[WB-1814.7] Refactor Modal to use semantic colors (#2468)
## Summary: Next step is to refactor the `Modal` package to use semantic colors. Besides the migration, this PR also includes the following changes: - Reworked the theme structure to split tokens by sub-component. - Updated stories to use the new semantic colors. - Adapted some components to use theming instead of hardcoded colors. ### Implementation plan: 1. #2439 2. #2440 3. #2441 4. #2446 5. #2449 6. #2464 7. Modal (current PR) 8. Popover, Tooltip 9. Dropdown 10. Clickable, Pill, Toolbar Issue: WB-1814 ## Test plan: Verify that the Modal Chromatic snapshots are mostly unchanged. URL: `/?path=/docs/packages-modal-onepanedialog--docs&globals=viewport:desktop` Author: jandrade Reviewers: jandrade, beaesguerra, marcysutton Required Reviewers: Approved By: beaesguerra Checks: ✅ Chromatic - Get results on regular PRs (ubuntu-latest, 20.x), ✅ Test / Test (ubuntu-latest, 20.x, 2/2), ✅ Lint / Lint (ubuntu-latest, 20.x), ✅ Test / Test (ubuntu-latest, 20.x, 1/2), ✅ Check build sizes (ubuntu-latest, 20.x), ✅ Publish npm snapshot (ubuntu-latest, 20.x), ✅ Check for .changeset entries for all changed files (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), ✅ gerald, ⏭️ dependabot Pull Request URL: #2468
1 parent 5bd2a95 commit 6d37702

File tree

13 files changed

+148
-72
lines changed

13 files changed

+148
-72
lines changed

.changeset/nice-maps-tap.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@khanacademy/wonder-blocks-theming": minor
3+
---
4+
5+
Expose WithoutTheme type for use in withScopedTheme HOC.

.changeset/odd-fans-provide.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@khanacademy/wonder-blocks-modal": patch
3+
---
4+
5+
Refactor Modal package to use semanticColors. Restructure theme contract.

__docs__/wonder-blocks-modal/modal-dialog.stories.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {View} from "@khanacademy/wonder-blocks-core";
77
import {Strut} from "@khanacademy/wonder-blocks-layout";
88
import {Body, Title} from "@khanacademy/wonder-blocks-typography";
99
import {ThemeSwitcherContext} from "@khanacademy/wonder-blocks-theming";
10-
import {color, spacing} from "@khanacademy/wonder-blocks-tokens";
10+
import {semanticColor, spacing} from "@khanacademy/wonder-blocks-tokens";
1111

1212
import {
1313
ModalLauncher,
@@ -366,7 +366,7 @@ const styles = StyleSheet.create({
366366
squareDialog: {
367367
maxHeight: 500,
368368
maxWidth: 500,
369-
backgroundColor: color.darkBlue,
369+
backgroundColor: semanticColor.surface.inverse,
370370
},
371371
smallSquarePanel: {
372372
maxHeight: 400,

__docs__/wonder-blocks-modal/modal-panel.stories.tsx

+8-3
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,11 @@ import type {Meta, StoryObj} from "@storybook/react";
55
import Button from "@khanacademy/wonder-blocks-button";
66
import {View} from "@khanacademy/wonder-blocks-core";
77
import {Strut} from "@khanacademy/wonder-blocks-layout";
8-
import {color, spacing} from "@khanacademy/wonder-blocks-tokens";
8+
import {
9+
border,
10+
semanticColor,
11+
spacing,
12+
} from "@khanacademy/wonder-blocks-tokens";
913
import {Body, Title} from "@khanacademy/wonder-blocks-typography";
1014

1115
import {
@@ -352,8 +356,9 @@ export const TwoPanels: StoryComponentType = {
352356
export const WithStyle: StoryComponentType = {
353357
render: () => {
354358
const modalStyles = {
355-
color: color.blue,
356-
border: `2px solid ${color.darkBlue}`,
359+
color: semanticColor.status.notice.foreground,
360+
background: semanticColor.status.notice.background,
361+
border: `${border.width.thin}px solid ${semanticColor.status.notice.foreground}`,
357362
borderRadius: 20,
358363
} as const;
359364

__docs__/wonder-blocks-modal/one-pane-dialog.stories.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import Button from "@khanacademy/wonder-blocks-button";
1111
import {View} from "@khanacademy/wonder-blocks-core";
1212
import {Strut} from "@khanacademy/wonder-blocks-layout";
1313
import Link from "@khanacademy/wonder-blocks-link";
14-
import {color, spacing} from "@khanacademy/wonder-blocks-tokens";
14+
import {semanticColor, spacing} from "@khanacademy/wonder-blocks-tokens";
1515
import {Body, LabelLarge} from "@khanacademy/wonder-blocks-typography";
1616

1717
import {ModalLauncher, OnePaneDialog} from "@khanacademy/wonder-blocks-modal";
@@ -363,7 +363,7 @@ export const WithStyle: StoryComponentType = () => (
363363
</Body>
364364
}
365365
style={{
366-
color: color.blue,
366+
color: semanticColor.status.notice.foreground,
367367
maxWidth: 1000,
368368
}}
369369
/>

packages/wonder-blocks-modal/src/components/modal-backdrop.tsx

+20-9
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,19 @@
11
import * as React from "react";
22
import * as ReactDOM from "react-dom";
3-
import {StyleSheet} from "aphrodite";
43

5-
import {color} from "@khanacademy/wonder-blocks-tokens";
64
import {View} from "@khanacademy/wonder-blocks-core";
5+
import {
6+
ThemedStylesFn,
7+
withScopedTheme,
8+
WithThemeProps,
9+
} from "@khanacademy/wonder-blocks-theming";
10+
11+
import {
12+
ModalDialogThemeContext,
13+
ModalDialogThemeContract,
14+
} from "../themes/themed-modal-dialog";
715
import {ModalLauncherPortalAttributeName} from "../util/constants";
8-
916
import {findFocusableNodes} from "../util/find-focusable-nodes";
10-
1117
import type {ModalElement} from "../util/types";
1218

1319
type Props = {
@@ -23,7 +29,7 @@ type Props = {
2329
* Test ID used for e2e testing.
2430
*/
2531
testId?: string;
26-
};
32+
} & WithThemeProps;
2733

2834
/**
2935
* A private component used by ModalLauncher. This is the fixed-position
@@ -35,7 +41,7 @@ type Props = {
3541
* and adding an `onClose` prop that will call `onCloseModal`. If an
3642
* `onClose` prop is already provided, the two are merged.
3743
*/
38-
export default class ModalBackdrop extends React.Component<Props> {
44+
class ModalBackdrop extends React.Component<Props> {
3945
componentDidMount() {
4046
// eslint-disable-next-line import/no-deprecated
4147
const node: HTMLElement = ReactDOM.findDOMNode(this) as any;
@@ -137,7 +143,7 @@ export default class ModalBackdrop extends React.Component<Props> {
137143

138144
return (
139145
<View
140-
style={styles.modalPositioner}
146+
style={this.props.wbThemeStyles.modalPositioner}
141147
onMouseDown={this.handleMouseDown}
142148
onMouseUp={this.handleMouseUp}
143149
testId={testId}
@@ -149,7 +155,7 @@ export default class ModalBackdrop extends React.Component<Props> {
149155
}
150156
}
151157

152-
const styles = StyleSheet.create({
158+
const themedStylesFn: ThemedStylesFn<ModalDialogThemeContract> = (theme) => ({
153159
modalPositioner: {
154160
position: "fixed",
155161
left: 0,
@@ -171,6 +177,11 @@ const styles = StyleSheet.create({
171177
// now!
172178
overflow: "auto",
173179

174-
background: color.offBlack64,
180+
background: theme.backdrop.color.background,
175181
},
176182
});
183+
184+
export default withScopedTheme(
185+
themedStylesFn,
186+
ModalDialogThemeContext,
187+
)(ModalBackdrop);

packages/wonder-blocks-modal/src/components/modal-dialog.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ const themedStylesFn: ThemedStylesFn<ModalDialogThemeContract> = (theme) => ({
123123
height: "100%",
124124
position: "relative",
125125
[small]: {
126-
padding: theme.spacing.dialog.small,
126+
padding: theme.dialog.spacing.padding,
127127
flexDirection: "column",
128128
},
129129
},
@@ -134,7 +134,7 @@ const themedStylesFn: ThemedStylesFn<ModalDialogThemeContract> = (theme) => ({
134134
dialog: {
135135
width: "100%",
136136
height: "100%",
137-
borderRadius: theme.border.radius,
137+
borderRadius: theme.root.border.radius,
138138
overflow: "hidden",
139139
},
140140

packages/wonder-blocks-modal/src/components/modal-footer.tsx

+15-4
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,15 @@
11
import * as React from "react";
2-
import {StyleSheet} from "aphrodite";
32
import {View} from "@khanacademy/wonder-blocks-core";
4-
import {color, spacing} from "@khanacademy/wonder-blocks-tokens";
3+
import {spacing} from "@khanacademy/wonder-blocks-tokens";
4+
import {
5+
ThemedStylesFn,
6+
useScopedTheme,
7+
useStyles,
8+
} from "@khanacademy/wonder-blocks-theming";
9+
import {
10+
ModalDialogThemeContext,
11+
ModalDialogThemeContract,
12+
} from "../themes/themed-modal-dialog";
513

614
type Props = {
715
children: React.ReactNode;
@@ -25,6 +33,9 @@ type Props = {
2533
* ```
2634
*/
2735
export default function ModalFooter({children}: Props) {
36+
const {theme} = useScopedTheme(ModalDialogThemeContext);
37+
const styles = useStyles(themedStylesFn, theme);
38+
2839
return <View style={styles.footer}>{children}</View>;
2940
}
3041

@@ -34,7 +45,7 @@ ModalFooter.isComponentOf = (instance: any): boolean => {
3445
return instance && instance.type && instance.type.__IS_MODAL_FOOTER__;
3546
};
3647

37-
const styles = StyleSheet.create({
48+
const themedStylesFn: ThemedStylesFn<ModalDialogThemeContract> = (theme) => ({
3849
footer: {
3950
flex: "0 0 auto",
4051
boxSizing: "border-box",
@@ -49,6 +60,6 @@ const styles = StyleSheet.create({
4960
alignItems: "center",
5061
justifyContent: "flex-end",
5162

52-
boxShadow: `0px -1px 0px ${color.offBlack16}`,
63+
boxShadow: `0px -1px 0px ${theme.footer.color.border}`,
5364
},
5465
});

packages/wonder-blocks-modal/src/components/modal-header.tsx

+13-12
Original file line numberDiff line numberDiff line change
@@ -153,41 +153,42 @@ const small = "@media (max-width: 767px)";
153153

154154
const themedStylesFn: ThemedStylesFn<ModalDialogThemeContract> = (theme) => ({
155155
header: {
156-
boxShadow: `0px 1px 0px ${theme.color.shadow.default}`,
156+
// TODO(WB-1878): Move this to an `elevation` theme token.
157+
boxShadow: `0px 1px 0px ${theme.header.color.border}`,
157158
display: "flex",
158159
flexDirection: "column",
159160
minHeight: 66,
160-
padding: `${theme.spacing.header.medium}px ${theme.spacing.header.large}px`,
161+
paddingBlock: theme.header.spacing.paddingBlockMd,
162+
paddingInline: theme.header.spacing.paddingInlineMd,
161163
position: "relative",
162164
width: "100%",
163165

164166
[small]: {
165-
paddingLeft: theme.spacing.header.small,
166-
paddingRight: theme.spacing.header.small,
167+
paddingInline: theme.header.spacing.paddingInlineSm,
167168
},
168169
},
169170

170171
dark: {
171-
background: theme.color.bg.inverse,
172-
color: theme.color.text.inverse,
172+
background: theme.root.color.inverse.background,
173+
color: theme.root.color.inverse.foreground,
173174
},
174175

175176
breadcrumbs: {
176-
color: theme.color.text.secondary,
177-
marginBottom: theme.spacing.header.xsmall,
177+
color: theme.header.color.secondary,
178+
marginBottom: theme.header.spacing.gap,
178179
},
179180

180181
title: {
181182
// Prevent title from overlapping the close button
182-
paddingRight: theme.spacing.header.small,
183+
paddingRight: theme.header.spacing.titleGapMd,
183184
[small]: {
184-
paddingRight: theme.spacing.header.large,
185+
paddingRight: theme.header.spacing.titleGapSm,
185186
},
186187
},
187188

188189
subtitle: {
189-
color: theme.color.text.secondary,
190-
marginTop: theme.spacing.header.xsmall,
190+
color: theme.header.color.secondary,
191+
marginTop: theme.header.spacing.gap,
191192
},
192193
});
193194

packages/wonder-blocks-modal/src/components/modal-panel.tsx

+9-8
Original file line numberDiff line numberDiff line change
@@ -170,8 +170,8 @@ const themedStylesFn: ThemedStylesFn<ModalDialogThemeContract> = (theme) => ({
170170

171171
closeButton: {
172172
position: "absolute",
173-
right: theme.spacing.panel.closeButton,
174-
top: theme.spacing.panel.closeButton,
173+
right: theme.closeButton.spacing.gap,
174+
top: theme.closeButton.spacing.gap,
175175
// This is to allow the button to be tab-ordered before the modal
176176
// content but still be above the header and content.
177177
zIndex: 1,
@@ -180,20 +180,21 @@ const themedStylesFn: ThemedStylesFn<ModalDialogThemeContract> = (theme) => ({
180180
// programmatic focus. This is a workaround to make sure the focus
181181
// outline is visible when this control is focused.
182182
":focus": {
183-
outlineWidth: theme.border.width,
184-
outlineColor: theme.border.color,
183+
outlineWidth: theme.root.border.width,
184+
outlineColor: theme.panel.color.border,
185185
outlineOffset: 1,
186186
outlineStyle: "solid",
187-
borderRadius: theme.border.radius,
187+
borderRadius: theme.root.border.radius,
188188
},
189189
},
190190

191191
dark: {
192-
background: theme.color.bg.inverse,
193-
color: theme.color.text.inverse,
192+
background: theme.root.color.inverse.background,
193+
color: theme.root.color.inverse.foreground,
194194
},
195195

196196
hasFooter: {
197-
paddingBottom: theme.spacing.panel.footer,
197+
// The space between the content and the footer
198+
paddingBlockEnd: theme.panel.spacing.gap,
198199
},
199200
});

packages/wonder-blocks-modal/src/themes/default.ts

+60-26
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,70 @@
1-
import * as tokens from "@khanacademy/wonder-blocks-tokens";
1+
import {
2+
border,
3+
semanticColor,
4+
spacing,
5+
} from "@khanacademy/wonder-blocks-tokens";
26

37
const theme = {
4-
color: {
5-
bg: {
6-
inverse: tokens.color.darkBlue,
8+
/**
9+
* Shared tokens
10+
*/
11+
root: {
12+
// TODO(WB-1852): Remove light variant.
13+
color: {
14+
inverse: {
15+
background: semanticColor.surface.inverse,
16+
foreground: semanticColor.text.inverse,
17+
},
718
},
8-
text: {
9-
inverse: tokens.color.white,
10-
secondary: tokens.color.offBlack64,
19+
border: {
20+
radius: border.radius.medium_4,
21+
width: border.width.thin,
1122
},
12-
shadow: {
13-
default: tokens.color.offBlack16,
23+
},
24+
/**
25+
* Building blocks
26+
*/
27+
backdrop: {
28+
color: {
29+
background: semanticColor.surface.overlay,
30+
},
31+
},
32+
dialog: {
33+
spacing: {
34+
padding: spacing.medium_16,
35+
},
36+
},
37+
footer: {
38+
color: {
39+
border: semanticColor.border.primary,
1440
},
1541
},
16-
border: {
17-
radius: tokens.border.radius.medium_4,
18-
width: tokens.border.width.thin,
19-
color: tokens.color.blue,
42+
header: {
43+
color: {
44+
border: semanticColor.border.primary,
45+
secondary: semanticColor.text.secondary,
46+
},
47+
spacing: {
48+
paddingBlockMd: spacing.large_24,
49+
paddingInlineMd: spacing.xLarge_32,
50+
paddingInlineSm: spacing.medium_16,
51+
gap: spacing.xSmall_8,
52+
// The space between the title and dismiss button.
53+
titleGapMd: spacing.medium_16,
54+
titleGapSm: spacing.xLarge_32,
55+
},
56+
},
57+
panel: {
58+
color: {
59+
border: semanticColor.border.focus,
60+
},
61+
spacing: {
62+
gap: spacing.xLarge_32,
63+
},
2064
},
21-
spacing: {
22-
dialog: {
23-
small: tokens.spacing.medium_16,
24-
},
25-
panel: {
26-
closeButton: tokens.spacing.medium_16,
27-
footer: tokens.spacing.xLarge_32,
28-
},
29-
header: {
30-
xsmall: tokens.spacing.xSmall_8,
31-
small: tokens.spacing.medium_16,
32-
medium: tokens.spacing.large_24,
33-
large: tokens.spacing.xLarge_32,
65+
closeButton: {
66+
spacing: {
67+
gap: spacing.medium_16,
3468
},
3569
},
3670
};

0 commit comments

Comments
 (0)