diff --git a/demo/src/polyfill.js b/demo/src/polyfill.js index 4826bf56..d09b4a21 100644 --- a/demo/src/polyfill.js +++ b/demo/src/polyfill.js @@ -26,6 +26,8 @@ import 'core-js/es7/object' // Used for reflect-metadata in JIT. If you use AOT (and only Angular decorators), you can remove. // import 'core-js/es7/reflect' +import 'element-closest' + // CustomEvent() constructor functionality in IE9, IE10, IE11 (function () { diff --git a/package.json b/package.json index 5f8aecee..92489a59 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@coreui/react", - "version": "2.0.4", + "version": "2.0.5", "description": "CoreUI React Bootstrap 4 components", "license": "MIT", "author": { @@ -38,17 +38,19 @@ "@coreui/icons": "0.2.0", "classnames": "^2.2.6", "core-js": "^2.5.7", + "element-closest": "^2.0.2", "prop-types": "^15.6.2", + "react-onclickout": "^2.0.8", "react-perfect-scrollbar": "^1.1.1", "react-router-dom": "^4.3.1", - "reactstrap": "^6.1.0" + "reactstrap": "^6.3.0" }, "peerDependencies": { "@coreui/coreui": "^2.0.2", "react": "16.x" }, "devDependencies": { - "babel-eslint": "^8.2.5", + "babel-eslint": "^8.2.6", "enzyme": "^3.3.0", "enzyme-adapter-react-16": "^1.1.1", "eslint": "^4.19.1", diff --git a/src/Shared/classes.js b/src/Shared/classes.js index aad4137c..6fc0420b 100644 --- a/src/Shared/classes.js +++ b/src/Shared/classes.js @@ -13,3 +13,9 @@ export const asideMenuCssClasses = [ 'aside-menu-lg-show', 'aside-menu-xl-show' ]; + +export const validBreakpoints = [ 'sm', 'md', 'lg', 'xl' ] + +export function checkBreakpoint (breakpoint, list) { + return list.indexOf(breakpoint) > -1 +} diff --git a/src/Shared/index.js b/src/Shared/index.js index f2e9f298..517f2787 100644 --- a/src/Shared/index.js +++ b/src/Shared/index.js @@ -1,3 +1,3 @@ -import { sidebarCssClasses, asideMenuCssClasses } from './classes'; +import { sidebarCssClasses, asideMenuCssClasses, validBreakpoints, checkBreakpoint } from './classes'; -export { sidebarCssClasses, asideMenuCssClasses }; +export { sidebarCssClasses, asideMenuCssClasses, validBreakpoints, checkBreakpoint }; diff --git a/src/Shared/toggle-classes.js b/src/Shared/toggle-classes.js index 6f6205f7..feac9832 100644 --- a/src/Shared/toggle-classes.js +++ b/src/Shared/toggle-classes.js @@ -1,6 +1,6 @@ -export default function toggleClasses (toggleClass, classList) { +export default function toggleClasses (toggleClass, classList, force) { const level = classList.indexOf(toggleClass) const removeClassList = classList.slice(0, level) removeClassList.map((className) => document.body.classList.remove(className)) - document.body.classList.toggle(toggleClass) + document.body.classList.toggle(toggleClass, force) } diff --git a/src/Sidebar.js b/src/Sidebar.js index 6896522f..5263bb90 100644 --- a/src/Sidebar.js +++ b/src/Sidebar.js @@ -2,6 +2,8 @@ import React, { Component } from 'react'; import classNames from 'classnames'; import PropTypes from 'prop-types'; import { sidebarCssClasses } from './Shared'; +import ClickOutHandler from 'react-onclickout' +import 'element-closest' const propTypes = { children: PropTypes.node, @@ -35,6 +37,7 @@ class AppSidebar extends Component { this.isMinimized = this.isMinimized.bind(this); this.isOffCanvas = this.isOffCanvas.bind(this); this.displayBreakpoint = this.displayBreakpoint.bind(this); + this.hideMobile = this.hideMobile.bind(this); } componentDidMount() { @@ -70,6 +73,19 @@ class AppSidebar extends Component { document.body.classList.add(cssClass); } + hideMobile() { + if (document.body.classList.contains('sidebar-show')) { + document.body.classList.remove('sidebar-show'); + } + } + + onClickOut(e) { + if (!e.target.closest('[data-sidebar-toggler]')) { + this.hideMobile(); + } + + } + render() { const { className, children, tag: Tag, ...attributes } = this.props; @@ -85,9 +101,11 @@ class AppSidebar extends Component { // sidebar-nav root return ( - - {children} - + {this.onClickOut(e)}}> + + {children} + + ); } } diff --git a/src/SidebarToggler.js b/src/SidebarToggler.js index 3d8f74b7..c0e5497b 100644 --- a/src/SidebarToggler.js +++ b/src/SidebarToggler.js @@ -1,7 +1,7 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; import classNames from 'classnames'; -import { sidebarCssClasses } from './Shared/index'; +import { sidebarCssClasses, validBreakpoints, checkBreakpoint } from './Shared/index'; import toggleClasses from './Shared/toggle-classes'; const propTypes = { @@ -28,18 +28,16 @@ class AppSidebarToggler extends Component { sidebarToggle(e) { e.preventDefault(); + this.toggle(); + } - if (this.props.mobile) { - document.body.classList.toggle('sidebar-show'); - } else { - const display = this.props.display; - const cssTemplate = `sidebar-${display}-show`; - let [cssClass] = sidebarCssClasses[0]; - if (display && sidebarCssClasses.indexOf(cssTemplate) > -1) { - cssClass = cssTemplate; - } - toggleClasses(cssClass, sidebarCssClasses); + toggle(force) { + const [display, mobile] = [this.props.display, this.props.mobile] + let cssClass = sidebarCssClasses[0] + if (!mobile && display && checkBreakpoint(display, validBreakpoints)) { + cssClass = `sidebar-${display}-show` } + toggleClasses(cssClass, sidebarCssClasses, force) } render() { @@ -51,7 +49,7 @@ class AppSidebarToggler extends Component { const classes = classNames(className, 'navbar-toggler'); return ( - this.sidebarToggle(event)}> + this.sidebarToggle(event)} data-sidebar-toggler> {children || } ); diff --git a/tests/SidebarToggler.test.js b/tests/SidebarToggler.test.js index 53e1662e..8d673f9f 100644 --- a/tests/SidebarToggler.test.js +++ b/tests/SidebarToggler.test.js @@ -13,7 +13,7 @@ configure({ adapter: new Adapter() }); describe('AppSidebarToggler', () => { it('renders button with class="navbar-toggler"', () => { expect(render()) - .toContain('') + .toContain('') }) it('should call sidebarToggle', () => { let component = mount();