UNPKG

19.3 kBSCSSView Raw
1//
2// Copyright 2021 Google Inc.
3//
4// Permission is hereby granted, free of charge, to any person obtaining a copy
5// of this software and associated documentation files (the "Software"), to deal
6// in the Software without restriction, including without limitation the rights
7// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8// copies of the Software, and to permit persons to whom the Software is
9// furnished to do so, subject to the following conditions:
10//
11// The above copyright notice and this permission notice shall be included in
12// all copies or substantial portions of the Software.
13//
14// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20// THE SOFTWARE.
21//
22
23// stylelint-disable selector-class-pattern --
24// Selector '.mdc-*' should only be used in this project.
25
26@use 'sass:color';
27@use 'sass:map';
28@use 'sass:meta';
29@use '@material/density/density';
30@use '@material/dom/dom';
31@use '@material/elevation/elevation-theme';
32@use '@material/ripple/ripple-theme';
33@use '@material/theme/color-palette';
34@use '@material/theme/custom-properties';
35@use '@material/theme/keys';
36@use '@material/theme/shadow-dom';
37@use '@material/theme/state';
38@use '@material/theme/theme-color';
39@use '@material/theme/theme';
40@use '@material/tokens/resolvers';
41@use '@material/shape/shape';
42@use './switch';
43
44$_density-config: (
45 size: (
46 minimum: 28px,
47 default: 48px,
48 maximum: 48px,
49 ),
50);
51$_hairline: color-palette.$grey-300;
52$_inverse-primary: color.scale(
53 theme-color.prop-value(primary),
54 $lightness: 75%
55);
56
57$_on-surface: color-palette.$grey-800;
58$_on-surface-variant: color-palette.$grey-700;
59$_on-surface-state-content: color-palette.$grey-900;
60$_primary-state-content: color.scale(
61 theme-color.prop-value(primary),
62 $blackness: 50%
63);
64
65/// TODO: Change to private when MWC has better access
66/// @access private
67$selectors: (
68 disabled: ':disabled',
69 focus: ':focus',
70 hover: ':hover',
71 pressed: ':active',
72 selected: '.mdc-switch--selected',
73 unselected: '.mdc-switch--unselected',
74);
75
76$light-theme: (
77 disabled-handle-elevation: 0,
78 disabled-handle-opacity: 0.38,
79 disabled-selected-handle-color: $_on-surface,
80 disabled-selected-icon-color: on-primary,
81 disabled-selected-icon-opacity: 0.38,
82 disabled-selected-track-color: $_on-surface,
83 disabled-track-opacity: 0.12,
84 disabled-unselected-handle-color: $_on-surface,
85 disabled-unselected-icon-color: on-primary,
86 disabled-unselected-icon-opacity: 0.38,
87 disabled-unselected-track-color: $_on-surface,
88 handle-elevation: 1,
89 handle-height: 20px,
90 handle-shadow-color: elevation-theme.$baseline-color,
91 handle-shape: 10px,
92 handle-surface-color: surface,
93 handle-width: 20px,
94 selected-focus-handle-color: $_primary-state-content,
95 selected-focus-state-layer-color: primary,
96 selected-focus-state-layer-opacity: 0.12,
97 selected-focus-track-color: $_inverse-primary,
98 selected-handle-color: primary,
99 selected-hover-handle-color: $_primary-state-content,
100 selected-hover-state-layer-color: primary,
101 selected-hover-state-layer-opacity: 0.04,
102 selected-hover-track-color: $_inverse-primary,
103 selected-icon-color: on-primary,
104 selected-icon-size: 18px,
105 selected-pressed-handle-color: $_primary-state-content,
106 selected-pressed-state-layer-color: primary,
107 selected-pressed-state-layer-opacity: 0.1,
108 selected-pressed-track-color: $_inverse-primary,
109 selected-track-color: $_inverse-primary,
110 state-layer-size: 48px,
111 track-height: 14px,
112 track-shape: 7px,
113 track-width: 36px,
114 unselected-focus-handle-color: $_on-surface-state-content,
115 unselected-focus-state-layer-color: $_on-surface,
116 unselected-focus-state-layer-opacity: 0.12,
117 unselected-focus-track-color: $_hairline,
118 unselected-handle-color: $_on-surface-variant,
119 unselected-hover-handle-color: $_on-surface-state-content,
120 unselected-hover-state-layer-color: $_on-surface,
121 unselected-hover-state-layer-opacity: 0.04,
122 unselected-hover-track-color: $_hairline,
123 unselected-icon-color: on-primary,
124 unselected-icon-size: 18px,
125 unselected-pressed-handle-color: $_on-surface-state-content,
126 unselected-pressed-state-layer-color: $_on-surface,
127 unselected-pressed-state-layer-opacity: 0.1,
128 unselected-pressed-track-color: $_hairline,
129 unselected-track-color: $_hairline,
130);
131
132$forced-colors-theme: (
133 disabled-handle-opacity: 1,
134 disabled-selected-icon-color: GrayText,
135 disabled-selected-icon-opacity: 1,
136 disabled-track-opacity: 1,
137 disabled-unselected-icon-color: GrayText,
138 disabled-unselected-icon-opacity: 1,
139 selected-icon-color: ButtonText,
140 unselected-icon-color: ButtonText,
141);
142
143@function density($density-scale) {
144 $size: density.prop-value(
145 $density-config: $_density-config,
146 $density-scale: $density-scale,
147 $property-name: size,
148 );
149
150 @return (state-layer-size: $size);
151}
152
153@mixin theme($theme, $resolvers: resolvers.$material) {
154 @include theme.validate-theme($light-theme, $theme);
155
156 // TODO(b/185172301): replace with improved feature targeting
157 // IE11 Fallback
158 @if shadow-dom.$css-selector-fallback-declarations {
159 @include custom-properties.configure($emit-custom-properties: false) {
160 @include dom.ie11-support {
161 @include theme-styles($theme, $resolvers: $resolvers);
162 }
163 }
164 }
165
166 $theme: _resolve-theme($theme, $resolvers);
167 @include keys.declare-custom-properties($theme, switch);
168}
169
170@function _resolve-theme($theme, $resolvers) {
171 @return map.merge(
172 $theme,
173 _resolve-theme-handle-elevation(
174 $theme,
175 map.get($resolvers, elevation),
176 disabled-handle-elevation,
177 handle-elevation
178 )
179 );
180}
181
182@function _resolve-theme-handle-elevation($theme, $resolver, $keys...) {
183 @if $resolver == null {
184 @return $theme;
185 }
186
187 @each $key in $keys {
188 // Resolve the value for each state key.
189 $resolved-value: meta.call(
190 $resolver,
191 $elevation: map.get($theme, $key),
192 $shadow-color: map.get($theme, handle-shadow-color)
193 );
194
195 // Update the theme with the resolved value.
196 $theme: map.set($theme, $key, $resolved-value);
197 }
198
199 @return $theme;
200}
201
202@mixin theme-styles($theme, $resolvers: resolvers.$material) {
203 @include theme.validate-theme($light-theme, $theme);
204
205 $theme: keys.create-theme-properties($theme, switch);
206
207 @include _selected-handle-color(
208 (
209 default: map.get($theme, selected-handle-color),
210 disabled: map.get($theme, disabled-selected-handle-color),
211 focus: map.get($theme, selected-focus-handle-color),
212 hover: map.get($theme, selected-hover-handle-color),
213 pressed: map.get($theme, selected-pressed-handle-color),
214 )
215 );
216
217 @include _unselected-handle-color(
218 (
219 default: map.get($theme, unselected-handle-color),
220 disabled: map.get($theme, disabled-unselected-handle-color),
221 focus: map.get($theme, unselected-focus-handle-color),
222 hover: map.get($theme, unselected-hover-handle-color),
223 pressed: map.get($theme, unselected-pressed-handle-color),
224 )
225 );
226
227 @include _handle-surface-color(map.get($theme, handle-surface-color));
228
229 @include _handle-elevation(
230 map.get($resolvers, elevation),
231 map.get($theme, handle-shadow-color),
232 (
233 default: map.get($theme, handle-elevation),
234 disabled: map.get($theme, disabled-handle-elevation),
235 )
236 );
237
238 @include _handle-height(map.get($theme, handle-height));
239 @include _handle-opacity(
240 (
241 disabled: map.get($theme, disabled-handle-opacity),
242 )
243 );
244
245 @include _handle-shape(map.get($theme, handle-shape));
246 @include _handle-width(map.get($theme, handle-width));
247
248 @include _selected-icon-color(
249 (
250 default: map.get($theme, selected-icon-color),
251 disabled: map.get($theme, disabled-selected-icon-color),
252 )
253 );
254
255 @include _unselected-icon-color(
256 (
257 default: map.get($theme, unselected-icon-color),
258 disabled: map.get($theme, disabled-unselected-icon-color),
259 )
260 );
261
262 @include _selected-icon-opacity(
263 (
264 disabled: map.get($theme, disabled-selected-icon-opacity),
265 )
266 );
267
268 @include _unselected-icon-opacity(
269 (
270 disabled: map.get($theme, disabled-unselected-icon-opacity),
271 )
272 );
273
274 @include _selected-icon-size(map.get($theme, selected-icon-size));
275 @include _unselected-icon-size(map.get($theme, unselected-icon-size));
276
277 @include _selected-ripple-color(
278 (
279 focus: map.get($theme, selected-focus-state-layer-color),
280 hover: map.get($theme, selected-hover-state-layer-color),
281 pressed: map.get($theme, selected-pressed-state-layer-color),
282 )
283 );
284
285 @include _unselected-ripple-color(
286 (
287 focus: map.get($theme, unselected-focus-state-layer-color),
288 hover: map.get($theme, unselected-hover-state-layer-color),
289 pressed: map.get($theme, unselected-pressed-state-layer-color),
290 )
291 );
292
293 @include _selected-ripple-opacity(
294 (
295 focus: map.get($theme, selected-focus-state-layer-opacity),
296 hover: map.get($theme, selected-hover-state-layer-opacity),
297 pressed: map.get($theme, selected-pressed-state-layer-opacity),
298 )
299 );
300
301 @include _unselected-ripple-opacity(
302 (
303 focus: map.get($theme, unselected-focus-state-layer-opacity),
304 hover: map.get($theme, unselected-hover-state-layer-opacity),
305 pressed: map.get($theme, unselected-pressed-state-layer-opacity),
306 )
307 );
308
309 @include _state-layer-size(map.get($theme, state-layer-size));
310 @include _track-height(map.get($theme, track-height));
311 @include _track-opacity(
312 (
313 disabled: map.get($theme, disabled-track-opacity),
314 )
315 );
316
317 @include _track-selected-color(
318 (
319 default: map.get($theme, selected-track-color),
320 disabled: map.get($theme, disabled-selected-track-color),
321 focus: map.get($theme, selected-focus-track-color),
322 hover: map.get($theme, selected-hover-track-color),
323 pressed: map.get($theme, selected-pressed-track-color),
324 )
325 );
326
327 @include _track-unselected-color(
328 (
329 default: map.get($theme, unselected-track-color),
330 disabled: map.get($theme, disabled-unselected-track-color),
331 focus: map.get($theme, unselected-focus-track-color),
332 hover: map.get($theme, unselected-hover-track-color),
333 pressed: map.get($theme, unselected-pressed-track-color),
334 )
335 );
336
337 @include _track-shape(map.get($theme, track-shape));
338 @include _track-width(map.get($theme, track-width));
339}
340
341@mixin _handle-color($colors) {
342 @include state.default($selectors) {
343 @include _set-handle-color(state.get-default-state($colors));
344 }
345
346 @include state.hover($selectors) {
347 @include _set-handle-color(state.get-hover-state($colors));
348 }
349
350 @include state.focus($selectors) {
351 @include _set-handle-color(state.get-focus-state($colors));
352 }
353
354 @include state.pressed($selectors) {
355 @include _set-handle-color(state.get-pressed-state($colors));
356 }
357
358 @include state.disabled($selectors) {
359 @include _set-handle-color(state.get-disabled-state($colors));
360 }
361}
362
363@mixin _set-handle-color($color) {
364 .mdc-switch__handle {
365 &::after {
366 @include theme.property(background, $color);
367 }
368 }
369}
370
371@mixin _selected-handle-color($colors) {
372 @include state.selected($selectors) {
373 @include _handle-color($colors);
374 }
375}
376
377@mixin _unselected-handle-color($colors) {
378 @include state.unselected($selectors) {
379 @include _handle-color($colors);
380 }
381}
382
383@mixin _handle-surface-color($color) {
384 .mdc-switch__handle {
385 // Sets the surface color for the handle. This is used so that when an
386 // opacity is applied to the "main" handle color, it will not bleed through
387 // and appear transparent on top of the track.
388 &::before {
389 @include theme.property(background, $color);
390 }
391 }
392}
393
394@mixin _handle-elevation($resolver, $shadow-color, $elevations) {
395 @include state.default($selectors) {
396 @include _set-handle-elevation(
397 $resolver,
398 $elevation: state.get-default-state($elevations),
399 $shadow-color: $shadow-color
400 );
401 }
402
403 @include state.disabled($selectors) {
404 @include _set-handle-elevation(
405 $resolver,
406 $elevation: state.get-disabled-state($elevations),
407 $shadow-color: $shadow-color
408 );
409 }
410}
411
412@mixin _set-handle-elevation($resolver, $args...) {
413 .mdc-switch__shadow {
414 @include elevation-theme.with-resolver($resolver, $args...);
415 }
416}
417
418@mixin _handle-height($height) {
419 .mdc-switch__handle {
420 @include theme.property(height, $height);
421 }
422}
423
424@mixin _handle-opacity($opacities) {
425 @include state.disabled($selectors) {
426 @include _set-handle-opacity(state.get-disabled-state($opacities));
427 }
428}
429
430@mixin _set-handle-opacity($opacity) {
431 .mdc-switch__handle {
432 // Only apply to the ::after pseudo element, which is the handle's "main"
433 // color. The ::before pseudo element is the surface color, which prevents
434 // the handle from bleeding through on the track.
435 &::after {
436 @include theme.property(opacity, $opacity);
437 }
438 }
439}
440
441@mixin _handle-shape($shape) {
442 .mdc-switch__handle {
443 @include shape.radius($shape);
444 }
445}
446
447@mixin _handle-width($width) {
448 .mdc-switch__handle {
449 @include theme.property(width, $width);
450 }
451
452 .mdc-switch__handle-track {
453 @include theme.property(
454 width,
455 'calc(100% - width)',
456 $replace: (width: $width)
457 );
458 }
459}
460
461@mixin _icon-color($colors) {
462 @include state.default($selectors) {
463 @include _set-icon-color(state.get-default-state($colors));
464 }
465
466 @include state.disabled($selectors) {
467 @include _set-icon-color(state.get-disabled-state($colors));
468 }
469}
470
471@mixin _set-icon-color($color) {
472 .mdc-switch__icon {
473 @include theme.property(fill, $color);
474 }
475}
476
477@mixin _selected-icon-color($colors) {
478 @include state.selected($selectors) {
479 @include _icon-color($colors);
480 }
481}
482
483@mixin _unselected-icon-color($colors) {
484 @include state.unselected($selectors) {
485 @include _icon-color($colors);
486 }
487}
488
489@mixin _icon-opacity($opacities) {
490 @include state.disabled($selectors) {
491 @include _set-icon-opacity(state.get-disabled-state($opacities));
492 }
493}
494
495@mixin _set-icon-opacity($opacity) {
496 .mdc-switch__icons {
497 @include theme.property(opacity, $opacity);
498 }
499}
500
501@mixin _selected-icon-opacity($opacities) {
502 @include state.selected($selectors) {
503 @include _icon-opacity($opacities);
504 }
505}
506
507@mixin _unselected-icon-opacity($opacities) {
508 @include state.unselected($selectors) {
509 @include _icon-opacity($opacities);
510 }
511}
512
513@mixin _icon-size($size) {
514 .mdc-switch__icon {
515 @include theme.property(width, $size);
516 @include theme.property(height, $size);
517 }
518}
519
520@mixin _selected-icon-size($size) {
521 @include state.selected($selectors) {
522 @include _icon-size($size);
523 }
524}
525
526@mixin _unselected-icon-size($size) {
527 @include state.unselected($selectors) {
528 @include _icon-size($size);
529 }
530}
531
532@mixin _ripple-color($colors) {
533 @include state.independent-elements(pressed) {
534 @include state.hover($selectors) {
535 @include ripple-theme.states-base-color(
536 state.get-hover-state($colors),
537 $ripple-target: switch.$ripple-target
538 );
539 }
540
541 @include state.focus($selectors) {
542 @include ripple-theme.states-base-color(
543 state.get-focus-state($colors),
544 $ripple-target: switch.$ripple-target
545 );
546 }
547
548 @include state.pressed($selectors) {
549 @include ripple-theme.states-base-color(
550 state.get-pressed-state($colors),
551 $ripple-target: switch.$ripple-target
552 );
553 }
554 }
555}
556
557@mixin _selected-ripple-color($colors) {
558 @include state.selected($selectors) {
559 @include _ripple-color($colors);
560 }
561}
562
563@mixin _unselected-ripple-color($colors) {
564 @include state.unselected($selectors) {
565 @include _ripple-color($colors);
566 }
567}
568
569@mixin _ripple-opacity($opacities) {
570 @include state.independent-elements(pressed) {
571 @include state.hover($selectors) {
572 @include ripple-theme.states-hover-opacity(
573 state.get-hover-state($opacities),
574 $ripple-target: switch.$ripple-target
575 );
576 }
577
578 @include state.focus($selectors) {
579 @include ripple-theme.states-focus-opacity(
580 state.get-focus-state($opacities),
581 $ripple-target: switch.$ripple-target
582 );
583 }
584
585 @include state.pressed($selectors) {
586 @include ripple-theme.states-press-opacity(
587 state.get-pressed-state($opacities),
588 $ripple-target: switch.$ripple-target
589 );
590 }
591 }
592}
593
594@mixin _selected-ripple-opacity($opacities) {
595 @include state.selected($selectors) {
596 @include _ripple-opacity($opacities);
597 }
598}
599
600@mixin _unselected-ripple-opacity($opacities) {
601 @include state.unselected($selectors) {
602 @include _ripple-opacity($opacities);
603 }
604}
605
606@mixin _state-layer-size($size) {
607 .mdc-switch__ripple {
608 @include theme.property(height, $size);
609 @include theme.property(width, $size);
610 }
611}
612
613@mixin _track-height($height) {
614 .mdc-switch__track {
615 @include theme.property(height, $height);
616 }
617}
618
619@mixin _track-opacity($opacities) {
620 @include state.disabled($selectors) {
621 @include _set-track-opacity(state.get-disabled-state($opacities));
622 }
623}
624
625@mixin _set-track-opacity($opacity) {
626 .mdc-switch__track {
627 @include theme.property(opacity, $opacity);
628 }
629}
630
631@mixin _track-selected-color($colors) {
632 @include state.default($selectors) {
633 @include _set-track-selected-color(state.get-default-state($colors));
634 }
635
636 @include state.hover($selectors) {
637 @include _set-track-selected-color(state.get-hover-state($colors));
638 }
639
640 @include state.focus($selectors) {
641 @include _set-track-selected-color(state.get-focus-state($colors));
642 }
643
644 @include state.pressed($selectors) {
645 @include _set-track-selected-color(state.get-pressed-state($colors));
646 }
647
648 @include state.disabled($selectors) {
649 @include _set-track-selected-color(state.get-disabled-state($colors));
650 }
651}
652
653@mixin _set-track-selected-color($color) {
654 .mdc-switch__track::after {
655 @include theme.property(background, $color);
656 }
657}
658
659@mixin _track-unselected-color($colors) {
660 @include state.default($selectors) {
661 @include _set-track-unselected-color(state.get-default-state($colors));
662 }
663
664 @include state.hover($selectors) {
665 @include _set-track-unselected-color(state.get-hover-state($colors));
666 }
667
668 @include state.focus($selectors) {
669 @include _set-track-unselected-color(state.get-focus-state($colors));
670 }
671
672 @include state.pressed($selectors) {
673 @include _set-track-unselected-color(state.get-pressed-state($colors));
674 }
675
676 @include state.disabled($selectors) {
677 @include _set-track-unselected-color(state.get-disabled-state($colors));
678 }
679}
680
681@mixin _set-track-unselected-color($color) {
682 .mdc-switch__track::before {
683 @include theme.property(background, $color);
684 }
685}
686
687@mixin _track-shape($shape) {
688 .mdc-switch__track {
689 @include shape.radius($shape);
690 }
691}
692
693@mixin _track-width($width) {
694 @include theme.property(width, $width);
695}