WIP Refactor sidenav

pull/10616/head
Alex P 2018-09-21 12:17:28 -07:00
parent 14de10ad98
commit c2b8350058
8 changed files with 272 additions and 95 deletions

View File

@ -0,0 +1,47 @@
// Libraries
import React, {PureComponent} from 'react'
import _ from 'lodash'
// Components
import {NavMenuItem} from 'src/side_nav/components/NavMenuItem'
import {ErrorHandling} from 'src/shared/decorators/errors'
interface Props {
navItems: NavItem[]
}
interface NavItem {
title: string
link: string
icon: string
location: string
highlightWhen: string[]
}
@ErrorHandling
class NavMenu extends PureComponent<Props> {
constructor(props) {
super(props)
}
public render() {
const {navItems} = this.props
return (
<nav className="nav">
{navItems.map(({highlightWhen, icon, link, location}) => (
<NavMenuItem
highlightWhen={highlightWhen}
link={link}
location={location}
>
<span className={`icon ${icon}`} />
</NavMenuItem>
))}
</nav>
)
}
}
export default NavMenu

View File

@ -0,0 +1,116 @@
// Libraries
import React, {PureComponent, SFC, ReactNode, ReactElement} from 'react'
import {Link} from 'react-router'
import classnames from 'classnames'
import _ from 'lodash'
interface Props {
title: string
link: string
children: JSX.Element
location: string
highlightWhen: string[]
}
const NavListItem: SFC<Props> = ({
title,
link,
children,
location,
highlightWhen,
}) => {
const {length} = _.intersection(_.split(location, '/'), highlightWhen)
const isActive = !!length
<Link
className={classnames('sidebar-menu--item', {active: isActive})}
to={link}
>
<div>
</div>
</Link>
)
}
interface NavHeaderProps {
link?: string
title?: string
useAnchor?: string
}
const NavHeader: SFC<NavHeaderProps> = ({link, title, useAnchor}) => {
// Some nav items, such as Logout, need to hit an external link rather
// than simply route to an internal page. Anchor tags serve that purpose.
return useAnchor ? (
<a className="sidebar-menu--heading" href={link}>
{title}
</a>
) : (
<Link className="sidebar-menu--heading" to={link}>
{title}
</Link>
)
}
interface NavBlockProps {
children?: ReactNode
link?: string
icon: string
location?: string
className?: string
highlightWhen: string[]
}
class NavBlock extends PureComponent<NavBlockProps> {
public render() {
const {location, className, highlightWhen} = this.props
const {length} = _.intersection(_.split(location, '/'), highlightWhen)
const isActive = !!length
const children = React.Children.map(
this.props.children,
(child: ReactElement<any>) => {
// FIXME
if (child && String(child.type) === String(NavListItem)) {
return React.cloneElement(child, {location})
}
return child
}
)
return (
<div
className={classnames('sidebar--item', className, {active: isActive})}
>
{this.renderSquare()}
<div className="sidebar-menu">
{children}
<div className="sidebar-menu--triangle" />
</div>
</div>
)
}
private renderSquare() {
const {link, icon} = this.props
if (!link) {
return (
<div className="sidebar--square">
<div className={`sidebar--icon icon ${icon}`} />
</div>
)
}
return (
<Link className="sidebar--square" to={link}>
<div className={`sidebar--icon icon ${icon}`} />
</Link>
)
}
}
export {NavBlock, NavHeader, NavListItem}

View File

@ -35,8 +35,10 @@ $sidebar-menu--gutter: 18px;
display: flex;
flex-direction: column;
width: $sidebar--width;
@include gradient-v($sidebar--gradient-start,$sidebar--gradient-end);
background-color: $g3-castle;
border-radius: 0 $radius $radius 0;
}
.sidebar--bottom {
position: absolute;
bottom: 0;

View File

@ -0,0 +1,100 @@
// Libraries
import React, {PureComponent} from 'react'
import {withRouter, WithRouterProps} from 'react-router'
import {connect} from 'react-redux'
import _ from 'lodash'
// Components
import NavMenu from 'src/side_nav/components/NavMenu'
// Types
import {Source} from 'src/types/v2'
import {ErrorHandling} from 'src/shared/decorators/errors'
interface Props extends WithRouterProps {
sources: Source[]
isHidden: boolean
}
interface NavItem {
title: string
link: string
icon: string
location: string
highlightWhen: string[]
}
@ErrorHandling
class SideNav extends PureComponent<Props> {
constructor(props) {
super(props)
}
public render() {
const {isHidden} = this.props
if (isHidden) {
return null
}
return <NavMenu navItems={this.NavigationItems} />
}
private get NavigationItems(): NavItem[] {
const {location} = this.props
return [
{
title: 'Status',
link: `/status/${this.sourceParam}`,
icon: 'cubo-uniform',
location: location.pathname,
highlightWhen: ['status'],
},
{
title: 'Flux Builder',
link: `/delorean/${this.sourceParam}`,
icon: 'capacitor2',
location: location.pathname,
highlightWhen: ['delorean'],
},
{
title: 'Dashboards',
link: `/dashboards/${this.sourceParam}`,
icon: 'dash-j',
location: location.pathname,
highlightWhen: ['dashboards'],
},
{
title: 'Configuration',
link: `/manage-sources/${this.sourceParam}`,
icon: 'wrench',
location: location.pathname,
highlightWhen: ['manage-sources'],
},
]
}
private get sourceParam(): string {
const {location, sources = []} = this.props
const {query} = location
const defaultSource = sources.find(s => s.default)
const id = query.sourceID || _.get(defaultSource, 'id', 0)
return `?sourceID=${id}`
}
}
const mapStateToProps = ({
sources,
app: {
ephemeral: {inPresentationMode},
},
}) => ({
sources,
isHidden: inPresentationMode,
})
export default connect(mapStateToProps)(withRouter(SideNav))

View File

@ -1,90 +0,0 @@
// Libraries
import React, {PureComponent} from 'react'
import {withRouter, Link, WithRouterProps} from 'react-router'
import {connect} from 'react-redux'
import _ from 'lodash'
// Components
import {NavBlock, NavHeader} from 'src/side_nav/components/NavItems'
// Types
import {Source} from 'src/types/v2'
import {ErrorHandling} from 'src/shared/decorators/errors'
interface Props extends WithRouterProps {
sources: Source[]
isHidden: boolean
}
@ErrorHandling
class SideNav extends PureComponent<Props> {
constructor(props) {
super(props)
}
public render() {
const {location, isHidden, sources = []} = this.props
const {pathname, query} = location
const defaultSource = sources.find(s => s.default)
const id = query.sourceID || _.get(defaultSource, 'id', 0)
const sourceParam = `?sourceID=${id}`
const isDefaultPage = pathname.split('/').includes('status')
return isHidden ? null : (
<nav className="sidebar">
<div
className={isDefaultPage ? 'sidebar--item active' : 'sidebar--item'}
>
<Link
to={`/status/${sourceParam}`}
className="sidebar--square sidebar--logo"
>
<span className="sidebar--icon icon cubo-uniform" />
</Link>
</div>
<NavBlock
highlightWhen={['delorean']}
icon="capacitor2"
link={`/delorean${sourceParam}`}
location={pathname}
>
<NavHeader link={`/delorean/${sourceParam}`} title="Flux Editor" />
</NavBlock>
<NavBlock
highlightWhen={['dashboards']}
icon="dash-j"
link={`/dashboards/${sourceParam}`}
location={pathname}
>
<NavHeader link={`/dashboards/${sourceParam}`} title="Dashboards" />
</NavBlock>
<NavBlock
highlightWhen={['manage-sources']}
icon="wrench"
link={`/manage-sources/${sourceParam}`}
location={pathname}
>
<NavHeader
link={`/manage-sources/${sourceParam}`}
title="Configuration"
/>
</NavBlock>
</nav>
)
}
}
const mapStateToProps = ({
sources,
app: {
ephemeral: {inPresentationMode},
},
}) => ({
sources,
isHidden: inPresentationMode,
})
export default connect(mapStateToProps)(withRouter(SideNav))

View File

@ -27,7 +27,7 @@
@import 'layout/page';
@import 'layout/page-header';
@import 'layout/page-subsections';
@import 'layout/sidebar';
@import '../side_nav/components/SideNav';
@import 'layout/overlay-technology';
// Components

View File

@ -15,7 +15,6 @@ $page-header-weight: 400 !important;
position: absolute;
top: 0;
left: 0;
background-color: $g0-obsidian;
border: none;
margin: 0;
}

View File

@ -8,17 +8,21 @@ $dash-ceo-z: $dygraph-legend-z + 10;
.chronograf-root {
display: flex;
align-items: stretch;
align-items: center;
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
color: $g17-whisper;
@include gradient-v($g2-kevlar,$g0-obsidian);
}
.page {
height: 100%;
flex-grow: 1;
}
.page-contents,
.page-contents--split {
position: absolute !important;
@ -26,7 +30,6 @@ $dash-ceo-z: $dygraph-legend-z + 10;
left: 0;
width: 100%;
height: calc(100% - #{$chronograf-page-header-height}) !important;
@include gradient-v($g2-kevlar,$g0-obsidian);
&:only-child {
top: 0;