_icon-button-theme.scss 11 KB

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