Skip to content

Commit

Permalink
fix(checkbox): fix error state of checkbox
Browse files Browse the repository at this point in the history
  • Loading branch information
semih.ozker committed Apr 21, 2024
1 parent af8bef0 commit fcbeb53
Show file tree
Hide file tree
Showing 3 changed files with 119 additions and 18 deletions.
51 changes: 49 additions & 2 deletions src/components/checkbox-group/checkbox/bl-checkbox.css
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,27 @@ label {
overflow-wrap: anywhere;
}

input {
.dirty.invalid label{
margin-bottom: var(--bl-size-3xs);
}

input[type="checkbox"] {
appearance: none;
position: absolute;
outline: none;
margin: 0;
box-sizing: border-box;
border: 1px solid var(--bl-color-neutral-lighter);
border-radius: var(--bl-border-radius-xs);
width: var(--bl-size-m);
height: var(--bl-size-m);
min-width: var(--bl-size-m);
min-height: var(--bl-size-m);
max-width: var(--bl-size-m);
max-height: var(--bl-size-m);
}

.check-mark {
position: absolute;
display: flex;
align-items: center;
justify-content: center;
Expand All @@ -43,6 +58,38 @@ input {
background-color: var(--bl-color-neutral-full);
}

.required-suffix{
color: var(--bl-color-danger);
margin-left: calc(var(--bl-size-2xs) * -1);
}

.dirty.invalid .check-mark{
border-color: var(--bl-color-danger);
}

.hint {
display: none;
font: var(--bl-font-body-text-3);
}

.hint p {
padding: 0;
margin: 0;
}

.dirty.invalid .hint {
display: block;
}

.invalid-text {
display: none;
color: var(--bl-color-danger);
}

.dirty.invalid .invalid-text {
display: block;
}

:host([checked]) .label,
:host(:hover) .label {
color: var(--bl-color-primary);
Expand Down
17 changes: 10 additions & 7 deletions src/components/checkbox-group/checkbox/bl-checkbox.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,16 @@ describe("bl-checkbox", () => {
assert.shadowDom.equal(
el,
`
<label>
<input type="checkbox"
aria-readonly="false"
aria-required="false" />
<div class="check-mark"></div>
<slot class="label"></slot>
</label>
<div>
<label>
<input type="checkbox"
aria-readonly="false"
aria-required="false" />
<div class="check-mark"></div>
<slot class="label"></slot>
</label>
<div class="hint"></div>
</div>
`
);
});
Expand Down
69 changes: 60 additions & 9 deletions src/components/checkbox-group/checkbox/bl-checkbox.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property, query } from "lit/decorators.js";
import { customElement, property, query, state } from "lit/decorators.js";
import { classMap } from "lit/directives/class-map.js";
import { ifDefined } from "lit/directives/if-defined.js";
import { live } from "lit/directives/live.js";
import { FormControlMixin } from "@open-wc/form-control";
import { FormControlMixin, requiredValidator } from "@open-wc/form-control";
import "element-internals-polyfill";
import { event, EventDispatcher } from "../../../utilities/event";
import "../../icon/bl-icon";
Expand All @@ -21,7 +22,12 @@ export default class BlCheckbox extends FormControlMixin(LitElement) {
static get styles(): CSSResultGroup {
return [style];
}
static shadowRootOptions = { ...LitElement.shadowRootOptions, delegatesFocus: true };
static shadowRootOptions = { ...LitElement.shadowRootOptions, delegatesFocus: false };

static formControlValidators = [requiredValidator];

@query("input")
validationTarget: HTMLInputElement;

/**
* Sets the checked state for checkbox
Expand All @@ -41,6 +47,12 @@ export default class BlCheckbox extends FormControlMixin(LitElement) {
@property({ type: Boolean, reflect: true })
required = false;

/**
* Set custom error message
*/
@property({ type: String, attribute: "invalid-text", reflect: true })
customInvalidText?: string;

/**
* Sets the disabled state for checkbox
*/
Expand Down Expand Up @@ -70,23 +82,45 @@ export default class BlCheckbox extends FormControlMixin(LitElement) {

@query("[type=checkbox]") checkboxElement: HTMLElement;

@state()
private dirty = false;

protected field: BlCheckboxGroup | null;

connectedCallback(): void {
super.connectedCallback();

this.field = this.closest<BlCheckboxGroup>(blCheckboxGroupTag);
this.field?.addEventListener(blChangeEventName, this.handleFieldValueChange);

this.form?.addEventListener("submit", e => {
if (!this.reportValidity()) {
e.preventDefault();
}
});
}

reportValidity() {
this.dirty = true;
return this.checkValidity();
}

disconnectedCallback(): void {
super.disconnectedCallback();
this.field?.removeEventListener(blChangeEventName, this.handleFieldValueChange);
}

updated(changedProperties: Map<string, unknown>): void {
if (changedProperties.has("checked") && this.required && this.checked) {
this.setValue(this.value);
protected async updated(changedProperties: Map<string, unknown>): Promise<void> {
if (changedProperties.has("checked") && this.required) {
if (this.checked) {
this.setValue(this.value);
} else if (!this.checked) {
this.setValue("");
}

await this.validationComplete;

this.requestUpdate();
}
}

Expand All @@ -98,6 +132,10 @@ export default class BlCheckbox extends FormControlMixin(LitElement) {
}
}

validityCallback(): string | void {
return this.customInvalidText || this.validationTarget?.validationMessage;
}

/**
* Focuses this option
*/
Expand All @@ -119,6 +157,7 @@ export default class BlCheckbox extends FormControlMixin(LitElement) {
private handleChange(event: CustomEvent) {
const target = event.target as HTMLInputElement;

this.dirty = true;
this.checked = target.checked;
this.onChange(target.checked);
this.indeterminate = false;
Expand All @@ -134,7 +173,18 @@ export default class BlCheckbox extends FormControlMixin(LitElement) {
if (this.checked) icon = "check";
if (this.indeterminate) icon = "minus";

return html`
const invalidMessage = !this.checkValidity()
? html`<p class="invalid-text">${this.validationMessage}</p>`
: "";

const requiredSuffix = this.required ? html`<span class="required-suffix">*</span>` : "";

const classes = {
"dirty": this.dirty,
"invalid": !this.checkValidity(),
};

return html`<div class=${classMap(classes)}>
<label>
<input
type="checkbox"
Expand All @@ -148,9 +198,10 @@ export default class BlCheckbox extends FormControlMixin(LitElement) {
@blur=${this.blur}
/>
<div class="check-mark">${icon ? html`<bl-icon name="${icon}"></bl-icon>` : null}</div>
<slot class="label"></slot>
<slot class="label"></slot>${requiredSuffix}
</label>
`;
<div class="hint">${invalidMessage}</div>
</div> `;
}
}

Expand Down

0 comments on commit fcbeb53

Please sign in to comment.