_radio-theme.scss 17 KB

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