Skip to content

Commit

Permalink
feat(components): CRadio
Browse files Browse the repository at this point in the history
  • Loading branch information
LeBenLeBen committed Feb 23, 2022
1 parent 75a5edf commit 495f8c7
Show file tree
Hide file tree
Showing 13 changed files with 333 additions and 2 deletions.
116 changes: 116 additions & 0 deletions packages/chusho/lib/components/CRadio/CRadio.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import { mount } from '@vue/test-utils';

import CRadio from './CRadio';

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

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

it('renders with correct attributes by default', () => {
const wrapper = mount(CRadio, {
props: {
value: true,
},
});

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

it('renders with extra attributes', () => {
const wrapper = mount(CRadio, {
props: {
value: true,
},
attrs: {
id: 'radio',
},
});

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

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

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

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

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

it('keeps the modelValue synchronized with multiple radios', async () => {
const wrapper = mount({
components: {
CRadio,
},
data() {
return {
value: false,
};
},
template: `<CRadio id="1" v-model="value" :value="true" /><CRadio id="2" v-model="value" :value="false" />`,
});
const radios = wrapper.findAllComponents(CRadio);

expect(radios[0].vm.modelValue).toBe(false);
expect(radios[0].element.checked).toBe(false);
expect(radios[1].vm.modelValue).toBe(false);
expect(radios[1].element.checked).toBe(true);

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

await wrapper.setData({ value: false });
expect(radios[0].vm.modelValue).toBe(false);
expect(radios[0].element.checked).toBe(false);
expect(radios[1].vm.modelValue).toBe(false);
expect(radios[1].element.checked).toBe(true);
});
});
51 changes: 51 additions & 0 deletions packages/chusho/lib/components/CRadio/CRadio.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
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: 'CRadio',

mixins: [componentMixin],

inheritAttrs: false,

props: {
/**
* Bind the Radio state with the parent component.
* @type {any}
*/
modelValue: {
type: ALL_TYPES,
default: null,
},
/**
* The value to be used when the Radio is checked.
* @type {any}
*/
value: {
type: ALL_TYPES,
required: true,
},
},

emits: ['update:modelValue'],

render() {
const radioConfig = inject<DollarChusho | null>('$chusho', null)?.options
?.components?.radio;
const checked = this.modelValue === this.value;
const attrs: Record<string, unknown> = {
...generateConfigClass(radioConfig?.class, {
...this.$props,
checked,
}),
type: 'radio',
value: this.$props.value,
checked,
onChange: () => this.$emit('update:modelValue', this.$props.value),
};

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

export { CRadio };
1 change: 1 addition & 0 deletions packages/chusho/lib/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export * from './CDialog';
export * from './CIcon';
export * from './CLabel';
export * from './CPicture';
export * from './CRadio';
export * from './CSelect';
export * from './CTabs';
export * from './CTextarea';
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 @@ -55,6 +55,7 @@ interface ComponentsOptions {
};
label?: ComponentCommonOptions;
picture?: ComponentCommonOptions;
radio?: ComponentCommonOptions;
select?: ComponentCommonOptions;
selectBtn?: ComponentCommonOptions;
selectOptions?: ComponentCommonOptions & {
Expand Down
11 changes: 11 additions & 0 deletions packages/chusho/src/chusho.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,17 @@ export default {
class: 'picture',
},

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

select: {
class: 'inline-block relative',
},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<template>
<div class="space-x-6">
<CLabel for="radio-1" variant="inline">
<CRadio
id="radio-1"
v-model="value"
variant="inline"
name="radio-group"
type="radio"
:value="true"
/>
True
</CLabel>
<CLabel for="radio-2" variant="inline">
<CRadio
id="radio-2"
v-model="value"
variant="inline"
name="radio-group"
type="radio"
:value="false"
/>
False
</CLabel>
<CLabel for="radio-3" variant="inline">
<CRadio
id="radio-3"
v-model="value"
variant="inline"
name="radio-group"
type="radio"
:value="null"
/>
Other
</CLabel>
</div>
</template>

<script setup>
import { ref } from 'vue';
const value = ref(false);
</script>
22 changes: 22 additions & 0 deletions packages/docs/.vuepress/components/Example/Radio.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<template>
<div class="space-x-6">
<CLabel variant="inline" for="radio-cats">
<CRadio v-model="value" variant="inline" id="radio-cats" value="cats" />
Cats
</CLabel>
<CLabel variant="inline" for="radio-dogs">
<CRadio v-model="value" variant="inline" id="radio-dogs" value="dogs" />
Dogs
</CLabel>
<CLabel variant="inline" for="radio-both">
<CRadio v-model="value" variant="inline" id="radio-both" value="both" />
Both
</CLabel>
</div>
</template>

<script setup>
import { ref } from 'vue';
const value = ref('both');
</script>
2 changes: 1 addition & 1 deletion packages/docs/.vuepress/components/PropsTable.vue
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
>
</td>
<td>
<code>{{ row.type.name }}</code>
<code v-if="row.type">{{ row.type.name }}</code>
</td>
<td>
<code v-if="row.defaultValue">{{
Expand Down
1 change: 1 addition & 0 deletions packages/docs/.vuepress/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ module.exports = {
'/guide/components/icon.md',
'/guide/components/label.md',
'/guide/components/picture.md',
'/guide/components/radio.md',
'/guide/components/select.md',
'/guide/components/tabs.md',
'/guide/components/textarea.md',
Expand Down
21 changes: 20 additions & 1 deletion packages/docs/chusho.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -106,13 +106,32 @@ export default {
},

label: {
class: 'cursor-pointer font-medium',
class({ variant }) {
return [
'cursor-pointer',
{
'block mb-1 font-bold': !variant,
'inline-flex items-center': variant?.includes('inline'),
},
];
},
},

picture: {
class: 'block h-auto rounded-2xl',
},

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

select: {
class: 'inline-block relative',
},
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 @@ -8,6 +8,7 @@
- [Icon](icon.html)
- [Label](label.html)
- [Picture](picture.html)
- [Radio](radio.html)
- [Select](select.html)
- [Tabs](tabs.html)
- [Textarea](textarea.html)
Expand Down
62 changes: 62 additions & 0 deletions packages/docs/guide/components/radio.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# Radio

Augmented form field for choice input.

<Showcase>
<ExampleRadio />
</Showcase>

## Config

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

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

### 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 ['radio', {
'radio--checked': checked,
}]
}
```

## API

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

## Examples

### Controlled

```vue
<template>
<CRadio v-model="value" value="A" />
<CRadio v-model="value" value="B" />
<CRadio v-model="value" value="C" />
</template>
<script>
export default {
data() {
return {
value: null, // None by default
};
},
};
</script>
```

0 comments on commit 495f8c7

Please sign in to comment.