Skip to content

Commit

Permalink
feat(FormCheck): Allow custom controls to render without a label (#5427)
Browse files Browse the repository at this point in the history
* docs(FormCheck): Document required props for switch

* Allow empty labels to render for custom check controls
  • Loading branch information
kyletsang committed Sep 21, 2020
1 parent f0a3847 commit 1ac7f50
Show file tree
Hide file tree
Showing 4 changed files with 48 additions and 14 deletions.
37 changes: 31 additions & 6 deletions src/FormCheck.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,10 @@ export type FormCheckType = 'checkbox' | 'radio' | 'switch';

export interface FormCheckProps
extends BsPrefixPropsWithChildren,
Pick<React.HTMLAttributes<HTMLElement>, 'style'> {
React.HTMLAttributes<HTMLInputElement> {
bsCustomPrefix?: string;
id?: string;
inline?: boolean;
disabled?: boolean;
title?: string;
label?: React.ReactNode;
custom?: boolean;
type?: FormCheckType;
Expand Down Expand Up @@ -65,7 +63,13 @@ const propTypes = {
*/
as: PropTypes.elementType,

/** A HTML id attribute, necessary for proper form accessibility. */
/**
* A HTML id attribute, necessary for proper form accessibility.
* An id is recommended for allowing label clicks to toggle the check control.
*
* This is **required** for custom check controls or when `type="switch"` due to
* how they are rendered.
*/
id: PropTypes.string,

/**
Expand All @@ -81,13 +85,30 @@ const propTypes = {
*/
children: PropTypes.node,

/**
* Groups controls horizontally with other `FormCheck`s.
*/
inline: PropTypes.bool,

/**
* Disables the control.
*/
disabled: PropTypes.bool,

/**
* `title` attribute for the underlying `FormCheckLabel`.
*/
title: PropTypes.string,

/**
* Label for the control.
*/
label: PropTypes.node,

/** Use Bootstrap's custom form elements to replace the browser defaults */
custom: PropTypes.bool,
custom: all(PropTypes.bool, ({ custom, id }) =>
custom && !id ? Error('Custom check controls require an id to work') : null,
),

/**
* The type of checkable.
Expand All @@ -99,6 +120,10 @@ const propTypes = {
type === 'switch' && custom === false
? Error('`custom` cannot be set to `false` when the type is `switch`')
: null,
({ type, id }) =>
type === 'switch' && !id
? Error('`id` must be defined when the type is `switch`')
: null,
),

/** Manually style the input as valid */
Expand Down Expand Up @@ -155,7 +180,7 @@ const FormCheck: FormCheck = (React.forwardRef(
[controlId, custom, id],
);

const hasLabel = label != null && label !== false && !children;
const hasLabel = custom || (label != null && label !== false && !children);

const input = (
<FormCheckInput
Expand Down
5 changes: 2 additions & 3 deletions src/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,8 @@ export interface BsCustomPrefixProps {
bsCustomPrefix?: string;
}

export interface BsPrefixProps<
As extends React.ElementType = React.ElementType
> extends BsPrefixAndClassNameOnlyProps {
export interface BsPrefixProps<As extends React.ElementType = React.ElementType>
extends BsPrefixAndClassNameOnlyProps {
as?: As;
}

Expand Down
14 changes: 9 additions & 5 deletions test/FormCheckSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,8 @@ describe('<FormCheck>', () => {
expect(instance.input.tagName).to.equal('INPUT');
});

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

wrapper
.assertSingle('div.custom-control')
Expand All @@ -93,12 +93,16 @@ describe('<FormCheck>', () => {
});

it('should support custom with inline', () => {
const wrapper = mount(<FormCheck custom inline label="My label" />);
const wrapper = mount(
<FormCheck custom inline label="My label" id="myid" />,
);
wrapper.assertSingle('div.custom-control-inline');
});

it('should supports switches', () => {
let wrapper = mount(<FormCheck type="switch" label="My label" />);
let wrapper = mount(
<FormCheck type="switch" label="My label" id="switch-id" />,
);

wrapper
.assertSingle('div.custom-control')
Expand All @@ -108,7 +112,7 @@ describe('<FormCheck>', () => {
wrapper.assertSingle('label.custom-control-label');
wrapper.unmount();

wrapper = mount(<Switch label="My label" />);
wrapper = mount(<Switch label="My label" id="switch-id2" />);

wrapper
.assertSingle('div.custom-control')
Expand Down
6 changes: 6 additions & 0 deletions www/src/pages/components/forms.js
Original file line number Diff line number Diff line change
Expand Up @@ -493,6 +493,12 @@ export default withLayout(function FormControlsSection({ data }) {
You can also use the <code>{'<Form.Switch>'}</code> alias which
encapsulates the above, in a very small component wrapper.
</Callout>
<Callout theme="danger">
<h5>Watch out!</h5>
You must specify an <code>id</code> when using custom check controls or
switches. Event handlers are triggered by linking the label with the
input via <code>id</code>.
</Callout>

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

0 comments on commit 1ac7f50

Please sign in to comment.