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-keys($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__focus-ring-wrapper,
420 .mdc-switch__handle {
421 @include theme.property(height, $height);
422 }
423}
424
425@mixin _handle-opacity($opacities) {
426 @include state.disabled($selectors) {
427 @include _set-handle-opacity(state.get-disabled-state($opacities));
428 }
429}
430
431@mixin _set-handle-opacity($opacity) {
432 .mdc-switch__handle {
433 // Only apply to the ::after pseudo element, which is the handle's "main"
434 // color. The ::before pseudo element is the surface color, which prevents
435 // the handle from bleeding through on the track.
436 &::after {
437 @include theme.property(opacity, $opacity);
438 }
439 }
440}
441
442@mixin _handle-shape($shape) {
443 .mdc-switch__handle {
444 @include shape.radius($shape);
445 }
446}
447
448@mixin _handle-width($width) {
449 .mdc-switch__handle {
450 @include theme.property(width, $width);
451 }
452
453 .mdc-switch__handle-track {
454 @include theme.property(
455 width,
456 'calc(100% - width)',
457 $replace: (width: $width)
458 );
459 }
460}
461
462@mixin _icon-color($colors) {
463 @include state.default($selectors) {
464 @include _set-icon-color(state.get-default-state($colors));
465 }
466
467 @include state.disabled($selectors) {
468 @include _set-icon-color(state.get-disabled-state($colors));
469 }
470}
471
472@mixin _set-icon-color($color) {
473 .mdc-switch__icon {
474 @include theme.property(fill, $color);
475 }
476}
477
478@mixin _selected-icon-color($colors) {
479 @include state.selected($selectors) {
480 @include _icon-color($colors);
481 }
482}
483
484@mixin _unselected-icon-color($colors) {
485 @include state.unselected($selectors) {
486 @include _icon-color($colors);
487 }
488}
489
490@mixin _icon-opacity($opacities) {
491 @include state.disabled($selectors) {
492 @include _set-icon-opacity(state.get-disabled-state($opacities));
493 }
494}
495
496@mixin _set-icon-opacity($opacity) {
497 .mdc-switch__icons {
498 @include theme.property(opacity, $opacity);
499 }
500}
501
502@mixin _selected-icon-opacity($opacities) {
503 @include state.selected($selectors) {
504 @include _icon-opacity($opacities);
505 }
506}
507
508@mixin _unselected-icon-opacity($opacities) {
509 @include state.unselected($selectors) {
510 @include _icon-opacity($opacities);
511 }
512}
513
514@mixin _icon-size($size) {
515 .mdc-switch__icon {
516 @include theme.property(width, $size);
517 @include theme.property(height, $size);
518 }
519}
520
521@mixin _selected-icon-size($size) {
522 @include state.selected($selectors) {
523 @include _icon-size($size);
524 }
525}
526
527@mixin _unselected-icon-size($size) {
528 @include state.unselected($selectors) {
529 @include _icon-size($size);
530 }
531}
532
533@mixin _ripple-color($colors) {
534 @include state.independent-elements(pressed) {
535 @include state.hover($selectors) {
536 @include ripple-theme.states-base-color(
537 state.get-hover-state($colors),
538 $ripple-target: switch.$ripple-target
539 );
540 }
541
542 @include state.focus($selectors) {
543 @include ripple-theme.states-base-color(
544 state.get-focus-state($colors),
545 $ripple-target: switch.$ripple-target
546 );
547 }
548
549 @include state.pressed($selectors) {
550 @include ripple-theme.states-base-color(
551 state.get-pressed-state($colors),
552 $ripple-target: switch.$ripple-target
553 );
554 }
555 }
556}
557
558@mixin _selected-ripple-color($colors) {
559 @include state.selected($selectors) {
560 @include _ripple-color($colors);
561 }
562}
563
564@mixin _unselected-ripple-color($colors) {
565 @include state.unselected($selectors) {
566 @include _ripple-color($colors);
567 }
568}
569
570@mixin _ripple-opacity($opacities) {
571 @include state.independent-elements(pressed) {
572 @include state.hover($selectors) {
573 @include ripple-theme.states-hover-opacity(
574 state.get-hover-state($opacities),
575 $ripple-target: switch.$ripple-target
576 );
577 }
578
579 @include state.focus($selectors) {
580 @include ripple-theme.states-focus-opacity(
581 state.get-focus-state($opacities),
582 $ripple-target: switch.$ripple-target
583 );
584 }
585
586 @include state.pressed($selectors) {
587 @include ripple-theme.states-press-opacity(
588 state.get-pressed-state($opacities),
589 $ripple-target: switch.$ripple-target
590 );
591 }
592 }
593}
594
595@mixin _selected-ripple-opacity($opacities) {
596 @include state.selected($selectors) {
597 @include _ripple-opacity($opacities);
598 }
599}
600
601@mixin _unselected-ripple-opacity($opacities) {
602 @include state.unselected($selectors) {
603 @include _ripple-opacity($opacities);
604 }
605}
606
607@mixin _state-layer-size($size) {
608 .mdc-switch__ripple {
609 @include theme.property(height, $size);
610 @include theme.property(width, $size);
611 }
612}
613
614@mixin _track-height($height) {
615 .mdc-switch__track {
616 @include theme.property(height, $height);
617 }
618}
619
620@mixin _track-opacity($opacities) {
621 @include state.disabled($selectors) {
622 @include _set-track-opacity(state.get-disabled-state($opacities));
623 }
624}
625
626@mixin _set-track-opacity($opacity) {
627 .mdc-switch__track {
628 @include theme.property(opacity, $opacity);
629 }
630}
631
632@mixin _track-selected-color($colors) {
633 @include state.default($selectors) {
634 @include _set-track-selected-color(state.get-default-state($colors));
635 }
636
637 @include state.hover($selectors) {
638 @include _set-track-selected-color(state.get-hover-state($colors));
639 }
640
641 @include state.focus($selectors) {
642 @include _set-track-selected-color(state.get-focus-state($colors));
643 }
644
645 @include state.pressed($selectors) {
646 @include _set-track-selected-color(state.get-pressed-state($colors));
647 }
648
649 @include state.disabled($selectors) {
650 @include _set-track-selected-color(state.get-disabled-state($colors));
651 }
652}
653
654@mixin _set-track-selected-color($color) {
655 .mdc-switch__track::after {
656 @include theme.property(background, $color);
657 }
658}
659
660@mixin _track-unselected-color($colors) {
661 @include state.default($selectors) {
662 @include _set-track-unselected-color(state.get-default-state($colors));
663 }
664
665 @include state.hover($selectors) {
666 @include _set-track-unselected-color(state.get-hover-state($colors));
667 }
668
669 @include state.focus($selectors) {
670 @include _set-track-unselected-color(state.get-focus-state($colors));
671 }
672
673 @include state.pressed($selectors) {
674 @include _set-track-unselected-color(state.get-pressed-state($colors));
675 }
676
677 @include state.disabled($selectors) {
678 @include _set-track-unselected-color(state.get-disabled-state($colors));
679 }
680}
681
682@mixin _set-track-unselected-color($color) {
683 .mdc-switch__track::before {
684 @include theme.property(background, $color);
685 }
686}
687
688@mixin _track-shape($shape) {
689 .mdc-switch__track {
690 @include shape.radius($shape);
691 }
692}
693
694@mixin _track-width($width) {
695 @include theme.property(width, $width);
696}