Preview: http://dashboards.webkom.co/react/airframe
This commit is contained in:
Tomasz Owczarczyk
2019-08-15 00:54:44 +02:00
parent f975443095
commit 37092d1d6c
626 changed files with 56691 additions and 0 deletions

View File

@@ -0,0 +1,9 @@
import React from 'react';
const MenuContext = React.createContext({
entries: { },
addEntry: () => { },
removeEntry: () => { }
});
export { MenuContext };

View File

@@ -0,0 +1,169 @@
import React from 'react';
import PropTypes from 'prop-types';
import { withRouter } from 'react-router-dom';
import _ from 'lodash';
import classNames from 'classnames';
import { withPageConfig } from './../Layout/withPageConfig'
import Common from './../../common';
import { MenuContext } from './MenuContext';
class SidebarMenu extends React.Component {
static propTypes = {
children: PropTypes.node,
currentUrl: PropTypes.string,
slim: PropTypes.bool,
location: PropTypes.object,
pageConfig: PropTypes.object,
disabled: PropTypes.bool
}
containerRef = React.createRef();
constructor(props) {
super(props);
this.state = {
entries: this.entries = { }
};
}
addEntry(entry) {
this.setState({
entries: this.entries = {
...this.entries,
[entry.id]: {
open: false,
active: false,
...entry
}
}
});
}
updateEntry(id, stateMods) {
this.setState({
entries: this.entries = {
...this.state.entries,
[id]: {
...this.state.entries[id],
...stateMods
}
}
});
}
removeEntry(id) {
// eslint-disable-next-line no-unused-vars
const { [id]: toRemove, ...rest } = this.state.entries;
this.setState({ entries: this.entries = rest });
}
setActiveEntries(openActive = false) {
const activeId = (childEntry, entries, previous = []) => {
if (childEntry.parentId) {
const parentEntry = entries[childEntry.parentId];
const activeIds = [...previous, parentEntry.id];
return activeId(parentEntry, entries, activeIds);
}
return previous;
}
const activeChild = _.find(this.state.entries, (entry) => {
const { pathname } = this.props.location;
const noTailSlashLocation = pathname[pathname.length - 1] === '/' && pathname.length > 1 ?
pathname.replace(/\/$/, '') : pathname;
return entry.exact ?
entry.url === noTailSlashLocation :
_.includes(noTailSlashLocation, entry.url)
});
if (activeChild) {
const activeEntries = [...activeId(activeChild, this.entries), activeChild.id];
this.setState({
entries: this.entries = _.mapValues(this.entries, (entry) => {
const isActive = _.includes(activeEntries, entry.id);
return {
...entry,
active: isActive,
open: openActive ? (!entry.url && isActive) : entry.open
};
})
});
}
}
componentDidMount() {
this.sidebarAnimation = new Common.SideMenuAnimate();
this.sidebarAnimation.assignParentElement(
this.containerRef.current
);
setTimeout(() => {
this.setActiveEntries(true);
}, 0);
}
componentDidUpdate(prevProps) {
if (this.props.location.pathname !== prevProps.location.pathname) {
this.setActiveEntries();
}
}
componentWillUnmount() {
if (this.sidebarAnimation) {
this.sidebarAnimation.destroy();
}
}
render() {
const isSlim = this.props.slim || (
this.props.pageConfig.sidebarSlim &&
this.props.pageConfig.sidebarCollapsed && (
this.props.pageConfig.screenSize === 'lg' ||
this.props.pageConfig.screenSize === 'xl'
)
);
const sidebarMenuClass = classNames('sidebar-menu', {
'sidebar-menu--slim': isSlim,
'sidebar-menu--disabled': this.props.disabled
});
return (
<MenuContext.Provider
value={{
entries: this.state.entries,
addEntry: this.addEntry.bind(this),
updateEntry: this.updateEntry.bind(this),
removeEntry: this.removeEntry.bind(this)
}}
>
<ul className={ sidebarMenuClass } ref={ this.containerRef }>
{
React.Children.map(this.props.children, (child) =>
<MenuContext.Consumer>
{
(ctx) => React.cloneElement(child, {
...ctx,
currentUrl: this.props.location.pathname,
slim: isSlim
})
}
</MenuContext.Consumer>
)
}
</ul>
</MenuContext.Provider>
)
}
}
const RouterSidebarMenu = withPageConfig(withRouter(SidebarMenu));
export {
RouterSidebarMenu as SidebarMenu
};

View File

@@ -0,0 +1,173 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Link } from 'react-router-dom';
import classNames from 'classnames';
import uuid from 'uuid/v4';
import { MenuContext } from './MenuContext';
/**
* Renders a collapse trigger or a ReactRouter Link
*/
const SidebarMenuItemLink = (props) => (
(props.to || props.href) ? (
props.to ? (
<Link to={ props.to } className={`${props.classBase}__entry__link`}>
{ props.children }
</Link>
) : (
<a
href={ props.href }
target="_blank"
rel="noopener noreferrer"
className={`${props.classBase}__entry__link`}
>
{ props.children }
</a>
)
) : (
<a
href="javascript:;"
className={`${props.classBase}__entry__link`}
onClick={ () => props.onToggle() }
>
{ props.children }
</a>
)
)
SidebarMenuItemLink.propTypes = {
to: PropTypes.string,
href: PropTypes.string,
active: PropTypes.bool,
onToggle: PropTypes.func,
children: PropTypes.node,
classBase: PropTypes.string
}
/**
* The main menu entry component
*/
export class SidebarMenuItem extends React.Component {
static propTypes = {
// MenuContext props
addEntry: PropTypes.func,
updateEntry: PropTypes.func,
removeEntry: PropTypes.func,
entries: PropTypes.object,
// Provided props
parentId: PropTypes.string,
children: PropTypes.node,
isSubNode: PropTypes.bool,
currentUrl: PropTypes.string,
slim: PropTypes.bool,
// User props
icon: PropTypes.node,
title: PropTypes.oneOfType([
PropTypes.string,
PropTypes.node
]),
to: PropTypes.string,
href: PropTypes.string,
exact: PropTypes.bool,
noCaret: PropTypes.bool,
}
static defaultProps = {
exact: true
}
constructor(props) {
super(props);
this.id = uuid();
}
componentDidMount() {
const entry = {
id: this.id,
parentId: this.props.parentId,
exact: !!this.props.exact
};
if (this.props.to) {
entry.url = this.props.to;
}
this.props.addEntry(entry);
}
componentWillUnmount() {
this.props.removeEntry(this.id);
}
getEntry() {
return this.props.entries[this.id];
}
toggleNode() {
const entry = this.getEntry();
this.props.updateEntry(this.id, { open: !entry.open });
}
render() {
const entry = this.getEntry();
const classBase = this.props.isSubNode ? "sidebar-submenu" : "sidebar-menu";
const itemClass = classNames(`${classBase}__entry`, {
[`${classBase}__entry--nested`]: !!this.props.children,
'open': entry && entry.open,
'active': entry && entry.active
});
return (
<li
className={classNames(itemClass, {
'sidebar-menu__entry--no-caret': this.props.noCaret,
})}
>
<SidebarMenuItemLink
to={ this.props.to || null }
href={ this.props.href || null }
onToggle={ this.toggleNode.bind(this) }
classBase={ classBase }
>
{
this.props.icon && React.cloneElement(this.props.icon, {
className: classNames(
this.props.icon.props.className,
`${classBase}__entry__icon`
)
})
}
{
typeof this.props.title === 'string' ?
<span>{ this.props.title }</span> :
this.props.title
}
</SidebarMenuItemLink>
{
this.props.children && (
<ul className="sidebar-submenu">
{
React.Children.map(this.props.children, (child) => (
<MenuContext.Consumer>
{
(ctx) => React.cloneElement(child, {
isSubNode: true,
parentId: this.id,
currentUrl: this.props.currentUrl,
slim: this.props.slim,
...ctx
})
}
</MenuContext.Consumer>
))
}
</ul>
)
}
</li>
);
}
}

View File

@@ -0,0 +1,6 @@
import { SidebarMenu } from './SidebarMenu';
import { SidebarMenuItem } from './SidebarMenuItem';
SidebarMenu.Item = SidebarMenuItem;
export default SidebarMenu;