diff --git a/README.md b/README.md index 87c83b9..e329c20 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,7 @@ This repo provides a basic setup for developing component libraries in Vite with - [x] LcInput - [x] LcModal - [x] LcPagination +- [x] LcRadioGroup & LcRadio - [x] LcTable - [x] LcTooltip diff --git a/src/components/LcForm.vue b/src/components/LcForm.vue index 191a7f2..9ec60ac 100644 --- a/src/components/LcForm.vue +++ b/src/components/LcForm.vue @@ -33,6 +33,12 @@ v-model="field.model" v-bind="field.attr" /> + @@ -86,6 +92,7 @@ import LcMultiselect from './LcMultiselect/LcMultiselect.vue' import LcCheckbox from './LcCheckbox.vue' import LcInput from './LcInput.vue' import LcTextarea from './LcTextarea' +import { LcRadioGroup } from './LcRadio' configure({ generateMessage: localize({ @@ -108,6 +115,7 @@ export default defineComponent({ LcInput, LcMultiselect, LcTextarea, + LcRadioGroup, vForm, }, props: { diff --git a/src/components/LcRadio/LcRadio.d.ts b/src/components/LcRadio/LcRadio.d.ts new file mode 100644 index 0000000..dd6e676 --- /dev/null +++ b/src/components/LcRadio/LcRadio.d.ts @@ -0,0 +1,3 @@ +import { DefineComponent } from 'vue' +const component: DefineComponent<{}, {}, any> +export default component diff --git a/src/components/LcRadio/LcRadio.stories.ts b/src/components/LcRadio/LcRadio.stories.ts new file mode 100644 index 0000000..d8314dd --- /dev/null +++ b/src/components/LcRadio/LcRadio.stories.ts @@ -0,0 +1,34 @@ +import { action } from '@storybook/addon-actions' + +import LcRadio from './LcRadio.vue' + +export default { + title: 'Example/LcRadio', + component: LcRadio, +} + +const Template = (args: any) => ({ + components: { LcRadio }, + setup() { + return { args } + }, + template: '', + methods: { + onChange: action('onChange'), + }, +}) + +export const Base = Template.bind({}) as any +Base.args = { + label: 'Monsieur', + modelValue: '', + name: 'civility', + value: 'mr', + vertical: false, +} + +export const Disabled = Template.bind({}) as any +Disabled.args = { + ...Base.args, + disabled: true, +} diff --git a/src/components/LcRadio/LcRadio.vue b/src/components/LcRadio/LcRadio.vue new file mode 100644 index 0000000..8aeeb93 --- /dev/null +++ b/src/components/LcRadio/LcRadio.vue @@ -0,0 +1,107 @@ + + + + diff --git a/src/components/LcRadio/LcRadioGroup.stories.ts b/src/components/LcRadio/LcRadioGroup.stories.ts new file mode 100644 index 0000000..0f8b963 --- /dev/null +++ b/src/components/LcRadio/LcRadioGroup.stories.ts @@ -0,0 +1,79 @@ +import { action } from '@storybook/addon-actions' + +import LcRadio from './LcRadio.vue' +import LcRadioGroup from './LcRadioGroup.vue' + +export default { + title: 'Example/LcRadioGroup', + component: LcRadioGroup, + subcomponents: { LcRadio }, +} + +const Template = (args: any) => ({ + components: { LcRadioGroup, LcRadio }, + setup() { + return { args } + }, + template: '', + methods: { + onChange: action('onChange'), + }, +}) + +export const Base = Template.bind({}) as any +Base.args = { + modelValue: '', + name: 'civility', + options: [ + { + label: 'Monsieur', + value: 'mr', + }, + { + label: 'Madame', + value: 'ms', + }, + { + label: 'Non binaire', + value: 'unknown', + }, + ], +} + +export const WithLabel = Template.bind({}) as any +WithLabel.args = { + ...Base.args, + label: 'Choisir votre civilité :', +} + +export const Vertical = Template.bind({}) as any +Vertical.args = { + ...Base.args, + vertical: true, +} + +export const VerticalWithLabel = Template.bind({}) as any +VerticalWithLabel.args = { + ...WithLabel.args, + vertical: true, +} + +export const Disabled = Template.bind({}) as any +Disabled.args = { + ...WithLabel.args, + options: [ + { + label: 'Monsieur', + value: 'mr', + disabled: true, + }, + { + label: 'Madame', + value: 'ms', + }, + { + label: 'Non binaire', + value: 'unknown', + }, + ], +} diff --git a/src/components/LcRadio/LcRadioGroup.vue b/src/components/LcRadio/LcRadioGroup.vue new file mode 100644 index 0000000..46eb8e3 --- /dev/null +++ b/src/components/LcRadio/LcRadioGroup.vue @@ -0,0 +1,102 @@ + + + + diff --git a/src/components/LcRadio/__tests__/LcRadio.spec.ts b/src/components/LcRadio/__tests__/LcRadio.spec.ts new file mode 100644 index 0000000..60a2217 --- /dev/null +++ b/src/components/LcRadio/__tests__/LcRadio.spec.ts @@ -0,0 +1,58 @@ +import { mount } from '@vue/test-utils' +import { LcRadio } from '../index' + +let wrapper: any + +beforeEach(() => { + wrapper = mount(LcRadio, { + props: { + name: 'civility', + value: 'mr', + vertical: false, + }, + }) +}) + +afterEach(() => { + wrapper?.unmount() +}) + +describe('LcRadio', () => { + it('is a Vue instance', () => { + expect(wrapper.vm).toBeTruthy() + }) + + describe('Input behaviour', () => { + it('should emit the event', () => { + const radioButton = wrapper.find('.lc-radio') + radioButton.trigger('change') + + expect(wrapper.emitted()).toHaveProperty('update:modelValue') + }) + + it('should emit the right value', () => { + const radioButton = wrapper.find('.lc-radio') + radioButton.trigger('change') + + const changeEvent = wrapper.emitted('update:modelValue') + + expect(changeEvent[0]).toEqual(['mr']) + }) + + it('should disabled the radio button', async() => { + await wrapper.setProps({ disabled: true }) + const radioButton = wrapper.find('.lc-radio') + + expect(radioButton.attributes()).toHaveProperty('disabled') + }) + }) + + describe('Input style', () => { + it('should set style for vertical layout', async() => { + await wrapper.setProps({ vertical: true }) + const label = wrapper.find('.lc-radio-label') + + expect(label.classes()).toContain('lc-radio-label--vertical') + }) + }) +}) diff --git a/src/components/LcRadio/__tests__/LcRadioGroup.spec.ts b/src/components/LcRadio/__tests__/LcRadioGroup.spec.ts new file mode 100644 index 0000000..aaa27a5 --- /dev/null +++ b/src/components/LcRadio/__tests__/LcRadioGroup.spec.ts @@ -0,0 +1,74 @@ +import { mount } from '@vue/test-utils' +import { LcRadioGroup } from '../index' + +let wrapper: any + +beforeEach(() => { + wrapper = mount(LcRadioGroup, { + props: { + options: [ + { label: 'Monsieur', value: 'mr' }, + { label: 'Madame', value: 'ms' }, + { label: 'Non spécifié', value: 'unspecified' }, + ], + name: 'civility', + modelValue: '', + }, + }) +}) + +afterEach(() => { + wrapper?.unmount() +}) + +describe('LcRadioGroup', () => { + it('is a Vue instance', () => { + expect(wrapper.vm).toBeTruthy() + }) + + describe('Input behaviour', () => { + it('should render good number of radio-buttons', () => { + const radiosButtons = wrapper.findAll('.lc-radio') + + expect(radiosButtons).toHaveLength(3) + }) + + it('should set good name attribute', () => { + const radiosButtons = wrapper.findAll('.lc-radio') + + expect(radiosButtons[1].attributes('value')).toEqual('ms') + }) + + it('should emit the event', () => { + const radioButton = wrapper.find('.lc-radio') + radioButton.trigger('change') + + expect(wrapper.emitted()).toHaveProperty('update:modelValue') + }) + + it('should emit the right value', () => { + const radioButton = wrapper.find('.lc-radio') + radioButton.trigger('change') + + const changeEvent = wrapper.emitted('update:modelValue') + + expect(changeEvent[0]).toEqual(['mr']) + }) + }) + + describe('Input layout', () => { + it('it should render the right label', async() => { + await wrapper.setProps({ label: 'Your civility :' }) + + const label = wrapper.find('.lc-radiogroup-label') + expect(label.text()).toEqual('Your civility :') + }) + + it('it should render vertical layout', async() => { + await wrapper.setProps({ vertical: true }) + + const wrapperBtn = wrapper.find('.lc-radiogroup-layout') + expect(wrapperBtn.classes()).toContain('lc-radiogroup-layout--vertical') + }) + }) +}) diff --git a/src/components/LcRadio/index.ts b/src/components/LcRadio/index.ts new file mode 100644 index 0000000..2a78255 --- /dev/null +++ b/src/components/LcRadio/index.ts @@ -0,0 +1,2 @@ +export { default as LcRadio } from './LcRadio.vue' +export { default as LcRadioGroup } from './LcRadioGroup.vue' diff --git a/src/library.ts b/src/library.ts index 618093f..501fe4d 100644 --- a/src/library.ts +++ b/src/library.ts @@ -6,6 +6,7 @@ import LcInput from './components/LcInput.vue' import LcModal from './components/LcModal.vue' import LcMultiselect from './components/LcMultiselect' import LcPagination from './components/LcPagination.vue' +import { LcRadio, LcRadioGroup } from './components/LcRadio' import LcTable from './components/LcTable.vue' import LcTextarea from './components/LcTextarea' import LcTooltip from './components/LcTooltip.vue' @@ -19,6 +20,8 @@ export { LcModal, LcMultiselect, LcPagination, + LcRadio, + LcRadioGroup, LcTable, LcTextarea, LcTooltip, diff --git a/src/stories/LcForm.stories.ts b/src/stories/LcForm.stories.ts index 2e0a8e4..ff2b2ce 100644 --- a/src/stories/LcForm.stories.ts +++ b/src/stories/LcForm.stories.ts @@ -72,11 +72,27 @@ Base.args = { model: false, inputType: 'checkbox', attr: { + class: 'mb-4', label: 'Je souhaite recevoir occasionnellement une sélection d’expériences et de maisons.', name: 'newsletter', rules: 'required', }, }, + { + model: '', + inputType: 'radio', + attr: { + label: 'Votre civilité :', + name: 'civility', + rules: 'required', + vertical: false, + }, + options: [ + { label: 'Monsieur', value: 'mr' }, + { label: 'Madame', value: 'ms' }, + { label: 'Non spécifié', value: 'unspecified' }, + ], + }, { model: null, inputType: 'select',