vira
Version:
A simple and highly versatile design system using element-vir.
219 lines (199 loc) • 7.59 kB
JavaScript
import { extractEventTarget } from '@augment-vir/web';
import { attributes, classMap, css, defineElementEvent, html, ifDefined, listen, listenToActivate, } from 'element-vir';
import { Check24Icon, viraIconCssVars } from '../icons/index.js';
import { viraDisabledStyles } from '../styles/disabled.js';
import { createFocusStyles } from '../styles/focus.js';
import { viraFormCssVars } from '../styles/form-styles.js';
import { defineViraElement } from '../util/define-vira-element.js';
import { ViraIcon } from './vira-icon.element.js';
/**
* A custom checkbox.
*
* @category Input
* @category Elements
* @see https://electrovir.github.io/vira/book/elements/vira-checkbox
*/
export const ViraCheckbox = defineViraElement()({
tagName: 'vira-checkbox',
slotNames: [
'vira-checkbox-label',
],
state() {
return {
hasSlottedLabel: false,
};
},
hostClasses: {
'vira-checkbox-horizontal': ({ inputs }) => !!inputs.useHorizontalLabel,
'vira-checkbox-filled-checked': ({ inputs }) => !!inputs.fillWhenChecked,
'vira-checkbox-filled-unchecked': ({ inputs }) => !!inputs.fillWhenUnchecked,
},
styles: ({ hostClasses }) => css `
:host {
display: inline-flex;
}
.custom-checkbox {
height: 24px;
aspect-ratio: 1;
box-sizing: border-box;
}
${ViraIcon} {
width: 100%;
height: 100%;
box-sizing: border-box;
${viraIconCssVars['vira-icon-stroke-width'].name}: 3px;
opacity: 0;
}
${hostClasses['vira-checkbox-filled-checked'].selector} {
& .custom-checkbox.checked {
color: ${viraFormCssVars['vira-form-background-color'].value};
background-color: ${viraFormCssVars['vira-form-accent-primary-color'].value};
}
label {
&:not(.disabled):hover .custom-checkbox.checked {
background-color: ${viraFormCssVars['vira-form-accent-primary-hover-color']
.value};
}
&:not(.disabled):active .custom-checkbox.checked {
background-color: ${viraFormCssVars['vira-form-accent-primary-active-color']
.value};
}
}
}
${hostClasses['vira-checkbox-filled-unchecked'].selector} {
& .custom-checkbox:not(.checked) {
color: ${viraFormCssVars['vira-form-background-color'].value};
background-color: ${viraFormCssVars['vira-form-error-color'].value};
}
label {
&:not(.disabled):hover .custom-checkbox:not(.checked) {
background-color: ${viraFormCssVars['vira-form-error-hover-color'].value};
}
&:not(.disabled):active .custom-checkbox:not(.checked) {
background-color: ${viraFormCssVars['vira-form-error-active-color'].value};
}
}
}
label {
display: inline-flex;
flex-direction: column;
align-items: flex-start;
gap: 4px;
&.disabled {
cursor: not-allowed;
}
& .label-text {
cursor: pointer;
font-weight: ${viraFormCssVars['vira-form-label-font-weight'].value};
&.empty {
display: none;
}
}
&:not(.disabled):hover .custom-checkbox {
background-color: ${viraFormCssVars['vira-form-selection-hover-color'].value};
}
&:not(.disabled):active .custom-checkbox {
background-color: ${viraFormCssVars['vira-form-selection-active-color'].value};
}
}
/* The visible custom box */
.custom-checkbox {
flex-shrink: 0;
border: 1px solid ${viraFormCssVars['vira-form-border-color'].value};
color: ${viraFormCssVars['vira-form-foreground-color'].value};
border-radius: ${viraFormCssVars['vira-form-radius'].value};
display: inline-block;
position: relative;
cursor: pointer;
${createFocusStyles({
elementBorderSize: '1px',
})}
&.checked {
& ${ViraIcon} {
opacity: 1;
}
}
&.error {
border-color: ${viraFormCssVars['vira-form-error-color'].value};
}
&.disabled {
${viraDisabledStyles};
}
}
${hostClasses['vira-checkbox-horizontal'].selector} label {
flex-direction: row-reverse;
align-items: center;
gap: 8px;
& .label-text {
padding-block: calc((24px - 1em) / 2);
}
}
`,
events: {
valueChange: defineElementEvent(),
},
render({ inputs, dispatch, events, slotNames, state, updateState }) {
function updateValue() {
if (!inputs.isDisabled) {
dispatch(new events.valueChange(!inputs.value));
}
}
const hasLabel = !!inputs.label || state.hasSlottedLabel;
const textLabel = html `
<span
class="label-text ${classMap({
empty: !hasLabel,
})}"
${attributes(inputs.attributePassthrough?.['text'])}
style=${ifDefined(inputs.stylePassthrough?.['text'])}
>
<slot
name=${slotNames['vira-checkbox-label']}
${listen('slotchange', (event) => {
const slotElement = extractEventTarget(event, HTMLSlotElement);
updateState({
hasSlottedLabel: !!slotElement.assignedNodes().length,
});
})}
>
${inputs.label}
</slot>
</span>
`;
return html `
<label
class=${classMap({
disabled: !!inputs.isDisabled,
})}
${attributes(inputs.attributePassthrough?.label)}
style=${ifDefined(inputs.stylePassthrough?.label)}
${listen('mousedown', updateValue)}
>
${textLabel}
<span
class="custom-checkbox ${classMap({
checked: inputs.value,
disabled: !!inputs.isDisabled,
error: !!inputs.hasError,
})}"
role="checkbox"
aria-label=${ifDefined(inputs.label || undefined)}
aria-checked=${inputs.value ? 'true' : 'false'}
aria-disabled=${inputs.isDisabled ? 'true' : 'false'}
tabindex=${inputs.isDisabled ? '-1' : '0'}
${attributes(inputs.attributePassthrough?.['custom-checkbox'])}
style=${ifDefined(inputs.stylePassthrough?.['custom-checkbox'])}
${listenToActivate(updateValue)}
>
<${ViraIcon.assign({
icon: Check24Icon,
fitContainer: true,
})}
${attributes(inputs.attributePassthrough?.[ViraIcon.tagName])}
style=${ifDefined(inputs.stylePassthrough?.[ViraIcon.tagName])}
></${ViraIcon}>
</span>
</label>
`;
},
});