diff --git a/CHANGELOG.md b/CHANGELOG.md
index c3e3fd04..e368b640 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,49 @@
### [@coreui/react](https://coreui.io/) changelog
+##### `v2.5.0`
+- BREAKING CHANGE release for use with `react-router-dom v5`
+ - feat(Breadcrumb2): mandatory prop `router` :boom: see: [Breadcrumb](./src/Breadcrumb.md)
+ - feat(SidebarNav2): mandatory prop `router` :boom: see: [SidebarNav](./src/SidebarNav.md)
+- refactor: demo update
+- refactor(SidebarNav): rename `options` prop for PerfectScrollbar
+
+###### dependencies update
+- update `react-router-dom` to `^5.0.0` -> moved to `peerDependencies`
+
+__BREAKING CHANGES:__ :boom:
+- removed `react-router-dom` from `dependencies`
+- deprecate 'Breadcrumb' in favour of `Breadcrumb2`
+- deprecate 'SidebarNav' in favour of `SidebarNav2`
+
+usage in `DefaultLayout.js`:
+```jsx
+import * as router from 'react-router-dom';
+import {
+AppBreadcrumb2 as AppBreadcrumb
+AppSidebarNav2 as AppSidebarNav
+} from '@coreui/react';
+// routes config
+import routes from '../../routes.js';
+```
+
+```html
+...
+
+
+
+
+
+
+
+ ...
+
+ ...
+
+...
+```
+
+---
+
##### `v2.1.7`
- maintenance release for use with:
- react-router `v4.3.x`
diff --git a/demo/src/containers/DefaultLayout/DefaultLayout.js b/demo/src/containers/DefaultLayout/DefaultLayout.js
index e4bb99df..2fce1fb0 100644
--- a/demo/src/containers/DefaultLayout/DefaultLayout.js
+++ b/demo/src/containers/DefaultLayout/DefaultLayout.js
@@ -1,11 +1,12 @@
import React, { Component } from 'react';
import { Redirect, Route, Switch } from 'react-router-dom';
+import * as router from 'react-router-dom';
import { Container, Nav, NavItem, NavLink, Badge, DropdownToggle, DropdownMenu } from 'reactstrap';
import {
AppAside,
AppAsideToggler,
- AppBreadcrumb,
+ AppBreadcrumb2 as AppBreadcrumb,
AppFooter,
AppHeader,
AppHeaderDropdown,
@@ -15,7 +16,7 @@ import {
AppSidebarForm,
AppSidebarHeader,
AppSidebarMinimizer,
- AppSidebarNav,
+ AppSidebarNav2 as AppSidebarNav,
AppSidebarToggler,
} from '../../../../src';
// sidebar nav config
@@ -64,13 +65,14 @@ class DefaultLayout extends Component {
-
+ {/**/}
+
-
-
+ {/**/}
+
{routes.map((route, idx) => {
diff --git a/package.json b/package.json
index ff69cbdc..ea0e0966 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "@coreui/react",
- "version": "2.1.7",
+ "version": "2.5.0",
"description": "CoreUI React Bootstrap 4 components",
"license": "MIT",
"author": {
@@ -41,12 +41,12 @@
"prop-types": "^15.7.2",
"react-onclickout": "^2.0.8",
"react-perfect-scrollbar": "^1.5.2",
- "react-router-dom": "~4.3.1",
"reactstrap": "^7.1.0"
},
"peerDependencies": {
"@coreui/coreui": "^2.1.9",
- "react": "^16.8.6"
+ "react": "^16.8.6",
+ "react-router-dom": "^5.0.0"
},
"devDependencies": {
"babel-eslint": "^10.0.1",
@@ -58,6 +58,7 @@
"nwb": "^0.23.0",
"react": "^16.8.6",
"react-dom": "^16.8.6",
+ "react-router-dom": "^5.0.0",
"sinon": "^5.1.1",
"webpack-dev-server": "~3.3.1"
},
diff --git a/src/Breadcrumb.md b/src/Breadcrumb.md
index 9a3d39c3..98433a96 100644
--- a/src/Breadcrumb.md
+++ b/src/Breadcrumb.md
@@ -1,3 +1,14 @@
### CoreUI `Breadcrumb` component
+usage in `DefaultLayout`:
+```jsx
+import * as router from 'react-router-dom';
+import { AppBreadcrumb2 as AppBreadcrumb } from '@coreui/react';
+// routes config
+import routes from '../../routes.js';
+```
+
+```html
+
+```
_todo_
diff --git a/src/Breadcrumb2.js b/src/Breadcrumb2.js
new file mode 100644
index 00000000..716f3b65
--- /dev/null
+++ b/src/Breadcrumb2.js
@@ -0,0 +1,108 @@
+import React, { Component } from 'react';
+import { Breadcrumb, BreadcrumbItem } from 'reactstrap';
+import PropTypes from 'prop-types';
+import classNames from 'classnames';
+
+let routes;
+let router;
+
+const getPaths = (pathname) => {
+ const paths = ['/'];
+
+ if (pathname === '/') return paths;
+
+ pathname.split('/').reduce((prev, curr) => {
+ const currPath = `${prev}/${curr}`;
+ paths.push(currPath);
+ return currPath;
+ });
+ return paths;
+};
+
+const findRouteName2 = (url) => {
+ const matchPath = router.matchPath;
+ const aroute = routes.find(route => matchPath(url, {path: route.path, exact: route.exact}));
+ return (aroute && aroute.name) ? aroute.name : null
+};
+
+const BreadcrumbsItem2 = ({ match }) => {
+ const routeName = findRouteName2(match.url);
+ const Link = router.Link;
+ if (routeName) {
+ return (
+ match.isExact ?
+ {routeName}
+ :
+
+
+ {routeName}
+
+
+ );
+ }
+ return null;
+};
+
+BreadcrumbsItem2.propTypes = {
+ match: PropTypes.shape({
+ url: PropTypes.string
+ })
+};
+
+const Breadcrumbs2 = (args) => {
+ const Route = router.Route;
+ const paths = getPaths(args.location.pathname);
+ const items = paths.map((path, i) => );
+ return (
+
+ {items}
+
+ );
+};
+
+const propTypes = {
+ children: PropTypes.node,
+ className: PropTypes.string,
+ appRoutes: PropTypes.any,
+ tag: PropTypes.oneOfType([PropTypes.func, PropTypes.string]),
+ router: PropTypes.any
+};
+
+const defaultProps = {
+ tag: 'div',
+ className: '',
+ appRoutes: [{ path: '/', exact: true, name: 'Home', component: null }]
+};
+
+class AppBreadcrumb2 extends Component {
+ constructor(props) {
+ super(props);
+
+ this.state = { routes: props.appRoutes };
+ routes = this.state.routes;
+ router = props.router;
+ }
+
+ render() {
+ const { className, tag: Tag, ...attributes } = this.props;
+
+ delete attributes.children
+ delete attributes.appRoutes
+ delete attributes.router
+
+ const classes = classNames(className);
+
+ const Route = router.Route;
+
+ return (
+
+
+
+ );
+ }
+}
+
+AppBreadcrumb2.propTypes = propTypes;
+AppBreadcrumb2.defaultProps = defaultProps;
+
+export default AppBreadcrumb2;
diff --git a/src/SidebarNav.js b/src/SidebarNav.js
index a8695cb3..549c01b2 100644
--- a/src/SidebarNav.js
+++ b/src/SidebarNav.js
@@ -185,7 +185,7 @@ class AppSidebarNav extends Component {
// sidebar-nav root
return (
-
+
diff --git a/src/SidebarNav.md b/src/SidebarNav.md
index c2979239..dfc96e40 100644
--- a/src/SidebarNav.md
+++ b/src/SidebarNav.md
@@ -1,34 +1,47 @@
### CoreUI `SidebarNav` subcomponent
+usage in `DefaultLayout`:
+```jsx
+import * as router from 'react-router-dom';
+import { AppSidebarNav2 as AppSidebarNav } from '@coreui/react';
+// sidebar nav config
+import navigation from '../../_nav';
+```
+```html
+
+```
+props:
-prop | default
---- | ---
-children | `this.navList(navConfig.items)`
-className | `sidebar-nav`
-navConfig | `{ items: [ { name url icon badge } ] }`
-isOpen | `false`
-tag | `nav`
+prop | default | notes
+--- | --- | ---
+children | `this.navList(navConfig.items)` |
+className | `sidebar-nav` |
+navConfig | `{ items: [ { name url icon badge } ] }` |
+isOpen | `false` |
+tag | `nav` |
+router | inject `react-router-dom@5` object | _mandatory for @coreui/react ^2.5.0_
-#### `navConfig` structure
+---
+#### `navConfig` shape
- title:
-````js
+```json5
{
title: true,
name: 'Theme',
- class: '' // optional class names space delimited list for title item ex: "text-center"
+ class: '', // optional class names space delimited list for title item ex: "text-center"
wrapper: { // optional wrapper object
element: '', // optional* valid HTML5 element tag ( *required when passing attributes)
attributes: {} // optional valid JS object with JS API naming ex: { className: "my-class", style: { fontFamily: "Verdana" }, id: "my-id"}
},
},
-````
+```
- item:
-````js
+```json5
{
name: 'Dashboard',
url: '/dashboard',
- icon: `icon-speedometer',
+ icon: 'icon-speedometer',
class: '', // optional
variant: 'success', // optional
badge: {
@@ -38,9 +51,9 @@ tag | `nav`
},
attributes: { target: '_blank', rel: "noreferrer noopener", disabled: false, hidden: false }, // (v2.1.0 up) optional valid JS object with JS API naming
},
-````
+```
- item with `children` array - works like `nav-dropdown-toggle` with `nav-dropdown-items`
-````js
+```json5
{
name: 'Icons',
url: '/icons',
@@ -57,16 +70,16 @@ tag | `nav`
},
]
}
-````
+```
- divider:
-````js
+```json5
{
divider: true
},
-````
+```
- order of precedence:
-````
+```
title > divider > children > item
-````
+```
diff --git a/src/SidebarNav2.js b/src/SidebarNav2.js
new file mode 100644
index 00000000..2ab4b7fa
--- /dev/null
+++ b/src/SidebarNav2.js
@@ -0,0 +1,203 @@
+import React, { Component } from 'react';
+import { Badge, Nav, NavItem, NavLink as RsNavLink } from 'reactstrap';
+import classNames from 'classnames';
+import PropTypes from 'prop-types';
+import PerfectScrollbar from 'react-perfect-scrollbar';
+import 'react-perfect-scrollbar/dist/css/styles.css';
+
+const propTypes = {
+ children: PropTypes.node,
+ className: PropTypes.string,
+ navConfig: PropTypes.any,
+ navFunc: PropTypes.oneOfType([PropTypes.func, PropTypes.string]),
+ isOpen: PropTypes.bool,
+ staticContext: PropTypes.any,
+ tag: PropTypes.oneOfType([PropTypes.func, PropTypes.string]),
+ router: PropTypes.any
+};
+
+const defaultProps = {
+ tag: 'nav',
+ navConfig: {
+ items: [
+ {
+ name: 'Dashboard',
+ url: '/dashboard',
+ icon: 'icon-speedometer',
+ badge: { variant: 'info', text: 'NEW' }
+ }]
+ },
+ isOpen: false,
+ router: {RsNavLink}
+};
+
+class AppSidebarNav2 extends Component {
+ constructor(props) {
+ super(props);
+
+ this.handleClick = this.handleClick.bind(this);
+ this.activeRoute = this.activeRoute.bind(this);
+ this.hideMobile = this.hideMobile.bind(this);
+ }
+
+ handleClick(e) {
+ e.preventDefault();
+ e.currentTarget.parentElement.classList.toggle('open');
+ }
+
+ activeRoute(routeName, props) {
+ return props.location.pathname.indexOf(routeName) > -1
+ ? 'nav-item nav-dropdown open'
+ : 'nav-item nav-dropdown';
+ }
+
+ hideMobile() {
+ if (document.body.classList.contains('sidebar-show')) {
+ document.body.classList.toggle('sidebar-show');
+ }
+ }
+
+ // nav list
+ navList(items) {
+ return items.map((item, index) => this.navType(item, index));
+ }
+
+ // nav type
+ navType(item, idx) {
+ return (
+ item.title ? this.navTitle(item, idx)
+ : item.divider ? this.navDivider(item, idx)
+ : item.label ? this.navLabel(item, idx)
+ : item.children ? this.navDropdown(item, idx)
+ : this.navItem(item, idx)
+ );
+ }
+
+ // nav list section title
+ navTitle(title, key) {
+ const classes = classNames('nav-title', title.class);
+ return {this.navWrapper(title)} ;
+ }
+
+ // simple wrapper for nav-title item
+ navWrapper(item) {
+ return item.wrapper && item.wrapper.element ? React.createElement(item.wrapper.element, item.wrapper.attributes, item.name) : item.name;
+ }
+
+ // nav list divider
+ navDivider(divider, key) {
+ const classes = classNames('divider', divider.class);
+ return ;
+ }
+
+ // nav label with nav link
+ navLabel(item, key) {
+ const classes = {
+ item: classNames('hidden-cn', item.class),
+ link: classNames('nav-label', item.class ? item.class : ''),
+ icon: classNames(
+ 'nav-icon',
+ !item.icon ? 'fa fa-circle' : item.icon,
+ item.label.variant ? `text-${item.label.variant}` : '',
+ item.label.class ? item.label.class : '',
+ )
+ };
+ return (
+ this.navLink(item, key, classes)
+ );
+ }
+
+ // nav dropdown
+ navDropdown(item, key) {
+ const classIcon = classNames('nav-icon', item.icon);
+ return (
+
+ {item.name}{this.navBadge(item.badge)}
+
+ {this.navList(item.children)}
+
+ );
+ }
+
+ // nav item with nav link
+ navItem(item, key) {
+ const classes = {
+ item: classNames(item.class),
+ link: classNames('nav-link', item.variant ? `nav-link-${item.variant}` : ''),
+ icon: classNames('nav-icon', item.icon)
+ };
+ return (
+ this.navLink(item, key, classes)
+ );
+ }
+
+ // nav link
+ navLink(item, key, classes) {
+ const url = item.url || '';
+ const itemIcon =
+ const itemBadge = this.navBadge(item.badge)
+ const attributes = item.attributes || {}
+ const NavLink = this.props.router.NavLink || RsNavLink
+ return (
+
+ { attributes.disabled ?
+
+ {itemIcon}{item.name}{itemBadge}
+
+ :
+ this.isExternal(url) || NavLink === RsNavLink ?
+
+ {itemIcon}{item.name}{itemBadge}
+ :
+
+ {itemIcon}{item.name}{itemBadge}
+
+ }
+
+ );
+ }
+
+ // badge addon to NavItem
+ navBadge(badge) {
+ if (badge) {
+ const classes = classNames(badge.class);
+ return (
+ {badge.text}
+ );
+ }
+ return null;
+ }
+
+ isExternal(url) {
+ const link = url ? url.substring(0, 4) : '';
+ return link === 'http';
+ }
+
+ render() {
+ const { className, children, navConfig, ...attributes } = this.props;
+
+ delete attributes.isOpen
+ delete attributes.staticContext
+ delete attributes.Tag
+ delete attributes.router
+
+ const navClasses = classNames(className, 'sidebar-nav');
+
+ // ToDo: find better rtl fix
+ const isRtl = getComputedStyle(document.querySelector('html')).direction === 'rtl'
+
+ // sidebar-nav root
+ return (
+
+
+
+ );
+ }
+}
+
+AppSidebarNav2.propTypes = propTypes;
+AppSidebarNav2.defaultProps = defaultProps;
+
+export default AppSidebarNav2;
diff --git a/src/index.js b/src/index.js
index 93bad494..b32539fa 100644
--- a/src/index.js
+++ b/src/index.js
@@ -1,6 +1,7 @@
export AppAside from './Aside';
export AppAsideToggler from './AsideToggler';
export AppBreadcrumb from './Breadcrumb';
+export AppBreadcrumb2 from './Breadcrumb2';
export AppFooter from './Footer';
export AppHeader from './Header';
export AppHeaderDropdown from './HeaderDropdown';
@@ -11,5 +12,6 @@ export AppSidebarForm from './SidebarForm';
export AppSidebarHeader from './SidebarHeader';
export AppSidebarMinimizer from './SidebarMinimizer';
export AppSidebarNav from './SidebarNav';
+export AppSidebarNav2 from './SidebarNav2';
export AppSidebarToggler from './SidebarToggler';
export AppSwitch from './Switch';