_switch-theme.scss 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696
  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:color';
  25. @use 'sass:map';
  26. @use 'sass:meta';
  27. @use '@material/density/density';
  28. @use '@material/dom/dom';
  29. @use '@material/elevation/elevation-theme';
  30. @use '@material/ripple/ripple-theme';
  31. @use '@material/theme/color-palette';
  32. @use '@material/theme/custom-properties';
  33. @use '@material/theme/keys';
  34. @use '@material/theme/shadow-dom';
  35. @use '@material/theme/state';
  36. @use '@material/theme/theme-color';
  37. @use '@material/theme/theme';
  38. @use '@material/tokens/resolvers';
  39. @use '@material/shape/shape';
  40. @use './switch';
  41. $_density-config: (
  42. size: (
  43. minimum: 28px,
  44. default: 48px,
  45. maximum: 48px,
  46. ),
  47. );
  48. $_hairline: color-palette.$grey-300;
  49. $_inverse-primary: color.scale(
  50. theme-color.prop-value(primary),
  51. $lightness: 75%
  52. );
  53. $_on-surface: color-palette.$grey-800;
  54. $_on-surface-variant: color-palette.$grey-700;
  55. $_on-surface-state-content: color-palette.$grey-900;
  56. $_primary-state-content: color.scale(
  57. theme-color.prop-value(primary),
  58. $blackness: 50%
  59. );
  60. /// TODO: Change to private when MWC has better access
  61. /// @access private
  62. $selectors: (
  63. disabled: ':disabled',
  64. focus: ':focus',
  65. hover: ':hover',
  66. pressed: ':active',
  67. selected: '.mdc-switch--selected',
  68. unselected: '.mdc-switch--unselected',
  69. );
  70. $light-theme: (
  71. disabled-handle-elevation: 0,
  72. disabled-handle-opacity: 0.38,
  73. disabled-selected-handle-color: $_on-surface,
  74. disabled-selected-icon-color: on-primary,
  75. disabled-selected-icon-opacity: 0.38,
  76. disabled-selected-track-color: $_on-surface,
  77. disabled-track-opacity: 0.12,
  78. disabled-unselected-handle-color: $_on-surface,
  79. disabled-unselected-icon-color: on-primary,
  80. disabled-unselected-icon-opacity: 0.38,
  81. disabled-unselected-track-color: $_on-surface,
  82. handle-elevation: 1,
  83. handle-height: 20px,
  84. handle-shadow-color: elevation-theme.$baseline-color,
  85. handle-shape: 10px,
  86. handle-surface-color: surface,
  87. handle-width: 20px,
  88. selected-focus-handle-color: $_primary-state-content,
  89. selected-focus-state-layer-color: primary,
  90. selected-focus-state-layer-opacity: 0.12,
  91. selected-focus-track-color: $_inverse-primary,
  92. selected-handle-color: primary,
  93. selected-hover-handle-color: $_primary-state-content,
  94. selected-hover-state-layer-color: primary,
  95. selected-hover-state-layer-opacity: 0.04,
  96. selected-hover-track-color: $_inverse-primary,
  97. selected-icon-color: on-primary,
  98. selected-icon-size: 18px,
  99. selected-pressed-handle-color: $_primary-state-content,
  100. selected-pressed-state-layer-color: primary,
  101. selected-pressed-state-layer-opacity: 0.1,
  102. selected-pressed-track-color: $_inverse-primary,
  103. selected-track-color: $_inverse-primary,
  104. state-layer-size: 48px,
  105. track-height: 14px,
  106. track-shape: 7px,
  107. track-width: 36px,
  108. unselected-focus-handle-color: $_on-surface-state-content,
  109. unselected-focus-state-layer-color: $_on-surface,
  110. unselected-focus-state-layer-opacity: 0.12,
  111. unselected-focus-track-color: $_hairline,
  112. unselected-handle-color: $_on-surface-variant,
  113. unselected-hover-handle-color: $_on-surface-state-content,
  114. unselected-hover-state-layer-color: $_on-surface,
  115. unselected-hover-state-layer-opacity: 0.04,
  116. unselected-hover-track-color: $_hairline,
  117. unselected-icon-color: on-primary,
  118. unselected-icon-size: 18px,
  119. unselected-pressed-handle-color: $_on-surface-state-content,
  120. unselected-pressed-state-layer-color: $_on-surface,
  121. unselected-pressed-state-layer-opacity: 0.1,
  122. unselected-pressed-track-color: $_hairline,
  123. unselected-track-color: $_hairline,
  124. );
  125. $forced-colors-theme: (
  126. disabled-handle-opacity: 1,
  127. disabled-selected-icon-color: GrayText,
  128. disabled-selected-icon-opacity: 1,
  129. disabled-track-opacity: 1,
  130. disabled-unselected-icon-color: GrayText,
  131. disabled-unselected-icon-opacity: 1,
  132. selected-icon-color: ButtonText,
  133. unselected-icon-color: ButtonText,
  134. );
  135. @function density($density-scale) {
  136. $size: density.prop-value(
  137. $density-config: $_density-config,
  138. $density-scale: $density-scale,
  139. $property-name: size,
  140. );
  141. @return (state-layer-size: $size);
  142. }
  143. @mixin theme($theme, $resolvers: resolvers.$material) {
  144. @include theme.validate-theme($light-theme, $theme);
  145. // TODO(b/185172301): replace with improved feature targeting
  146. // IE11 Fallback
  147. @if shadow-dom.$css-selector-fallback-declarations {
  148. @include custom-properties.configure($emit-custom-properties: false) {
  149. @include dom.ie11-support {
  150. @include theme-styles($theme, $resolvers: $resolvers);
  151. }
  152. }
  153. }
  154. $theme: _resolve-theme($theme, $resolvers);
  155. @include keys.declare-custom-properties($theme, switch);
  156. }
  157. @function _resolve-theme($theme, $resolvers) {
  158. @return map.merge(
  159. $theme,
  160. _resolve-theme-handle-elevation(
  161. $theme,
  162. map.get($resolvers, elevation),
  163. disabled-handle-elevation,
  164. handle-elevation
  165. )
  166. );
  167. }
  168. @function _resolve-theme-handle-elevation($theme, $resolver, $keys...) {
  169. @if $resolver == null {
  170. @return $theme;
  171. }
  172. @each $key in $keys {
  173. // Resolve the value for each state key.
  174. $resolved-value: meta.call(
  175. $resolver,
  176. $elevation: map.get($theme, $key),
  177. $shadow-color: map.get($theme, handle-shadow-color)
  178. );
  179. // Update the theme with the resolved value.
  180. $theme: map.set($theme, $key, $resolved-value);
  181. }
  182. @return $theme;
  183. }
  184. @mixin theme-styles($theme, $resolvers: resolvers.$material) {
  185. @include theme.validate-theme-styles($light-theme, $theme);
  186. $theme: keys.create-theme-properties($theme, switch);
  187. @include _selected-handle-color(
  188. (
  189. default: map.get($theme, selected-handle-color),
  190. disabled: map.get($theme, disabled-selected-handle-color),
  191. focus: map.get($theme, selected-focus-handle-color),
  192. hover: map.get($theme, selected-hover-handle-color),
  193. pressed: map.get($theme, selected-pressed-handle-color),
  194. )
  195. );
  196. @include _unselected-handle-color(
  197. (
  198. default: map.get($theme, unselected-handle-color),
  199. disabled: map.get($theme, disabled-unselected-handle-color),
  200. focus: map.get($theme, unselected-focus-handle-color),
  201. hover: map.get($theme, unselected-hover-handle-color),
  202. pressed: map.get($theme, unselected-pressed-handle-color),
  203. )
  204. );
  205. @include _handle-surface-color(map.get($theme, handle-surface-color));
  206. @include _handle-elevation(
  207. map.get($resolvers, elevation),
  208. map.get($theme, handle-shadow-color),
  209. (
  210. default: map.get($theme, handle-elevation),
  211. disabled: map.get($theme, disabled-handle-elevation),
  212. )
  213. );
  214. @include _handle-height(map.get($theme, handle-height));
  215. @include _handle-opacity(
  216. (
  217. disabled: map.get($theme, disabled-handle-opacity),
  218. )
  219. );
  220. @include _handle-shape(map.get($theme, handle-shape));
  221. @include _handle-width(map.get($theme, handle-width));
  222. @include _selected-icon-color(
  223. (
  224. default: map.get($theme, selected-icon-color),
  225. disabled: map.get($theme, disabled-selected-icon-color),
  226. )
  227. );
  228. @include _unselected-icon-color(
  229. (
  230. default: map.get($theme, unselected-icon-color),
  231. disabled: map.get($theme, disabled-unselected-icon-color),
  232. )
  233. );
  234. @include _selected-icon-opacity(
  235. (
  236. disabled: map.get($theme, disabled-selected-icon-opacity),
  237. )
  238. );
  239. @include _unselected-icon-opacity(
  240. (
  241. disabled: map.get($theme, disabled-unselected-icon-opacity),
  242. )
  243. );
  244. @include _selected-icon-size(map.get($theme, selected-icon-size));
  245. @include _unselected-icon-size(map.get($theme, unselected-icon-size));
  246. @include _selected-ripple-color(
  247. (
  248. focus: map.get($theme, selected-focus-state-layer-color),
  249. hover: map.get($theme, selected-hover-state-layer-color),
  250. pressed: map.get($theme, selected-pressed-state-layer-color),
  251. )
  252. );
  253. @include _unselected-ripple-color(
  254. (
  255. focus: map.get($theme, unselected-focus-state-layer-color),
  256. hover: map.get($theme, unselected-hover-state-layer-color),
  257. pressed: map.get($theme, unselected-pressed-state-layer-color),
  258. )
  259. );
  260. @include _selected-ripple-opacity(
  261. (
  262. focus: map.get($theme, selected-focus-state-layer-opacity),
  263. hover: map.get($theme, selected-hover-state-layer-opacity),
  264. pressed: map.get($theme, selected-pressed-state-layer-opacity),
  265. )
  266. );
  267. @include _unselected-ripple-opacity(
  268. (
  269. focus: map.get($theme, unselected-focus-state-layer-opacity),
  270. hover: map.get($theme, unselected-hover-state-layer-opacity),
  271. pressed: map.get($theme, unselected-pressed-state-layer-opacity),
  272. )
  273. );
  274. @include _state-layer-size(map.get($theme, state-layer-size));
  275. @include _track-height(map.get($theme, track-height));
  276. @include _track-opacity(
  277. (
  278. disabled: map.get($theme, disabled-track-opacity),
  279. )
  280. );
  281. @include _track-selected-color(
  282. (
  283. default: map.get($theme, selected-track-color),
  284. disabled: map.get($theme, disabled-selected-track-color),
  285. focus: map.get($theme, selected-focus-track-color),
  286. hover: map.get($theme, selected-hover-track-color),
  287. pressed: map.get($theme, selected-pressed-track-color),
  288. )
  289. );
  290. @include _track-unselected-color(
  291. (
  292. default: map.get($theme, unselected-track-color),
  293. disabled: map.get($theme, disabled-unselected-track-color),
  294. focus: map.get($theme, unselected-focus-track-color),
  295. hover: map.get($theme, unselected-hover-track-color),
  296. pressed: map.get($theme, unselected-pressed-track-color),
  297. )
  298. );
  299. @include _track-shape(map.get($theme, track-shape));
  300. @include _track-width(map.get($theme, track-width));
  301. }
  302. @mixin _handle-color($colors) {
  303. @include state.default($selectors) {
  304. @include _set-handle-color(state.get-default-state($colors));
  305. }
  306. @include state.hover($selectors) {
  307. @include _set-handle-color(state.get-hover-state($colors));
  308. }
  309. @include state.focus($selectors) {
  310. @include _set-handle-color(state.get-focus-state($colors));
  311. }
  312. @include state.pressed($selectors) {
  313. @include _set-handle-color(state.get-pressed-state($colors));
  314. }
  315. @include state.disabled($selectors) {
  316. @include _set-handle-color(state.get-disabled-state($colors));
  317. }
  318. }
  319. @mixin _set-handle-color($color) {
  320. .mdc-switch__handle {
  321. &::after {
  322. @include theme.property(background, $color);
  323. }
  324. }
  325. }
  326. @mixin _selected-handle-color($colors) {
  327. @include state.selected($selectors) {
  328. @include _handle-color($colors);
  329. }
  330. }
  331. @mixin _unselected-handle-color($colors) {
  332. @include state.unselected($selectors) {
  333. @include _handle-color($colors);
  334. }
  335. }
  336. @mixin _handle-surface-color($color) {
  337. .mdc-switch__handle {
  338. // Sets the surface color for the handle. This is used so that when an
  339. // opacity is applied to the "main" handle color, it will not bleed through
  340. // and appear transparent on top of the track.
  341. &::before {
  342. @include theme.property(background, $color);
  343. }
  344. }
  345. }
  346. @mixin _handle-elevation($resolver, $shadow-color, $elevations) {
  347. @include state.default($selectors) {
  348. @include _set-handle-elevation(
  349. $resolver,
  350. $elevation: state.get-default-state($elevations),
  351. $shadow-color: $shadow-color
  352. );
  353. }
  354. @include state.disabled($selectors) {
  355. @include _set-handle-elevation(
  356. $resolver,
  357. $elevation: state.get-disabled-state($elevations),
  358. $shadow-color: $shadow-color
  359. );
  360. }
  361. }
  362. @mixin _set-handle-elevation($resolver, $args...) {
  363. .mdc-switch__shadow {
  364. @include elevation-theme.with-resolver($resolver, $args...);
  365. }
  366. }
  367. @mixin _handle-height($height) {
  368. .mdc-switch__focus-ring-wrapper,
  369. .mdc-switch__handle {
  370. @include theme.property(height, $height);
  371. }
  372. }
  373. @mixin _handle-opacity($opacities) {
  374. @include state.disabled($selectors) {
  375. @include _set-handle-opacity(state.get-disabled-state($opacities));
  376. }
  377. }
  378. @mixin _set-handle-opacity($opacity) {
  379. .mdc-switch__handle {
  380. // Only apply to the ::after pseudo element, which is the handle's "main"
  381. // color. The ::before pseudo element is the surface color, which prevents
  382. // the handle from bleeding through on the track.
  383. &::after {
  384. @include theme.property(opacity, $opacity);
  385. }
  386. }
  387. }
  388. @mixin _handle-shape($shape) {
  389. .mdc-switch__handle {
  390. @include shape.radius($shape);
  391. }
  392. }
  393. @mixin _handle-width($width) {
  394. .mdc-switch__handle {
  395. @include theme.property(width, $width);
  396. }
  397. .mdc-switch__handle-track {
  398. @include theme.property(
  399. width,
  400. 'calc(100% - width)',
  401. $replace: (width: $width)
  402. );
  403. }
  404. }
  405. @mixin _icon-color($colors) {
  406. @include state.default($selectors) {
  407. @include _set-icon-color(state.get-default-state($colors));
  408. }
  409. @include state.disabled($selectors) {
  410. @include _set-icon-color(state.get-disabled-state($colors));
  411. }
  412. }
  413. @mixin _set-icon-color($color) {
  414. .mdc-switch__icon {
  415. @include theme.property(fill, $color);
  416. }
  417. }
  418. @mixin _selected-icon-color($colors) {
  419. @include state.selected($selectors) {
  420. @include _icon-color($colors);
  421. }
  422. }
  423. @mixin _unselected-icon-color($colors) {
  424. @include state.unselected($selectors) {
  425. @include _icon-color($colors);
  426. }
  427. }
  428. @mixin _icon-opacity($opacities) {
  429. @include state.disabled($selectors) {
  430. @include _set-icon-opacity(state.get-disabled-state($opacities));
  431. }
  432. }
  433. @mixin _set-icon-opacity($opacity) {
  434. .mdc-switch__icons {
  435. @include theme.property(opacity, $opacity);
  436. }
  437. }
  438. @mixin _selected-icon-opacity($opacities) {
  439. @include state.selected($selectors) {
  440. @include _icon-opacity($opacities);
  441. }
  442. }
  443. @mixin _unselected-icon-opacity($opacities) {
  444. @include state.unselected($selectors) {
  445. @include _icon-opacity($opacities);
  446. }
  447. }
  448. @mixin _icon-size($size) {
  449. .mdc-switch__icon {
  450. @include theme.property(width, $size);
  451. @include theme.property(height, $size);
  452. }
  453. }
  454. @mixin _selected-icon-size($size) {
  455. @include state.selected($selectors) {
  456. @include _icon-size($size);
  457. }
  458. }
  459. @mixin _unselected-icon-size($size) {
  460. @include state.unselected($selectors) {
  461. @include _icon-size($size);
  462. }
  463. }
  464. @mixin _ripple-color($colors) {
  465. @include state.independent-elements(pressed) {
  466. @include state.hover($selectors) {
  467. @include ripple-theme.states-base-color(
  468. state.get-hover-state($colors),
  469. $ripple-target: switch.$ripple-target
  470. );
  471. }
  472. @include state.focus($selectors) {
  473. @include ripple-theme.states-base-color(
  474. state.get-focus-state($colors),
  475. $ripple-target: switch.$ripple-target
  476. );
  477. }
  478. @include state.pressed($selectors) {
  479. @include ripple-theme.states-base-color(
  480. state.get-pressed-state($colors),
  481. $ripple-target: switch.$ripple-target
  482. );
  483. }
  484. }
  485. }
  486. @mixin _selected-ripple-color($colors) {
  487. @include state.selected($selectors) {
  488. @include _ripple-color($colors);
  489. }
  490. }
  491. @mixin _unselected-ripple-color($colors) {
  492. @include state.unselected($selectors) {
  493. @include _ripple-color($colors);
  494. }
  495. }
  496. @mixin _ripple-opacity($opacities) {
  497. @include state.independent-elements(pressed) {
  498. @include state.hover($selectors) {
  499. @include ripple-theme.states-hover-opacity(
  500. state.get-hover-state($opacities),
  501. $ripple-target: switch.$ripple-target
  502. );
  503. }
  504. @include state.focus($selectors) {
  505. @include ripple-theme.states-focus-opacity(
  506. state.get-focus-state($opacities),
  507. $ripple-target: switch.$ripple-target
  508. );
  509. }
  510. @include state.pressed($selectors) {
  511. @include ripple-theme.states-press-opacity(
  512. state.get-pressed-state($opacities),
  513. $ripple-target: switch.$ripple-target
  514. );
  515. }
  516. }
  517. }
  518. @mixin _selected-ripple-opacity($opacities) {
  519. @include state.selected($selectors) {
  520. @include _ripple-opacity($opacities);
  521. }
  522. }
  523. @mixin _unselected-ripple-opacity($opacities) {
  524. @include state.unselected($selectors) {
  525. @include _ripple-opacity($opacities);
  526. }
  527. }
  528. @mixin _state-layer-size($size) {
  529. .mdc-switch__ripple {
  530. @include theme.property(height, $size);
  531. @include theme.property(width, $size);
  532. }
  533. }
  534. @mixin _track-height($height) {
  535. .mdc-switch__track {
  536. @include theme.property(height, $height);
  537. }
  538. }
  539. @mixin _track-opacity($opacities) {
  540. @include state.disabled($selectors) {
  541. @include _set-track-opacity(state.get-disabled-state($opacities));
  542. }
  543. }
  544. @mixin _set-track-opacity($opacity) {
  545. .mdc-switch__track {
  546. @include theme.property(opacity, $opacity);
  547. }
  548. }
  549. @mixin _track-selected-color($colors) {
  550. @include state.default($selectors) {
  551. @include _set-track-selected-color(state.get-default-state($colors));
  552. }
  553. @include state.hover($selectors) {
  554. @include _set-track-selected-color(state.get-hover-state($colors));
  555. }
  556. @include state.focus($selectors) {
  557. @include _set-track-selected-color(state.get-focus-state($colors));
  558. }
  559. @include state.pressed($selectors) {
  560. @include _set-track-selected-color(state.get-pressed-state($colors));
  561. }
  562. @include state.disabled($selectors) {
  563. @include _set-track-selected-color(state.get-disabled-state($colors));
  564. }
  565. }
  566. @mixin _set-track-selected-color($color) {
  567. .mdc-switch__track::after {
  568. @include theme.property(background, $color);
  569. }
  570. }
  571. @mixin _track-unselected-color($colors) {
  572. @include state.default($selectors) {
  573. @include _set-track-unselected-color(state.get-default-state($colors));
  574. }
  575. @include state.hover($selectors) {
  576. @include _set-track-unselected-color(state.get-hover-state($colors));
  577. }
  578. @include state.focus($selectors) {
  579. @include _set-track-unselected-color(state.get-focus-state($colors));
  580. }
  581. @include state.pressed($selectors) {
  582. @include _set-track-unselected-color(state.get-pressed-state($colors));
  583. }
  584. @include state.disabled($selectors) {
  585. @include _set-track-unselected-color(state.get-disabled-state($colors));
  586. }
  587. }
  588. @mixin _set-track-unselected-color($color) {
  589. .mdc-switch__track::before {
  590. @include theme.property(background, $color);
  591. }
  592. }
  593. @mixin _track-shape($shape) {
  594. .mdc-switch__track {
  595. @include shape.radius($shape);
  596. }
  597. }
  598. @mixin _track-width($width) {
  599. @include theme.property(width, $width);
  600. }