Skip to content

Commit 88d3d3f

Browse files
committed
feat(Label): added a generic form field label
1 parent fb4a8a8 commit 88d3d3f

File tree

9 files changed

+125
-0
lines changed

9 files changed

+125
-0
lines changed
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { boolean, text } from '@storybook/addon-knobs';
2+
import React from 'react';
3+
import { Input } from '../Input';
4+
import { Label } from '../Label';
5+
import readme from './README.md';
6+
7+
export default { title: 'Components/Label', component: Label, parameters: { readme } };
8+
9+
export const Basic = () => (
10+
<Label label={text('label', 'Labeled input field')}>
11+
<Input />
12+
</Label>
13+
);
14+
15+
export const WithCustomId = () => (
16+
<Label label={text('label', 'Labeled input field')}>
17+
<Input id="custom_input_id" />
18+
</Label>
19+
);
20+
21+
export const WithDisabledFormElement = () => (
22+
<Label label={text('label', 'Labeled input field')}>
23+
<Input id="custom_input_id" disabled={boolean('disabled', false)} />
24+
</Label>
25+
);
26+
27+
export const Detached = () => (
28+
<>
29+
<Label htmlFor="custom_input_id" label={text('label', 'Labeled input field')} />
30+
<p>Some other stuff</p>
31+
<Input id="custom_input_id" disabled={boolean('disabled', false)} />
32+
</>
33+
);
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { shallow } from 'enzyme';
2+
import React from 'react';
3+
import { Label } from '../Label';
4+
5+
describe('Label component', () => {
6+
it('renders its contents', () => {
7+
const label = shallow(<Label label="" />);
8+
expect(label.find('.ui__label')).toHaveLength(1);
9+
});
10+
11+
it('snapshot renders default label', () => {
12+
const label = shallow(<Label label="" />);
13+
expect(label).toMatchSnapshot();
14+
});
15+
});

src/components/Label/Label.tsx

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import classnames from 'classnames';
2+
import React, { cloneElement, FC, LabelHTMLAttributes, ReactElement, ReactNode, useMemo } from 'react';
3+
import { getStringId } from '../../utils';
4+
5+
export interface LabelProps extends LabelHTMLAttributes<HTMLLabelElement> {
6+
/**
7+
* The label to apply to any form element.
8+
*/
9+
label: ReactNode;
10+
/**
11+
* Pass a child element which can accept an `id` prop. If you don't specify
12+
* the `id` prop, one will be generated to link the label to the element. If
13+
* you wish to link this label to a form field without passing children, you
14+
* should specify the `htmlFor` prop with the `id` of your form field.
15+
*/
16+
children?: ReactElement;
17+
}
18+
19+
export const Label: FC<LabelProps> = ({ label, children, className, htmlFor, ...props }) => {
20+
const childrenId = children?.props.id;
21+
const id = useMemo(() => {
22+
if (childrenId) return childrenId;
23+
return getStringId('label');
24+
}, [childrenId]);
25+
26+
const labelClass = classnames(
27+
'ui__base ui__label',
28+
{ 'ui__label--disabled': children?.props.disabled, 'ui__label--attached': children },
29+
className,
30+
);
31+
32+
return (
33+
<>
34+
<label {...props} className={labelClass} htmlFor={htmlFor ?? id}>
35+
{label}
36+
</label>
37+
{children ? cloneElement(children, { id }) : undefined}
38+
</>
39+
);
40+
};

src/components/Label/README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
A generic label for labelling form fields. If the child form field has an `id`
2+
specified, that will be automatically parsed and used in the label's `htmlFor`
3+
attribute to link the two. If no `id` is specified for the child, one will be
4+
automatically generated.
5+
6+
You can also link the label to a form field without passing it as a child by
7+
giving the field an `id` and passing the same `id` to the `htmlFor` prop for the
8+
input. This is useful if the two elements do not sit near each other on the
9+
page.
10+
11+
If the child element is disabled, the label will automatically change appearance
12+
to a disabled state. This will not happen if the form field is not the child.

src/components/Label/_Label.scss

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
.ui__label {
2+
&--attached::after {
3+
height: 2px;
4+
width: 1px;
5+
content: '';
6+
display: block;
7+
}
8+
9+
&--disabled {
10+
opacity: 0.7;
11+
}
12+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`Label component snapshot renders default label 1`] = `
4+
<Fragment>
5+
<label
6+
className="ui__base ui__label"
7+
htmlFor="label_1"
8+
/>
9+
</Fragment>
10+
`;

src/components/Label/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './Label';

src/components/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ export * from './Icon';
1717
export * from './IconButton';
1818
export * from './Image';
1919
export * from './Input';
20+
export * from './Label';
2021
export * from './Modal';
2122
export * from './Overlay';
2223
export * from './Spinner';

src/index.scss

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
@import './components/IconButton/IconButton';
3535
@import './components/Image/Image';
3636
@import './components/Input/Input';
37+
@import './components/Label/Label';
3738
@import './components/Modal/Modal';
3839
@import './components/Overlay/Overlay';
3940
@import './components/Spinner/Spinner';

0 commit comments

Comments
 (0)