@cfpb/cfpb-design-system
Version:
CFPB's UI framework
176 lines (154 loc) • 4.59 kB
JavaScript
import { LitElement, html, css, unsafeCSS } from 'lit';
import { defineComponent } from '../cfpb-utilities/shared-config';
import { classMap } from 'lit/directives/class-map.js';
import { ref, createRef } from 'lit/directives/ref.js';
import styles from './styles.component.scss?inline';
import { CfpbCheckboxIcon } from '../cfpb-checkbox-icon';
// The validation states are error, warning, or success.
const VALID_VALIDATION = ['error', 'warning', 'success'];
// The types are a checkbox or radio button.
const VALID_TYPES = ['checkbox', 'radio'];
/**
* @element cfpb-form-choice
* @slot - The label for the form input.
*/
export class CfpbFormChoice extends LitElement {
static styles = css`
${unsafeCSS(styles)}
`;
#checkboxIcon = createRef();
/**
* @property {boolean} checked - Whether the choice is checked or not.
* @property {boolean} disabled - Whether the choice is disabled or not.
* @property {boolean} large - Whether the choice has a large target area.
* @property {string} validation - Validation style: error, warning, success.
* @property {string} type - Choice type: checkbox or radio.
* @property {string} name - The name within a form.
* @property {string} value - The value to submit within a form.
* @returns {object} The map of properties.
*/
static properties = {
checked: { type: Boolean, reflect: true },
disabled: { type: Boolean },
large: { type: Boolean },
validation: { type: String },
type: { type: String },
name: { type: String },
value: { type: String },
};
constructor() {
super();
this.checked = false;
this.disabled = false;
this.large = false;
this.validation = '';
this.type = 'checkbox';
this.name = '';
this.value = '';
}
#onChange(evt) {
this.checked = evt.target.checked;
this.dispatchEvent(
new Event('change', {
bubbles: true,
composed: true,
}),
);
}
#onInput() {
this.dispatchEvent(
new Event('input', {
bubbles: true,
composed: true,
}),
);
}
#onFocus() {
if (this.#checkboxIcon.value) {
this.#checkboxIcon.value.focus();
}
}
#onBlur() {
if (this.#checkboxIcon.value) {
this.#checkboxIcon.value.blur();
}
}
#onMouseOver() {
if (this.#checkboxIcon.value) this.#checkboxIcon.value.mouseover();
}
#onMouseLeave() {
if (this.#checkboxIcon.value) this.#checkboxIcon.value.mouseleave();
}
focus() {
this.shadowRoot.querySelector('input').focus();
}
/**
* Ensure the validation value is valid, and fall back to a default if not.
* @returns {string|undefined} A valid validation value string, or undefined.
*/
get #validValidation() {
return VALID_VALIDATION.includes(this.validation)
? this.validation
: undefined;
}
/**
* Ensure the type value is valid, and fall back to a default if not.
* @returns {string} A type value string.
*/
get #validType() {
return VALID_TYPES.includes(this.type) ? this.type : 'checkbox';
}
get #baseClass() {
const classes = {
'm-form-field': true,
[`m-form-field--${this.type}`]: true,
'm-form-field--lg-target': this.large,
};
if (this.#validValidation)
classes[[`m-form-field--${this.type}-${this.validation}`]] =
this.validation;
return classes;
}
#renderCheckbox() {
return html`
<cfpb-checkbox-icon
.checked=${this.checked}
?disabled=${this.disabled}
validation=${this.#validValidation}
${ref(this.#checkboxIcon)}
></cfpb-checkbox-icon>
`;
}
render() {
const classes = classMap(this.#baseClass);
return html`
<div
class=${classes}
?large=${this.large}
=${this.#onMouseOver}
=${this.#onMouseLeave}
>
<input
class="a-${this.type}"
type=${this.#validType}
id="choice-input"
?disabled=${this.disabled}
.checked=${this.checked}
=${this.#onChange}
=${this.#onInput}
=${this.#onFocus}
=${this.#onBlur}
aria-invalid=${this.#validValidation === 'error' ? 'true' : 'false'}
/>
<label class="a-label" for="choice-input">
${this.type === 'checkbox' ? this.#renderCheckbox() : null}
<slot></slot>
</label>
</div>
`;
}
static init() {
CfpbCheckboxIcon.init();
defineComponent('cfpb-form-choice', CfpbFormChoice);
}
}