diff --git a/src/components/checkbox/checkbox.scss b/src/components/checkbox/checkbox.scss index 7e923cb564..d15dd93e8c 100644 --- a/src/components/checkbox/checkbox.scss +++ b/src/components/checkbox/checkbox.scss @@ -1,110 +1,212 @@ -@use '@material/checkbox'; -@use '@material/form-field'; +@use '../../style/mixins'; /** * @prop --checkbox-unchecked-border-color: Affects the border color of the default state of the checkbox (when it is not checked). Defaults to `--contrast-900`. */ +$box-size: 1.25rem; +$gap-size: 0.5rem; -@mixin custom-checkbox-styles { - // This is used in other components too, such as `limel-list` - --mdc-checkbox-checked-color: var( - --lime-primary-color, - var(--limel-theme-primary-color) - ); - --mdc-checkbox-unchecked-color: var( - --checkbox-unchecked-border-color, - rgb(var(--contrast-900)) - ); - --lime-checkbox-unchecked-color: rgb(var(--contrast-300)); +:host(limel-checkbox) { + min-height: var(--limel-checkbox-min-height, 2.5rem); // prevents flickering + // when switching between `readonly` and normal states in `limel-checkbox`, + // but not where `CheckboxTemplate` is imported & used. } -:host(limel-checkbox) { - @include custom-checkbox-styles; - min-height: 2.5rem; +*, +*:before, +*:after { + box-sizing: border-box; } -limel-dynamic-label { - margin-top: 0.375rem; - margin-left: 0.375rem; - gap: 0.375rem; +.checkbox { + position: relative; + isolation: isolate; + + display: flex; + align-items: center; + + min-height: var( + --limel-checkbox-min-height, + 2.5rem + ); // helps align with other fields in the form, or within table rows + width: 100%; } -@include checkbox.core-styles; -@include form-field.core-styles; - -.mdc-form-field { - // As long as this component is depended on MDC, - // we need to force it to be font-agnostic. - // When MDC-dependency is removed, this block can also be removed. - // However, on removal of MDC-dependency, we should also make sure to check - // other font-related styles that might be set by MDC, - // such as `letter-spacing` or `font-size`. - font-family: inherit; - font-size: var(--limel-theme-default-font-size); +input[type='checkbox'] { + // Hide the native checkbox + @include mixins.visually-hidden; + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; } -.mdc-form-field { - display: flex; - align-items: flex-start; - - .mdc-checkbox { - .mdc-checkbox__native-control { - &[disabled], - &:enabled { - &:not(:checked):not(:indeterminate) { - ~ .mdc-checkbox__background { - background-color: var(--lime-checkbox-unchecked-color); - } - } - } - } +label { + // Ensure the label is always clickable, even when empty + min-width: $box-size; + min-height: $box-size; + padding-top: 0.125rem; + // ==== - &.mdc-checkbox--invalid { - .mdc-checkbox__native-control:enabled:not(:checked):not( - :indeterminate - ) - ~ .mdc-checkbox__background { - border-color: var(--limel-theme-error-color); - } - } - .mdc-checkbox__native-control { - &:focus-visible { - + .mdc-checkbox__background { - &:after { - content: ''; - display: block; - position: absolute; - inset: -0.25rem; - border-radius: 0.25rem; - box-shadow: var(--shadow-depth-8-focused); - } - } - } + cursor: pointer; + position: relative; + width: 100%; + + font-size: var(--limel-theme-default-small-font-size); + color: var(--limel-theme-text-primary-on-background-color); + + padding-left: calc($box-size + $gap-size); + + .disabled:not([readonly]):not([readonly='true']) & { + cursor: not-allowed; + color: var(--limel-theme-text-disabled-color); + } + + .required & { + &:after { + margin-left: 0.0625rem; + content: '*'; } } - .mdc-checkbox--disabled { - opacity: 0.5; + + .invalid:not(.readonly) & { + color: var(--limel-theme-error-text-color); } +} - label { - cursor: pointer; - line-height: normal; - letter-spacing: normal; +.box { + position: absolute; // since `label` is the clickable part, + // and thus needs to + // stretch below the checkbox + pointer-events: none; - padding-top: 0.75rem; - padding-left: 0; + transition: + border-color 0.4s ease 0.2s, + background-color 0.2s ease, + box-shadow var(--limel-clickable-transform-speed, 0.4s) ease; - color: var(--limel-theme-text-primary-on-background-color); + display: inline-block; + vertical-align: middle; - &.mdc-checkbox--required::after { - margin-left: 0.0625rem; - content: '*'; + width: $box-size; + height: $box-size; + + margin-right: $gap-size; + border-radius: 0.25rem; + border: 0.125rem solid; + + border-color: var( + --checkbox-unchecked-border-color, + rgb(var(--contrast-900)) + ); + background-color: var( + --limel-checkbox-background-color, + rgb(var(--contrast-300)) + ); + + .checked &, + .checkbox:has(input[type='checkbox']:checked) & { + background-color: var( + --lime-primary-color, + var(--limel-theme-primary-color) + ); + border-color: var( + --lime-primary-color, + var(--limel-theme-primary-color) + ); + } + + .disabled & { + opacity: 0.4; + } + + .checkbox:not(.disabled):has(label:hover) & { + will-change: box-shadow; + box-shadow: var(--button-shadow-hovered); + } + + .checkbox:not(.disabled):has(label:active) & { + will-change: box-shadow; + box-shadow: var(--button-shadow-pressed); + } + + &:before { + // For indicating the hover or focused state + transition: mixins.$clickable-normal-state-transitions; + content: ''; + position: absolute; + inset: -0.1875rem; // 3px + border-radius: inherit; + + .checkbox:has(input[type='checkbox']:focus-visible) & { + will-change: box-shadow; + + box-shadow: var(--shadow-depth-8-focused); + } + } + + &:after { + // For indicating the indeterminate state + transition: + opacity 0.2s ease, + width 0.4s ease; + content: ''; + position: absolute; + inset: 0; + margin: auto; + + height: 0.125rem; + width: 0.25rem; + + border-radius: 1rem; + opacity: 0; + + background-color: rgb(var(--color-white)); + + .indeterminate & { + opacity: 1; + width: calc($box-size - 0.5rem); } + } +} + +svg { + position: absolute; + z-index: 1; + inset: 0; + + transform: translate3d(-0.125rem, -0.125rem, 0); + + width: $box-size; + height: $box-size; + + padding: 0.25rem; + + color: rgb(var(--color-white)); + opacity: 0; - &.mdc-checkbox--invalid { - color: var(--limel-theme-error-text-color); + stroke-width: 0.1875rem; // 3px + stroke: currentColor; + stroke-linecap: round; + stroke-linejoin: round; + + path { + stroke-dashoffset: 29.7833; + stroke-dasharray: 29.7833; + transition: stroke-dashoffset 180ms cubic-bezier(0.4, 0, 0.6, 1); + } + + .checkbox:not(.indeterminate):has(input[type='checkbox']:checked) & { + opacity: 1; + + path { + stroke-dashoffset: 0; } } } -@import './partial-styles/_helper-text.scss'; +limel-dynamic-label { + margin-top: 0.375rem; + margin-left: -0.25rem; +} + +@include mixins.hide-helper-line-when-not-needed(limel-checkbox); diff --git a/src/components/checkbox/checkbox.template.tsx b/src/components/checkbox/checkbox.template.tsx index 94e2d993ca..6467f17510 100644 --- a/src/components/checkbox/checkbox.template.tsx +++ b/src/components/checkbox/checkbox.template.tsx @@ -50,52 +50,37 @@ export const CheckboxTemplate: FunctionalComponent = ( } return [ -
-
- -
- - - -
-
+
+ +
+
- +
,