WIP Refactor sidenav
parent
14de10ad98
commit
c2b8350058
|
@ -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
|
|
@ -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}
|
|
@ -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;
|
|
@ -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))
|
|
@ -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))
|
|
@ -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
|
||||
|
|
|
@ -15,7 +15,6 @@ $page-header-weight: 400 !important;
|
|||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
background-color: $g0-obsidian;
|
||||
border: none;
|
||||
margin: 0;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in New Issue