Skip to content

Commit

Permalink
fix(#2410): broken rounded corners for dropdown in input group
Browse files Browse the repository at this point in the history
  • Loading branch information
illiteratewriter committed Jun 8, 2022
1 parent af95b46 commit c23d847
Show file tree
Hide file tree
Showing 6 changed files with 81 additions and 30 deletions.
42 changes: 37 additions & 5 deletions src/Dropdown.js
Expand Up @@ -7,6 +7,7 @@ import { Manager } from 'react-popper';
import classNames from 'classnames';
import { DropdownContext } from './DropdownContext';
import { mapToCssModules, omit, keyCodes, tagPropType } from './utils';
import { InputGroupContext } from './InputGroupContext';

const propTypes = {
a11y: PropTypes.bool,
Expand Down Expand Up @@ -56,15 +57,22 @@ class Dropdown extends React.Component {
this.removeEvents = this.removeEvents.bind(this);
this.toggle = this.toggle.bind(this);
this.handleMenuRef = this.handleMenuRef.bind(this);
this.handleToggleRef = this.handleToggleRef.bind(this);

this.containerRef = React.createRef();
this.menuRef = React.createRef();
this.toggleRef = React.createRef();
// ref for DropdownToggle
}

handleMenuRef(menuRef) {
this.menuRef.current = menuRef;
}

handleToggleRef(toggleRef) {
this.toggleRef.current = toggleRef;
}

getContextValue() {
return {
toggle: this.toggle,
Expand All @@ -75,6 +83,7 @@ class Dropdown extends React.Component {
// Callback that should be called by DropdownMenu to provide a ref to
// a HTML tag that's used for the DropdownMenu
onMenuRef: this.handleMenuRef,
onToggleRef: this.handleToggleRef,
menuRole: this.props.menuRole
};
}
Expand All @@ -101,14 +110,18 @@ class Dropdown extends React.Component {
return this.menuRef.current;
}

getToggle() {
return this.toggleRef.current;
}

getMenuCtrl() {
if (this._$menuCtrl) return this._$menuCtrl;
this._$menuCtrl = this.getContainer().querySelector('[aria-expanded]');
this._$menuCtrl = this.toggleRef.current;
return this._$menuCtrl;
}

getItemType() {
if(this.context.menuRole === 'listbox') {
if (this.context.menuRole === 'listbox') {
return 'option'
}
return 'menuitem'
Expand Down Expand Up @@ -138,10 +151,18 @@ class Dropdown extends React.Component {
if (e && (e.which === 3 || (e.type === 'keyup' && e.which !== keyCodes.tab))) return;
const container = this.getContainer();
const menu = this.getMenu();
const clickIsInContainer = container.contains(e.target) && container !== e.target;
const clickIsInInput = container.classList.contains('input-group') && container.classList.contains('dropdown') && e.target.tagName === 'INPUT';
const toggle = this.getToggle();

const targetIsToggle = e.target === toggle;
const clickIsInMenu = menu && menu.contains(e.target) && menu !== e.target;
if (((clickIsInContainer && !clickIsInInput) || clickIsInMenu) && (e.type !== 'keyup' || e.which === keyCodes.tab)) {

let clickIsInInput = false;
if (container) {
// this is only for InputGroup with type dropdown
clickIsInInput = container.classList.contains('input-group') && container.classList.contains('dropdown') && e.target.tagName === 'INPUT';
}

if (((targetIsToggle && !clickIsInInput) || clickIsInMenu) && (e.type !== 'keyup' || e.which === keyCodes.tab)) {
return;
}

Expand Down Expand Up @@ -283,6 +304,16 @@ class Dropdown extends React.Component {
}
), cssModule);

if (this.context.insideInputGroup) {
return (
<DropdownContext.Provider value={this.getContextValue()}>
<Manager>
{this.props.children.map(child => React.cloneElement(child, { onKeyDown: this.handleKeyDown }))}
</Manager>
</DropdownContext.Provider>
);
}

return (
<DropdownContext.Provider value={this.getContextValue()}>
<Manager>
Expand All @@ -300,5 +331,6 @@ class Dropdown extends React.Component {

Dropdown.propTypes = propTypes;
Dropdown.defaultProps = defaultProps;
Dropdown.contextType = InputGroupContext

export default Dropdown;
6 changes: 4 additions & 2 deletions src/DropdownMenu.js
Expand Up @@ -39,7 +39,7 @@ const directionPositionMap = {
class DropdownMenu extends React.Component {

getRole() {
if(this.context.menuRole === 'listbox') {
if (this.context.menuRole === 'listbox') {
return 'listbox'
}
return 'menu'
Expand Down Expand Up @@ -84,7 +84,7 @@ class DropdownMenu extends React.Component {
name: 'flip',
enabled: !!flip,
},
];
];

const popper = (
<Popper
Expand Down Expand Up @@ -126,12 +126,14 @@ class DropdownMenu extends React.Component {
return popper;
}
}
const { onMenuRef } = this.context;

return (
<Tag
tabIndex="-1"
role={this.getRole()}
{...attrs}
ref={onMenuRef}
aria-hidden={!this.context.isOpen}
className={classes}
data-popper-placement={attrs.placement}
Expand Down
33 changes: 21 additions & 12 deletions src/DropdownToggle.js
Expand Up @@ -84,12 +84,14 @@ class DropdownToggle extends React.Component {
Tag = tag;
}


if (this.context.inNavbar) {
return (
<Tag
{...props}
className={classes}
onClick={this.onClick}
ref={this.context.onToggleRef}
aria-expanded={this.context.isOpen}
aria-haspopup={this.getRole()}
children={children}
Expand All @@ -99,18 +101,25 @@ class DropdownToggle extends React.Component {

return (
<Reference innerRef={innerRef}>
{({ ref }) => (
<Tag
{...props}
{...{ [typeof Tag === 'string' ? 'ref' : 'innerRef']: ref }}

className={classes}
onClick={this.onClick}
aria-expanded={this.context.isOpen}
aria-haspopup={this.getRole()}
children={children}
/>
)}
{({ ref }) => {
const handleRef = (tagRef) => {
ref(tagRef);
const { onToggleRef } = this.context;
if (onToggleRef) onToggleRef(tagRef)
}

return (
<Tag
{...props}
{...{ [typeof Tag === 'string' ? 'ref' : 'innerRef']: handleRef }}
className={classes}
onClick={this.onClick}
aria-expanded={this.context.isOpen}
aria-haspopup={this.getRole()}
children={children}
/>
)
}}
</Reference>
);
}
Expand Down
9 changes: 7 additions & 2 deletions src/InputGroup.js
Expand Up @@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
import classNames from 'classnames';
import { mapToCssModules, tagPropType } from './utils';
import Dropdown from './Dropdown';
import { InputGroupContext } from './InputGroupContext';

const propTypes = {
tag: tagPropType,
Expand Down Expand Up @@ -32,11 +33,15 @@ const InputGroup = (props) => {
), cssModule);

if (props.type === 'dropdown') {
return <Dropdown {...attributes} className={classes} />
return (
<Dropdown {...attributes} className={classes} />
)
}

return (
<Tag {...attributes} className={classes} />
<InputGroupContext.Provider value={{ insideInputGroup: true }}>
<Tag {...attributes} className={classes} />
</InputGroupContext.Provider>
);
};

Expand Down
3 changes: 3 additions & 0 deletions src/InputGroupContext.js
@@ -0,0 +1,3 @@
import React from 'react';

export const InputGroupContext = React.createContext({});
18 changes: 9 additions & 9 deletions src/__tests__/InputGroup.spec.js
Expand Up @@ -7,7 +7,7 @@ describe('InputGroup', () => {
it('should render with "div" tag', () => {
const wrapper = shallow(<InputGroup>Yo!</InputGroup>);

expect(wrapper.type()).toBe('div');
expect(wrapper.childAt(0).type()).toBe('div');
});

it('should render children', () => {
Expand All @@ -19,48 +19,48 @@ describe('InputGroup', () => {
it('should render with "input-group" class', () => {
const wrapper = shallow(<InputGroup>Yo!</InputGroup>);

expect(wrapper.hasClass('input-group')).toBe(true);
expect(wrapper.childAt(0).hasClass('input-group')).toBe(true);
});

it('should render with "input-group-${size}" class when size is passed', () => {
const wrapper = shallow(<InputGroup size="whatever">Yo!</InputGroup>);

expect(wrapper.hasClass('input-group-whatever')).toBe(true);
expect(wrapper.childAt(0).hasClass('input-group-whatever')).toBe(true);
});

it('should render additional classes', () => {
const wrapper = shallow(<InputGroup className="other">Yo!</InputGroup>);

expect(wrapper.hasClass('other')).toBe(true);
expect(wrapper.hasClass('input-group')).toBe(true);
expect(wrapper.childAt(0).hasClass('other')).toBe(true);
expect(wrapper.childAt(0).hasClass('input-group')).toBe(true);
});

it('should render custom tag', () => {
const wrapper = shallow(<InputGroup tag="main">Yo!</InputGroup>);

expect(wrapper.type()).toBe('main');
expect(wrapper.childAt(0).type()).toBe('main');
});

describe('When type="dropdown"', () => {
it('should render Dropdown', () => {
const wrapper = shallow(<InputGroup type="dropdown" />);

expect(wrapper.type()).toBe(Dropdown);
});

it('should call toggle when input is clicked', () => {
jest.spyOn(Dropdown.prototype, 'toggle');

const wrapper = mount(
<InputGroup type="dropdown" isOpen={true} toggle={() => {}}>
<InputGroup type="dropdown" isOpen={true} toggle={() => { }}>
<Input />
<DropdownToggle>Toggle</DropdownToggle>
<DropdownMenu right>
<DropdownItem>Test</DropdownItem>
<DropdownItem id="divider" divider />
</DropdownMenu>
</InputGroup>
, { attachTo: document.body });
, { attachTo: document.body });

expect(Dropdown.prototype.toggle.mock.calls.length).toBe(0);

Expand Down

0 comments on commit c23d847

Please sign in to comment.