Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
amd/* linguist-generated=true
es/* linguist-generated=true
es/**/* linguist-generated=true
dist/* linguist-generated=true
lib/* linguist-generated=true
lib/**/* linguist-generated=true
15 changes: 7 additions & 8 deletions src/ButtonGroup.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import classNames from 'classnames';
import React from 'react';
import PropTypes from 'prop-types';
import all from 'prop-types-extra/lib/all';
import warning from 'warning';

import Button from './Button';
import {
Expand All @@ -19,13 +19,7 @@ const propTypes = {
* Display block buttons; only useful when used with the "vertical" prop.
* @type {bool}
*/
block: all(
PropTypes.bool,
({ block, vertical }) =>
block && !vertical
? new Error('`block` requires `vertical` to be set to have any effect')
: null
)
block: PropTypes.bool
};

const defaultProps = {
Expand All @@ -39,6 +33,11 @@ class ButtonGroup extends React.Component {
const { block, justified, vertical, className, ...props } = this.props;
const [bsProps, elementProps] = splitBsProps(props);

warning(
!(block && !vertical),
'`block` requires `vertical` to be set to have any effect'
);

const classes = {
...getClassSet(bsProps),
[prefix(bsProps)]: !vertical,
Expand Down
28 changes: 22 additions & 6 deletions src/Dropdown.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,17 @@ import activeElement from 'dom-helpers/activeElement';
import contains from 'dom-helpers/query/contains';
import React, { cloneElement } from 'react';
import PropTypes from 'prop-types';
import all from 'prop-types-extra/lib/all';
import elementType from 'prop-types-extra/lib/elementType';
import isRequiredForA11y from 'prop-types-extra/lib/isRequiredForA11y';
import uncontrollable from 'uncontrollable';
import warning from 'warning';

import ButtonGroup from './ButtonGroup';
import DropdownMenu from './DropdownMenu';
import DropdownToggle from './DropdownToggle';
import { bsClass as setBsClass, prefix } from './utils/bootstrapUtils';
import createChainedFunction from './utils/createChainedFunction';
import { exclusiveRoles, requiredRoles } from './utils/PropTypes';
import { getDuplicateRoleError, getMissingRoleError } from './utils/PropTypes';
import ValidComponentChildren from './utils/ValidComponentChildren';
import { getElementRef, makeMergedRef } from './utils/mergeRefs';

Expand Down Expand Up @@ -41,10 +41,7 @@ const propTypes = {
* The children of a Dropdown may be a `<Dropdown.Toggle>` or a `<Dropdown.Menu>`.
* @type {node}
*/
children: all(
requiredRoles(TOGGLE_ROLE, MENU_ROLE),
exclusiveRoles(MENU_ROLE)
),
children: PropTypes.node,

/**
* Whether or not component is disabled.
Expand Down Expand Up @@ -293,6 +290,25 @@ class Dropdown extends React.Component {

delete props.onToggle;

const missingRoleError = getMissingRoleError(
'Dropdown',
children,
TOGGLE_ROLE,
MENU_ROLE
);
if (missingRoleError) {
warning(false, missingRoleError);
}

const duplicateRoleError = getDuplicateRoleError(
'Dropdown',
children,
MENU_ROLE
);
if (duplicateRoleError) {
warning(false, duplicateRoleError);
}

const classes = {
[bsClass]: true,
open,
Expand Down
15 changes: 7 additions & 8 deletions src/MenuItem.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import classNames from 'classnames';
import React from 'react';
import PropTypes from 'prop-types';
import all from 'prop-types-extra/lib/all';
import warning from 'warning';

import SafeAnchor from './SafeAnchor';
import { bsClass, prefix, splitBsPropsAndOmit } from './utils/bootstrapUtils';
Expand All @@ -22,13 +22,7 @@ const propTypes = {
* Styles the menu item as a horizontal rule, providing visual separation between
* groups of menu items.
*/
divider: all(
PropTypes.bool,
({ divider, children }) =>
divider && children
? new Error('Children will not be rendered for dividers')
: null
),
divider: PropTypes.bool,

/**
* Value passed to the `onSelect` handler, useful for identifying the selected menu item.
Expand Down Expand Up @@ -101,6 +95,11 @@ class MenuItem extends React.Component {
...props
} = this.props;

warning(
!(divider && props.children),
'Children will not be rendered for dividers'
);

const [bsProps, elementProps] = splitBsPropsAndOmit(props, [
'eventKey',
'onSelect'
Expand Down
14 changes: 6 additions & 8 deletions src/Nav.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import classNames from 'classnames';
import React, { cloneElement, useContext } from 'react';
import PropTypes from 'prop-types';
import all from 'prop-types-extra/lib/all';
import warning from 'warning';

import NavbarContext from './NavbarContext';
Expand Down Expand Up @@ -40,13 +39,7 @@ const propTypes = {
*/
stacked: PropTypes.bool,

justified: all(
PropTypes.bool,
({ justified, navbar }) =>
justified && navbar
? Error('justified navbar `Nav`s are not supported')
: null
),
justified: PropTypes.bool,

/**
* A callback fired when a NavItem is selected.
Expand Down Expand Up @@ -287,6 +280,11 @@ class Nav extends React.Component {

const [bsProps, elementProps] = splitBsProps(props);

warning(
!(justified && propsNavbar),
'justified navbar `Nav`s are not supported'
);

Comment thread
coderabbitai[bot] marked this conversation as resolved.
const classes = {
...getClassSet(bsProps),
[prefix(bsProps, 'stacked')]: stacked,
Expand Down
22 changes: 11 additions & 11 deletions src/ProgressBar.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import classNames from 'classnames';
import React, { cloneElement } from 'react';
import PropTypes from 'prop-types';
import warning from 'warning';

import {
bsClass as setBsClass,
Expand All @@ -17,12 +18,7 @@ const ROUND_PRECISION = 1000;
/**
* Validate that children, if any, are instances of `<ProgressBar>`.
*/
function onlyProgressBar(props, propName, componentName) {
const children = props[propName];
if (!children) {
return null;
}

function getInvalidChildError(children) {
let error = null;

React.Children.forEach(children, child => {
Expand All @@ -42,10 +38,9 @@ function onlyProgressBar(props, propName, componentName) {
const childIdentifier = React.isValidElement(child)
? child.type.displayName || child.type.name || child.type
: child;
error = new Error(
`Children of ${componentName} can contain only ProgressBar ` +
`components. Found ${childIdentifier}.`
);
error =
`Children of ProgressBar can contain only ProgressBar ` +
`components. Found ${childIdentifier}.`;
});

return error;
Expand All @@ -59,7 +54,7 @@ const propTypes = {
srOnly: PropTypes.bool,
striped: PropTypes.bool,
active: PropTypes.bool,
children: onlyProgressBar,
children: PropTypes.node,

/**
* @private
Expand Down Expand Up @@ -139,6 +134,11 @@ class ProgressBar extends React.Component {
...wrapperProps
} = props;

const childError = children ? getInvalidChildError(children) : null;
if (childError) {
warning(false, childError);
}

return (
<div {...wrapperProps} className={classNames(className, 'progress')}>
{children
Expand Down
104 changes: 54 additions & 50 deletions src/utils/PropTypes.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import PropTypes from 'prop-types';
import createChainableTypeChecker from 'prop-types-extra/lib/utils/createChainableTypeChecker';

import ValidComponentChildren from './ValidComponentChildren';

Expand All @@ -23,62 +22,67 @@ export function generatedId(name) {
};
}

export function requiredRoles(...roles) {
return createChainableTypeChecker((props, propName, component) => {
let missing;

roles.every(role => {
if (
!ValidComponentChildren.some(
props.children,
child => child.props.bsRole === role
)
) {
missing = role;
return false;
}

return true;
});

if (missing) {
return new Error(
`(children) ${component} - Missing a required child with bsRole: ` +
`${missing}. ${component} must have at least one child of each of ` +
`the following bsRoles: ${roles.join(', ')}`
);
/**
* Return a warning message if `children` is missing a child for any of the
* required `roles`, otherwise `null`. `bsRole` is matched against each child's
* `bsRole` prop.
*/
export function getMissingRoleError(component, children, ...roles) {
let missing;

roles.every(role => {
if (
!ValidComponentChildren.some(
children,
child => child.props.bsRole === role
)
) {
missing = role;
return false;
}

return null;
return true;
});
}

export function exclusiveRoles(...roles) {
return createChainableTypeChecker((props, propName, component) => {
let duplicate;

roles.every(role => {
const childrenWithRole = ValidComponentChildren.filter(
props.children,
child => child.props.bsRole === role
);

if (childrenWithRole.length > 1) {
duplicate = role;
return false;
}
if (missing) {
return (
`(children) ${component} - Missing a required child with bsRole: ` +
`${missing}. ${component} must have at least one child of each of ` +
`the following bsRoles: ${roles.join(', ')}`
);
}

return true;
});
return null;
}

if (duplicate) {
return new Error(
`(children) ${component} - Duplicate children detected of bsRole: ` +
`${duplicate}. Only one child each allowed with the following ` +
`bsRoles: ${roles.join(', ')}`
);
/**
* Return a warning message if `children` contains more than one child for any
* of the exclusive `roles`, otherwise `null`.
*/
export function getDuplicateRoleError(component, children, ...roles) {
let duplicate;

roles.every(role => {
const childrenWithRole = ValidComponentChildren.filter(
children,
child => child.props.bsRole === role
);

if (childrenWithRole.length > 1) {
duplicate = role;
return false;
}

return null;
return true;
});

if (duplicate) {
return (
`(children) ${component} - Duplicate children detected of bsRole: ` +
`${duplicate}. Only one child each allowed with the following ` +
`bsRoles: ${roles.join(', ')}`
);
}

return null;
}
Loading