Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
268 changes: 185 additions & 83 deletions src/components/checkbox/checkbox.scss
Original file line number Diff line number Diff line change
@@ -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);
75 changes: 30 additions & 45 deletions src/components/checkbox/checkbox.template.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,52 +50,37 @@ export const CheckboxTemplate: FunctionalComponent<CheckboxTemplateProps> = (
}

return [
<div class="mdc-form-field ">
<div
class={{
'mdc-checkbox': true,
'mdc-checkbox--invalid': props.invalid,
'mdc-checkbox--disabled': props.disabled,
'mdc-checkbox--required': props.required,
'mdc-checkbox--indeterminate': props.indeterminate,
'lime-checkbox--readonly': props.readonly,
}}
>
<input
type="checkbox"
class="mdc-checkbox__native-control"
id={props.id}
checked={props.checked}
disabled={props.disabled || props.readonly}
required={props.required}
onChange={props.onChange}
aria-controls={props.helperTextId}
aria-describedby={props.helperTextId}
{...inputProps}
/>
<div class="mdc-checkbox__background">
<svg class="mdc-checkbox__checkmark" viewBox="0 0 24 24">
<path
class="mdc-checkbox__checkmark-path"
fill="none"
d="M1.73,12.91 8.1,19.28 22.79,4.59"
/>
</svg>
<div class="mdc-checkbox__mixedmark" />
</div>
<div
class={{
'mdc-form-field': true, // required by MDC to work
'mdc-checkbox': true, // required by MDC to work
checkbox: true,
checked: props.checked,
invalid: props.invalid,
disabled: props.disabled,
required: props.required,
indeterminate: props.indeterminate,
readonly: props.readonly,
}}
>
<input
type="checkbox"
class="mdc-checkbox__native-control" // required by MDC to work
id={props.id}
checked={props.checked}
disabled={props.disabled || props.readonly}
required={props.required}
onChange={props.onChange}
aria-controls={props.helperTextId}
aria-describedby={props.helperTextId}
{...inputProps}
/>
<div class="box">
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false">
<path fill="none" d="M1.73,12.91 8.1,19.28 22.79,4.59" />
</svg>
</div>
<label
class={{
'mdc-checkbox--invalid': props.invalid,
'mdc-checkbox--disabled': props.disabled,
'mdc-checkbox--required': props.required,
'mdc-checkbox--indeterminate': props.indeterminate,
'lime-checkbox--readonly': props.readonly,
}}
htmlFor={props.id}
>
{props.label}
</label>
<label htmlFor={props.id}>{props.label}</label>
</div>,
<HelperText
text={props.helperText}
Expand Down
2 changes: 0 additions & 2 deletions src/components/checkbox/partial-styles/_helper-text.scss

This file was deleted.

2 changes: 1 addition & 1 deletion src/components/dynamic-label/dynamic-label.scss
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
:host(limel-dynamic-label) {
--limel-dynamic-label-min-height: 1.75rem;
display: flex;
gap: 0.5rem;
gap: 0.25rem;
align-items: flex-start;
border-radius: 0.5rem;
min-width: 0;
Expand Down
Loading