_elevation-theme.scss 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444
  1. //
  2. // Copyright 2017 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:map';
  25. @use 'sass:math';
  26. @use 'sass:meta';
  27. @use '@material/animation/variables' as animation-variables;
  28. @use '@material/theme/custom-properties';
  29. @use '@material/base/mixins' as base-mixins;
  30. @use '@material/feature-targeting/feature-targeting';
  31. @use '@material/rtl/rtl';
  32. @use '@material/theme/css';
  33. @use '@material/theme/theme';
  34. @use '@material/theme/validate';
  35. @use '@material/theme/theme-color';
  36. $baseline-color: black !default;
  37. $umbra-opacity: 0.2 !default;
  38. $penumbra-opacity: 0.14 !default;
  39. $ambient-opacity: 0.12 !default;
  40. $umbra-map: (
  41. 0: '0px 0px 0px 0px',
  42. 1: '0px 2px 1px -1px',
  43. 2: '0px 3px 1px -2px',
  44. 3: '0px 3px 3px -2px',
  45. 4: '0px 2px 4px -1px',
  46. 5: '0px 3px 5px -1px',
  47. 6: '0px 3px 5px -1px',
  48. 7: '0px 4px 5px -2px',
  49. 8: '0px 5px 5px -3px',
  50. 9: '0px 5px 6px -3px',
  51. 10: '0px 6px 6px -3px',
  52. 11: '0px 6px 7px -4px',
  53. 12: '0px 7px 8px -4px',
  54. 13: '0px 7px 8px -4px',
  55. 14: '0px 7px 9px -4px',
  56. 15: '0px 8px 9px -5px',
  57. 16: '0px 8px 10px -5px',
  58. 17: '0px 8px 11px -5px',
  59. 18: '0px 9px 11px -5px',
  60. 19: '0px 9px 12px -6px',
  61. 20: '0px 10px 13px -6px',
  62. 21: '0px 10px 13px -6px',
  63. 22: '0px 10px 14px -6px',
  64. 23: '0px 11px 14px -7px',
  65. 24: '0px 11px 15px -7px',
  66. ) !default;
  67. $penumbra-map: (
  68. 0: '0px 0px 0px 0px',
  69. 1: '0px 1px 1px 0px',
  70. 2: '0px 2px 2px 0px',
  71. 3: '0px 3px 4px 0px',
  72. 4: '0px 4px 5px 0px',
  73. 5: '0px 5px 8px 0px',
  74. 6: '0px 6px 10px 0px',
  75. 7: '0px 7px 10px 1px',
  76. 8: '0px 8px 10px 1px',
  77. 9: '0px 9px 12px 1px',
  78. 10: '0px 10px 14px 1px',
  79. 11: '0px 11px 15px 1px',
  80. 12: '0px 12px 17px 2px',
  81. 13: '0px 13px 19px 2px',
  82. 14: '0px 14px 21px 2px',
  83. 15: '0px 15px 22px 2px',
  84. 16: '0px 16px 24px 2px',
  85. 17: '0px 17px 26px 2px',
  86. 18: '0px 18px 28px 2px',
  87. 19: '0px 19px 29px 2px',
  88. 20: '0px 20px 31px 3px',
  89. 21: '0px 21px 33px 3px',
  90. 22: '0px 22px 35px 3px',
  91. 23: '0px 23px 36px 3px',
  92. 24: '0px 24px 38px 3px',
  93. ) !default;
  94. $ambient-map: (
  95. 0: '0px 0px 0px 0px',
  96. 1: '0px 1px 3px 0px',
  97. 2: '0px 1px 5px 0px',
  98. 3: '0px 1px 8px 0px',
  99. 4: '0px 1px 10px 0px',
  100. 5: '0px 1px 14px 0px',
  101. 6: '0px 1px 18px 0px',
  102. 7: '0px 2px 16px 1px',
  103. 8: '0px 3px 14px 2px',
  104. 9: '0px 3px 16px 2px',
  105. 10: '0px 4px 18px 3px',
  106. 11: '0px 4px 20px 3px',
  107. 12: '0px 5px 22px 4px',
  108. 13: '0px 5px 24px 4px',
  109. 14: '0px 5px 26px 4px',
  110. 15: '0px 6px 28px 5px',
  111. 16: '0px 6px 30px 5px',
  112. 17: '0px 6px 32px 5px',
  113. 18: '0px 7px 34px 6px',
  114. 19: '0px 7px 36px 6px',
  115. 20: '0px 8px 38px 7px',
  116. 21: '0px 8px 40px 7px',
  117. 22: '0px 8px 42px 7px',
  118. 23: '0px 9px 44px 8px',
  119. 24: '0px 9px 46px 8px',
  120. ) !default;
  121. // The css property used for elevation. In most cases this should not be changed. It is exposed
  122. // as a variable for abstraction / easy use when needing to reference the property directly, for
  123. // example in a `will-change` rule.
  124. $property: box-shadow !default;
  125. // The default color for the elevation overlay.
  126. $overlay-color: #fff;
  127. // The css property used for elevation overlay transitions. In most cases this should not be changed. It is exposed
  128. // as a variable for abstraction / easy use when needing to reference the property directly, for
  129. // example in a `will-change` rule.
  130. $overlay-property: opacity !default;
  131. // The default duration value for elevation transitions.
  132. $transition-duration: 280ms !default;
  133. // The default easing value for elevation transitions.
  134. $transition-timing-function: animation-variables.$standard-curve-timing-function !default;
  135. ///
  136. /// Sets the elevation transition value.
  137. ///
  138. /// @param {String} $duration - The duration of the transition.
  139. /// @param {String} $easing - The easing function for the transition.
  140. /// @return {String}
  141. ///
  142. @function transition-value(
  143. $duration: $transition-duration,
  144. $easing: $transition-timing-function
  145. ) {
  146. @return #{$property} #{$duration} #{$easing};
  147. }
  148. ///
  149. /// Sets the elevation overlay transition value.
  150. ///
  151. /// @param {String} $duration - The duration of the transition.
  152. /// @param {String} $easing - The easing function for the transition.
  153. /// @return {String}
  154. ///
  155. @function overlay-transition-value(
  156. $duration: $transition-duration,
  157. $easing: $transition-timing-function
  158. ) {
  159. @return #{$overlay-property} #{$duration} #{$easing};
  160. }
  161. ///
  162. /// Creates a box-shadow from the Material elevation system.
  163. /// @param {Number} $level - the level of the Material elevation system.
  164. /// @param {String} $color - the color of the shadow.
  165. /// @param {Number} $opacity-boost [0] - optional opacity boost for the shadow.
  166. /// @return {List} the complete box shadow.
  167. ///
  168. @function _box-shadow($level, $color, $opacity-boost: 0) {
  169. $color: theme-color.prop-value($color);
  170. $umbra-z-value: map.get($umbra-map, $level);
  171. $penumbra-z-value: map.get($penumbra-map, $level);
  172. $ambient-z-value: map.get($ambient-map, $level);
  173. $umbra-color: rgba($color, $umbra-opacity + $opacity-boost);
  174. $penumbra-color: rgba($color, $penumbra-opacity + $opacity-boost);
  175. $ambient-color: rgba($color, $ambient-opacity + $opacity-boost);
  176. @return (
  177. #{'#{$umbra-z-value} #{$umbra-color}'},
  178. #{'#{$penumbra-z-value} #{$penumbra-color}'},
  179. #{$ambient-z-value} $ambient-color
  180. );
  181. }
  182. // Returns the correct box-shadow specified by $z-value.
  183. // The $z-value must be between 0 and 24.
  184. // If $color has an alpha channel, it will be ignored and overridden. To increase the opacity of the shadow, use
  185. // $opacity-boost.
  186. @function elevation-box-shadow(
  187. $z-value,
  188. $color: $baseline-color,
  189. $opacity-boost: 0
  190. ) {
  191. @if $z-value == null {
  192. @return null;
  193. }
  194. @if meta.type-of($z-value) != number or not math.is-unitless($z-value) {
  195. @error "$z-value must be a unitless number, but received '#{$z-value}'";
  196. }
  197. @if $z-value < 0 or $z-value > 24 {
  198. @error "$z-value must be between 0 and 24, but received '#{$z-value}'";
  199. }
  200. @return _box-shadow($z-value, $color, $opacity-boost);
  201. }
  202. ///
  203. /// Returns a shadow or null if params are invalid.
  204. /// @param {Number} $level - the level of the Material elevation system.
  205. /// @param {String} $color - the color of the shadow.
  206. /// @return {List|null} the complete box shadow or null.
  207. ///
  208. @function _shadow($level, $color) {
  209. @if $level == null and $color == null {
  210. // Do not emit a warning if both are null, which means the user did not
  211. // provide tokens.
  212. @return null;
  213. }
  214. @if $level == null or $color == null {
  215. // If one of the tokens is null, emit a warning: the user may not realize
  216. // that both are required.
  217. @warn "both $level and $color are required; received $level: '#{$level}', $color: '#{$color}'";
  218. @return null;
  219. }
  220. @if $level < 0 or $level > 24 {
  221. @warn "$level must be between 0 and 24; received '#{$level}'";
  222. @return null;
  223. }
  224. @return _box-shadow($level, $color);
  225. }
  226. @function get-elevation($level) {
  227. @return (box-shadow: elevation-box-shadow($level));
  228. }
  229. ///
  230. /// Sets the shadow of the element.
  231. ///
  232. /// @param {String} $box-shadow - The shadow to apply to the element.
  233. ///
  234. @mixin shadow($box-shadow, $query: feature-targeting.all()) {
  235. $feat-color: feature-targeting.create-target($query, color);
  236. @include feature-targeting.targets($feat-color) {
  237. @include theme.property(box-shadow, $box-shadow);
  238. }
  239. }
  240. ///
  241. /// Sets the elevation overlay surface required positioning.
  242. ///
  243. @mixin overlay-surface-position($query: feature-targeting.all()) {
  244. $feat-structure: feature-targeting.create-target($query, structure);
  245. @include feature-targeting.targets($feat-structure) {
  246. /* @alternate */
  247. position: relative;
  248. }
  249. }
  250. ///
  251. /// Sets the dimensions of the elevation overlay, including positioning and sizing.
  252. ///
  253. /// @param {Number} $width - The width of the elevation overlay
  254. /// @param {Number} [$height] - The height of the elevation overlay
  255. /// @param {Boolean} [$has-content-sizing] - Set to false if the container has no content sizing
  256. ///
  257. @mixin overlay-dimensions(
  258. $width,
  259. $height: $width,
  260. $has-content-sizing: true,
  261. $query: feature-targeting.all()
  262. ) {
  263. $feat-structure: feature-targeting.create-target($query, structure);
  264. .mdc-elevation-overlay {
  265. @include feature-targeting.targets($feat-structure) {
  266. @include theme.property(width, $width);
  267. @include theme.property(height, $height);
  268. @if $has-content-sizing {
  269. top: 0;
  270. @include rtl.ignore-next-line();
  271. left: 0;
  272. } @else {
  273. top: 50%;
  274. @include rtl.ignore-next-line();
  275. left: 50%;
  276. @include rtl.ignore-next-line();
  277. transform: translate(-50%, -50%);
  278. }
  279. }
  280. }
  281. }
  282. ///
  283. /// Sets the elevation overlay fill color.
  284. /// Expected to be called directly on the elevation overlay element.
  285. ///
  286. /// @param {Color} $color - The color of the elevation overlay.
  287. ///
  288. @mixin overlay-fill-color($color, $query: feature-targeting.all()) {
  289. $feat-color: feature-targeting.create-target($query, color);
  290. @include feature-targeting.targets($feat-color) {
  291. @include theme.property(background-color, $color);
  292. }
  293. }
  294. ///
  295. /// Applies the given color to the container of the overlay.
  296. /// @param {color} $color - the color of the overlay container
  297. ///
  298. @mixin overlay-container-color($color, $query: feature-targeting.all()) {
  299. .mdc-elevation-overlay {
  300. @include overlay-fill-color($color, $query: $query);
  301. }
  302. }
  303. ///
  304. /// Sets the elevation overlay opacity.
  305. /// Expected to be called from a parent element.
  306. ///
  307. /// @param {Number} $opacity - The opacity of the elevation overlay.
  308. ///
  309. @mixin overlay-opacity($opacity, $query: feature-targeting.all()) {
  310. $feat-color: feature-targeting.create-target($query, color);
  311. .mdc-elevation-overlay {
  312. @include feature-targeting.targets($feat-color) {
  313. @include theme.property(opacity, $opacity);
  314. }
  315. }
  316. }
  317. // Applies the correct CSS rules to an element to give it the elevation specified by $z-value.
  318. // The $z-value must be between 0 and 24.
  319. // If $color has an alpha channel, it will be ignored and overridden. To increase the opacity of the shadow, use
  320. // $opacity-boost.
  321. @mixin elevation(
  322. $z-value,
  323. $color: $baseline-color,
  324. $opacity-boost: 0,
  325. $query: feature-targeting.all()
  326. ) {
  327. $box-shadow: elevation-box-shadow(
  328. $z-value,
  329. $color: $color,
  330. $opacity-boost: $opacity-boost
  331. );
  332. @include shadow($box-shadow, $query: $query);
  333. }
  334. ///
  335. /// Represents the configurable values of the elevation theme.
  336. ///
  337. $_theme-values: (
  338. shadow: null,
  339. overlay-opacity: null,
  340. overlay-color: null,
  341. );
  342. ///
  343. /// Applies the shadow theme with the given $resolver function.
  344. /// @param {Function} $resolver - a function that returns a valid theme config.
  345. /// @see resolver for an example and expected arguments and return value.
  346. /// Accepts the following optional keyword args:
  347. /// @param {Number} $elevation - the level in the elevation system.
  348. /// @param {String} $shadow-color - the color used for the shadow.
  349. ///
  350. @mixin with-resolver($resolver, $query: feature-targeting.all(), $args...) {
  351. @if $resolver {
  352. @include _theme(meta.call($resolver, $args...), $query: $query);
  353. }
  354. }
  355. ///
  356. /// Applies the given theme with validation.
  357. /// @param {Map} $theme - @see $_theme-values for accepted theme properties.
  358. ///
  359. @mixin theme-styles($theme: (), $query: feature-targeting.all()) {
  360. $theme: validate.theme-styles($_theme-values, $theme, $require-all: false);
  361. @include _theme($theme, $query: $query);
  362. }
  363. ///
  364. /// Applies the given theme.
  365. /// @param {Map} $theme - @see $_theme-values for accepted theme properties.
  366. ///
  367. @mixin _theme($theme: (), $query: feature-targeting.all()) {
  368. @include shadow(map.get($theme, shadow), $query: $query);
  369. @include overlay-opacity(map.get($theme, overlay-opacity), $query: $query);
  370. @include overlay-container-color(
  371. map.get($theme, overlay-color),
  372. $query: $query
  373. );
  374. }
  375. ///
  376. /// Transforms the following optional parameters into a theme config.
  377. /// @param {Number} $elevation - the level of the elevation system in Material.
  378. /// @param {String} $shadow-color - the color to be used by the shadow.
  379. /// @return {Map} @see $_theme-values for accepted theme properties.
  380. ///
  381. @function resolver($args...) {
  382. $opts: meta.keywords($args);
  383. $elevation: map.get($opts, elevation);
  384. $shadow-color: map.get($opts, shadow-color);
  385. @if custom-properties.is-custom-prop($elevation) {
  386. @return _resolve-custom-props($elevation, $shadow-color);
  387. }
  388. @return (shadow: _shadow($elevation, $shadow-color));
  389. }
  390. @function _resolve-custom-props($elevation, $shadow-color) {
  391. $fallback-dp: custom-properties.get-fallback($elevation);
  392. $fallback-shadow-color: custom-properties.get-fallback($shadow-color);
  393. $shadow: custom-properties.set-fallback(
  394. $elevation,
  395. _shadow($fallback-dp, $fallback-shadow-color)
  396. );
  397. @return (shadow: $shadow);
  398. }