Skip to content

Commit c774f7d

Browse files
authored
[WB-1847] Dropdown: Update SelectOpener to match Design specs. (#2438)
## Summary: - Updated `SelectOpener` to match design specs. - Also converted `color` tokens to use `semanticColor` tokens. Figma: https://www.figma.com/design/VbVu3h2BpBhH80niq101MHHE/%F0%9F%92%A0-Main-Components?node-id=13693-11133&t=W16iu9a1X5vqz5ez-4 NOTE: The `light` version was removed on a separate PR, so those design specs are not included here anymore. Issue: https://khanacademy.atlassian.net/browse/WB-1847 ## Test plan: Verify that the `SelectOpener` component looks as expected in the All Variants stories. Author: jandrade Reviewers: jandrade, marcysutton, beaesguerra 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: #2438
1 parent a00747f commit c774f7d

File tree

2 files changed

+87
-42
lines changed

2 files changed

+87
-42
lines changed

.changeset/six-jobs-promise.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@khanacademy/wonder-blocks-dropdown": patch
3+
---
4+
5+
Updates `SelectOpener` (internal component) from `Dropdown` to match Design specs. Also converts `color` tokens to use `semanticColor` tokens.

packages/wonder-blocks-dropdown/src/components/select-opener.tsx

+82-42
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,11 @@ import type {AriaProps} from "@khanacademy/wonder-blocks-core";
66
import {addStyle} from "@khanacademy/wonder-blocks-core";
77
import {PhosphorIcon} from "@khanacademy/wonder-blocks-icon";
88
import {LabelMedium} from "@khanacademy/wonder-blocks-typography";
9-
import * as tokens from "@khanacademy/wonder-blocks-tokens";
9+
import {
10+
border,
11+
semanticColor,
12+
spacing,
13+
} from "@khanacademy/wonder-blocks-tokens";
1014
import caretDownIcon from "@phosphor-icons/core/bold/caret-down-bold.svg";
1115
import {DROPDOWN_ITEM_HEIGHT} from "../util/constants";
1216
import {OptionLabel} from "../util/types";
@@ -138,8 +142,8 @@ export default class SelectOpener extends React.Component<
138142
const stateStyles = _generateStyles(isPlaceholder, error);
139143

140144
const iconColor = disabled
141-
? tokens.color.offBlack32
142-
: tokens.color.offBlack64;
145+
? semanticColor.action.disabled.default
146+
: semanticColor.icon.primary;
143147

144148
const style = [
145149
styles.shared,
@@ -189,15 +193,15 @@ const styles = StyleSheet.create({
189193
display: "inline-flex",
190194
alignItems: "center",
191195
justifyContent: "space-between",
192-
color: tokens.color.offBlack,
196+
color: semanticColor.text.primary,
193197
height: DROPDOWN_ITEM_HEIGHT,
194198
// This asymmetry arises from the Icon on the right side, which has
195199
// extra padding built in. To have the component look more balanced,
196200
// we need to take off some paddingRight here.
197-
paddingLeft: tokens.spacing.medium_16,
198-
paddingRight: tokens.spacing.small_12,
201+
paddingLeft: spacing.medium_16,
202+
paddingRight: spacing.small_12,
199203
borderWidth: 0,
200-
borderRadius: tokens.border.radius.medium_4,
204+
borderRadius: border.radius.medium_4,
201205
borderStyle: "solid",
202206
outline: "none",
203207
textDecoration: "none",
@@ -209,7 +213,7 @@ const styles = StyleSheet.create({
209213
},
210214

211215
text: {
212-
marginRight: tokens.spacing.xSmall_8,
216+
marginRight: spacing.xSmall_8,
213217
whiteSpace: "nowrap",
214218
userSelect: "none",
215219
overflow: "hidden",
@@ -221,12 +225,6 @@ const styles = StyleSheet.create({
221225
},
222226
});
223227

224-
// These values are default padding (16 and 12) minus 1, because
225-
// changing the borderWidth to 2 messes up the button width
226-
// and causes it to move a couple pixels. This fixes that.
227-
const adjustedPaddingLeft = tokens.spacing.medium_16 - 1;
228-
const adjustedPaddingRight = tokens.spacing.small_12 - 1;
229-
230228
const stateStyles: Record<string, any> = {};
231229

232230
const _generateStyles = (placeholder: boolean, error: boolean) => {
@@ -236,54 +234,96 @@ const _generateStyles = (placeholder: boolean, error: boolean) => {
236234
return stateStyles[styleKey];
237235
}
238236

237+
// The different states that the component can be in.
238+
const states = {
239+
// Resting state
240+
default: {
241+
border: semanticColor.border.strong,
242+
background: semanticColor.surface.primary,
243+
foreground: semanticColor.text.primary,
244+
},
245+
disabled: {
246+
border: semanticColor.border.primary,
247+
background: semanticColor.action.disabled.secondary,
248+
foreground: semanticColor.text.secondary,
249+
},
250+
// Form validation error state
251+
error: {
252+
border: semanticColor.status.critical.foreground,
253+
background: semanticColor.status.critical.background,
254+
foreground: semanticColor.text.primary,
255+
},
256+
};
257+
258+
// The color is based on the action color.
259+
const actionType = error ? "destructive" : "progressive";
260+
// NOTE: We are using the outlined action type for all the non-resting
261+
// states as the opener is a bit different from a regular button in its
262+
// resting/default state.
263+
const action = semanticColor.action.outlined[actionType];
264+
265+
// TODO(WB-1856): Define global semantic outline tokens.
266+
const sharedOutlineStyling = {
267+
// Outline sits inside the border (inset)
268+
outlineOffset: -border.width.thin,
269+
outlineStyle: "solid",
270+
outlineWidth: border.width.thin,
271+
};
272+
239273
const focusHoverStyling = {
240-
borderColor: error ? tokens.color.red : tokens.color.blue,
241-
borderWidth: tokens.border.width.thin,
242-
paddingLeft: adjustedPaddingLeft,
243-
paddingRight: adjustedPaddingRight,
274+
// TODO(WB-1856): Use `border.focus` when we define a global pattern for
275+
// focus indicators.
276+
outlineColor: action.hover.border,
277+
...sharedOutlineStyling,
244278
};
245-
const activePressedStyling = {
246-
background: error ? tokens.color.fadedRed : tokens.color.fadedBlue,
247-
borderColor: error ? tokens.color.red : tokens.color.activeBlue,
248-
borderWidth: tokens.border.width.thin,
249-
paddingLeft: adjustedPaddingLeft,
250-
paddingRight: adjustedPaddingRight,
279+
const pressStyling = {
280+
background: action.press.background,
281+
color: placeholder
282+
? error
283+
? semanticColor.text.secondary
284+
: semanticColor.action.outlined.progressive.press.foreground
285+
: semanticColor.text.primary,
286+
outlineColor: action.press.border,
287+
...sharedOutlineStyling,
251288
};
252289

253-
const newStyles: Record<string, any> = {
290+
const currentState = error ? states.error : states.default;
291+
292+
const newStyles = {
254293
default: {
255-
background: error ? tokens.color.fadedRed8 : tokens.color.white,
256-
borderColor: error ? tokens.color.red : tokens.color.offBlack50,
257-
borderWidth: tokens.border.width.hairline,
294+
background: currentState.background,
295+
borderColor: currentState.border,
296+
borderWidth: border.width.hairline,
258297
color: placeholder
259-
? tokens.color.offBlack64
260-
: tokens.color.offBlack,
298+
? semanticColor.text.secondary
299+
: currentState.foreground,
261300
":hover:not([aria-disabled=true])": focusHoverStyling,
262301
// Allow hover styles on non-touch devices only. This prevents an
263302
// issue with hover being sticky on touch devices (e.g. mobile).
264303
["@media not (hover: hover)"]: {
265304
":hover:not([aria-disabled=true])": {
266-
borderColor: error
267-
? tokens.color.red
268-
: tokens.color.offBlack50,
269-
borderWidth: tokens.border.width.hairline,
270-
paddingLeft: tokens.spacing.medium_16,
271-
paddingRight: tokens.spacing.small_12,
305+
borderColor: currentState.border,
306+
borderWidth: border.width.hairline,
307+
paddingLeft: spacing.medium_16,
308+
paddingRight: spacing.small_12,
272309
},
273310
},
274311
":focus-visible:not([aria-disabled=true])": focusHoverStyling,
275-
":active:not([aria-disabled=true])": activePressedStyling,
312+
":active:not([aria-disabled=true])": pressStyling,
276313
},
277314
disabled: {
278-
background: tokens.color.offWhite,
279-
borderColor: tokens.color.offBlack16,
280-
color: tokens.color.offBlack64,
315+
background: states.disabled.background,
316+
borderColor: states.disabled.border,
317+
color: states.disabled.foreground,
281318
cursor: "not-allowed",
282319
":focus-visible": {
283-
boxShadow: `0 0 0 1px ${tokens.color.white}, 0 0 0 3px ${tokens.color.offBlack32}`,
320+
// TODO(WB-1856): Use `border.focus` when we define a global
321+
// pattern for focus indicators.
322+
outlineColor: semanticColor.action.disabled.default,
323+
...sharedOutlineStyling,
284324
},
285325
},
286-
press: activePressedStyling,
326+
press: pressStyling,
287327
};
288328

289329
stateStyles[styleKey] = StyleSheet.create(newStyles);

0 commit comments

Comments
 (0)