-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(components): add radio button and radio group (#127)
resolve #126
- Loading branch information
1 parent
9a9504e
commit f928846
Showing
13 changed files
with
394 additions
and
44 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
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,37 @@ | ||
<template> | ||
<div class="pdap-input pdap-input-radio"> | ||
<input | ||
:id="id" | ||
:name="name" | ||
:defaultChecked="defaultChecked" | ||
:value="value" | ||
v-bind="$attrs" | ||
type="radio" | ||
@input="onInput" | ||
/> | ||
|
||
<label v-if="$slots.label" :for="id"><slot name="label" /></label> | ||
<label v-else-if="label" :for="id">{{ label }}</label> | ||
</div> | ||
</template> | ||
|
||
<script setup lang="ts"> | ||
import { inject, useSlots } from 'vue'; | ||
import { PdapInputRadioProps } from './types'; | ||
import { PdapFormProvideV2 } from '../FormV2/types'; | ||
import { provideKey } from '../FormV2/util'; | ||
const { label, name } = defineProps<PdapInputRadioProps>(); | ||
const slots = useSlots(); | ||
if (!slots.label && !label) | ||
throw new Error( | ||
'All form inputs must have a label, passed as a slot or a prop' | ||
); | ||
const { setValues } = inject<PdapFormProvideV2>(provideKey)!; | ||
function onInput(e: Event) { | ||
setValues({ [name]: (e.target as unknown as HTMLInputElement).value }); | ||
} | ||
</script> |
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,53 @@ | ||
# InputRadio | ||
Radio input. Designed to be wrapped with `RadioGroup` | ||
|
||
## Props - required | ||
|
||
| name | required? | types | description | default | | ||
| ---------------- | ----------------------------- | --------- | --------------------------------------------- | ------- | | ||
| `defaultChecked` | no | `boolean` | radio is checked by default. Only 1 per group | | | ||
| `id` | yes | `string` | id attr | | | ||
| `label` | yes, if label slot not passed | `string` | label content | | | ||
| `name` | yes | `string` | name attr | | | ||
|
||
## Slots | ||
|
||
| name | required? | types | description | default | | ||
| ------- | ----------------------------- | --------- | ------------------------------------ | ------- | | ||
| `label` | yes, if label prop not passed | `Element` | slot content to be rendered as label | | | ||
|
||
## Example | ||
|
||
```vue | ||
<template> | ||
<FormV2 | ||
id="form-id" | ||
name="ice-cream-preference" | ||
:schema="SCHEMA" | ||
@submit="(values) => onSubmit({ values })" | ||
@change="(values, event) => onChange({ values, event })" | ||
> | ||
<!-- Other inputs... --> | ||
<RadioGroup :name="INPUT_RADIO_GROUP_NAME"> | ||
<h4 class="text-lg"> | ||
Select another flavor, with radio buttons this time! | ||
</h4> | ||
<InputRadio | ||
v-for="{ label, value, defaultChecked } of ICE_CREAM_FLAVORS" | ||
:id="value" | ||
:key="label" | ||
:default-checked="defaultChecked" | ||
:name="INPUT_RADIO_GROUP_NAME" | ||
:value="value" | ||
:label="label" | ||
/> | ||
</RadioGroup> | ||
</FormV2> | ||
</template> | ||
<script setup> | ||
import { RadioGroup, InputRadio, FormV2 } from 'pdap-design-system'; | ||
</script> | ||
... | ||
``` |
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 @@ | ||
export { default as InputRadio } from './PdapInputRadio.vue'; |
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,85 @@ | ||
import { describe, it, expect, vi, beforeEach } from 'vitest'; | ||
import { mount, VueWrapper } from '@vue/test-utils'; | ||
import PdapInputRadio from './PdapInputRadio.vue'; | ||
import { provideKey } from '../FormV2/util'; | ||
import { ref } from 'vue'; | ||
|
||
describe('PdapInputRadio', () => { | ||
let wrapper: VueWrapper; | ||
const mockSetValues = vi.fn(); | ||
const mockValues = ref({}); | ||
const mockV$ = ref({ | ||
testName: { | ||
$error: false, | ||
$errors: [], | ||
}, | ||
}); | ||
|
||
const defaultProps = { | ||
name: 'testName', | ||
id: 'test-id', | ||
label: 'Test Label', | ||
value: 'test-value', | ||
}; | ||
|
||
beforeEach(() => { | ||
mockSetValues.mockClear(); | ||
}); | ||
|
||
const createWrapper = (props = {}, provide = {}, slots = {}) => { | ||
return mount(PdapInputRadio, { | ||
props: { | ||
...defaultProps, | ||
...props, | ||
}, | ||
global: { | ||
provide: { | ||
[provideKey as symbol]: { | ||
setValues: mockSetValues, | ||
values: mockValues, | ||
v$: mockV$, | ||
...provide, | ||
}, | ||
}, | ||
}, | ||
slots: { ...slots }, | ||
}); | ||
}; | ||
|
||
it('renders correctly with default props', () => { | ||
wrapper = createWrapper(); | ||
expect(wrapper.find('input[type="radio"]').exists()).toBe(true); | ||
expect(wrapper.find('label').text()).toBe('Test Label'); | ||
}); | ||
|
||
it('renders with slot label instead of prop label', () => { | ||
wrapper = createWrapper( | ||
{}, | ||
{}, | ||
{ | ||
label: '<span>Slot Label</span>', | ||
} | ||
); | ||
expect(wrapper.find('label span').text()).toBe('Slot Label'); | ||
}); | ||
|
||
it('throws error when no label passed as slot or prop', async () => { | ||
expect(() => { | ||
wrapper = createWrapper({ label: undefined }, {}, {}); | ||
}).toThrow('All form inputs must have a label, passed as a slot or a prop'); | ||
}); | ||
|
||
it('emits input event and calls setValues when changed', async () => { | ||
wrapper = createWrapper(); | ||
const input = wrapper.find('input'); | ||
await input.setValue(true); | ||
expect(mockSetValues).toHaveBeenCalledWith({ | ||
[defaultProps.name]: defaultProps.value, | ||
}); | ||
}); | ||
|
||
it('renders with defaultChecked prop', () => { | ||
wrapper = createWrapper({ defaultChecked: true }); | ||
expect(wrapper.find('input').element.defaultChecked).toBe(true); | ||
}); | ||
}); |
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,7 @@ | ||
export interface PdapInputRadioProps { | ||
id: string; | ||
label?: string; | ||
name: string; | ||
defaultChecked?: boolean; | ||
value: string; | ||
} |
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,22 @@ | ||
<template> | ||
<div class="pdap-input-radio-group" :class="{ ['pdap-input-error']: error }"> | ||
<div v-if="$slots.error && error" class="pdap-input-error-message"> | ||
<slot name="error" /> | ||
</div> | ||
<div v-else-if="error" class="pdap-input-error-message">{{ error }}</div> | ||
|
||
<slot /> | ||
</div> | ||
</template> | ||
|
||
<script setup lang="ts"> | ||
import { computed, inject } from 'vue'; | ||
import { PdapFormProvideV2 } from '../FormV2/types'; | ||
import { provideKey } from '../FormV2/util'; | ||
const { name } = defineProps<{ name: string }>(); | ||
const { v$ } = inject<PdapFormProvideV2>(provideKey)!; | ||
const error = computed(() => v$.value[name]?.$errors?.[0]?.$message); | ||
</script> |
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 @@ | ||
export { default as RadioGroup } from './PdapInputRadioGroup.vue'; |
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,78 @@ | ||
import { describe, it, expect, vi, beforeEach } from 'vitest'; | ||
import { mount, VueWrapper } from '@vue/test-utils'; | ||
import RadioGroup from './PdapInputRadioGroup.vue'; | ||
import { provideKey } from '../FormV2/util'; | ||
import { ref } from 'vue'; | ||
|
||
describe('RadioGroup', () => { | ||
let wrapper: VueWrapper; | ||
const mockSetValues = vi.fn(); | ||
const mockValues = ref({}); | ||
const mockV$ = ref({ | ||
testName: { | ||
$error: false, | ||
$errors: [], | ||
}, | ||
}); | ||
|
||
const defaultProps = { | ||
name: 'testName', | ||
}; | ||
|
||
beforeEach(() => { | ||
mockSetValues.mockClear(); | ||
}); | ||
|
||
const createWrapper = (props = {}, slots = {}) => { | ||
return mount(RadioGroup, { | ||
props: { | ||
...defaultProps, | ||
...props, | ||
}, | ||
slots, | ||
global: { | ||
provide: { | ||
[provideKey as symbol]: { | ||
setValues: mockSetValues, | ||
values: mockValues, | ||
v$: mockV$, | ||
}, | ||
}, | ||
}, | ||
}); | ||
}; | ||
|
||
it('renders correctly with default props', () => { | ||
wrapper = createWrapper(); | ||
expect(wrapper.find('.pdap-input-radio-group').exists()).toBe(true); | ||
}); | ||
|
||
it('renders slot content', () => { | ||
wrapper = createWrapper( | ||
{}, | ||
{ | ||
default: '<div class="test-content">Test Content</div>', | ||
} | ||
); | ||
expect(wrapper.find('.test-content').exists()).toBe(true); | ||
}); | ||
|
||
it('shows error message when error exists', async () => { | ||
mockV$.value.testName.$error = true; | ||
// @ts-expect-error | ||
mockV$.value.testName.$errors = [{ $message: 'Test error message' }]; | ||
wrapper = createWrapper(); | ||
expect(wrapper.find('.pdap-input-error').exists()).toBe(true); | ||
}); | ||
|
||
it('shows custom error slot when provided and error exists', () => { | ||
mockV$.value.testName.$error = true; | ||
wrapper = createWrapper( | ||
{}, | ||
{ | ||
error: '<div class="custom-error">Custom Error</div>', | ||
} | ||
); | ||
expect(wrapper.find('.custom-error').exists()).toBe(true); | ||
}); | ||
}); |
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
Oops, something went wrong.