Elizabeth Mitchell 6c2aef6901 chore(all): add keep-sorted comments to Sass imports
PiperOrigin-RevId: 509583504
2023-02-14 11:10:23 -08:00

458 lines
12 KiB
SCSS

//
// Copyright 2022 Google LLC
// SPDX-License-Identifier: Apache-2.0
//
// go/keep-sorted start
@use 'sass:map';
@use 'sass:math';
// go/keep-sorted end
// go/keep-sorted start
@use '../../focus/focus-ring';
@use '../../ripple/ripple';
@use '../../sass/theme';
@use '../../tokens';
// go/keep-sorted end
// Motion token values.
$_md-sys-motion: tokens.md-sys-motion-values();
// The stroke width of the icon marks.
$_mark-stroke: 2px;
// The coordinates in an 18px viewBox of the bottom left corner of the
// indeterminate icon. Y is negative to fix an issue in Safari (see below).
$_indeterminate-bottom-left: 4px, -10px;
// The coordinates in an 18px viewBox of the bottom left corner of the
// checkmark icon. Y is negative to fix an issue in Safari (see below).
$_checkmark-bottom-left: 7px, -14px;
@mixin theme($tokens) {
$tokens: _resolve-tokens($tokens);
$tokens: theme.validate-theme(
_resolve-tokens(tokens.md-comp-checkbox-values()),
$tokens
);
$tokens: theme.create-theme-vars($tokens, checkbox);
@include theme.emit-theme-vars($tokens);
}
@mixin styles() {
$tokens: _resolve-tokens(tokens.md-comp-checkbox-values());
$tokens: theme.create-theme-vars($tokens, checkbox);
:host {
@each $token, $value in $tokens {
--_#{$token}: #{$value};
}
border-radius: var(--_container-shape);
display: inline-flex;
height: 48px;
position: relative;
vertical-align: top; // Fix extra space when placed inside display: block
width: 48px;
-webkit-tap-highlight-color: transparent;
}
input {
appearance: none;
inset: 0;
margin: 0;
outline: none;
position: absolute;
opacity: 0;
block-size: 100%;
inline-size: 100%;
}
.container {
border-radius: inherit;
height: 100%;
position: relative;
width: 100%;
}
// Center elements within the container.
.outline,
.background,
md-ripple,
.icon {
inset: 0;
margin: auto;
position: absolute;
}
.outline,
.background {
border-radius: inherit;
height: var(--_container-size);
width: var(--_container-size);
}
.outline {
border-color: var(--_unselected-outline-color);
border-style: solid;
border-width: var(--_unselected-outline-width);
box-sizing: border-box;
}
.background {
background-color: var(--_selected-container-color);
}
// Background and icon transitions.
.background,
.icon {
opacity: 0; // Background and icon fade in
transition-duration: 150ms, 50ms; // Exit duration for scale and opacity.
transition-property: transform, opacity;
// Exit easing function for scale, linear for opacity.
transition-timing-function: map.get(
$_md-sys-motion,
easing-emphasized-accelerate
),
linear;
transform: scale(0.6); // Background and icon scale from 60% to 100%.
}
.selected .background,
.selected .icon {
opacity: 1;
// Enter duration for scale and opacity.
transition-duration: 350ms, 50ms;
// Enter easing function for scale, linear for opacity.
transition-timing-function: map.get(
$_md-sys-motion,
easing-emphasized-decelerate
),
linear;
transform: scale(1);
}
// Focus ring
md-focus-ring {
@include focus-ring.theme(
(
offset-vertical: -2px,
offset-horizontal: -2px,
shape: map.get(tokens.md-sys-shape-values(), corner-full),
)
);
}
// Ripple
md-ripple {
height: var(--_state-layer-size);
width: var(--_state-layer-size);
@include ripple.theme(
(
focus-color: var(--_unselected-focus-state-layer-color),
focus-opacity: var(--_unselected-focus-state-layer-opacity),
hover-color: var(--_unselected-hover-state-layer-color),
hover-opacity: var(--_unselected-hover-state-layer-opacity),
pressed-color: var(--_unselected-pressed-state-layer-color),
pressed-opacity: var(--_unselected-pressed-state-layer-opacity),
shape: var(--_state-layer-shape),
)
);
}
.selected md-ripple {
@include ripple.theme(
(
focus-color: var(--_selected-focus-state-layer-color),
focus-opacity: var(--_selected-focus-state-layer-opacity),
hover-color: var(--_selected-hover-state-layer-color),
hover-opacity: var(--_selected-hover-state-layer-opacity),
pressed-color: var(--_selected-pressed-state-layer-color),
pressed-opacity: var(--_selected-pressed-state-layer-opacity),
)
);
}
.error md-ripple {
@include ripple.theme(
(
focus-color: var(--_error-focus-state-layer-color),
focus-opacity: var(--_error-focus-state-layer-opacity),
hover-color: var(--_error-hover-state-layer-color),
hover-opacity: var(--_error-hover-state-layer-opacity),
pressed-color: var(--_error-pressed-state-layer-color),
pressed-opacity: var(--_error-pressed-state-layer-opacity),
)
);
}
// Icon and icon marks
.icon {
// The icon is created with two <rect> marks for animation:
// 1. Short end
// - the smaller leading part of the checkmark
// - hidden behind long end for indeterminate mark
// 2. Long end
// - the larger trailing part of the checkmark
// - the entirety of the indeterminate mark
fill: var(--_selected-icon-color);
height: var(--_icon-size);
width: var(--_icon-size);
}
// The short end of the checkmark. Initially hidden underneath the
// indeterminate mark.
.mark.short {
height: $_mark-stroke;
transition-property: transform, height;
width: $_mark-stroke;
}
// The long end of the checkmark. Initially the indeterminate mark.
.mark.long {
height: $_mark-stroke;
transition-property: transform, width;
width: 10px;
}
// Exit duration and easing.
.mark {
animation-duration: 150ms;
animation-timing-function: map.get(
$_md-sys-motion,
easing-emphasized-accelerate
);
transition-duration: 150ms;
transition-timing-function: map.get(
$_md-sys-motion,
easing-emphasized-accelerate
);
}
// Enter duration and easing.
.selected .mark {
animation-duration: 350ms;
animation-timing-function: map.get(
$_md-sys-motion,
easing-emphasized-decelerate
);
transition-duration: 350ms;
transition-timing-function: map.get(
$_md-sys-motion,
easing-emphasized-decelerate
);
}
// Creates the checkmark icon.
.checked,
// Keep the checkmark shape when unselecting a checked checkbox.
.prev-checked.unselected {
.mark {
// Transform from the bottom left of the rectangles, whch turn into the
// bottom-most point of the checkmark.
// Fix Safari's transform-origin bug from "top left" to "bottom left"
$scale: scaleY(-1);
// Move the "bottom left" corner to the checkmark location.
$translate: translate($_checkmark-bottom-left);
// Rotate the checkmark.
$rotate: rotate(45deg);
transform: $scale $translate $rotate;
}
.mark.short {
// The right triangle that forms the short end has an X, Y length of
// 4dp, 4dp. The hypoteneuse is √(4*4 + 4*4), which is the length of the
// short end when checked.
height: 1px * math.sqrt(32);
}
.mark.long {
// The right triangle that forms the long end has an X, Y length of
// 8dp, 8dp. The hypoteneuse is √(8*8 + 8*8), which is the length of the
// long end when checked.
width: 1px * math.sqrt(128);
}
}
// Creates the indeterminate icon.
.indeterminate,
// Keep the indeterminate shape when unselecting an indeterminate checkbox.
.prev-indeterminate.unselected {
.mark {
transform: scaleY(-1) translate($_indeterminate-bottom-left) rotate(0deg);
}
}
// When selecting an unselected checkbox, don't transition between the
// checked and indeterminate states. The checkmark icon or indeterminate icon
// should fade in from its final position.
.prev-unselected .mark {
transition-property: none;
}
// When checking a checkbox, the long mark of the checkmark grows from the
// bottom-most point of the checkmark. An animation provides the starting
// width to animate from.
.prev-unselected.checked .mark.long {
animation-name: prev-unselected-to-checked;
}
@keyframes prev-unselected-to-checked {
from {
width: 0;
}
}
// States
.error .outline {
border-color: var(--_unselected-error-outline-color);
// TODO(b/262410085): add once token is added
// border-width: var(--_unselected-error-outline-width);
}
.error .background {
background: var(--_selected-error-container-color);
}
.error .icon {
fill: var(--_selected-error-icon-color);
}
:host(:hover) .outline {
border-color: var(--_unselected-hover-outline-color);
border-width: var(--_unselected-hover-outline-width);
}
:host(:hover) .background {
background: var(--_selected-hover-container-color);
}
:host(:hover) .icon {
fill: var(--_selected-hover-icon-color);
}
:host(:hover) .error .outline {
border-color: var(--_unselected-error-hover-outline-color);
}
:host(:hover) .error .background {
background: var(--_selected-error-hover-container-color);
}
:host(:hover) .error .icon {
fill: var(--_selected-error-hover-icon-color);
}
:host(:focus-within) .outline {
border-color: var(--_unselected-focus-outline-color);
border-width: var(--_unselected-focus-outline-width);
}
:host(:focus-within) .background {
background: var(--_selected-focus-container-color);
}
:host(:focus-within) .icon {
fill: var(--_selected-focus-icon-color);
}
:host(:focus-within) .error .outline {
border-color: var(--_unselected-error-focus-outline-color);
}
:host(:focus-within) .error .background {
background: var(--_selected-error-focus-container-color);
}
:host(:focus-within) .error .icon {
fill: var(--_selected-error-focus-icon-color);
}
:host(:active) .outline {
border-color: var(--_unselected-pressed-outline-color);
border-width: var(--_unselected-pressed-outline-width);
}
:host(:active) .background {
background: var(--_selected-pressed-container-color);
}
:host(:active) .icon {
fill: var(--_selected-pressed-icon-color);
}
:host(:active) .error .outline {
border-color: var(--_unselected-error-pressed-outline-color);
}
:host(:active) .error .background {
background: var(--_selected-error-pressed-container-color);
}
:host(:active) .error .icon {
fill: var(--_selected-error-pressed-icon-color);
}
// Don't animate to/from disabled states because the outline is hidden when
// selected. Without this, there'd be a FOUC if the checkbox state is
// programmatically changed while disabled.
:host([disabled]),
.prev-disabled {
.background,
.icon,
.mark {
animation-duration: 0s;
transition-duration: 0s;
}
}
:host([disabled]) .outline {
border-color: var(--_unselected-disabled-outline-color);
border-width: var(--_unselected-disabled-outline-width);
opacity: var(--_unselected-disabled-container-opacity);
}
:host([disabled]) .selected .outline {
// Hide the outline behind the transparent selected container color.
// This can be removed once disabled colors are flattened.
visibility: hidden;
}
:host([disabled]) .selected .background {
// Set disabled opacity only when selected since opacity is used to show
// or hide the container background.
background: var(--_selected-disabled-container-color);
opacity: var(--_selected-disabled-container-opacity);
}
:host([disabled]) .icon {
fill: var(--_selected-disabled-icon-color);
}
}
@function _resolve-tokens($tokens) {
// Remove deprecated tokens
$tokens: map.remove(
$tokens,
'disabled-unselected-icon-color',
'disabled-unselected-icon-opacity',
'disabled-selected-icon-color',
'disabled-selected-icon-opacity',
'unselected-icon-color',
'unselected-focus-icon-color',
'unselected-hover-icon-color',
'unselected-pressed-icon-color'
);
// Remove unsupported tokens
$tokens: map.remove(
$tokens,
'selected-disabled-container-outline-width',
'selected-focus-outline-width',
'selected-hover-outline-width',
'selected-outline-width',
'selected-pressed-outline-width'
);
@return $tokens;
}