Skip to content

Commit

Permalink
fix(Dropdown): fix html structure when inside InputGroup (#5713)
Browse files Browse the repository at this point in the history
  • Loading branch information
kyletsang committed May 14, 2021
1 parent 6562a52 commit d4b086a
Show file tree
Hide file tree
Showing 8 changed files with 123 additions and 24 deletions.
30 changes: 18 additions & 12 deletions src/Dropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import DropdownContext from './DropdownContext';
import DropdownItem from './DropdownItem';
import DropdownMenu from './DropdownMenu';
import DropdownToggle from './DropdownToggle';
import InputGroupContext from './InputGroupContext';
import SelectableContext from './SelectableContext';
import { useBootstrapPrefix } from './ThemeProvider';
import createWithBsPrefix from './createWithBsPrefix';
Expand Down Expand Up @@ -149,6 +150,7 @@ const Dropdown: BsPrefixRefForwardingComponent<
} = useUncontrolled(pProps, { show: 'onToggle' });

const onSelectCtx = useContext(SelectableContext);
const isInputGroup = useContext(InputGroupContext);
const prefix = useBootstrapPrefix(bsPrefix, 'dropdown');

const handleToggle = useEventCallback(
Expand Down Expand Up @@ -194,18 +196,22 @@ const Dropdown: BsPrefixRefForwardingComponent<
focusFirstItemOnShow={focusFirstItemOnShow}
itemSelector={`.${prefix}-item:not(.disabled):not(:disabled)`}
>
<Component
{...props}
ref={ref}
className={classNames(
className,
show && 'show',
(!drop || drop === 'down') && prefix,
drop === 'up' && 'dropup',
drop === 'end' && 'dropend',
drop === 'start' && 'dropstart',
)}
/>
{isInputGroup ? (
props.children
) : (
<Component
{...props}
ref={ref}
className={classNames(
className,
show && 'show',
(!drop || drop === 'down') && prefix,
drop === 'up' && 'dropup',
drop === 'end' && 'dropend',
drop === 'start' && 'dropstart',
)}
/>
)}
</BaseDropdown>
</SelectableContext.Provider>
</DropdownContext.Provider>
Expand Down
4 changes: 3 additions & 1 deletion src/DropdownMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
import useMergedRefs from '@restart/hooks/useMergedRefs';
import warning from 'warning';
import DropdownContext from './DropdownContext';
import InputGroupContext from './InputGroupContext';
import NavbarContext from './NavbarContext';
import { useBootstrapPrefix } from './ThemeProvider';
import useWrappedRefWithWarning from './useWrappedRefWithWarning';
Expand Down Expand Up @@ -123,6 +124,7 @@ const DropdownMenu: BsPrefixRefForwardingComponent<
const prefix = useBootstrapPrefix(bsPrefix, 'dropdown-menu');
const { align: contextAlign } = useContext(DropdownContext);
align = align || contextAlign;
const isInputGroup = useContext(InputGroupContext);

const alignClasses: string[] = [];
if (align) {
Expand Down Expand Up @@ -167,7 +169,7 @@ const DropdownMenu: BsPrefixRefForwardingComponent<
menuProps.ref,
);

if (!hasShown && !renderOnMount) return null;
if (!hasShown && !renderOnMount && !isInputGroup) return null;

// For custom components provide additional, non-DOM, props;
if (typeof Component !== 'string') {
Expand Down
12 changes: 11 additions & 1 deletion src/DropdownToggle.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@ import classNames from 'classnames';
import PropTypes from 'prop-types';
import isRequiredForA11y from 'prop-types-extra/lib/isRequiredForA11y';
import * as React from 'react';
import { useContext } from 'react';
import { useDropdownToggle } from 'react-overlays/DropdownToggle';
import DropdownContext from 'react-overlays/DropdownContext';
import useMergedRefs from '@restart/hooks/useMergedRefs';
import Button, { ButtonProps, CommonButtonProps } from './Button';
import InputGroupContext from './InputGroupContext';
import { useBootstrapPrefix } from './ThemeProvider';
import useWrappedRefWithWarning from './useWrappedRefWithWarning';
import { BsPrefixRefForwardingComponent } from './helpers';
Expand Down Expand Up @@ -61,6 +64,8 @@ const DropdownToggle: DropdownToggleComponent = React.forwardRef(
ref,
) => {
const prefix = useBootstrapPrefix(bsPrefix, 'dropdown-toggle');
const dropdownContext = useContext(DropdownContext);
const isInputGroup = useContext(InputGroupContext);

if (childBsPrefix !== undefined) {
(props as any).bsPrefix = childBsPrefix;
Expand All @@ -77,7 +82,12 @@ const DropdownToggle: DropdownToggleComponent = React.forwardRef(
// underlying component, to allow it to render size and style variants.
return (
<Component
className={classNames(className, prefix, split && `${prefix}-split`)}
className={classNames(
className,
prefix,
split && `${prefix}-split`,
!!isInputGroup && dropdownContext?.show && 'show',
)}
{...toggleProps}
{...props}
/>
Expand Down
28 changes: 18 additions & 10 deletions src/InputGroup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@ import classNames from 'classnames';
import PropTypes from 'prop-types';

import * as React from 'react';
import { useMemo } from 'react';

import createWithBsPrefix from './createWithBsPrefix';
import { useBootstrapPrefix } from './ThemeProvider';
import FormCheckInput from './FormCheckInput';
import InputGroupContext from './InputGroupContext';
import { BsPrefixProps, BsPrefixRefForwardingComponent } from './helpers';

const InputGroupText = createWithBsPrefix('input-group-text', {
Expand Down Expand Up @@ -76,17 +78,23 @@ const InputGroup: BsPrefixRefForwardingComponent<
) => {
bsPrefix = useBootstrapPrefix(bsPrefix, 'input-group');

// Intentionally an empty object. Used in detecting if a dropdown
// exists under an input group.
const contextValue = useMemo(() => ({}), []);

return (
<Component
ref={ref}
{...props}
className={classNames(
className,
bsPrefix,
size && `${bsPrefix}-${size}`,
hasValidation && 'has-validation',
)}
/>
<InputGroupContext.Provider value={contextValue}>
<Component
ref={ref}
{...props}
className={classNames(
className,
bsPrefix,
size && `${bsPrefix}-${size}`,
hasValidation && 'has-validation',
)}
/>
</InputGroupContext.Provider>
);
},
);
Expand Down
6 changes: 6 additions & 0 deletions src/InputGroupContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import * as React from 'react';

const context = React.createContext<unknown | null>(null);
context.displayName = 'InputGroupContext';

export default context;
29 changes: 29 additions & 0 deletions test/DropdownSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import * as React from 'react';
import ReactDOM from 'react-dom';
import simulant from 'simulant';
import Dropdown from '../src/Dropdown';
import InputGroup from '../src/InputGroup';

describe('<Dropdown>', () => {
const dropdownChildren = [
Expand Down Expand Up @@ -304,4 +305,32 @@ describe('<Dropdown>', () => {
</Dropdown.Menu>,
).assertSingle('#custom-component');
});

describe('InputGroup Dropdowns', () => {
it('should not render a .dropdown element when inside input group', () => {
const wrapper = mount(
<InputGroup>
<Dropdown>{dropdownChildren}</Dropdown>
</InputGroup>,
);

expect(wrapper.find('.dropdown').length).to.equal(0);
});

it('should render .show on the dropdown toggle', () => {
mount(
<InputGroup>
<Dropdown show>{dropdownChildren}</Dropdown>
</InputGroup>,
).assertSingle('button.dropdown-toggle.show');
});

it('should always render dropdown menu even if renderOnMount=false', () => {
mount(
<InputGroup>
<Dropdown renderOnMount={false}>{dropdownChildren}</Dropdown>
</InputGroup>,
).assertSingle('div.dropdown-menu');
});
});
});
32 changes: 32 additions & 0 deletions www/src/examples/InputGroup/SegmentedButtonDropdowns.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<>
<InputGroup className="mb-3">
<SplitButton
variant="outline-secondary"
title="Action"
id="segmented-button-dropdown-1"
>
<Dropdown.Item href="#">Action</Dropdown.Item>
<Dropdown.Item href="#">Another action</Dropdown.Item>
<Dropdown.Item href="#">Something else here</Dropdown.Item>
<Dropdown.Divider />
<Dropdown.Item href="#">Separated link</Dropdown.Item>
</SplitButton>
<FormControl aria-label="Text input with dropdown button" />
</InputGroup>

<InputGroup className="mb-3">
<FormControl aria-label="Text input with dropdown button" />
<SplitButton
variant="outline-secondary"
title="Action"
id="segmented-button-dropdown-2"
alignRight
>
<Dropdown.Item href="#">Action</Dropdown.Item>
<Dropdown.Item href="#">Another action</Dropdown.Item>
<Dropdown.Item href="#">Something else here</Dropdown.Item>
<Dropdown.Divider />
<Dropdown.Item href="#">Separated link</Dropdown.Item>
</SplitButton>
</InputGroup>
</>;
6 changes: 6 additions & 0 deletions www/src/pages/components/input-group.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import Buttons from '../../examples/InputGroup/Buttons';
import Checkboxes from '../../examples/InputGroup/Checkboxes';
import MultipleAddons from '../../examples/InputGroup/MultipleAddons';
import MultipleInputs from '../../examples/InputGroup/MultipleInputs';
import SegmentedButtonDropdowns from '../../examples/InputGroup/SegmentedButtonDropdowns';
import Sizes from '../../examples/InputGroup/Sizes';
import withLayout from '../../withLayout';

Expand Down Expand Up @@ -68,6 +69,11 @@ export default withLayout(function InputGroupSection({ data }) {
</LinkedHeading>
<ReactPlayground codeText={ButtonDropdowns} />

<LinkedHeading h="2" id="input-group-segmented-buttons">
Segmented buttons
</LinkedHeading>
<ReactPlayground codeText={SegmentedButtonDropdowns} />

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

0 comments on commit d4b086a

Please sign in to comment.