Skip to content

Commit

Permalink
feat(formfield): add formfield component
Browse files Browse the repository at this point in the history
  • Loading branch information
RobinWijnant committed Jun 1, 2020
1 parent 68dd068 commit effdfe7
Show file tree
Hide file tree
Showing 7 changed files with 176 additions and 0 deletions.
67 changes: 67 additions & 0 deletions src/components/FormField/FormField.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
---
name: FormField
menu: Components
route: /form-field
---

import { Playground, Props } from 'docz';
import { FormField, Input } from '../../index.ts';
import styles from './docAssets/docs.module.css';

# FormField

This component is used to wrap around one or multiple form elements. The label describes the set of form elements. An error message can be set if the user did not interact correctly these elements.

## Examples

### Basic usage

<Playground>
<FormField label='First name'>
<Input />
</FormField>
</Playground>

### FormField with error message

<Playground>
<FormField label='Last name' errorMessage='This field contains the following illegal characters: ;'>
<Input value='Doe;' />
</FormField>
</Playground>

### FormField with hint

<Playground>
<FormField label='MAC address' hint='The mac address is located at the back side of the device'>
<Input placeholder='00:00:00:00:00:00' />
</FormField>
</Playground>

### FormField with preview

<Playground>
{() => {
const [hexColor, setHexColor] = React.useState('#03176b');
const [label, setLabel] = React.useState('Devops');
return (
<FormField
label='Custom label'
hint={
<span style={{backgroundColor: hexColor}} className={styles.label}>
{label}
</span>
}
>
<div className={styles.flexRow}>
<Input value={hexColor} onChange={event => setHexColor(event.target.value)} />
<Input value={label} onChange={event => setLabel(event.target.value)} />
</div>
</FormField>
);
}}
</Playground>

## API

<Props of={FormField} />
18 changes: 18 additions & 0 deletions src/components/FormField/FormField.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
.label {
display: block;
margin-bottom: 10px;
font-weight: 500;
font-size: 15px;
}

.errorMessage {
display: block;
margin-top: 5px;
color: var(--color-error);
}

.hint {
display: block;
margin-top: 5px;
color: var(--color-neutral-6);
}
36 changes: 36 additions & 0 deletions src/components/FormField/FormField.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import React from 'react';
import { render } from '@testing-library/react';

import FormField from './FormField';

describe('FormField', () => {
test('snapshot', () => {
const { asFragment } = render(<FormField>Form elements here</FormField>);
expect(asFragment()).toMatchSnapshot();
});

test('errorMessage should be rendered', () => {
const { queryByText } = render(
<FormField errorMessage="Something went wrong">Form elements here</FormField>,
);
expect(queryByText(/Something went wrong/)).not.toBe(null);
});

test('hint should be rendered', () => {
const { queryByText } = render(
<FormField hint={<span>You should look here</span>}>Form elements here</FormField>,
);
expect(queryByText(/You should look here/)).not.toBe(null);
});

test('className prop', () => {
const className = 'center';
const { getByText } = render(<FormField className={className}>Form elements here</FormField>);
const formFieldElement = getByText(/Form elements here/)!;

const renderedClassNames = formFieldElement.className.split(' ');
expect(renderedClassNames).toContain(className);
// className in prop should be the last in the row
expect(renderedClassNames.indexOf(className)).toBe(renderedClassNames.length - 1);
});
});
27 changes: 27 additions & 0 deletions src/components/FormField/FormField.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import React, { ReactNode } from 'react';
import classNames from 'classnames';

import cssReset from '../../css-reset.module.css';
import styles from './FormField.module.css';

type Props = {
label?: string;
hint?: ReactNode;
errorMessage?: string;
className?: string;
children: ReactNode;
};

const FormField: React.FC<Props> = ({ label, className, children, hint, errorMessage }: Props) => {
const mergedClassNames = classNames(cssReset.ventura, className);
return (
<div className={mergedClassNames}>
{Boolean(label) && <span className={styles.label}>{label}</span>}
{children}
{Boolean(errorMessage) && <span className={styles.errorMessage}>{errorMessage}</span>}
{Boolean(hint) && <div className={styles.hint}>{hint}</div>}
</div>
);
};

export default FormField;
11 changes: 11 additions & 0 deletions src/components/FormField/__snapshots__/FormField.test.tsx.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`FormField snapshot 1`] = `
<DocumentFragment>
<div
class="ventura"
>
Form elements here
</div>
</DocumentFragment>
`;
16 changes: 16 additions & 0 deletions src/components/FormField/docAssets/docs.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
.flexRow {
display: flex;
}

.flexRow > *:first-of-type {
width: 100px;
margin-right: 10px;
}

.label {
display: inline-block;
padding: 0 8px;
border-radius: 3px;
margin-top: 10px;
color: white;
}
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ export { default as Button } from './components/Button/Button';
export { default as Input } from './components/Input/Input';
export { default as Radio } from './components/Radio/Radio';
export { default as Modal } from './components/Modal/Modal';
export { default as FormField } from './components/FormField/FormField';
export * from 'react-feather';

0 comments on commit effdfe7

Please sign in to comment.