@@ -6,7 +6,11 @@ import type {AriaProps} from "@khanacademy/wonder-blocks-core";
6
6
import { addStyle } from "@khanacademy/wonder-blocks-core" ;
7
7
import { PhosphorIcon } from "@khanacademy/wonder-blocks-icon" ;
8
8
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" ;
10
14
import caretDownIcon from "@phosphor-icons/core/bold/caret-down-bold.svg" ;
11
15
import { DROPDOWN_ITEM_HEIGHT } from "../util/constants" ;
12
16
import { OptionLabel } from "../util/types" ;
@@ -138,8 +142,8 @@ export default class SelectOpener extends React.Component<
138
142
const stateStyles = _generateStyles ( isPlaceholder , error ) ;
139
143
140
144
const iconColor = disabled
141
- ? tokens . color . offBlack32
142
- : tokens . color . offBlack64 ;
145
+ ? semanticColor . action . disabled . default
146
+ : semanticColor . icon . primary ;
143
147
144
148
const style = [
145
149
styles . shared ,
@@ -189,15 +193,15 @@ const styles = StyleSheet.create({
189
193
display : "inline-flex" ,
190
194
alignItems : "center" ,
191
195
justifyContent : "space-between" ,
192
- color : tokens . color . offBlack ,
196
+ color : semanticColor . text . primary ,
193
197
height : DROPDOWN_ITEM_HEIGHT ,
194
198
// This asymmetry arises from the Icon on the right side, which has
195
199
// extra padding built in. To have the component look more balanced,
196
200
// 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 ,
199
203
borderWidth : 0 ,
200
- borderRadius : tokens . border . radius . medium_4 ,
204
+ borderRadius : border . radius . medium_4 ,
201
205
borderStyle : "solid" ,
202
206
outline : "none" ,
203
207
textDecoration : "none" ,
@@ -209,7 +213,7 @@ const styles = StyleSheet.create({
209
213
} ,
210
214
211
215
text : {
212
- marginRight : tokens . spacing . xSmall_8 ,
216
+ marginRight : spacing . xSmall_8 ,
213
217
whiteSpace : "nowrap" ,
214
218
userSelect : "none" ,
215
219
overflow : "hidden" ,
@@ -221,12 +225,6 @@ const styles = StyleSheet.create({
221
225
} ,
222
226
} ) ;
223
227
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
-
230
228
const stateStyles : Record < string , any > = { } ;
231
229
232
230
const _generateStyles = ( placeholder : boolean , error : boolean ) => {
@@ -236,54 +234,96 @@ const _generateStyles = (placeholder: boolean, error: boolean) => {
236
234
return stateStyles [ styleKey ] ;
237
235
}
238
236
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
+
239
273
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 ,
244
278
} ;
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 ,
251
288
} ;
252
289
253
- const newStyles : Record < string , any > = {
290
+ const currentState = error ? states . error : states . default ;
291
+
292
+ const newStyles = {
254
293
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 ,
258
297
color : placeholder
259
- ? tokens . color . offBlack64
260
- : tokens . color . offBlack ,
298
+ ? semanticColor . text . secondary
299
+ : currentState . foreground ,
261
300
":hover:not([aria-disabled=true])" : focusHoverStyling ,
262
301
// Allow hover styles on non-touch devices only. This prevents an
263
302
// issue with hover being sticky on touch devices (e.g. mobile).
264
303
[ "@media not (hover: hover)" ] : {
265
304
":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 ,
272
309
} ,
273
310
} ,
274
311
":focus-visible:not([aria-disabled=true])" : focusHoverStyling ,
275
- ":active:not([aria-disabled=true])" : activePressedStyling ,
312
+ ":active:not([aria-disabled=true])" : pressStyling ,
276
313
} ,
277
314
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 ,
281
318
cursor : "not-allowed" ,
282
319
":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 ,
284
324
} ,
285
325
} ,
286
- press : activePressedStyling ,
326
+ press : pressStyling ,
287
327
} ;
288
328
289
329
stateStyles [ styleKey ] = StyleSheet . create ( newStyles ) ;
0 commit comments