UNPKG

11.7 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:math';
27@use 'sass:map';
28@use 'sass:meta';
29@use '@material/density/functions' as density-functions;
30@use '@material/density/variables' as density-variables;
31@use '@material/elevation/elevation-theme';
32@use '@material/feature-targeting/feature-targeting';
33@use '@material/focus-ring/focus-ring';
34@use '@material/ripple/ripple-theme';
35@use '@material/rtl/rtl';
36@use '@material/dom/dom';
37@use '@material/theme/keys';
38@use '@material/theme/state';
39@use '@material/theme/theme';
40@use '@material/theme/theme-color';
41@use '@material/touch-target/mixins' as touch-target-mixins;
42
43$ripple-target: '.mdc-icon-button__ripple';
44
45$icon-size: 24px !default;
46$size: 48px !default;
47$minimum-height: 28px !default;
48$maximum-height: $size !default;
49$container-shape: 50%;
50$density-scale: density-variables.$default-scale !default;
51$density-config: (
52 size: (
53 default: $size,
54 maximum: $maximum-height,
55 minimum: $minimum-height,
56 ),
57) !default;
58
59$_custom-property-prefix: 'icon-button';
60
61$light-theme: (
62 disabled-icon-color: theme-color.$on-surface,
63 disabled-icon-opacity: 0.38,
64 icon-color: theme-color.$primary,
65 icon-size: $icon-size,
66 focus-icon-color: theme-color.$primary,
67 focus-state-layer-color: theme-color.$primary,
68 focus-state-layer-opacity: 0.12,
69 hover-icon-color: theme-color.$primary,
70 hover-state-layer-color: theme-color.$primary,
71 hover-state-layer-opacity: 0.08,
72 pressed-icon-color: theme-color.$primary,
73 pressed-state-layer-color: theme-color.$primary,
74 pressed-state-layer-opacity: 0.12,
75 state-layer-size: $size,
76);
77
78@mixin theme($theme) {
79 @include theme.validate-theme($light-theme, $theme);
80
81 @include keys.declare-custom-properties(
82 $theme,
83 $prefix: $_custom-property-prefix
84 );
85}
86
87@mixin theme-styles($theme) {
88 @include theme.validate-theme($light-theme, $theme);
89
90 $theme: keys.create-theme-properties(
91 $theme,
92 $prefix: $_custom-property-prefix
93 );
94
95 @include _state-layer-size($size: map.get($theme, state-layer-size));
96 @include _icon-size(map.get($theme, icon-size));
97 @include _disabled-icon-opacity(map.get($theme, disabled-icon-opacity));
98 @include _icon-color-with-map(
99 (
100 default: map.get($theme, icon-color),
101 disabled: map.get($theme, disabled-icon-color),
102 focus: map.get($theme, focus-icon-color),
103 hover: map.get($theme, hover-icon-color),
104 pressed: map.get($theme, pressed-icon-color),
105 )
106 );
107
108 // States styles
109 @include ripple-theme.theme-styles(
110 (
111 focus-state-layer-color: map.get($theme, focus-state-layer-color),
112 focus-state-layer-opacity: map.get($theme, focus-state-layer-opacity),
113 hover-state-layer-color: map.get($theme, hover-state-layer-color),
114 hover-state-layer-opacity: map.get($theme, hover-state-layer-opacity),
115 pressed-state-layer-color: map.get($theme, pressed-state-layer-color),
116 pressed-state-layer-opacity: map.get($theme, pressed-state-layer-opacity),
117 ),
118 $ripple-target: $ripple-target
119 );
120}
121
122///
123/// Sets the density scale for icon button.
124///
125/// @param {Number | String} $density-scale - Density scale value for component.
126/// Supported density scale values range from `-5` to `0`, with `0` being the default.
127///
128@mixin density($density-scale, $query: feature-targeting.all()) {
129 $size: density-functions.prop-value(
130 $density-config: $density-config,
131 $density-scale: $density-scale,
132 $property-name: size,
133 );
134
135 @include size($size, $query: $query);
136}
137
138///
139/// Sets the size of the icon-button.
140///
141/// @param {Number} $size - Size value for icon-button.
142/// Size will set the width, height, and padding for the overall component.
143///
144@mixin size($size, $query: feature-targeting.all()) {
145 $feat-structure: feature-targeting.create-target($query, structure);
146
147 @include feature-targeting.targets($feat-structure) {
148 width: $size;
149 height: $size;
150 padding: math.div($size - $icon-size, 2);
151 }
152
153 .mdc-icon-button__focus-ring {
154 @include feature-targeting.targets($feat-structure) {
155 display: none;
156 }
157 }
158
159 @include ripple-theme.focus {
160 .mdc-icon-button__focus-ring {
161 @include dom.forced-colors-mode($exclude-ie11: true) {
162 @include focus-ring.focus-ring(
163 $query: $query,
164 $container-outer-padding-vertical: 0,
165 $container-outer-padding-horizontal: 0
166 );
167 }
168
169 @include feature-targeting.targets($feat-structure) {
170 display: block;
171 max-height: $size;
172 max-width: $size;
173 }
174 }
175 }
176
177 &.mdc-icon-button--reduced-size {
178 $component-size: $size;
179 // Icon button ripple size is capped at 40px for icon buttons with
180 // densities -1 and 0 (icon buttons with sizes 44x44 and 48x48px).
181 // See http://b/192353968 for more info.
182 @if $size >= 40px and $size <= 48px {
183 $component-size: 40px;
184 }
185
186 .mdc-icon-button__ripple {
187 @include feature-targeting.targets($feat-structure) {
188 width: $component-size;
189 height: $component-size;
190 }
191
192 @include touch-target-mixins.margin(
193 $component-height: $component-size,
194 $component-width: $component-size,
195 $touch-target-height: $size,
196 $touch-target-width: $size,
197 $query: $query
198 );
199 }
200
201 @include ripple-theme.focus {
202 .mdc-icon-button__focus-ring {
203 @include feature-targeting.targets($feat-structure) {
204 max-height: $component-size;
205 max-width: $component-size;
206 }
207 }
208 }
209 }
210
211 .mdc-icon-button__touch {
212 @include touch-target-mixins.touch-target(
213 $set-width: true,
214 $query: $query,
215 $height: $size,
216 $width: $size
217 );
218 }
219}
220
221///
222/// Sets the width, height and padding of icon button. Also changes the size of
223/// the icon itself based on button size.
224///
225/// @param {Number} $width - Width value for icon-button.
226/// @param {Number} $height - Height value for icon-button. (default: $width)
227/// @param {Number} $padding - Padding value for icon-button. (default: max($width, $height) / 2)
228/// @deprecated
229/// This mixin provides too much of low level customization.
230/// Please use mdc-icon-button-size instead.
231///
232@mixin icon-size(
233 $width,
234 $height: $width,
235 $padding: math.div(math.max($width, $height), 2),
236 $query: feature-targeting.all()
237) {
238 $feat-structure: feature-targeting.create-target($query, structure);
239
240 $component-width: $width + $padding * 2;
241 $component-height: $height + $padding * 2;
242
243 @include feature-targeting.targets($feat-structure) {
244 width: $component-width;
245 height: $component-height;
246 padding: $padding;
247 font-size: math.max($width, $height);
248 }
249
250 svg,
251 img {
252 @include feature-targeting.targets($feat-structure) {
253 width: $width;
254 height: $height;
255 }
256 }
257}
258
259///
260/// Sets the font color and the ripple color to the provided color value.
261/// @param {Color} $color - The desired font and ripple color.
262///
263@mixin ink-color($color, $query: feature-targeting.all()) {
264 @include ink-color_($color, $query: $query);
265 @include ripple-theme.states(
266 $color,
267 $query: $query,
268 $ripple-target: $ripple-target
269 );
270}
271
272///
273/// Flips icon only in RTL context.
274///
275@mixin flip-icon-in-rtl($query: feature-targeting.all()) {
276 $feat-structure: feature-targeting.create-target($query, structure);
277
278 .mdc-button__icon {
279 @include rtl.rtl {
280 @include feature-targeting.targets($feat-structure) {
281 @include rtl.ignore-next-line();
282 transform: rotate(180deg);
283 }
284 }
285 }
286}
287
288///
289/// Sets the font color to the provided color value for a disabled icon button.
290/// @param {Color} $color - The desired font color.
291///
292@mixin disabled-ink-color($color, $query: feature-targeting.all()) {
293 @include if-disabled_ {
294 @include ink-color_($color, $query: $query);
295 }
296}
297
298///
299/// Includes ad-hoc high contrast mode support.
300///
301@mixin high-contrast-mode-shim($query: feature-targeting.all()) {
302 $feat-structure: feature-targeting.create-target($query, structure);
303
304 @include feature-targeting.targets($feat-structure) {
305 // TODO(b/175806874): Use the DOM border mixin after the ripple is moved
306 // away from :before to a dedicated element.
307 outline: solid 3px transparent;
308
309 &:focus {
310 outline: double 5px transparent;
311 }
312 }
313}
314
315///
316/// Sets the font color to the provided color value. This can be wrapped in
317/// a state qualifier such as `mdc-icon-button-if-disabled_`.
318/// @access private
319///
320@mixin ink-color_($color, $query: feature-targeting.all()) {
321 $feat-color: feature-targeting.create-target($query, color);
322
323 @include feature-targeting.targets($feat-color) {
324 @include theme.property(color, $color);
325 }
326}
327
328@mixin _state-layer-size($size) {
329 @include theme.property(height, $size);
330 @include theme.property(width, $size);
331}
332
333@mixin _icon-size($size) {
334 @include theme.property(font-size, $size);
335
336 svg,
337 img {
338 @include theme.property(width, $size);
339 @include theme.property(height, $size);
340 }
341}
342
343///
344/// Sets the icon opacity to the given opacity.
345/// @access private
346///
347@mixin _disabled-icon-opacity($opacity) {
348 &:disabled {
349 @include theme.property(opacity, $opacity);
350 }
351}
352
353///
354/// Sets the icon color to the given color.
355/// @param {map} $color-map - The desired icon color, specified as a map of
356/// colors with states {default, disabled, focus, hover, pressed} as keys.
357/// @access private
358///
359@mixin _icon-color-with-map($color-map) {
360 @include ink-color_(state.get-default-state($color-map));
361
362 $disabled: state.get-disabled-state($color-map);
363 @if $disabled {
364 &:disabled {
365 @include ink-color_($disabled);
366 }
367 }
368
369 $focus: state.get-focus-state($color-map);
370 @if $focus {
371 @include ripple-theme.focus {
372 @include ink-color_($focus);
373 }
374 }
375
376 $hover: state.get-hover-state($color-map);
377 @if $hover {
378 &:hover {
379 @include ink-color_($hover);
380 }
381 }
382
383 $pressed: state.get-pressed-state($color-map);
384 @if $pressed {
385 @include ripple-theme.active {
386 @include ink-color_($pressed);
387 }
388 }
389}
390
391@mixin _states-colors($color-map) {
392 // TODO(b/191298796): support focused & pressed key colors.
393
394 $hover: map.get($color-map, hover);
395 @if $hover {
396 @include ripple-theme.states-base-color(
397 $color: $hover,
398 $ripple-target: $ripple-target
399 );
400 }
401}
402
403///
404/// Helps style the icon button in its disabled state.
405/// @access private
406///
407@mixin if-disabled_ {
408 &:disabled {
409 @content;
410 }
411}