Skip to content

Commit 6802774

Browse files
authored
feat(v5): Add floating labels (react-bootstrap#5567)
* feat(v5): Add floating labels * Add tests * Clean up * Clean up * Fix bad merge
1 parent 3b1a8b7 commit 6802774

File tree

11 files changed

+233
-0
lines changed

11 files changed

+233
-0
lines changed

src/FloatingLabel.tsx

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import classNames from 'classnames';
2+
import PropTypes from 'prop-types';
3+
import * as React from 'react';
4+
5+
import FormGroup, { FormGroupProps } from './FormGroup';
6+
import { BsPrefixProps, BsPrefixRefForwardingComponent } from './helpers';
7+
import { useBootstrapPrefix } from './ThemeProvider';
8+
9+
export interface FloatingLabelProps extends FormGroupProps, BsPrefixProps {
10+
controlId?: string;
11+
label: React.ReactNode;
12+
}
13+
14+
const propTypes = {
15+
as: PropTypes.elementType,
16+
17+
/**
18+
* Sets `id` on `<FormControl>` and `htmlFor` on `<label>`.
19+
*/
20+
controlId: PropTypes.string,
21+
22+
/**
23+
* Form control label.
24+
*/
25+
label: PropTypes.node.isRequired,
26+
};
27+
28+
const FloatingLabel: BsPrefixRefForwardingComponent<
29+
'div',
30+
FloatingLabelProps
31+
> = React.forwardRef(
32+
({ bsPrefix, className, children, controlId, label, ...props }, ref) => {
33+
bsPrefix = useBootstrapPrefix(bsPrefix, 'form-floating');
34+
35+
return (
36+
<FormGroup
37+
ref={ref}
38+
className={classNames(className, bsPrefix)}
39+
controlId={controlId}
40+
{...props}
41+
>
42+
{children}
43+
<label htmlFor={controlId}>{label}</label>
44+
</FormGroup>
45+
);
46+
},
47+
);
48+
49+
FloatingLabel.displayName = 'FloatingLabel';
50+
FloatingLabel.propTypes = propTypes;
51+
52+
export default FloatingLabel;

src/Form.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,14 @@ import PropTypes from 'prop-types';
33
import * as React from 'react';
44
import FormCheck from './FormCheck';
55
import FormControl from './FormControl';
6+
import FormFloating from './FormFloating';
67
import FormGroup from './FormGroup';
78
import FormLabel from './FormLabel';
89
import FormRange from './FormRange';
910
import FormSelect from './FormSelect';
1011
import FormText from './FormText';
1112
import Switch from './Switch';
13+
import FloatingLabel from './FloatingLabel';
1214
import { BsPrefixRefForwardingComponent, AsProp } from './helpers';
1315

1416
export interface FormProps
@@ -64,10 +66,12 @@ Form.propTypes = propTypes as any;
6466
export default Object.assign(Form, {
6567
Group: FormGroup,
6668
Control: FormControl,
69+
Floating: FormFloating,
6770
Check: FormCheck,
6871
Switch,
6972
Label: FormLabel,
7073
Text: FormText,
7174
Range: FormRange,
7275
Select: FormSelect,
76+
FloatingLabel,
7377
});

src/FormFloating.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import createWithBsPrefix from './createWithBsPrefix';
2+
3+
export default createWithBsPrefix('form-floating');

src/index.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,11 @@ export type { FormControlProps } from './FormControl';
7474
export { default as FormCheck } from './FormCheck';
7575
export type { FormCheckProps } from './FormCheck';
7676

77+
export { default as FormFloating } from './FormFloating';
78+
79+
export { default as FloatingLabel } from './FloatingLabel';
80+
export type { FloatingLabelProps } from './FloatingLabel';
81+
7782
export { default as FormGroup } from './FormGroup';
7883
export type { FormGroupProps } from './FormGroup';
7984

test/FloatingLabelSpec.js

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import { mount } from 'enzyme';
2+
3+
import FloatingLabel from '../src/FloatingLabel';
4+
import Form from '../src/Form';
5+
6+
describe('<FloatingLabel>', () => {
7+
it('should render correctly', () => {
8+
const wrapper = mount(
9+
<FloatingLabel label="MyLabel">
10+
<Form.Control type="text" />
11+
</FloatingLabel>,
12+
);
13+
14+
wrapper
15+
.assertSingle('div.form-floating')
16+
.assertSingle('input[type="text"]');
17+
18+
wrapper.assertSingle('label').text().should.equal('MyLabel');
19+
});
20+
21+
it('should pass controlId to input and label', () => {
22+
const wrapper = mount(
23+
<FloatingLabel label="MyLabel" controlId="MyId">
24+
<Form.Control type="text" />
25+
</FloatingLabel>,
26+
);
27+
28+
wrapper.assertSingle('input[id="MyId"]');
29+
wrapper.assertSingle('label[htmlFor="MyId"]');
30+
});
31+
32+
it('should support "as"', () => {
33+
mount(
34+
<FloatingLabel label="MyLabel" as="span">
35+
<Form.Control type="text" />
36+
</FloatingLabel>,
37+
).assertSingle('span.form-floating');
38+
});
39+
});
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<>
2+
<FloatingLabel
3+
controlId="floatingInput"
4+
label="Email address"
5+
className="mb-3"
6+
>
7+
<Form.Control type="email" placeholder="[email protected]" />
8+
</FloatingLabel>
9+
<FloatingLabel controlId="floatingPassword" label="Password">
10+
<Form.Control type="password" placeholder="Password" />
11+
</FloatingLabel>
12+
</>;
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<>
2+
<Form.Floating className="mb-3">
3+
<Form.Control
4+
id="floatingInputCustom"
5+
type="email"
6+
placeholder="[email protected]"
7+
/>
8+
<label htmlFor="floatingInputCustom">Email address</label>
9+
</Form.Floating>
10+
<Form.Floating>
11+
<Form.Control
12+
id="floatingPasswordCustom"
13+
type="password"
14+
placeholder="Password"
15+
/>
16+
<label htmlFor="floatingPasswordCustom">Password</label>
17+
</Form.Floating>
18+
</>;
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<Row className="g-2">
2+
<Col md>
3+
<FloatingLabel controlId="floatingInputGrid" label="Email address">
4+
<Form.Control type="email" placeholder="[email protected]" />
5+
</FloatingLabel>
6+
</Col>
7+
<Col md>
8+
<FloatingLabel controlId="floatingSelectGrid" label="Works with selects">
9+
<Form.Select aria-label="Floating label select example">
10+
<option>Open this select menu</option>
11+
<option value="1">One</option>
12+
<option value="2">Two</option>
13+
<option value="3">Three</option>
14+
</Form.Select>
15+
</FloatingLabel>
16+
</Col>
17+
</Row>;
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<FloatingLabel controlId="floatingSelect" label="Works with selects">
2+
<Form.Select aria-label="Floating label select example">
3+
<option>Open this select menu</option>
4+
<option value="1">One</option>
5+
<option value="2">Two</option>
6+
<option value="3">Three</option>
7+
</Form.Select>
8+
</FloatingLabel>;
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<>
2+
<FloatingLabel controlId="floatingTextarea" label="Comments" className="mb-3">
3+
<Form.Control as="textarea" placeholder="Leave a comment here" />
4+
</FloatingLabel>
5+
<FloatingLabel controlId="floatingTextarea2" label="Comments">
6+
<Form.Control
7+
as="textarea"
8+
placeholder="Leave a comment here"
9+
style={{ height: '100px' }}
10+
/>
11+
</FloatingLabel>
12+
</>;

0 commit comments

Comments
 (0)