optimizely-oui
Version:
Optimizely's Component Library.
205 lines (188 loc) • 6.79 kB
JavaScript
import React from "react";
import classNames from "classnames";
import PropTypes from "prop-types";
import Link from "../Link";
import Badge from "../Badge";
import IconLink from "./IconLink";
import CurrentUserMenu from "./CurrentUserMenu";
const LINK = "link";
const PUSH_STATE = "pushstate";
const BUTTON = "button";
const linkPropTypes = {
/** CSS class names. */
className: PropTypes.string,
/* Boolean, whether an external icon should show */
hasExternalIcon: PropTypes.bool,
/** Should show a separator line before this link. */
hasSeparator: PropTypes.bool,
/** Url to Navigate to when type is link. */
href: PropTypes.string,
/** Name of Icon. */
iconName: PropTypes.string.isRequired,
/** Whether the link is highlighted blue as the current active nav link. */
isActive: PropTypes.bool,
/** Condition in which this link is visible. */
isVisible: PropTypes.bool,
/** Description of url. */
linkLabel: PropTypes.oneOfType([PropTypes.string, PropTypes.node]),
/** Handler called when link is clicked. */
onClick: PropTypes.func,
/** Name of test data section. */
testSection: PropTypes.string,
/** Type of Link. */
type: PropTypes.oneOf([LINK, PUSH_STATE, BUTTON]),
};
const linkDefaultProps = {
href: "",
isActive: false,
isVisible: true,
linkLabel: "",
testSection: null,
hasSeparator: false,
onClick: () => null,
type: LINK,
};
const PrimaryLink = props => <IconLink {...props} isSecondaryLink={false} />;
PrimaryLink.propTypes = linkPropTypes;
PrimaryLink.defaultProps = linkDefaultProps;
PrimaryLink.displayName = "NavBar.PrimaryLink";
const SecondaryLink = props => <IconLink {...props} isSecondaryLink={true} />;
SecondaryLink.propTypes = linkPropTypes;
SecondaryLink.defaultProps = linkDefaultProps;
SecondaryLink.displayName = "NavBar.SecondaryLink";
/**
*
* @param {Object} props - Properties passed to component
* @returns {ReactElement}
*/
const NavBar = ({
badgeText,
children,
className,
footerContent,
homeUrl,
isOpen,
logoUrl,
title,
trialContent,
...props
}) => {
const renderChildrenByType = ComponentType =>
React.Children.toArray(children)
.filter(child => child.type === ComponentType)
.map(element => React.cloneElement(element, { isOpen }));
const logoClasses = classNames("push-double--left", {
"root-nav__logo--full": isOpen,
"root-nav__logo--mark": !isOpen,
});
const renderPrivacy = () => {
let year = new Date().getFullYear();
return (
<div className="root-nav__user root-nav__link root-nav__link--plain hard--bottom muted">
<span className="display--inline-block">
©2010–{year} Optimizely.{" "}
<a href="https://www.optimizely.com/privacy/" className="muted underline">
Privacy
</a>
</span>
</div>
);
};
const renderFooterContent = () => {
return (
<React.Fragment>
<hr className="oui-rule push-double--sides hard" />
<div className="root-nav__link root-nav__link--plain flex flex--column flex--1">{footerContent}</div>
</React.Fragment>
);
};
return (
<nav
className={classNames({
"root-nav": true,
"root-nav--open": isOpen,
}, className)}
data-test-section="p13n-root-navbar"
{...props}
>
<div data-test-section="navbar-header">
{logoUrl && (
<div className="root-nav__logo push--bottom">
<Link href={homeUrl}>
<img alt="logo" src={logoUrl} className={logoClasses} />
</Link>
</div>
)}
{trialContent}
<div
className={classNames({
"root-nav__project": true,
"root-nav-fader": !isOpen,
})}
>
<div className="epsilon truncate" data-test-section="project-name">
{title}
</div>
<Badge color="purple" testSection="project-badge">
{badgeText}
</Badge>
</div>
</div>
<ul className="push--ends">{renderChildrenByType(PrimaryLink)}</ul>
<div className="anchor--bottom">
<ul className="push--ends">{renderChildrenByType(SecondaryLink)}</ul>
{renderChildrenByType(CurrentUserMenu)}
{isOpen && footerContent && renderFooterContent()}
{isOpen && renderPrivacy()}
</div>
</nav>
);
};
NavBar.propTypes = {
/** Text to appear below title in a badge. */
badgeText: PropTypes.string,
/** Allowed Children are <NavBar.PrimaryLink>, <NavBar.SecondaryLink>, <NavBar.CurrentUserMenu>. Only one of <NavBar.CurrentUserMenu> is allowed. */
children: (props, propName) => {
const prop = props[propName];
let error = null;
React.Children.forEach(prop, child => {
if (child === null) {
// Skip checking empty child nodes
return;
}
if (![SecondaryLink, PrimaryLink, CurrentUserMenu].includes(child.type)) {
error = new Error("Children should be of type PrimaryLink, SecondaryLink, or CurrentUserMenu.");
}
});
if (!error && React.Children.toArray(prop).filter(child => child.type === CurrentUserMenu).length !== 1) {
error = new Error("There should be only one instance of `CurrentUserMenu`");
}
return error;
},
/** CSS class names. */
className: PropTypes.string,
/** Component to appear above the privacy statement. */
footerContent: PropTypes.node,
/** Url to navigate to when Brand Logo is clicked. */
homeUrl: PropTypes.string,
/** Is Navigation Bar open or closed. */
isOpen: PropTypes.bool,
/** Brand logo URL. */
logoUrl: PropTypes.string,
/** Title of navigation bar */
title: PropTypes.string,
/** Component to appear above title and below brand logo. */
trialContent: PropTypes.node,
};
NavBar.defaultProps = {
badgeText: "",
homeUrl: "",
isOpen: true,
logoUrl: null,
title: "",
trialContent: null,
};
NavBar.PrimaryLink = PrimaryLink;
NavBar.SecondaryLink = SecondaryLink;
NavBar.CurrentUserMenu = CurrentUserMenu;
export default NavBar;