-
Notifications
You must be signed in to change notification settings - Fork 179
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: implement CvForm, CvFormGroup and CvFormItem (#1452)
* feat(cv-form): add CvForm component * feat(cv-form-group): add CvFormGroup component * feat(cv-form-item): add CvFormItem component * feat: add bottom margin to bx--form-item elements Repeat the styling used on v2 for form item element class to give them some spacing at the stories * docs: add stories for form, form-group and form-item components
- Loading branch information
1 parent
8faa627
commit d93b825
Showing
9 changed files
with
375 additions
and
0 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,193 @@ | ||
import { Canvas, Meta, Story } from '@storybook/addon-docs'; | ||
import { action } from '@storybook/addon-actions'; | ||
import { sbCompPrefix } from '../../global/storybook-utils'; | ||
import CvButton from '../CvButton'; | ||
import CvTextInput from '../CvTextInput'; | ||
import CvTextArea from '../CvTextArea'; | ||
import { CvForm, CvFormGroup, CvFormItem } from '.'; | ||
|
||
<Meta title={`${sbCompPrefix}/CvForm`} component={CvForm} /> | ||
|
||
export const Template = args => ({ | ||
components: { | ||
CvForm, | ||
CvFormGroup, | ||
CvFormItem, | ||
CvButton, | ||
CvTextInput, | ||
CvTextArea, | ||
}, | ||
setup: () => { | ||
const doSubmit = action('cv-form -submit event'); | ||
return { | ||
args: { ...args, template: undefined }, | ||
onSubmit(ev) { | ||
console.dir([].slice.call(ev.target, [0, ev.target.length])); | ||
doSubmit(ev); | ||
}, | ||
} | ||
}, | ||
template: args.template, | ||
}); | ||
|
||
export const defaultTemplate = ` | ||
<cv-form @submit.prevent="onSubmit"> | ||
<cv-text-input | ||
label="Text input label" | ||
placeholder="Optional placeholder text" | ||
helperText="Optional helper text here; if message is more than one line text should wrap (~100 character count maximum)" | ||
/> | ||
<cv-text-area | ||
label="Text area label" | ||
placeholder="Optional placeholder text" | ||
helperText="Optional helper text here; if message is more than one line text should wrap (~100 character count maximum)" | ||
/> | ||
<cv-button>Submit</cv-button> | ||
</cv-form> | ||
`; | ||
|
||
export const formGroupTemplate = ` | ||
<cv-form-group v-bind="args"> | ||
<template #label> | ||
FormGroup label-legend | ||
</template> | ||
<template #content> | ||
<cv-text-input label="First name" /> | ||
<cv-text-input label="Last name" /> | ||
</template> | ||
</cv-form-group> | ||
`; | ||
|
||
export const formItemTemplate = ` | ||
<cv-form-item> | ||
<label for="text-input-3" class="bx--label">Text Input label</label> | ||
<input id="text-input-3" type="text" class="bx--text-input" placeholder="Optional placeholder text" /> | ||
</cv-form-item> | ||
`; | ||
|
||
# CvForm | ||
|
||
These components are purely wrapper elements for use in creating forms. | ||
|
||
## Usage CvForm | ||
|
||
CvForm has no properties and a single default slot | ||
|
||
<Canvas> | ||
<Story | ||
name="Default" | ||
parameters={{ | ||
controls: { | ||
exclude: ['template'] | ||
}, | ||
docs: { source: { code: defaultTemplate } }, | ||
}} | ||
args={{ | ||
template: defaultTemplate, | ||
}} | ||
argTypes={{ | ||
default: { | ||
control: 'none', | ||
table: { | ||
type: { summary: 'text | html | Component' }, | ||
category: 'slots', | ||
} | ||
}, | ||
}} | ||
> | ||
{Template.bind({})} | ||
</Story> | ||
</Canvas> | ||
|
||
|
||
## CvFormGroup | ||
|
||
Used inside a form to group components such as checkboxes and radio buttons. | ||
|
||
<Canvas> | ||
<Story | ||
name="FormGroup-Default" | ||
parameters={{ | ||
controls: { | ||
exclude: ['template', 'default'] | ||
}, | ||
docs: { source: { code: formGroupTemplate.replace('v-bind="args"', '') } }, | ||
}} | ||
args={{ | ||
template: formGroupTemplate, | ||
}} | ||
argTypes={{ | ||
content: { | ||
control: 'none', | ||
table: { | ||
type: { summary: 'text | html | Component' }, | ||
category: 'slots', | ||
}, | ||
}, | ||
label: { | ||
control: 'none', | ||
table: { | ||
type: { summary: 'text | html | Component' }, | ||
category: 'slots', | ||
}, | ||
description: 'Legend element content', | ||
}, | ||
invalid: { | ||
type: 'boolean', | ||
table: { | ||
type: { summary: 'boolean' }, | ||
category: 'props', | ||
}, | ||
description: 'Specify whether the `FormGroup` is invalid', | ||
}, | ||
message: { | ||
type: 'string', | ||
table: { | ||
type: { summary: 'string' }, | ||
category: 'props', | ||
}, | ||
description: 'Specify a message to be placed at the end of the `FormGroup`', | ||
}, | ||
noMargin: { | ||
type: 'boolean', | ||
table: { | ||
type: { summary: 'boolean' }, | ||
category: 'props', | ||
}, | ||
description: 'Remove default margins set on `FormGroup`', | ||
}, | ||
}} | ||
> | ||
{Template.bind({})} | ||
</Story> | ||
</Canvas> | ||
|
||
## CvFormItem | ||
|
||
Used inside a form to provide positional styling. | ||
|
||
<Canvas> | ||
<Story | ||
name="FormItem-Default" | ||
parameters={{ | ||
controls: { | ||
exclude: ['template'] | ||
}, | ||
docs: { source: { code: formItemTemplate } }, | ||
}} | ||
args={{ | ||
template: formItemTemplate, | ||
}} | ||
argTypes={{ | ||
default: { | ||
control: 'none', | ||
table: { | ||
type: { summary: 'text | html | Component' }, | ||
category: 'slots', | ||
} | ||
}, | ||
}} | ||
> | ||
{Template.bind({})} | ||
</Story> | ||
</Canvas> |
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,9 @@ | ||
<template> | ||
<form :class="`cv-form ${carbonPrefix}--form`"> | ||
<slot></slot> | ||
</form> | ||
</template> | ||
|
||
<script setup> | ||
import { carbonPrefix } from '../../global/settings'; | ||
</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,27 @@ | ||
<template> | ||
<fieldset | ||
:class="[ | ||
`cv-form-group ${carbonPrefix}--fieldset`, | ||
{ [`${carbonPrefix}--fieldset--no-margin`]: noMargin }, | ||
]" | ||
:data-invalid="invalid" | ||
> | ||
<legend :class="`${carbonPrefix}--label`"> | ||
<slot name="label"></slot> | ||
</legend> | ||
<slot name="content"></slot> | ||
<div v-if="message" :class="`${carbonPrefix}--form__requirements`"> | ||
{{ message }} | ||
</div> | ||
</fieldset> | ||
</template> | ||
|
||
<script setup> | ||
import { carbonPrefix } from '../../global/settings'; | ||
const props = defineProps({ | ||
invalid: Boolean, | ||
message: String, | ||
noMargin: Boolean, | ||
}); | ||
</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,9 @@ | ||
<template> | ||
<div :class="`cv-form-item ${carbonPrefix}--form-item`"> | ||
<slot></slot> | ||
</div> | ||
</template> | ||
|
||
<script setup> | ||
import { carbonPrefix } from '../../global/settings'; | ||
</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,32 @@ | ||
import { render } from '@testing-library/vue'; | ||
import { CvForm } from '..'; | ||
|
||
describe('CvForm', () => { | ||
it('defines a form as the root element', () => { | ||
const { container } = render(CvForm); | ||
|
||
const wrapper = container.firstElementChild; | ||
expect(wrapper.tagName).toBe('FORM'); | ||
}); | ||
|
||
it('slots content inside the form', () => { | ||
const dummyElement = '<button>OK</button>'; | ||
const { container } = render(CvForm, { | ||
slots: { default: dummyElement }, | ||
}); | ||
|
||
const form = container.firstElementChild; | ||
const button = form.querySelector('button'); | ||
expect(button).not.toBeNull(); | ||
}); | ||
|
||
it('sets attributes at the form element', () => { | ||
const dummyId = 'dummy-id'; | ||
const { getByTestId } = render(CvForm, { | ||
attrs: { 'data-testid': dummyId }, | ||
}); | ||
|
||
const form = getByTestId('dummy-id'); | ||
expect(form).not.toBeNull(); | ||
}); | ||
}); |
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,61 @@ | ||
import { render } from '@testing-library/vue'; | ||
import { CvFormGroup } from '..'; | ||
|
||
describe('CvFormGroup', () => { | ||
it('defines a fieldset as the root element', () => { | ||
const { container } = render(CvFormGroup); | ||
|
||
const wrapper = container.firstElementChild; | ||
expect(wrapper.tagName).toBe('FIELDSET'); | ||
}); | ||
|
||
it('sets a legend element with a slot space', () => { | ||
const dummyLabel = 'dummy label'; | ||
const { getByText } = render(CvFormGroup, { | ||
slots: { label: dummyLabel }, | ||
}); | ||
|
||
const legend = getByText(dummyLabel); | ||
expect(legend).not.toBeNull(); | ||
expect(legend.tagName).toBe('LEGEND'); | ||
}); | ||
|
||
it('slots content inside the fieldset', () => { | ||
const dummyElement = '<input type="text" />'; | ||
const { container } = render(CvFormGroup, { | ||
slots: { content: dummyElement }, | ||
}); | ||
|
||
const fieldset = container.firstElementChild; | ||
const input = fieldset.querySelector('input'); | ||
expect(input).not.toBeNull(); | ||
}); | ||
|
||
it('displays a message if one is passed', () => { | ||
const dummyMessage = 'some dummy message'; | ||
const { getByText } = render(CvFormGroup, { | ||
props: { message: dummyMessage }, | ||
}); | ||
|
||
const element = getByText(dummyMessage); | ||
expect(element).not.toBeNull(); | ||
}); | ||
|
||
it('styles fieldset with no margin class when noMargin is passed', () => { | ||
const { container } = render(CvFormGroup, { | ||
props: { noMargin: true }, | ||
}); | ||
|
||
const wrapper = container.firstElementChild; | ||
expect(wrapper.classList.contains('bx--fieldset--no-margin')).toBeTruthy(); | ||
}); | ||
|
||
it('defines "data-invalid" attribute when invalid is passed', () => { | ||
const { container } = render(CvFormGroup, { | ||
props: { invalid: true }, | ||
}); | ||
|
||
const wrapper = container.firstElementChild; | ||
expect(wrapper.getAttribute('data-invalid')).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,34 @@ | ||
import { render } from '@testing-library/vue'; | ||
import { CvFormItem } from '..'; | ||
|
||
describe('CvForm', () => { | ||
it('defines a div with corresponding form item classes as the root element', () => { | ||
const { container } = render(CvFormItem); | ||
|
||
const wrapper = container.firstElementChild; | ||
expect(wrapper.tagName).toBe('DIV'); | ||
expect(wrapper.classList.contains('cv-form-item')).toBeTruthy(); | ||
expect(wrapper.classList.contains('bx--form-item')).toBeTruthy(); | ||
}); | ||
|
||
it('slots content inside the form item wrapper', () => { | ||
const dummyElement = '<input type="text" />'; | ||
const { container } = render(CvFormItem, { | ||
slots: { default: dummyElement }, | ||
}); | ||
|
||
const form = container.firstElementChild; | ||
const input = form.querySelector('input'); | ||
expect(input).not.toBeNull(); | ||
}); | ||
|
||
it('sets attributes at the form item wrapper element', () => { | ||
const dummyId = 'dummy-id'; | ||
const { getByTestId } = render(CvFormItem, { | ||
attrs: { 'data-testid': dummyId }, | ||
}); | ||
|
||
const element = getByTestId('dummy-id'); | ||
expect(element).not.toBeNull(); | ||
}); | ||
}); |
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 @@ | ||
import CvForm from './CvForm'; | ||
import CvFormGroup from './CvFormGroup'; | ||
import CvFormItem from './CvFormItem'; | ||
|
||
export { CvForm, CvFormGroup, CvFormItem }; |