Skip to content

Commit

Permalink
feat: add suppoprt for custom checkboxes and radios (#3343)
Browse files Browse the repository at this point in the history
  • Loading branch information
Elad Levy authored and jquense committed Nov 9, 2018
1 parent 50f54b4 commit 97a5b2f
Show file tree
Hide file tree
Showing 9 changed files with 118 additions and 9 deletions.
14 changes: 11 additions & 3 deletions src/FormCheck.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ class FormCheck extends React.Component {
title: PropTypes.string,
label: PropTypes.node,

/** Use Bootstrap's custom form elements to replace the browser defaults */
custom: PropTypes.bool,

/** The type of checkable. */
type: PropTypes.oneOf(['radio', 'checkbox']).isRequired,

Expand Down Expand Up @@ -83,6 +86,7 @@ class FormCheck extends React.Component {
type,
label,
children,
custom,
...props
} = this.props;

Expand All @@ -102,14 +106,18 @@ class FormCheck extends React.Component {

return (
<FormContext.Transform
mapToValue={({ controlId }) => ({ controlId: id || controlId })}
mapToValue={({ controlId }) => ({
controlId: id || controlId,
custom,
})}
>
<div
style={style}
className={classNames(
className,
bsPrefix,
inline && `${bsPrefix}-inline`,
!custom && bsPrefix,
custom && `custom-control custom-${type}`,
inline && `${custom ? 'custom-control' : bsPrefix}-inline`,
)}
>
{children || (
Expand Down
5 changes: 3 additions & 2 deletions src/FormCheckInput.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,14 +51,15 @@ function FormCheckInput({
}) {
return (
<FormContext.Consumer>
{({ controlId }) => (
{({ controlId, custom }) => (
<input
{...props}
ref={innerRef}
id={id || controlId}
className={classNames(
className,
bsPrefix,
!custom && bsPrefix,
custom && 'custom-control-input',
isValid && 'is-valid',
isInvalid && 'is-invalid',
isStatic && 'position-static',
Expand Down
8 changes: 6 additions & 2 deletions src/FormCheckLabel.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,16 @@ const defaultProps = {
function FormCheckLabel({ bsPrefix, className, innerRef, htmlFor, ...props }) {
return (
<FormContext.Consumer>
{({ controlId }) => (
{({ controlId, custom }) => (
<label // eslint-disable-line jsx-a11y/label-has-associated-control
{...props}
ref={innerRef}
htmlFor={htmlFor || controlId}
className={classNames(className, bsPrefix)}
className={classNames(
className,
!custom && bsPrefix,
custom && 'custom-control-label',
)}
/>
)}
</FormContext.Consumer>
Expand Down
16 changes: 16 additions & 0 deletions test/FormCheckSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,4 +80,20 @@ describe('<FormCheck>', () => {

expect(instance.input.tagName).to.equal('INPUT');
});

it('should support custom', () => {
const wrapper = mount(<FormCheck custom label="My label" />);

wrapper
.assertSingle('div.custom-control')
.assertSingle('div.custom-checkbox')
.assertSingle('input.custom-control-input');

wrapper.assertSingle('label.custom-control-label');
});

it('should support custom with inline', () => {
const wrapper = mount(<FormCheck custom inline label="My label" />);
wrapper.assertSingle('div.custom-control-inline');
});
});
2 changes: 1 addition & 1 deletion www/src/examples/Form/Basic.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
<Form.Label>Password</Form.Label>
<Form.Control type="password" placeholder="Password" />
</Form.Group>
<Form.Group id="formBasicChecbox">
<Form.Group controlId="formBasicChecbox">
<Form.Check type="checkbox" label="Check me out" />
</Form.Group>
<Button variant="primary" type="submit">
Expand Down
2 changes: 1 addition & 1 deletion www/src/examples/Form/CheckApi.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Form>
{['checkbox', 'radio'].map(type => (
<div key={type} className="mb-3">
<Form.Check id={`check-api-${type}`}>
<Form.Check type={type} id={`check-api-${type}`}>
<Form.Check.Input type={type} isValid />
<Form.Check.Label>{`Custom api ${type}`}</Form.Check.Label>
<Form.Control.Feedback type="valid">You did it!</Form.Control.Feedback>
Expand Down
20 changes: 20 additions & 0 deletions www/src/examples/Form/CheckCustom.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<Form>
{['checkbox', 'radio'].map(type => (
<div key={`custom-${type}`} className="mb-3">
<Form.Check // prettier-ignore
custom
type={type}
id={`custom-${type}`}
label={`Check this custom ${type}`}
/>

<Form.Check
custom
disabled
type={type}
label={`disabled ${type}`}
id={`disabled-custom-${type}`}
/>
</div>
))}
</Form>;
28 changes: 28 additions & 0 deletions www/src/examples/Form/CheckCustomInline.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<Form>
{['checkbox', 'radio'].map(type => (
<div key={`custom-inline-${type}`} className="mb-3">
<Form.Check
custom
inline
label="1"
type={type}
id={`custom-inline-${type}-1`}
/>
<Form.Check
custom
inline
label="2"
type={type}
id={`custom-inline-${type}-2`}
/>
<Form.Check
custom
inline
disabled
label="3 (disabled)"
type={type}
id={`custom-inline-${type}-3`}
/>
</div>
))}
</Form>;
32 changes: 32 additions & 0 deletions www/src/pages/components/forms.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ import Horizontal from '../../examples/Form/Horizontal';
import ValidationNative from '../../examples/Form/ValidationNative';
import ValidationFormik from '../../examples/Form/ValidationFormik';
import CheckApi from '../../examples/Form/CheckApi';
import CheckCustom from '../../examples/Form/CheckCustom';
import CheckCustomInline from '../../examples/Form/CheckCustomInline';

export default withLayout(function FormControlsSection({ data }) {
return (
Expand Down Expand Up @@ -235,6 +237,36 @@ export default withLayout(function FormControlsSection({ data }) {
Examples
</LinkedHeading>

<LinkedHeading h="2" id="forms-custom">
Custom forms
</LinkedHeading>
<p>
For even more customization and cross browser consistency, use our
completely custom form elements to replace the browser defaults. They’re
built on top of semantic and accessible markup, so they’re solid
replacements for any default form control.
</p>
<LinkedHeading h="3" id="forms-custom-checkboxes-and-radios">
Checkboxes and radios
</LinkedHeading>
<p>
Custom checkbox and radio styles are achieved with a resourceful use of
the <code>:checked</code> selector and <code>:after</code> psuedo
elements, but are Structurally similar to the default{' '}
<code>FormCheck</code>. By default the checked and indeterminate icons
use embedded svg icons from{' '}
<a href="https://useiconic.com/open">Open Iconic</a>.
</p>

<p>
Apply Bootstrap's custom elements by adding the <code>custom</code>{' '}
prop.
</p>
<ReactPlayground codeText={CheckCustom} />

<h3>Inline</h3>
<ReactPlayground codeText={CheckCustomInline} />

<LinkedHeading h="2" id="forms-api">
API
</LinkedHeading>
Expand Down

0 comments on commit 97a5b2f

Please sign in to comment.