Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(Modal, Dropdown{*}, PopperContent, Tabs): go to React Async Rendering #1427

Merged
merged 5 commits into from Apr 3, 2019
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
17 changes: 6 additions & 11 deletions __mocks__/react-popper.js
@@ -1,17 +1,12 @@
import React from 'react';

export function Manager({ tag: Tag = 'div', ...props }) {
return <Tag {...props} />;
export function Manager({ children }) {
return (children);
}

export function Popper({ component: Tag = 'div', ...props }) {
return <Tag {...props} />;
export function Popper({ children, placement }) {
return children({ ref: () => {}, placement, style: {}, arrowProps: { ref: () => {}, style: {} } });
}

export function Arrow({ component: Tag = 'div', ...props }) {
return <Tag {...props} />;
}

export function Target({ component: Tag = 'div', ...props }) {
return <Tag {...props} />;
export function Reference({ children }) {
return children({ ref: () => {} });
}
2 changes: 1 addition & 1 deletion package.json
Expand Up @@ -105,7 +105,7 @@
"lodash.tonumber": "^4.0.3",
"prop-types": "^15.5.8",
"react-lifecycles-compat": "^3.0.4",
"react-popper": "^0.10.4",
"react-popper": "^1.3.3",
"react-transition-group": "^2.3.1"
},
"peerDependencies": {
Expand Down
33 changes: 19 additions & 14 deletions src/Dropdown.js
Expand Up @@ -3,9 +3,9 @@

import React from 'react';
import PropTypes from 'prop-types';
import ReactDOM from 'react-dom';
import { Manager } from 'react-popper';
import classNames from 'classnames';
import { DropdownContext } from './DropdownContext';
import { mapToCssModules, omit, keyCodes, deprecated, tagPropType } from './utils';

const propTypes = {
Expand Down Expand Up @@ -37,12 +37,6 @@ const defaultProps = {
setActiveFromChild: false
};

const childContextTypes = {
toggle: PropTypes.func.isRequired,
isOpen: PropTypes.bool.isRequired,
direction: PropTypes.oneOf(['up', 'down', 'left', 'right']).isRequired,
inNavbar: PropTypes.bool.isRequired,
};

class Dropdown extends React.Component {
constructor(props) {
Expand All @@ -53,9 +47,11 @@ class Dropdown extends React.Component {
this.handleKeyDown = this.handleKeyDown.bind(this);
this.removeEvents = this.removeEvents.bind(this);
this.toggle = this.toggle.bind(this);

this.containerRef = React.createRef();
}

getChildContext() {
getContextValue() {
return {
toggle: this.props.toggle,
isOpen: this.props.isOpen,
Expand All @@ -79,9 +75,7 @@ class Dropdown extends React.Component {
}

getContainer() {
if (this._$container) return this._$container;
this._$container = ReactDOM.findDOMNode(this);
return ReactDOM.findDOMNode(this);
return this.containerRef.current;
}

getMenuCtrl() {
Expand Down Expand Up @@ -206,12 +200,13 @@ class Dropdown extends React.Component {
setActiveFromChild,
active,
addonType,
tag,
...attrs
} = omit(this.props, ['toggle', 'disabled', 'inNavbar', 'direction']);

const direction = (this.props.direction === 'down' && dropup) ? 'up' : this.props.direction;

attrs.tag = attrs.tag || (nav ? 'li' : 'div');
const Tag = tag || (nav ? 'li' : 'div');

let subItemIsActive = false;
if (setActiveFromChild) {
Expand All @@ -237,12 +232,22 @@ class Dropdown extends React.Component {
}
), cssModule);

return <Manager {...attrs} className={classes} onKeyDown={this.handleKeyDown} />;
return (
<DropdownContext.Provider value={this.getContextValue()}>
<Manager>
<Tag
{...attrs}
{...{ [typeof Tag === 'string' ? 'ref' : 'innerRef']: this.containerRef }}
onKeyDown={this.handleKeyDown}
className={classes}
/>
</Manager>
</DropdownContext.Provider>
);
}
}

Dropdown.propTypes = propTypes;
Dropdown.defaultProps = defaultProps;
Dropdown.childContextTypes = childContextTypes;

export default Dropdown;
12 changes: 12 additions & 0 deletions src/DropdownContext.js
@@ -0,0 +1,12 @@
import React from 'react';

/**
* DropdownContext
* {
* toggle: PropTypes.func.isRequired,
* isOpen: PropTypes.bool.isRequired,
* direction: PropTypes.oneOf(['up', 'down', 'left', 'right']).isRequired,
* inNavbar: PropTypes.bool.isRequired,
* }
*/
export const DropdownContext = React.createContext({});
7 changes: 2 additions & 5 deletions src/DropdownItem.js
@@ -1,6 +1,7 @@
import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import { DropdownContext } from './DropdownContext';
import { mapToCssModules, omit, tagPropType } from './utils';

const propTypes = {
Expand All @@ -16,10 +17,6 @@ const propTypes = {
toggle: PropTypes.bool
};

const contextTypes = {
toggle: PropTypes.func
};

const defaultProps = {
tag: 'button',
toggle: true
Expand Down Expand Up @@ -104,6 +101,6 @@ class DropdownItem extends React.Component {

DropdownItem.propTypes = propTypes;
DropdownItem.defaultProps = defaultProps;
DropdownItem.contextTypes = contextTypes;
DropdownItem.contextType = DropdownContext;

export default DropdownItem;
94 changes: 55 additions & 39 deletions src/DropdownMenu.js
Expand Up @@ -2,6 +2,7 @@ import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import { Popper } from 'react-popper';
import { DropdownContext } from './DropdownContext';
import { mapToCssModules, tagPropType } from './utils';

const propTypes = {
Expand All @@ -20,12 +21,6 @@ const defaultProps = {
flip: true,
};

const contextTypes = {
isOpen: PropTypes.bool.isRequired,
direction: PropTypes.oneOf(['up', 'down', 'left', 'right']).isRequired,
inNavbar: PropTypes.bool.isRequired,
};

const noFlipModifier = { flip: { enabled: false } };

const directionPositionMap = {
Expand All @@ -35,46 +30,67 @@ const directionPositionMap = {
down: 'bottom',
};

const DropdownMenu = (props, context) => {
const { className, cssModule, right, tag, flip, modifiers, persist, ...attrs } = props;
const classes = mapToCssModules(classNames(
className,
'dropdown-menu',
{
'dropdown-menu-right': right,
show: context.isOpen,
}
), cssModule);
class DropdownMenu extends React.Component {

let Tag = tag;
render() {
const { className, cssModule, right, tag, flip, modifiers, persist, ...attrs } = this.props;
const classes = mapToCssModules(classNames(
className,
'dropdown-menu',
{
'dropdown-menu-right': right,
show: this.context.isOpen,
}
), cssModule);

if (persist || (context.isOpen && !context.inNavbar)) {
Tag = Popper;
const Tag = tag;

const position1 = directionPositionMap[context.direction] || 'bottom';
const position2 = right ? 'end' : 'start';
attrs.placement = `${position1}-${position2}`;
attrs.component = tag;
attrs.modifiers = !flip ? {
...modifiers,
...noFlipModifier,
} : modifiers;
}
if (persist || (this.context.isOpen && !this.context.inNavbar)) {

return (
<Tag
tabIndex="-1"
role="menu"
{...attrs}
aria-hidden={!context.isOpen}
className={classes}
x-placement={attrs.placement}
/>
);
const position1 = directionPositionMap[this.context.direction] || 'bottom';
const position2 = right ? 'end' : 'start';
const poperPlacement = `${position1}-${position2}`;
const poperModifiers = !flip ? {
...modifiers,
...noFlipModifier,
} : modifiers;

return (
<Popper
placement={poperPlacement}
modifiers={poperModifiers}
>
{({ ref, style, placement }) => (
<Tag
tabIndex="-1"
role="menu"
ref={ref}
style={style}
{...attrs}
aria-hidden={!this.context.isOpen}
className={classes}
x-placement={placement}
/>
)}
</Popper>
);
}

return (
<Tag
tabIndex="-1"
role="menu"
{...attrs}
aria-hidden={!this.context.isOpen}
className={classes}
x-placement={attrs.placement}
/>
);
}
};

DropdownMenu.propTypes = propTypes;
DropdownMenu.defaultProps = defaultProps;
DropdownMenu.contextTypes = contextTypes;
DropdownMenu.contextType = DropdownContext;

export default DropdownMenu;
32 changes: 16 additions & 16 deletions src/DropdownToggle.js
@@ -1,7 +1,8 @@
import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import { Target } from 'react-popper';
import { Reference } from 'react-popper';
import { DropdownContext } from './DropdownContext';
import { mapToCssModules, tagPropType } from './utils';
import Button from './Button';

Expand All @@ -24,12 +25,6 @@ const defaultProps = {
color: 'secondary',
};

const contextTypes = {
isOpen: PropTypes.bool.isRequired,
toggle: PropTypes.func.isRequired,
inNavbar: PropTypes.bool.isRequired,
};

class DropdownToggle extends React.Component {
constructor(props) {
super(props);
Expand Down Expand Up @@ -93,20 +88,25 @@ class DropdownToggle extends React.Component {
}

return (
<Target
{...props}
className={classes}
component={Tag}
onClick={this.onClick}
aria-expanded={this.context.isOpen}
children={children}
/>
<Reference>
{({ ref }) => (
<Tag
{...props}
{...{ [typeof Tag === 'string' ? 'ref' : 'innerRef']: ref }}

className={classes}
onClick={this.onClick}
aria-expanded={this.context.isOpen}
children={children}
/>
)}
</Reference>
);
}
}

DropdownToggle.propTypes = propTypes;
DropdownToggle.defaultProps = defaultProps;
DropdownToggle.contextTypes = contextTypes;
DropdownToggle.contextType = DropdownContext;

export default DropdownToggle;
16 changes: 6 additions & 10 deletions src/Modal.js
Expand Up @@ -120,19 +120,15 @@ class Modal extends React.Component {
this._isMounted = true;
}

componentWillReceiveProps(nextProps) {
if (nextProps.isOpen && !this.props.isOpen) {
this.setState({ isOpen: nextProps.isOpen });
}
}

componentWillUpdate(nextProps, nextState) {
if (nextState.isOpen && !this.state.isOpen) {
componentDidUpdate(prevProps, prevState) {
if (this.props.isOpen && !prevProps.isOpen) {
this.init();
this.setState({ isOpen: true});
// let render() renders Modal Dialog first
return;
}
}

componentDidUpdate(prevProps, prevState) {
// now Modal Dialog is rendered and we can refer this._element and this._dialog
if (this.props.autoFocus && this.state.isOpen && !prevState.isOpen) {
this.setFocus();
}
Expand Down