rc-adminlte
Version:
AdminLTE template ported to React
140 lines (134 loc) • 4.22 kB
JSX
import React from 'react';
import PropTypes from 'prop-types';
import { Link, withRouter } from 'react-router-dom';
import uuidv4 from 'uuid';
import ReactRouterPropTypes from 'react-router-prop-types';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Colors, TypeMappings } from '../PropTypes';
import { splitIcon } from '../Utilities';
const isActiveItem = ({ link, activeOn, history }) => {
const { location } = history || {};
const { pathname } = location || { pathname: '' };
let activeArray = [];
if (activeOn) {
activeArray = activeOn.length && typeof activeOn !== 'string' ? activeOn : [activeOn];
}
const active = pathname === link || !!(activeArray.find(p => pathname.match(new RegExp(p))));
return active;
};
const Item = ({
id, icon, link, text, labels, color, history, children, activeOn, to, highlighted,
}) => {
const localTo = to || link;
const active = isActiveItem({ link: localTo, activeOn, history });
const localLabels = labels && labels.length ? labels : (labels && [labels]) || [];
const mappedLabels = localLabels.map(p => (p.small ? <small key={uuidv4()} className={`label pull-right bg-${p.color}`}>{p.text}</small> : <span key={uuidv4()} className={`label label-${p.type} pull-right`}>{p.text}</span>));
const localColor = color ? TypeMappings.byColor[color].colorCode : null;
const localIcon = splitIcon(icon);
const hasChildren = !!(children);
const hasLabels = Array.isArray(localLabels);
let localChildren = children;
let activeChild = false;
if (hasChildren) {
// eslint-disable-next-line no-param-reassign
children = children.length ? children : [children];
localChildren = children.map(p => React.cloneElement(p, { key: p.to }));
activeChild = !!(localChildren.find(p => isActiveItem({
history,
link: p.props.to || p.props.link,
...p.props,
})));
}
let actualComponent = (
<React.Fragment>
<FontAwesomeIcon
color={localColor}
icon={localIcon}
style={{ marginRight: '6px' }}
/>
{' '}
<span>{text}</span>
{(hasChildren || hasLabels) && (
<span className="pull-right-container">
{hasChildren && <FontAwesomeIcon className="pull-right" icon="angle-left" />}
{hasLabels && mappedLabels}
</span>
)}
</React.Fragment>
);
if (localTo) {
actualComponent = (
<Link to={localTo}>
{actualComponent}
</Link>
);
} else {
actualComponent = (
// eslint-disable-next-line no-script-url, jsx-a11y/anchor-is-valid
<a href="#">
{actualComponent}
</a>
);
}
const liClasses = [
(active) ? 'active' : null,
hasChildren ? 'treeview' : null,
activeChild ? 'menu-open' : null,
highlighted ? 'highlighted' : undefined,
].filter(p => p).join(' ');
return (
<li className={liClasses}>
{actualComponent}
{hasChildren && (
<ul className="treeview-menu" style={{ display: activeChild ? 'block' : 'none' }}>
{children}
</ul>
)}
</li>
);
};
Item.propTypes = {
id: PropTypes.string,
children: PropTypes.oneOfType([
PropTypes.node,
PropTypes.arrayOf(PropTypes.node),
]),
icon: PropTypes.string,
// eslint-disable-next-line consistent-return
link(props, propName) {
const prop = props[propName];
if (prop) {
return new Error('This prop is deprecated and will be removed in future releases, please use the prop "to" instead');
}
},
text: PropTypes.string.isRequired,
labels: PropTypes.oneOfType([
PropTypes.node,
PropTypes.shape({
}),
PropTypes.arrayOf(PropTypes.node),
PropTypes.arrayOf(PropTypes.shape({
})),
]),
color: PropTypes.oneOf(Colors),
history: ReactRouterPropTypes.history,
activeOn: PropTypes.oneOfType([
PropTypes.string,
PropTypes.arrayOf(PropTypes.string),
]),
to: PropTypes.string,
highlighted: PropTypes.bool,
};
Item.defaultProps = {
id: undefined,
children: null,
icon: 'far-circle',
link: undefined,
labels: null,
color: null,
history: null,
activeOn: null,
to: undefined,
highlighted: false,
};
export default withRouter(Item);