-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Implement checkbox component (#100)
* feat: Implement checkbox component * tests: Add tests for checkbox component * docs: Add route for Checkbox component * feat(field): Don't render label if not provided * chore: PR review * chore: Update phone-field description
- Loading branch information
Showing
9 changed files
with
337 additions
and
12 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 |
---|---|---|
@@ -0,0 +1,117 @@ | ||
import { array, fn } from '@ember/helper'; | ||
import { action, set } from '@ember/object'; | ||
import Component from '@glimmer/component'; | ||
import { tracked } from '@glimmer/tracking'; | ||
import Checkbox from '@nrg-ui/ember/components/form/checkbox'; | ||
import bind from '@nrg-ui/ember/helpers/bind'; | ||
import FreestyleUsage from 'ember-freestyle/components/freestyle/usage'; | ||
import FreestyleSection from 'ember-freestyle/components/freestyle-section'; | ||
|
||
import CodeBlock from '../../code-block'; | ||
|
||
// TypeScript doesn't recognize that this function is used in the template | ||
// eslint-disable-next-line @typescript-eslint/no-unused-vars | ||
function log(...msg: string[]) { | ||
console.log(msg.join(' ')); | ||
} | ||
|
||
class Model { | ||
@tracked | ||
property = ''; | ||
} | ||
|
||
export default class extends Component { | ||
model = new Model(); | ||
|
||
@tracked | ||
class = ''; | ||
|
||
@tracked | ||
disabled = false; | ||
|
||
@tracked | ||
inline; | ||
|
||
@tracked | ||
label = 'Checkbox label'; | ||
|
||
@tracked | ||
type = 'checkbox'; | ||
|
||
@action | ||
update(key: string, value: unknown) { | ||
set(this, key, value); | ||
} | ||
|
||
<template> | ||
<FreestyleSection @name="Checkbox" as |Section|> | ||
<Section.subsection @name="Basic"> | ||
<FreestyleUsage> | ||
<:example> | ||
<Checkbox | ||
class={{this.class}} | ||
@binding={{bind this.model "property"}} | ||
@disabled={{this.disabled}} | ||
@inline={{this.inline}} | ||
@label={{this.label}} | ||
@type={{this.type}} | ||
@onChange={{fn log "The value changed to"}} | ||
/> | ||
</:example> | ||
<:api as |Args|> | ||
<Args.String | ||
@name="class" | ||
@description="The class to apply to the group input. Note that this is not an argument but rather a class applied directly to the input" | ||
@value={{this.class}} | ||
@onInput={{fn this.update "class"}} | ||
@options={{this.classOptions}} | ||
/> | ||
<Args.Bool | ||
@name="binding" | ||
@description="Create a two-way binding with the value" | ||
@value={{this.model.property}} | ||
@onInput={{fn this.update "model.property"}} | ||
/> | ||
<Args.Bool | ||
@name="disabled" | ||
@defaultValue={{false}} | ||
@description="When true, the input will be disabled" | ||
@value={{this.disabled}} | ||
@onInput={{fn this.update "disabled"}} | ||
/> | ||
<Args.Bool | ||
@name="inline" | ||
@defaultValue={{false}} | ||
@description="When true, the input will be displayed inline" | ||
@value={{this.inline}} | ||
@onInput={{fn this.update "inline"}} | ||
/> | ||
<Args.String | ||
@name="label" | ||
@description="The label to display next to the checkbox" | ||
@value={{this.label}} | ||
@onInput={{fn this.update "label"}} | ||
/> | ||
<Args.String | ||
@defaultValue="checkbox" | ||
@name="type" | ||
@description="The type of checkbox to render" | ||
@options={{array "checkbox" "switch"}} | ||
@value={{this.type}} | ||
@onInput={{fn this.update "type"}} | ||
/> | ||
<Args.Action | ||
@name="onChange" | ||
@description="The action to call when the value changes" | ||
> | ||
<CodeBlock | ||
@lang="typescript" | ||
@code="(newValue: boolean) => unknown" | ||
/> | ||
</Args.Action> | ||
</:api> | ||
</FreestyleUsage> | ||
</Section.subsection> | ||
</FreestyleSection> | ||
</template> | ||
} |
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
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
5 changes: 5 additions & 0 deletions
5
apps/ember-test-app/app/templates/components/form/checkbox.hbs
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,5 @@ | ||
{{page-title "Checkbox"}} | ||
|
||
<div class="container mx-auto"> | ||
<F::Form::Checkbox /> | ||
</div> |
104 changes: 104 additions & 0 deletions
104
apps/ember-test-app/tests/integration/components/form/checkbox-test.gts
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 { click, render, settled } from '@ember/test-helpers'; | ||
import { tracked } from '@glimmer/tracking'; | ||
import Checkbox from '@nrg-ui/ember/components/form/checkbox'; | ||
import bind from '@nrg-ui/ember/helpers/bind'; | ||
import { setupRenderingTest } from 'ember-test-app/tests/helpers'; | ||
import { module, test } from 'qunit'; | ||
|
||
module('Integration | Component | form/checkbox', function (hooks) { | ||
setupRenderingTest(hooks); | ||
|
||
class Model { | ||
@tracked | ||
value: boolean = false; | ||
} | ||
|
||
test('it renders', async function (assert) { | ||
const model = new Model(); | ||
await render(<template> | ||
<Checkbox | ||
@binding={{bind model "value"}} | ||
@id="my-id" | ||
@label="This is a checkbox" | ||
/> | ||
</template>); | ||
|
||
assert | ||
.dom('.form-check > input') | ||
.hasAttribute('role', 'checkbox') | ||
.hasAttribute('type', 'checkbox') | ||
.hasClass('form-check-input') | ||
.hasValue('false') | ||
.isNotChecked(); | ||
|
||
const labelId = this.element.querySelector('.form-check > input').id; | ||
|
||
assert | ||
.dom('.form-check > label') | ||
.hasAttribute('for', labelId) | ||
.hasText('This is a checkbox'); | ||
|
||
model.value = true; | ||
await settled(); | ||
|
||
assert.dom('.form-check > input').hasValue('true').isChecked(); | ||
}); | ||
|
||
test('it renders (switch)', async function (assert) { | ||
const model = new Model(); | ||
await render(<template> | ||
<Checkbox | ||
@binding={{bind model "value"}} | ||
@id="my-id" | ||
@label="This is a checkbox" | ||
@type="switch" | ||
/> | ||
</template>); | ||
|
||
assert.dom('div').hasClass('form-switch'); | ||
|
||
assert | ||
.dom('.form-check > input') | ||
.hasAttribute('role', 'switch') | ||
.hasAttribute('type', 'checkbox') | ||
.hasClass('form-check-input') | ||
.hasValue('false') | ||
.isNotChecked(); | ||
|
||
const labelId = this.element.querySelector('.form-check > input').id; | ||
|
||
assert | ||
.dom('.form-check > label') | ||
.hasAttribute('for', labelId) | ||
.hasText('This is a checkbox'); | ||
|
||
model.value = true; | ||
await settled(); | ||
|
||
assert.dom('.form-check > input').hasValue('true').isChecked(); | ||
}); | ||
|
||
test('it works', async function (assert) { | ||
const model = new Model(); | ||
await render(<template> | ||
<Checkbox @binding={{bind model "value"}} @label="This is a checkbox" /> | ||
</template>); | ||
|
||
assert.dom('.form-check > input').hasValue('false').isNotChecked(); | ||
|
||
model.value = true; | ||
await settled(); | ||
|
||
assert.dom('.form-check > input').hasValue('true').isChecked(); | ||
|
||
await click('.form-check > input'); | ||
|
||
assert.dom('.form-check > input').hasValue('false').isNotChecked(); | ||
assert.false(model.value); | ||
|
||
await click('.form-check > input'); | ||
|
||
assert.dom('.form-check > input').hasValue('true').isChecked(); | ||
assert.true(model.value); | ||
}); | ||
}); |
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,82 @@ | ||
import { on } from '@ember/modifier'; | ||
import { action } from '@ember/object'; | ||
|
||
import BoundValue from './bound-value.ts'; | ||
|
||
export interface CheckboxSignature { | ||
Element: HTMLInputElement; | ||
Args: { | ||
describedBy?: string; | ||
disabled?: boolean; | ||
id?: string; | ||
inline?: boolean; | ||
isInvalid?: boolean; | ||
isWarning?: boolean; | ||
label?: string; | ||
type?: 'checkbox' | 'switch'; | ||
}; | ||
Blocks: { | ||
default: []; | ||
}; | ||
} | ||
|
||
export default class FormCheckbox extends BoundValue< | ||
CheckboxSignature, | ||
boolean | ||
> { | ||
get classList() { | ||
const classes = ['form-check-input']; | ||
|
||
if (this.args.isInvalid) { | ||
classes.push('is-invalid'); | ||
} else if (this.args.isWarning) { | ||
classes.push('is-warning'); | ||
} | ||
|
||
return classes.join(' '); | ||
} | ||
|
||
get divClassList() { | ||
const classList = ['form-check']; | ||
|
||
if (this.isSwitch) { | ||
classList.push('form-switch'); | ||
} | ||
|
||
if (this.args.inline) { | ||
classList.push('form-check-inline'); | ||
} | ||
|
||
return classList.join(' '); | ||
} | ||
|
||
get isSwitch() { | ||
return this.args.type === 'switch'; | ||
} | ||
|
||
@action | ||
change(evt: Event) { | ||
const target = evt.target as HTMLInputElement; | ||
this.onChange(target.checked); | ||
} | ||
|
||
<template> | ||
<div class={{this.divClassList}}> | ||
<input | ||
aria-describedby={{@describedBy}} | ||
checked={{this.value}} | ||
class={{this.classList}} | ||
disabled={{@disabled}} | ||
id={{@id}} | ||
role={{if this.isSwitch "switch" "checkbox"}} | ||
type="checkbox" | ||
value={{this.value}} | ||
{{on "change" this.change}} | ||
...attributes | ||
/> | ||
<label class="form-check-label" for={{@id}}> | ||
{{@label}} | ||
</label> | ||
</div> | ||
</template> | ||
} |
Oops, something went wrong.