From d93b8256b650ef75a65797bd9dbf5aec0214df44 Mon Sep 17 00:00:00 2001 From: Felipe Date: Tue, 16 May 2023 19:11:42 -0300 Subject: [PATCH] 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 --- .storybook/styles.scss | 5 + src/components/CvForm/CvForm.stories.mdx | 193 ++++++++++++++++++ src/components/CvForm/CvForm.vue | 9 + src/components/CvForm/CvFormGroup.vue | 27 +++ src/components/CvForm/CvFormItem.vue | 9 + .../CvForm/__tests__/CvForm.spec.js | 32 +++ .../CvForm/__tests__/CvFormGroup.spec.js | 61 ++++++ .../CvForm/__tests__/CvFormItem.spec.js | 34 +++ src/components/CvForm/index.js | 5 + 9 files changed, 375 insertions(+) create mode 100644 src/components/CvForm/CvForm.stories.mdx create mode 100644 src/components/CvForm/CvForm.vue create mode 100644 src/components/CvForm/CvFormGroup.vue create mode 100644 src/components/CvForm/CvFormItem.vue create mode 100644 src/components/CvForm/__tests__/CvForm.spec.js create mode 100644 src/components/CvForm/__tests__/CvFormGroup.spec.js create mode 100644 src/components/CvForm/__tests__/CvFormItem.spec.js create mode 100644 src/components/CvForm/index.js diff --git a/.storybook/styles.scss b/.storybook/styles.scss index d9addb775..eaa4beb5d 100644 --- a/.storybook/styles.scss +++ b/.storybook/styles.scss @@ -52,6 +52,11 @@ $carbon--theme: $carbon--theme--white; padding: $layout-04; background-color: $ui-background; color: $text-01; + + // set space between form items + .bx--form-item { + margin-bottom: 2rem; + } } /** diff --git a/src/components/CvForm/CvForm.stories.mdx b/src/components/CvForm/CvForm.stories.mdx new file mode 100644 index 000000000..556faa016 --- /dev/null +++ b/src/components/CvForm/CvForm.stories.mdx @@ -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 '.'; + + + +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 = ` + + + + Submit + +`; + +export const formGroupTemplate = ` + + + + +`; + +export const formItemTemplate = ` + + + + +`; + +# CvForm + +These components are purely wrapper elements for use in creating forms. + +## Usage CvForm + +CvForm has no properties and a single default slot + + + + {Template.bind({})} + + + + +## CvFormGroup + +Used inside a form to group components such as checkboxes and radio buttons. + + + + {Template.bind({})} + + + +## CvFormItem + +Used inside a form to provide positional styling. + + + + {Template.bind({})} + + diff --git a/src/components/CvForm/CvForm.vue b/src/components/CvForm/CvForm.vue new file mode 100644 index 000000000..c28d1daa4 --- /dev/null +++ b/src/components/CvForm/CvForm.vue @@ -0,0 +1,9 @@ + + + diff --git a/src/components/CvForm/CvFormGroup.vue b/src/components/CvForm/CvFormGroup.vue new file mode 100644 index 000000000..da314529c --- /dev/null +++ b/src/components/CvForm/CvFormGroup.vue @@ -0,0 +1,27 @@ + + + diff --git a/src/components/CvForm/CvFormItem.vue b/src/components/CvForm/CvFormItem.vue new file mode 100644 index 000000000..31f782660 --- /dev/null +++ b/src/components/CvForm/CvFormItem.vue @@ -0,0 +1,9 @@ + + + diff --git a/src/components/CvForm/__tests__/CvForm.spec.js b/src/components/CvForm/__tests__/CvForm.spec.js new file mode 100644 index 000000000..3cf4c3c92 --- /dev/null +++ b/src/components/CvForm/__tests__/CvForm.spec.js @@ -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 = ''; + 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(); + }); +}); diff --git a/src/components/CvForm/__tests__/CvFormGroup.spec.js b/src/components/CvForm/__tests__/CvFormGroup.spec.js new file mode 100644 index 000000000..396313722 --- /dev/null +++ b/src/components/CvForm/__tests__/CvFormGroup.spec.js @@ -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 = ''; + 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'); + }); +}); diff --git a/src/components/CvForm/__tests__/CvFormItem.spec.js b/src/components/CvForm/__tests__/CvFormItem.spec.js new file mode 100644 index 000000000..52ee15fe5 --- /dev/null +++ b/src/components/CvForm/__tests__/CvFormItem.spec.js @@ -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 = ''; + 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(); + }); +}); diff --git a/src/components/CvForm/index.js b/src/components/CvForm/index.js new file mode 100644 index 000000000..0ed9c1d8b --- /dev/null +++ b/src/components/CvForm/index.js @@ -0,0 +1,5 @@ +import CvForm from './CvForm'; +import CvFormGroup from './CvFormGroup'; +import CvFormItem from './CvFormItem'; + +export { CvForm, CvFormGroup, CvFormItem };