Skip to content

Commit

Permalink
feat(components): CCheckbox
Browse files Browse the repository at this point in the history
  • Loading branch information
LeBenLeBen committed Feb 23, 2022
1 parent 8159587 commit 20c6bc3
Show file tree
Hide file tree
Showing 16 changed files with 365 additions and 10 deletions.
105 changes: 105 additions & 0 deletions packages/chusho/lib/components/CCheckbox/CCheckbox.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import { mount } from '@vue/test-utils';

import CCheckbox from './CCheckbox';

describe('CCheckbox', () => {
it('renders with config checked class', () => {
const wrapper = mount(CCheckbox, {
global: {
provide: {
$chusho: {
options: {
components: {
checkbox: {
class: ({ checked }) => {
return { checked };
},
},
},
},
},
},
},
props: {
modelValue: true,
},
});

expect(wrapper.classes()).toEqual(['checked']);
});

it('renders with correct attributes by default', () => {
const wrapper = mount(CCheckbox);

expect(wrapper.html()).toEqual('<input type="checkbox">');
});

it('renders with extra attributes', () => {
const wrapper = mount(CCheckbox, {
attrs: {
id: 'checkbox',
},
});

expect(wrapper.html()).toEqual('<input id="checkbox" type="checkbox">');
});

it('keeps the modelValue synchronized with the checkbox value', async () => {
const wrapper = mount({
components: {
CCheckbox,
},
data() {
return {
value: false,
};
},
template: '<CCheckbox v-model="value" />',
});
const checkbox = wrapper.findComponent(CCheckbox);

expect(checkbox.vm.modelValue).toBe(false);
expect(checkbox.element.checked).toBe(false);

await checkbox.setValue(true);
expect(checkbox.vm.modelValue).toBe(true);
expect(checkbox.element.checked).toBe(true);
expect(checkbox.emitted('update:modelValue')).toEqual([[true]]);

await wrapper.setData({ value: false });
expect(checkbox.vm.modelValue).toBe(false);
expect(checkbox.element.checked).toBe(false);
});

it('accepts custom true/false values', async () => {
const wrapper = mount({
components: {
CCheckbox,
},
data() {
return {
value: 'off',
};
},
template: '<CCheckbox v-model="value" trueValue="on" falseValue="off" />',
});
const checkbox = wrapper.findComponent(CCheckbox);

expect(checkbox.vm.modelValue).toBe('off');
expect(checkbox.element.checked).toBe(false);

await checkbox.setValue('on');
expect(checkbox.vm.modelValue).toBe('on');
expect(checkbox.element.checked).toBe(true);

await checkbox.setValue('off');
expect(checkbox.vm.modelValue).toBe('off');
expect(checkbox.element.checked).toBe(false);

expect(checkbox.emitted('update:modelValue')).toEqual([['on'], ['off']]);

await wrapper.setData({ value: 'on' });
expect(checkbox.vm.modelValue).toBe('on');
expect(checkbox.element.checked).toBe(true);
});
});
64 changes: 64 additions & 0 deletions packages/chusho/lib/components/CCheckbox/CCheckbox.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { defineComponent, h, inject, mergeProps } from 'vue';
import { DollarChusho } from '../../types';
import { ALL_TYPES, generateConfigClass } from '../../utils/components';
import componentMixin from '../mixins/componentMixin';

export default defineComponent({
name: 'CCheckbox',

mixins: [componentMixin],

inheritAttrs: false,

props: {
/**
* Bind the Checkbox state with the parent component.
* @type {any}
*/
modelValue: {
type: ALL_TYPES,
default: null,
},
/**
* Value set when the checkbox is checked.
* @type {any}
*/
trueValue: {
type: ALL_TYPES,
default: true,
},
/**
* Value set when the checkbox is unchecked.
* @type {any}
*/
falseValue: {
type: ALL_TYPES,
default: false,
},
},

emits: ['update:modelValue'],

render() {
const checkboxConfig = inject<DollarChusho | null>('$chusho', null)?.options
?.components?.checkbox;
const checked = this.modelValue === this.trueValue;
const attrs: Record<string, unknown> = {
...generateConfigClass(checkboxConfig?.class, {
...this.$props,
checked,
}),
type: 'checkbox',
checked,
onChange: (e: Event) => {
const checked = (e.target as HTMLInputElement).checked;
this.$emit(
'update:modelValue',
checked ? this.trueValue : this.falseValue
);
},
};

return h('input', mergeProps(this.$attrs, attrs), this.$slots);
},
});
3 changes: 3 additions & 0 deletions packages/chusho/lib/components/CCheckbox/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import CCheckbox from './CCheckbox';

export { CCheckbox };
1 change: 1 addition & 0 deletions packages/chusho/lib/components/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export * from './CAlert';
export * from './CBtn';
export * from './CCheckbox';
export * from './CCollapse';
export * from './CDialog';
export * from './CIcon';
Expand Down
1 change: 1 addition & 0 deletions packages/chusho/lib/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ type ComponentCommonOptions = {
interface ComponentsOptions {
alert?: ComponentCommonOptions;
btn?: ComponentCommonOptions;
checkbox?: ComponentCommonOptions;
collapse?: ComponentCommonOptions & {
transition?: BaseTransitionProps;
};
Expand Down
11 changes: 11 additions & 0 deletions packages/chusho/lib/utils/components.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,17 @@ import { h, Transition, BaseTransitionProps, VNode } from 'vue';
import { isPlainObject } from '../utils/objects';
import { ClassGenerator, VueClassBinding } from '../types';

export const ALL_TYPES = [
String,
Number,
Boolean,
Array,
Object,
Date,
Function,
Symbol,
];

export function generateConfigClass(
configClass?: VueClassBinding | ClassGenerator,
ctx?: Record<string, unknown>
Expand Down
10 changes: 10 additions & 0 deletions packages/chusho/src/chusho.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,16 @@ export default {
},
},

checkbox: {
class({ checked }) {
return [
'appearance-none inline-block w-3 h-3 rounded-sm border-2 border-white ring-2 ring-gray-500',
{ 'bg-white': !checked },
{ 'bg-accent-500': checked },
];
},
},

collapseBtn: {
inheritBtnClass: false,
class({ active }) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<template>
<CCheckbox v-model="value" />
</template>

<script>
export default {
data() {
return {
value: false,
};
},
};
</script>
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<template>
<CLabel variant="inline" for="checkbox">
<CCheckbox id="checkbox" v-model="value" variant="inline" />
Check me if you can
</CLabel>
</template>

<script>
export default {
data() {
return {
value: false,
};
},
};
</script>
12 changes: 12 additions & 0 deletions packages/docs/.vuepress/components/Example/Checkbox.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<template>
<CLabel variant="inline" for="my-checkbox">
<CCheckbox v-model="value" variant="inline" id="my-checkbox" />
Check me if you can
</CLabel>
</template>

<script setup>
import { ref } from 'vue';
const value = ref(false);
</script>
1 change: 1 addition & 0 deletions packages/docs/.vuepress/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ module.exports = {
children: [
'/guide/components/alert.md',
'/guide/components/button.md',
'/guide/components/checkbox.md',
'/guide/components/collapse.md',
'/guide/components/dialog.md',
'/guide/components/icon.md',
Expand Down
11 changes: 11 additions & 0 deletions packages/docs/chusho.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,17 @@ export default {
},
},

checkbox: {
class({ variant, checked }) {
return [
'appearance-none inline-block w-3 h-3 rounded-sm border-2 border-white ring-2 ring-gray-500',
{ 'mr-3': variant?.includes('inline') },
{ 'bg-white': !checked },
{ 'bg-accent-500': checked },
];
},
},

collapse: {
class({ variant }) {
return {
Expand Down
1 change: 1 addition & 0 deletions packages/docs/guide/components/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

- [Alert](alert.html)
- [Button](button.html)
- [Checkbox](checkbox.html)
- [Collapse](collapse.html)
- [Dialog](dialog.html)
- [Icon](icon.html)
Expand Down
78 changes: 78 additions & 0 deletions packages/docs/guide/components/checkbox.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# Checkbox

Augmented form field for boolean input.

<Showcase>
<ExampleCheckbox />
</Showcase>

## Config

The options below are to be set in the [global configuration](/guide/config.html) at the following location:

```js
{
components: {
checkbox: { ... },
},
}
```

### class

Classes applied to the input element, except when the prop `bare` is set to `true`. See [styling components](/guide/styling-components/).

- **type:** `Array<String | Object> | Object | String | (props: Object) => {}`
- **default:** `null`

#### Example

```js
class({ checked }) {
return ['checkbox', {
'checkbox--checked': checked,
}]
}
```

## API

<Docgen :components="['CCheckbox']" />

## Examples

### Controlled

```vue
<template>
<CCheckbox v-model="value" />
</template>
<script>
export default {
data() {
return {
value: true, // Checked by default
};
},
};
</script>
```

### Controlled with custom values

```vue
<template>
<CCheckbox v-model="value" trueValue="on" falseValue="off" />
</template>
<script>
export default {
data() {
return {
value: 'off', // Unchecked by default
};
},
};
</script>
```

0 comments on commit 20c6bc3

Please sign in to comment.