-
Notifications
You must be signed in to change notification settings - Fork 116
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* feat(checkbox): draft checkbox component * docs(checkbox): add storybook files for checkbox and checkbox group * test(checkbox): add test files for checkbox and checkbox group * refactor(checkbox): refactor for checkbox and checkbox group * fix(checkbox): make label property optional * fix(checkbox): delete checkbox group element and allow single checkbox to be indeterminate * style(checkbox): fix css prettier issue * refactor(checkbox): some logic simplified * refactor(checkbox): remove unneeded style * refactor(input): lint fix * fix(checkbox): disabled color corrected * fix(checkbox): prevent user selection for checkbox label * feat(checkbox): draft checkbox component * docs(checkbox): add storybook files for checkbox and checkbox group * test(checkbox): add test files for checkbox and checkbox group * refactor(checkbox): refactor for checkbox and checkbox group * fix(checkbox): make label property optional * fix(checkbox): delete checkbox group element and allow single checkbox to be indeterminate * style(checkbox): fix css prettier issue * refactor(checkbox): some logic simplified * refactor(checkbox): remove unneeded style * refactor(input): lint fix * fix(checkbox): disabled color corrected * fix(checkbox): prevent user selection for checkbox label * fix(input): lint fix * fix(checkbox): cursor pointer for label and checkbox * feat(checkbox): indeterminate state handling added * docs(checkbox): indeterminate and checked state docs added Co-authored-by: Ali Balbars <[email protected]> Co-authored-by: Murat Çorlu <[email protected]> Co-authored-by: Murat Çorlu <[email protected]> Co-authored-by: olkeoguz <[email protected]> Co-authored-by: Talha Dogrul <[email protected]>
- Loading branch information
1 parent
6a7d747
commit a940f3f
Showing
6 changed files
with
344 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -15,6 +15,7 @@ module.exports = { | |
'tab', | ||
'tooltip', | ||
'progress-indicator', | ||
'checkbox', | ||
'alert' | ||
], | ||
], | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
:host { | ||
display: inline-block; | ||
vertical-align: middle; | ||
} | ||
|
||
label { | ||
display: flex; | ||
align-items: center; | ||
gap: var(--bl-size-2xs); | ||
color: var(--bl-color-secondary); | ||
font: var(--bl-font-title-3); | ||
cursor: pointer; | ||
user-select: none; | ||
} | ||
|
||
input { | ||
appearance: none; | ||
position: absolute; | ||
} | ||
|
||
.check-mark { | ||
display: flex; | ||
align-items: center; | ||
justify-content: center; | ||
box-sizing: border-box; | ||
width: var(--bl-size-m); | ||
height: var(--bl-size-m); | ||
border: 1px solid var(--bl-color-border); | ||
border-radius: var(--bl-border-radius-xs); | ||
color: var(--bl-color-primary-background); | ||
font-size: var(--bl-font-size-2xs); | ||
} | ||
|
||
:host([checked]) .label { | ||
color: var(--bl-color-primary); | ||
} | ||
|
||
:host(:is([checked], [indeterminate])) .check-mark { | ||
background-color: var(--bl-color-primary); | ||
border: none; | ||
} | ||
|
||
:host([disabled]) .check-mark, | ||
:host([disabled]) .label { | ||
color: var(--bl-color-content-passive); | ||
} | ||
|
||
:host([disabled]) .check-mark { | ||
background-color: var(--bl-color-secondary-background); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
import { html } from 'lit'; | ||
import { ifDefined } from 'lit/directives/if-defined.js'; | ||
import { Meta, Canvas, ArgsTable, Story } from '@storybook/addon-docs'; | ||
|
||
<Meta | ||
title="Components/Checkbox" | ||
component="bl-checkbox" | ||
argTypes={{ | ||
label: { | ||
control: 'text' | ||
}, | ||
disabled: { | ||
control: 'boolean', | ||
default: false | ||
}, | ||
checked: { | ||
control: 'boolean', | ||
default: false | ||
}, | ||
indeterminate: { | ||
control: 'boolean', | ||
default: false | ||
} | ||
}} | ||
/> | ||
|
||
export const CheckboxTemplate = (args) => html` | ||
<bl-checkbox | ||
?disabled=${args.disabled} | ||
?checked=${args.checked} | ||
?indeterminate=${args.indeterminate}>${args.label}</bl-checkbox> | ||
`; | ||
|
||
# Checkbox | ||
Checkbox component can be used to control checked / unchecked statuses. | ||
|
||
### Usage | ||
|
||
Use checkbox component for getting true/false input from users. | ||
|
||
* Don't use checkbox as an action button. | ||
* Checkbox label is not required but if you want to use checbox without a label, set `aria-label` attribute. | ||
|
||
## Basic | ||
|
||
You can show label by just using slot. | ||
|
||
<Canvas> | ||
<Story name="Basic Usage" args={{ label: 'Label' }}> | ||
{CheckboxTemplate.bind({})} | ||
</Story> | ||
</Canvas> | ||
|
||
## Checked | ||
|
||
Checked state can be set via `checked` attribute. | ||
|
||
<Canvas> | ||
<Story name="Checked" args={{ label: 'checkbox', checked: true }}> | ||
{CheckboxTemplate.bind({})} | ||
</Story> | ||
</Canvas> | ||
|
||
## Indeterminate | ||
|
||
Indeterminate state is regardless with `checked` state. A checkbox can be both `checked` and `indeterminate` at the | ||
same time.Indeterminate state is mainly used for parent/child selections while parent checkbox represents if all of | ||
the child will/should be checked or not. User interaction with checkbox (if checkbox is not disabled) takes checkbox | ||
from `indeterminate` state. Checkbox doesn't go this state with a user interaction. | ||
|
||
<Canvas> | ||
<Story name="Indeterminate" args={{ label: 'checkbox', indeterminate: true }}> | ||
{CheckboxTemplate.bind({})} | ||
</Story> | ||
</Canvas> | ||
|
||
## Indeterminate And Checked | ||
|
||
Indeterminate state is regardless with `checked` state. A checkbox cannot be both `checked` and `indeterminate` at the | ||
same time. Unless there is a user interaction, when `indeterminate` state is active `checked` state changes ignored. | ||
|
||
<Canvas> | ||
<Story name="Indeterminate And Checked" args={{ label: 'checkbox', indeterminate: true, checked: true }}> | ||
{CheckboxTemplate.bind({})} | ||
</Story> | ||
</Canvas> | ||
|
||
## Disabled | ||
|
||
Disabled state can be set via `disabled` attribute. A checkbox can be `disabled` and `checked` (and even `indeterminate`) at the same time. | ||
|
||
<Canvas> | ||
<Story name="Disabled" args={{ label: 'Disabled', disabled: true }}> | ||
{CheckboxTemplate.bind({})} | ||
</Story> | ||
<Story name="Disabled and Checked" args={{ label: 'Disabled and Checked', disabled: true, checked: true }}> | ||
{CheckboxTemplate.bind({})} | ||
</Story> | ||
<Story name="Disabled, Checked and Indeterminate" args={{ label: 'Disabled, Checked and Indeterminate', disabled: true, checked: true, indeterminate: true }}> | ||
{CheckboxTemplate.bind({})} | ||
</Story> | ||
</Canvas> | ||
|
||
## Reference | ||
|
||
<ArgsTable of="bl-checkbox" /> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
import { assert, fixture, html, elementUpdated, expect, oneEvent } from '@open-wc/testing'; | ||
import BlCheckbox from './bl-checkbox'; | ||
|
||
describe('bl-checkbox', () => { | ||
it('should be defined checkbox instance', () => { | ||
const el = document.createElement('bl-checkbox'); | ||
assert.instanceOf(el, BlCheckbox); | ||
}); | ||
|
||
it('should be rendered with default values', async () => { | ||
const el = await fixture(html`<bl-checkbox></bl-checkbox>`); | ||
|
||
assert.shadowDom.equal( | ||
el, | ||
` | ||
<label> | ||
<input type="checkbox" name="checkbox" /> | ||
<div class="check-mark"></div> | ||
<span class="label"><slot></slot></span> | ||
</label> | ||
` | ||
); | ||
}); | ||
|
||
it('should be rendered with correct label attribute', async () => { | ||
const el = await fixture(html`<bl-checkbox label="test label"></bl-checkbox>`); | ||
|
||
expect(el.shadowRoot?.querySelector('span')).to.exist; | ||
expect(el.getAttribute('label')).to.eq('test label'); | ||
}); | ||
|
||
it('should be rendered with correct label attribute when label attribute was changed', async () => { | ||
const el = await fixture(html`<bl-checkbox label="test label"></bl-checkbox>`); | ||
|
||
el.setAttribute('label', 'new test label'); | ||
|
||
await elementUpdated(el); | ||
|
||
expect(el.getAttribute('label')).to.eq('new test label'); | ||
}); | ||
|
||
it('should be rendered with check icon when checkbox checked', async () => { | ||
const el = await fixture(html`<bl-checkbox checked></bl-checkbox>`); | ||
const iconEl = el.shadowRoot?.querySelector('bl-icon'); | ||
|
||
expect(iconEl?.getAttribute('name')).to.eq('check'); | ||
}); | ||
|
||
it('should render with `checked` attribute as checked value', async () => { | ||
const el = await fixture(html`<bl-checkbox checked></bl-checkbox>`); | ||
expect(el.shadowRoot?.querySelector('input')?.checked).to.eq(true); | ||
}); | ||
|
||
describe('attributes', () => { | ||
it('should render with `disabled` attribute as disabled', async () => { | ||
const el = await fixture(html`<bl-checkbox disabled></bl-checkbox>`); | ||
expect(el.shadowRoot?.querySelector('input')?.hasAttribute('disabled')).to.eq(true); | ||
}); | ||
|
||
it('should not render with `indeterminate` attribute as indeterminate', async () => { | ||
const el = await fixture(html`<bl-checkbox indeterminate></bl-checkbox>`); | ||
expect(el.shadowRoot?.querySelector('input')?.hasAttribute('indeterminate')).to.eq(false); | ||
}); | ||
}); | ||
|
||
describe('update', () => { | ||
it('should set checked to false when indeterminate set to true', async () => { | ||
const el = await fixture(html`<bl-checkbox checked></bl-checkbox>`); | ||
|
||
el.setAttribute('indeterminate', 'true'); | ||
await elementUpdated(el); | ||
|
||
expect(el.hasAttribute('checked')).to.eq(false); | ||
}); | ||
it('should set checked to false when indeterminate and checked set to true at start', async () => { | ||
const el = await fixture(html`<bl-checkbox indeterminate checked></bl-checkbox>`); | ||
expect(el.hasAttribute('checked')).to.eq(false); | ||
}); | ||
}); | ||
|
||
describe('events', () => { | ||
it('should fire bl-checkbox-change event with detail is true when checkbox is unchecked', async () => { | ||
const el = await fixture(html`<bl-checkbox></bl-checkbox>`); | ||
const checkbox = el.shadowRoot?.querySelector('input'); | ||
|
||
setTimeout(() => checkbox?.click()); | ||
const ev = await oneEvent(el, 'bl-checkbox-change'); | ||
|
||
expect(ev).to.exist; | ||
expect(ev.detail).to.be.equal(true); | ||
}); | ||
|
||
it('should fire bl-checkbox-change event with detail is false when checkbox is checked', async () => { | ||
const el = await fixture(html`<bl-checkbox checked></bl-checkbox>`); | ||
const checkbox = el.shadowRoot?.querySelector('input'); | ||
|
||
setTimeout(() => checkbox?.click()); | ||
const ev = await oneEvent(el, 'bl-checkbox-change'); | ||
|
||
expect(ev).to.exist; | ||
expect(ev.detail).to.be.equal(false); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
import { CSSResultGroup, html, LitElement, TemplateResult } from 'lit'; | ||
import { customElement, property } from 'lit/decorators.js'; | ||
import { live } from 'lit/directives/live.js'; | ||
import { event, EventDispatcher } from '../../utilities/event'; | ||
import '../icon/bl-icon'; | ||
import style from './bl-checkbox.css'; | ||
|
||
/** | ||
* @tag bl-checkbox | ||
* @summary Baklava Checkbox component | ||
*/ | ||
@customElement('bl-checkbox') | ||
export default class BlCheckbox extends LitElement { | ||
static get styles(): CSSResultGroup { | ||
return [style]; | ||
} | ||
|
||
/** | ||
* Sets the checked state for checkbox | ||
*/ | ||
@property({ type: Boolean, reflect: true }) | ||
checked = false; | ||
|
||
/** | ||
* Sets the disabled state for checkbox | ||
*/ | ||
@property({ type: Boolean, reflect: true }) | ||
disabled = false; | ||
|
||
/** | ||
* Sets the indeterminate state for checkbox | ||
*/ | ||
@property({ type: Boolean, reflect: true }) | ||
indeterminate = false; | ||
|
||
/** | ||
* Fires whenever user change the value of the checkbox. | ||
*/ | ||
@event('bl-checkbox-change') private onChange: EventDispatcher<boolean>; | ||
|
||
handleChange(event: CustomEvent) { | ||
const target = event.target as HTMLInputElement; | ||
this.checked = target.checked; | ||
this.onChange(target.checked); | ||
this.indeterminate = false; | ||
} | ||
|
||
update(changedProperties: Map<string, unknown>) { | ||
super.update(changedProperties); | ||
if (this.indeterminate && this.checked) { | ||
this.checked = false; | ||
this.requestUpdate('checked', true); | ||
} | ||
} | ||
|
||
render(): TemplateResult { | ||
let icon = ''; | ||
if (this.checked) icon = 'check'; | ||
if (this.indeterminate) icon = 'minus'; | ||
|
||
return html` | ||
<label> | ||
<input | ||
type="checkbox" | ||
name="checkbox" | ||
.checked=${live(this.checked)} | ||
?disabled=${this.disabled} | ||
.indeterminate=${this.indeterminate} | ||
@change=${this.handleChange} | ||
/> | ||
<div class="check-mark">${icon ? html`<bl-icon name="${icon}"></bl-icon>` : null}</div> | ||
<span class="label"><slot></slot></span> | ||
</label> | ||
`; | ||
} | ||
} | ||
|
||
declare global { | ||
interface HTMLElementTagNameMap { | ||
'bl-checkbox': BlCheckbox; | ||
} | ||
} |