UNPKG

17.2 kBSCSSView Raw
1//
2// Copyright 2016 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:map';
27@use '@material/density/functions' as density-functions;
28@use '@material/feature-targeting/feature-targeting';
29@use '@material/theme/theme';
30@use '@material/theme/keys';
31@use '@material/density/variables' as density-variables;
32@use '@material/theme/theme-color';
33@use '@material/ripple/ripple-theme';
34
35$ripple-size: 40px !default;
36$icon-size: 20px !default;
37$transition-duration: 120ms !default;
38$ripple-opacity: 0.14 !default;
39$baseline-theme-color: secondary !default;
40$unchecked-color: rgba(theme-color.prop-value(on-surface), 0.54) !default;
41$disabled-circle-color: rgba(theme-color.prop-value(on-surface), 0.38) !default;
42
43$minimum-size: 28px !default;
44$maximum-size: $ripple-size !default;
45$density-scale: density-variables.$default-scale !default;
46$density-config: (
47 size: (
48 minimum: $minimum-size,
49 default: $ripple-size,
50 maximum: $maximum-size,
51 ),
52) !default;
53
54$ripple-target: '.mdc-radio__ripple';
55$unselected-ripple-target: '.mdc-radio__native-control:enabled:not(:checked) ~ #{$ripple-target}';
56$custom-property-prefix: 'radio';
57
58// TODO(b/188417756): `icon-size` token key is not supported.
59$light-theme: (
60 disabled-selected-icon-color: theme-color.$on-surface,
61 disabled-selected-icon-opacity: 0.38,
62 disabled-unselected-icon-color: theme-color.$on-surface,
63 disabled-unselected-icon-opacity: 0.38,
64 selected-focus-icon-color: theme-color.$primary,
65 selected-focus-state-layer-color: theme-color.$primary,
66 selected-focus-state-layer-opacity: 0.12,
67 selected-hover-icon-color: theme-color.$primary,
68 selected-hover-state-layer-color: theme-color.$primary,
69 selected-hover-state-layer-opacity: 0.04,
70 selected-icon-color: theme-color.$primary,
71 selected-pressed-icon-color: theme-color.$primary,
72 selected-pressed-state-layer-color: theme-color.$primary,
73 selected-pressed-state-layer-opacity: 0.1,
74 state-layer-size: $ripple-size,
75 unselected-focus-icon-color: theme-color.$on-surface,
76 unselected-focus-state-layer-color: theme-color.$on-surface,
77 unselected-focus-state-layer-opacity: 0.12,
78 unselected-hover-icon-color: theme-color.$on-surface,
79 unselected-hover-state-layer-color: theme-color.$on-surface,
80 unselected-hover-state-layer-opacity: 0.04,
81 unselected-icon-color: theme-color.$on-surface,
82 unselected-pressed-icon-color: theme-color.$on-surface,
83 unselected-pressed-state-layer-color: theme-color.$on-surface,
84 unselected-pressed-state-layer-opacity: 0.1,
85);
86
87@mixin theme($theme) {
88 @include theme.validate-theme($light-theme, $theme);
89 @include keys.declare-custom-properties(
90 $theme,
91 $prefix: $custom-property-prefix
92 );
93}
94
95@mixin theme-styles($theme) {
96 @include theme.validate-theme-keys($light-theme, $theme);
97
98 $theme: keys.create-theme-properties(
99 $theme,
100 $prefix: $custom-property-prefix
101 );
102
103 @include _disabled-selected-icon-color(
104 map.get($theme, disabled-selected-icon-color)
105 );
106 @include _disabled-selected-icon-opacity(
107 map.get($theme, disabled-selected-icon-opacity)
108 );
109 @include _disabled-unselected-icon-color(
110 map.get($theme, disabled-unselected-icon-color)
111 );
112 @include _disabled-unselected-icon-opacity(
113 map.get($theme, disabled-unselected-icon-opacity)
114 );
115
116 // selected
117 @include ripple-theme.focus() {
118 @include _selected-icon-color(map.get($theme, selected-focus-icon-color));
119 @include _selected-state-layer-color(
120 map.get($theme, selected-focus-state-layer-color)
121 );
122 @include _selected-focus-state-layer-opacity(
123 map.get($theme, selected-focus-state-layer-opacity)
124 );
125 }
126 @include ripple-theme.hover() {
127 @include _selected-icon-color(map.get($theme, selected-hover-icon-color));
128 @include _selected-state-layer-color(
129 map.get($theme, selected-hover-state-layer-color)
130 );
131 @include _selected-hover-state-layer-opacity(
132 map.get($theme, selected-hover-state-layer-opacity)
133 );
134 }
135 @include _selected-icon-color(map.get($theme, selected-icon-color));
136 @include ripple-theme.active() {
137 @include _selected-icon-color(map.get($theme, selected-pressed-icon-color));
138 @include _selected-state-layer-color(
139 map.get($theme, selected-pressed-state-layer-color)
140 );
141 @include _selected-pressed-state-layer-opacity(
142 map.get($theme, selected-pressed-state-layer-opacity)
143 );
144 }
145
146 // unselected
147 @include ripple-theme.focus() {
148 @include _unselected-icon-color(
149 map.get($theme, unselected-focus-icon-color)
150 );
151 @include _unselected-state-layer-color(
152 map.get($theme, unselected-focus-state-layer-color)
153 );
154 @include _unselected-focus-state-layer-opacity(
155 map.get($theme, unselected-focus-state-layer-opacity)
156 );
157 }
158 @include ripple-theme.hover() {
159 @include _unselected-icon-color(
160 map.get($theme, unselected-hover-icon-color)
161 );
162 @include _unselected-state-layer-color(
163 map.get($theme, unselected-hover-state-layer-color)
164 );
165 @include _unselected-hover-state-layer-opacity(
166 map.get($theme, unselected-hover-state-layer-opacity)
167 );
168 }
169 @include _unselected-icon-color(map.get($theme, unselected-icon-color));
170 @include ripple-theme.active() {
171 @include _unselected-icon-color(
172 map.get($theme, unselected-pressed-icon-color)
173 );
174 @include _unselected-state-layer-color(
175 map.get($theme, unselected-pressed-state-layer-color)
176 );
177 @include _unselected-pressed-state-layer-opacity(
178 map.get($theme, unselected-pressed-state-layer-opacity)
179 );
180 }
181
182 @include ripple-size(map.get($theme, state-layer-size));
183 // Set touch target size same as ripple size.
184 @include touch-target(
185 $size: map.get($theme, state-layer-size),
186 $ripple-size: map.get($theme, state-layer-size)
187 );
188}
189
190@mixin _disabled-selected-icon-color($color) {
191 @include disabled-checked-stroke-color($color);
192 @include disabled-ink-color($color);
193}
194
195@mixin _disabled-selected-icon-opacity($opacity) {
196 @include _disabled-checked-stroke-opacity($opacity);
197 @include _disabled-ink-opacity($opacity);
198}
199
200@mixin _disabled-unselected-icon-color($color) {
201 @include disabled-unchecked-stroke-color($color);
202}
203
204@mixin _disabled-unselected-icon-opacity($opacity) {
205 @include _disabled-unchecked-stroke-opacity($opacity);
206}
207
208@mixin _selected-icon-color($color) {
209 @include checked-stroke-color($color);
210 @include ink-color($color);
211}
212
213@mixin _selected-state-layer-color($color) {
214 @include ripple-theme.states-base-color(
215 $color: $color,
216 $ripple-target: $ripple-target
217 );
218}
219
220@mixin _selected-hover-state-layer-opacity($opacity) {
221 @include ripple-theme.states-hover-opacity(
222 $opacity: $opacity,
223 $ripple-target: $ripple-target
224 );
225}
226
227@mixin _selected-focus-state-layer-opacity($opacity) {
228 @include ripple-theme.states-focus-opacity(
229 $opacity: $opacity,
230 $ripple-target: $ripple-target
231 );
232}
233
234@mixin _selected-pressed-state-layer-opacity($opacity) {
235 @include ripple-theme.states-press-opacity(
236 $opacity: $opacity,
237 $ripple-target: $ripple-target
238 );
239}
240
241@mixin _unselected-icon-color($color) {
242 @include unchecked-stroke-color($color);
243}
244
245@mixin _unselected-state-layer-color($color) {
246 @include ripple-theme.states-base-color(
247 $color: $color,
248 $ripple-target: $unselected-ripple-target
249 );
250}
251
252@mixin _unselected-hover-state-layer-opacity($opacity) {
253 @include ripple-theme.states-hover-opacity(
254 $opacity: $opacity,
255 $ripple-target: $unselected-ripple-target
256 );
257}
258
259@mixin _unselected-focus-state-layer-opacity($opacity) {
260 @include ripple-theme.states-focus-opacity(
261 $opacity: $opacity,
262 $ripple-target: $unselected-ripple-target
263 );
264}
265
266@mixin _unselected-pressed-state-layer-opacity($opacity) {
267 @include ripple-theme.states-press-opacity(
268 $opacity: $opacity,
269 $ripple-target: $unselected-ripple-target
270 );
271}
272
273///
274/// Sets the stroke color of an unchecked, enabled radio button.
275/// @param {Color} $color - The desired stroke color.
276///
277@mixin unchecked-stroke-color($color, $query: feature-targeting.all()) {
278 @include _if-enabled-unchecked {
279 @include _stroke-color($color, $query: $query);
280 }
281}
282
283///
284/// Sets the stroke color of a checked, enabled radio button.
285/// @param {Color} $color - The desired stroke color.
286///
287@mixin checked-stroke-color($color, $query: feature-targeting.all()) {
288 @include _if-enabled-checked {
289 @include _stroke-color($color, $query: $query);
290 }
291}
292
293///
294/// Sets the ink color of an enabled radio button.
295/// @param {Color} $color - The desired ink color.
296///
297@mixin ink-color($color, $query: feature-targeting.all()) {
298 @include _if-enabled {
299 @include _ink-color($color, $query: $query);
300 }
301}
302
303///
304/// Sets the stroke color of an unchecked, disabled radio button.
305/// @param {Color} $color - The desired stroke color.
306///
307@mixin disabled-unchecked-stroke-color(
308 $color,
309 $query: feature-targeting.all()
310) {
311 @include _if-disabled-unchecked {
312 @include _stroke-color($color, $query: $query);
313 }
314}
315
316@mixin _disabled-unchecked-stroke-opacity($opacity) {
317 @include _if-disabled-unchecked {
318 @include _stroke-opacity($opacity);
319 }
320}
321
322///
323/// Sets the stroke color of a checked, disabled radio button.
324/// @param {Color} $color - The desired stroke color.
325///
326@mixin disabled-checked-stroke-color($color, $query: feature-targeting.all()) {
327 @include if-disabled-checked_ {
328 @include _stroke-color($color, $query: $query);
329 }
330}
331
332@mixin _disabled-checked-stroke-opacity($opacity) {
333 @include if-disabled-checked_ {
334 @include _stroke-opacity($opacity);
335 }
336}
337
338///
339/// Sets the ink color of a disabled radio button.
340/// @param {Color} $color - The desired ink color
341///
342@mixin disabled-ink-color($color, $query: feature-targeting.all()) {
343 @include if-disabled_ {
344 @include _ink-color($color, $query: $query);
345 }
346}
347
348@mixin _disabled-ink-opacity($opacity) {
349 @include if-disabled_ {
350 @include _ink-opacity($opacity);
351 }
352}
353
354@mixin focus-indicator-color($color, $query: feature-targeting.all()) {
355 $feat-color: feature-targeting.create-target($query, color);
356
357 .mdc-radio__background::before {
358 @include feature-targeting.targets($feat-color) {
359 @include theme.property(background-color, $color);
360 }
361 }
362}
363
364///
365/// Sets radio touch target size which can be more than the ripple size. Param `$ripple-size` is required for custom
366/// ripple size.
367///
368/// @param {Number} $size Size of touch target (Native input) in `px`.
369/// @param {Number} $ripple-size Size of ripple in `px`. Required only for custom ripple size.
370///
371@mixin touch-target(
372 $size: $ripple-size,
373 $ripple-size: $ripple-size,
374 $query: feature-targeting.all()
375) {
376 $feat-structure: feature-targeting.create-target($query, structure);
377 $offset: 'calc((__ripple_size - __size) / 2)';
378 $replace: (
379 __ripple_size: $ripple-size,
380 __size: $size,
381 );
382
383 .mdc-radio__native-control {
384 @include feature-targeting.targets($feat-structure) {
385 @include theme.property('top', $offset, $replace: $replace);
386 @include theme.property('right', $offset, $replace: $replace);
387 @include theme.property('left', $offset, $replace: $replace);
388 @include theme.property('width', $size);
389 @include theme.property('height', $size);
390 }
391 }
392}
393
394///
395/// Sets density scale for radio.
396///
397/// @param {Number | String} $density-scale - Density scale value for component. Supported density scale values
398/// `-3`, `-2`, `-1`, `0`.
399///
400@mixin density($density-scale, $query: feature-targeting.all()) {
401 $size: density-functions.prop-value(
402 $density-config: $density-config,
403 $density-scale: $density-scale,
404 $property-name: size,
405 );
406
407 @include ripple-size($size, $query: $query);
408 // Sets touch target size same as ripple size.
409 @include touch-target($size: $size, $ripple-size: $size, $query: $query);
410
411 @if $density-scale != 0 {
412 @include touch-target-reset_($query: $query);
413 }
414}
415
416///
417/// Sets radio ripple size.
418///
419/// @param {Number} $size - Ripple size in `px`.
420///
421@mixin ripple-size($size, $query: feature-targeting.all()) {
422 $feat-structure: feature-targeting.create-target($query, structure);
423 $replace: (
424 __size: $size,
425 __icon_size: $icon-size,
426 );
427
428 @include feature-targeting.targets($feat-structure) {
429 $padding: 'calc((__size - __icon_size) / 2)';
430
431 @include theme.property('padding', $padding, $replace: $replace);
432 }
433
434 .mdc-radio__background::before {
435 @include feature-targeting.targets($feat-structure) {
436 $padding-offset: 'calc(-1 * (__size - __icon_size) / 2)';
437
438 @include theme.property('top', $padding-offset, $replace: $replace);
439 @include theme.property('left', $padding-offset, $replace: $replace);
440 @include theme.property('width', $size);
441 @include theme.property('height', $size);
442 }
443 }
444}
445
446///
447/// Resets touch target-related styles. This is called from the density mixin to
448/// automatically remove the increased touch target, since dense components
449/// don't have the same default a11y requirements.
450/// @access private
451///
452@mixin touch-target-reset_($query: feature-targeting.all()) {
453 $feat-structure: feature-targeting.create-target($query, structure);
454
455 @include feature-targeting.targets($feat-structure) {
456 margin: 0;
457 }
458}
459
460///
461/// Helps select the radio background only when its native control is in the
462/// enabled state.
463/// @access private
464///
465@mixin _if-enabled {
466 .mdc-radio__native-control:enabled + {
467 @content;
468 }
469}
470
471///
472/// Helps select the radio background only when its native control is in the
473/// enabled & unchecked state.
474/// @access private
475///
476@mixin _if-enabled-unchecked {
477 .mdc-radio__native-control:enabled:not(:checked) + {
478 @content;
479 }
480}
481
482///
483/// Helps select the radio background only when its native control is in the
484/// enabled & checked state.
485/// @access private
486///
487@mixin _if-enabled-checked {
488 .mdc-radio__native-control:enabled:checked + {
489 @content;
490 }
491}
492
493///
494/// Helps select the radio background only when its native control is in the
495/// disabled state.
496/// @access private
497///
498@mixin if-disabled_ {
499 [aria-disabled='true'] .mdc-radio__native-control,
500 .mdc-radio__native-control:disabled {
501 + {
502 @content;
503 }
504 }
505}
506
507///
508/// Helps select the radio background only when its native control is in the
509/// disabled & unchecked state.
510/// @access private
511///
512@mixin _if-disabled-unchecked {
513 [aria-disabled='true'] .mdc-radio__native-control,
514 .mdc-radio__native-control:disabled {
515 &:not(:checked) + {
516 @content;
517 }
518 }
519}
520
521///
522/// Helps select the radio background only when its native control is in the
523/// disabled & checked state.
524/// @access private
525///
526@mixin if-disabled-checked_ {
527 [aria-disabled='true'] .mdc-radio__native-control,
528 .mdc-radio__native-control:disabled {
529 &:checked + {
530 @content;
531 }
532 }
533}
534
535///
536/// Sets the ink color for radio. This is wrapped in a mixin
537/// that qualifies state such as `_if-enabled`
538/// @access private
539///
540@mixin _ink-color($color, $query: feature-targeting.all()) {
541 $feat-color: feature-targeting.create-target($query, color);
542
543 .mdc-radio__background .mdc-radio__inner-circle {
544 @include feature-targeting.targets($feat-color) {
545 @include theme.property(border-color, $color);
546 }
547 }
548}
549
550@mixin _ink-opacity($opacity) {
551 .mdc-radio__background .mdc-radio__inner-circle {
552 @include theme.property(opacity, $opacity);
553 }
554}
555
556///
557/// Sets the stroke color for radio. This is wrapped in a mixin
558/// that qualifies state such as `_if-enabled`
559/// @access private
560///
561@mixin _stroke-color($color, $query: feature-targeting.all()) {
562 $feat-color: feature-targeting.create-target($query, color);
563
564 .mdc-radio__background .mdc-radio__outer-circle {
565 @include feature-targeting.targets($feat-color) {
566 @include theme.property(border-color, $color);
567 }
568 }
569}
570
571@mixin _stroke-opacity($opacity) {
572 .mdc-radio__background .mdc-radio__outer-circle {
573 @include theme.property(opacity, $opacity);
574 }
575}