Flesh out page_layout components

pull/10616/head
Alex P 2018-09-21 16:44:49 -07:00
parent 8e4f740806
commit 2c7bc52204
16 changed files with 382 additions and 332 deletions

View File

@ -1,11 +1,7 @@
import React, {Component, MouseEvent} from 'react'
import DashboardsTable from 'src/dashboards/components/DashboardsTable'
import ImportDashboardOverlay from 'src/dashboards/components/ImportDashboardOverlay'
import SearchBar from 'src/shared/components/SearchBar'
import FancyScrollbar from 'src/shared/components/FancyScrollbar'
import {ErrorHandling} from 'src/shared/decorators/errors'
import OverlayTechnology from 'src/reusable_ui/components/overlays/OverlayTechnology'
import {Dashboard} from 'src/types/v2'
import {Notification} from 'src/types/notifications'
@ -18,132 +14,44 @@ interface Props {
dashboard: Dashboard
) => (event: MouseEvent<HTMLButtonElement>) => void
onExportDashboard: (dashboard: Dashboard) => () => void
onImportDashboard: (dashboard: Dashboard) => void
notify: (message: Notification) => void
}
interface State {
searchTerm: string
isOverlayVisible: boolean
}
@ErrorHandling
class DashboardsPageContents extends Component<Props, State> {
constructor(props) {
super(props)
this.state = {
searchTerm: '',
isOverlayVisible: false,
}
}
class DashboardsPageContents extends Component<Props> {
public render() {
const {
onDeleteDashboard,
onCreateDashboard,
onCloneDashboard,
onExportDashboard,
onCreateDashboard,
} = this.props
return (
<FancyScrollbar className="page-contents">
<div className="container-fluid">
<div className="row">
<div className="col-md-12">
<div className="panel">
{this.renderPanelHeading}
<div className="panel-body">
<DashboardsTable
dashboards={this.filteredDashboards}
onDeleteDashboard={onDeleteDashboard}
onCreateDashboard={onCreateDashboard}
onCloneDashboard={onCloneDashboard}
onExportDashboard={onExportDashboard}
/>
</div>
</div>
</div>
</div>
</div>
</FancyScrollbar>
)
}
private get renderPanelHeading(): JSX.Element {
const {onCreateDashboard} = this.props
return (
<>
<div className="panel-heading">
<h2 className="panel-title">{this.panelTitle}</h2>
<div className="panel-controls">
<SearchBar
placeholder="Filter by Name..."
onSearch={this.filterDashboards}
<div className="col-md-12">
<div className="panel">
<div className="panel-body">
<DashboardsTable
dashboards={this.filteredDashboards}
onDeleteDashboard={onDeleteDashboard}
onCreateDashboard={onCreateDashboard}
onCloneDashboard={onCloneDashboard}
onExportDashboard={onExportDashboard}
/>
<button
className="btn btn-sm btn-default"
onClick={this.handleToggleOverlay}
>
<span className="icon import" /> Import Dashboard
</button>
<button
className="btn btn-sm btn-primary"
onClick={onCreateDashboard}
>
<span className="icon plus" /> Create Dashboard
</button>
</div>
</div>
{this.renderImportOverlay}
</>
</div>
)
}
private get filteredDashboards(): Dashboard[] {
const {dashboards} = this.props
const {searchTerm} = this.state
const {dashboards, searchTerm} = this.props
return dashboards.filter(d =>
d.name.toLowerCase().includes(searchTerm.toLowerCase())
)
}
private get panelTitle(): string {
const {dashboards} = this.props
if (dashboards === null) {
return 'Loading Dashboards...'
} else if (dashboards.length === 1) {
return '1 Dashboard'
}
return `${dashboards.length} Dashboards`
}
private filterDashboards = (searchTerm: string): void => {
this.setState({searchTerm})
}
private handleToggleOverlay = (): void => {
this.setState({isOverlayVisible: !this.state.isOverlayVisible})
}
private get renderImportOverlay(): JSX.Element {
const {onImportDashboard, notify} = this.props
const {isOverlayVisible} = this.state
return (
<OverlayTechnology visible={isOverlayVisible}>
<ImportDashboardOverlay
onDismissOverlay={this.handleToggleOverlay}
onImportDashboard={onImportDashboard}
notify={notify}
/>
</OverlayTechnology>
)
}
}
export default DashboardsPageContents

View File

@ -7,8 +7,12 @@ import _ from 'lodash'
// Components
import DashboardsContents from 'src/dashboards/components/DashboardsPageContents'
import PageHeader from 'src/reusable_ui/components/page_layout/PageHeader'
import {Page, PageHeader, PageContents} from 'src/page_layout'
import SearchBar from 'src/shared/components/SearchBar'
import OverlayTechnology from 'src/reusable_ui/components/overlays/OverlayTechnology'
import ImportDashboardOverlay from 'src/dashboards/components/ImportDashboardOverlay'
// Utils
import {getDeep} from 'src/utils/wrappers'
// APIs
@ -34,7 +38,9 @@ import {Notification} from 'src/types/notifications'
import {DashboardFile, Cell} from 'src/types/v2/dashboards'
import {Links, Dashboard} from 'src/types/v2'
// Decorators
import {ErrorHandling} from 'src/shared/decorators/errors'
interface Props {
router: InjectedRouter
links: Links
@ -46,8 +52,22 @@ interface Props {
dashboards: Dashboard[]
}
interface State {
searchTerm: string
isImportingDashboard: boolean
}
@ErrorHandling
class DashboardsPage extends PureComponent<Props> {
class DashboardsPage extends PureComponent<Props, State> {
constructor(props: Props) {
super(props)
this.state = {
searchTerm: '',
isImportingDashboard: false,
}
}
public async componentDidMount() {
const {handleGetDashboards, dashboards, links} = this.props
await handleGetDashboards(links.dashboards)
@ -57,20 +77,48 @@ class DashboardsPage extends PureComponent<Props> {
public render() {
const {dashboards, notify} = this.props
const {searchTerm} = this.state
return (
<div className="page">
<PageHeader titleText="Dashboards" sourceIndicator={false} />
<DashboardsContents
dashboards={dashboards}
onDeleteDashboard={this.handleDeleteDashboard}
onCreateDashboard={this.handleCreateDashboard}
onCloneDashboard={this.handleCloneDashboard}
onExportDashboard={this.handleExportDashboard}
onImportDashboard={this.handleImportDashboard}
notify={notify}
/>
</div>
<>
<Page>
<PageHeader fullWidth={false}>
<PageHeader.Left>
<h1 className="page--title">Dashboards</h1>
</PageHeader.Left>
<PageHeader.Right>
<SearchBar
placeholder="Filter by Name..."
onSearch={this.filterDashboards}
/>
<button
className="btn btn-sm btn-default"
onClick={this.handleToggleOverlay}
>
<span className="icon import" /> Import Dashboard
</button>
<button
className="btn btn-sm btn-primary"
onClick={this.handleCreateDashboard}
>
<span className="icon plus" /> Create Dashboard
</button>
</PageHeader.Right>
</PageHeader>
<PageContents fullWidth={false} scrollable={true}>
<DashboardsContents
dashboards={dashboards}
onDeleteDashboard={this.handleDeleteDashboard}
onCreateDashboard={this.handleCreateDashboard}
onCloneDashboard={this.handleCloneDashboard}
onExportDashboard={this.handleExportDashboard}
notify={notify}
searchTerm={searchTerm}
/>
</PageContents>
</Page>
{this.renderImportOverlay}
</>
)
}
@ -156,6 +204,29 @@ class DashboardsPage extends PureComponent<Props> {
cells: cellsWithDefaultsApplied,
})
}
private filterDashboards = (searchTerm: string): void => {
this.setState({searchTerm})
}
private handleToggleOverlay = (): void => {
this.setState({isImportingDashboard: !this.state.isImportingDashboard})
}
private get renderImportOverlay(): JSX.Element {
const {notify} = this.props
const {isImportingDashboard} = this.state
return (
<OverlayTechnology visible={isImportingDashboard}>
<ImportDashboardOverlay
onDismissOverlay={this.handleToggleOverlay}
onImportDashboard={this.handleImportDashboard}
notify={notify}
/>
</OverlayTechnology>
)
}
}
const mstp = state => {

View File

@ -0,0 +1,36 @@
/*
Page Layout Styles
-----------------------------------------------------------------------------
*/
$nav-size: 54px;
$nav-breakpoint: 660px;
$page-header-size: 80px;
$page-max-width: 1300px;
$page-gutter: 54px;
$page-title-size: 21px;
$page-title-weight: 400;
$sidebar--width: 54px; //delete this later
.chronograf-root {
display: flex;
flex-direction: column;
align-items: center;
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
color: $g17-whisper;
@include gradient-v($g2-kevlar,$g0-obsidian);
}
@media screen and (min-width: $nav-breakpoint) {
.chronograf-root {
flex-direction: row;
}
}
@import 'components/Nav';
@import 'components/Page';

View File

@ -3,18 +3,17 @@
----------------------------------------------
*/
$sidebar--width: 54px;
$sidebar-menu--gutter: 16px;
$nav--gutter: 16px;
$sidebar-menu--bg: $c-pool;
$sidebar-menu--bg-accent: $c-comet;
$nav--bg: $c-pool;
$nav--bg-accent: $c-comet;
.nav {
display: flex;
flex-direction: column;
background-color: $g3-castle;
border-radius: 0 $radius $radius 0;
width: $sidebar--width;
width: $nav-size;
a:link,
a:active,
@ -25,8 +24,8 @@ $sidebar-menu--bg-accent: $c-comet;
}
.nav--item {
width: $sidebar--width;
height: $sidebar--width;
width: $nav-size;
height: $nav-size;
position: relative;
}
@ -44,15 +43,15 @@ $sidebar-menu--bg-accent: $c-comet;
top: 50%;
left: 50%;
transform: translate(-50%,-50%);
font-size: $sidebar--width * 0.4222;
font-size: $nav-size * 0.4222;
}
}
.nav--item-menu {
position: absolute;
top: 0;
left: $sidebar--width;
@include gradient-h($sidebar-menu--bg,$sidebar-menu--bg-accent);
left: $nav-size;
@include gradient-h($nav--bg,$nav--bg-accent);
display: none;
flex-direction: column;
align-items: stretch;
@ -63,11 +62,11 @@ $sidebar-menu--bg-accent: $c-comet;
.nav--item-header {
display: block;
color: $g20-white;
height: $sidebar--width;
line-height: $sidebar--width;
height: $nav-size;
line-height: $nav-size;
font-size: 19px;
font-weight: 400;
padding: 0px $sidebar-menu--gutter;
padding: 0px $nav--gutter;
white-space: nowrap;
}
@ -77,7 +76,7 @@ $sidebar-menu--bg-accent: $c-comet;
.nav--item-icon {
color: $g20-white;
background-color: $sidebar-menu--bg;
background-color: $nav--bg;
}
.nav--item-menu {
@ -100,7 +99,7 @@ $sidebar-menu--bg-accent: $c-comet;
// Active Hover State
.nav--item.active:hover {
.nav--item-icon {
background-color: $sidebar-menu--bg;
background-color: $nav--bg;
text-shadow:
0 0 9px $c-yeti,
0 0 15px $c-hydrogen,

View File

@ -0,0 +1,80 @@
/*
Page Styles
-----------------------------------------------------------------------------
*/
.page {
width: 100%;
flex: 1 0 calc(100% - #{$nav-size});
display: flex;
flex-direction: column;
align-items: stretch;
}
@media screen and (min-width: $nav-breakpoint) {
.page {
width: auto;
height: 100%;
}
}
.page-header {
height: $page-header-size;
padding: 0 $page-gutter;
display: flex;
justify-content: center;
align-items: center;
}
.page-header--container {
display: flex;
align-items: center;
justify-content: space-between;
flex-wrap: nowrap;
width: 100%;
max-width: ($page-max-width - ($page-gutter * 2));
}
.page-header.full-width .page-header--container {
max-width: 100%;
}
.page-header--left,
.page-header--right {
display: flex;
align-items: center;
}
.page-header--left {
flex: 1 0 0;
justify-content: flex-start;
> * {
margin: 0 4px 0 0;
}
}
.page-header--right {
justify-content: flex-end;
> * {
margin: 0 0 0 4px !important;
&:only-child {
margin-right: 0 !important;
}
}
}
.page--title {
letter-spacing: 0;
text-transform: none;
font-size: $page-title-size;
font-weight: $page-title-weight;
margin: 0;
display: inline-block;
@include no-user-select();
cursor: default;
}
.page-contents {
height: calc(100% - #{$page-header-size}) !important;
}

View File

@ -0,0 +1,20 @@
// Libraries
import React, {Component} from 'react'
// Decorators
import {ErrorHandling} from 'src/shared/decorators/errors'
interface Props {
children: JSX.Element | JSX.Element[]
}
@ErrorHandling
class Page extends Component<Props> {
public render() {
const {children} = this.props
return <div className="page">{children}</div>
}
}
export default Page

View File

@ -0,0 +1,47 @@
// Libraries
import React, {Component} from 'react'
// Components
import FancyScrollbar from 'src/shared/components/FancyScrollbar'
// Decorators
import {ErrorHandling} from 'src/shared/decorators/errors'
interface Props {
children: JSX.Element[] | JSX.Element
fullWidth: boolean
scrollable: boolean
}
@ErrorHandling
class PageContents extends Component<Props> {
public render() {
const {scrollable} = this.props
if (scrollable) {
return (
<FancyScrollbar className="page-contents">
{this.children}
</FancyScrollbar>
)
}
return <div className="page-contents">{this.children}</div>
}
private get children(): JSX.Element | JSX.Element[] {
const {fullWidth, children} = this.props
if (fullWidth) {
return children
}
return (
<div className="container-fluid">
<div className="row">{children}</div>
</div>
)
}
}
export default PageContents

View File

@ -0,0 +1,41 @@
// Libraries
import React, {Component} from 'react'
import classnames from 'classnames'
// Components
import PageHeaderLeft from 'src/page_layout/components/PageHeaderLeft'
import PageHeaderRight from 'src/page_layout/components/PageHeaderRight'
// Decorators
import {ErrorHandling} from 'src/shared/decorators/errors'
interface Props {
children: JSX.Element[]
fullWidth: boolean
}
@ErrorHandling
class PageHeader extends Component<Props> {
public static Left = PageHeaderLeft
public static Right = PageHeaderRight
public render() {
const {children} = this.props
return (
<div className={this.className}>
<div className="page-header--container">{children}</div>
</div>
)
}
private get className(): string {
const {fullWidth} = this.props
return classnames('page-header', {
'full-width': fullWidth,
})
}
}
export default PageHeader

View File

@ -0,0 +1,12 @@
// Libraries
import React, {SFC} from 'react'
interface Props {
children: JSX.Element | JSX.Element[]
}
const PageHeaderLeft: SFC<Props> = ({children}) => (
<div className="page-header--left">{children}</div>
)
export default PageHeaderLeft

View File

@ -0,0 +1,12 @@
// Libraries
import React, {SFC} from 'react'
interface Props {
children?: JSX.Element | JSX.Element[]
}
const PageHeaderRight: SFC<Props> = ({children}) => (
<div className="page-header--right">{children}</div>
)
export default PageHeaderRight

View File

@ -1,2 +1,7 @@
import Nav from 'src/page_layout/containers/Nav'
import Page from 'src/page_layout/components/Page'
import PageHeader from 'src/page_layout/components/PageHeader'
import PageContents from 'src/page_layout/components/PageContents'
export {Page, PageHeader, PageContents}
export default Nav

View File

@ -2,12 +2,12 @@
import React, {Component} from 'react'
// Components
import FancyScrollbar from 'src/shared/components/FancyScrollbar'
import PageHeader from 'src/reusable_ui/components/page_layout/PageHeader'
import {Page, PageHeader, PageContents} from 'src/page_layout'
// Types
import {Source, Cell} from 'src/types/v2'
// Decorators
import {ErrorHandling} from 'src/shared/decorators/errors'
interface Props {
@ -21,18 +21,19 @@ interface Props {
class StatusPage extends Component<Props> {
public render() {
return (
<div className="page">
<PageHeader
titleText="Status"
fullWidth={true}
sourceIndicator={true}
/>
<FancyScrollbar className="page-contents">
<Page>
<PageHeader fullWidth={true}>
<PageHeader.Left>
<h1 className="page--title">Status Page</h1>
</PageHeader.Left>
<PageHeader.Right />
</PageHeader>
<PageContents fullWidth={true} scrollable={true}>
<div className="dashboard container-fluid full-width">
{JSON.stringify(this.cells)}
</div>
</FancyScrollbar>
</div>
</PageContents>
</Page>
)
}

View File

@ -22,9 +22,9 @@
@import 'components/code-mirror/theme';
// Layout
@import '../page_layout/components/Nav';
@import '../page_layout/PageLayout';
@import 'layout/page';
@import 'layout/page-header';
@import 'layout/page-subsections';
@import 'layout/overlay-technology';

View File

@ -1,121 +0,0 @@
/*
Page Header
------------------------------------------------------------------------------
*/
$page-header-size: 19px;
$page-header-weight: 400 !important;
.page-header {
height: 80px;
width: 100%;
padding: 0 $page-wrapper-padding;
display: flex;
justify-content: center;
align-items: center;
}
.page-header--container {
display: flex;
align-items: center;
justify-content: space-between;
flex-wrap: nowrap;
width: 100%;
max-width: ($page-wrapper-max-width - $page-wrapper-padding - $page-wrapper-padding);
}
// If specified as a full width page header
.page-header.full-width .page-header--container {
max-width: 100%;
}
// Left, Center, and Right pieces of the page header
.page-header--left,
.page-header--right {
display: flex;
align-items: center;
}
.page-header--left {
flex: 1 0 0;
justify-content: flex-start;
> * {
margin: 0 4px 0 0;
}
}
.page-header--right {
justify-content: flex-end;
> * {
margin: 0 0 0 4px !important;
&:only-child {
margin-right: 0 !important;
}
}
}
// Intended to be a <h1>
.page-header--title {
letter-spacing: 0;
text-transform: none;
font-size: $page-header-size;
font-weight: $page-header-weight;
margin: 0;
display: inline-block;
vertical-align: middle;
@include no-user-select();
cursor: default;
}
// Misc styles to be refactors
.page-header--container.page-header__source-page {
justify-content: center;
}
.page-header__col-md-8 {
width: 100%;
display: flex;
align-items: center;
justify-content: space-between;
}
@media screen and (min-width: 992px) {
/*
NOTE:
Breakpoint and % width are based on the bootstrap grid
If the source form column sizing is ever changed, this
will have to be manually updated
*/
.page-header__col-md-8 {
width: 66.66667%;
}
}
.page-header__dismiss {
width: ($chronograf-page-header-height - 20px);
height: ($chronograf-page-header-height - 20px);
position: relative;
/* Use psuedo elements to render the X */
&:before,
&:after {
content: '';
position: absolute;
top: 50%;
left: 50%;
width: 22px;
height: 2px;
border-radius: 1px;
background-color: $g11-sidewalk;
transition: background-color 0.25s ease;
}
&:before {
transform: translate(-50%,-50%) rotate(45deg);
}
&:after {
transform: translate(-50%,-50%) rotate(-45deg);
}
/* Hover State */
&:hover {
cursor: pointer;
}
&:hover:before,
&:hover:after {
background-color: $g18-cloud;
}
}

View File

@ -6,39 +6,7 @@
$dygraph-legend-z: 500;
$dash-ceo-z: $dygraph-legend-z + 10;
.chronograf-root {
display: flex;
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: 1 0 calc(100% - 50px);
display: flex;
flex-direction: column;
align-items: stretch;
}
.page-contents {
height: calc(100% - 80px) !important;
&:only-child {
top: 0;
height: 100% !important;
}
}
.template-control-bar.show + .page-contents {
top: $chronograf-page-header-height * 2;
height: calc(100% - #{$chronograf-page-header-height * 2}) !important;
}
.container-fluid {
margin: 0 auto;
padding: ($chronograf-page-header-height / 2) $page-wrapper-padding;
@ -55,29 +23,4 @@ $dash-ceo-z: $dygraph-legend-z + 10;
height: 100% !important;
.container-fluid {padding: 8px !important;}
.template-control--manage {display: none;}
}
/*
Dashboard Page
------------------------------------------------------------------------------
Using a flex based layout so that the Template Variable Control Bar can
have any height without disrupting the layout
*/
.page.dashboard-page {
display: flex;
flex-direction: column;
align-items: stretch;
flex-wrap: nowrap;
.page-header {
position: relative;
}
.page-contents {
position: relative !important;
flex: 1 0 0;
height: 100% !important;
top: 0;
}
}

View File

@ -1,6 +1,8 @@
/*
Setting lowermost styles here
Reset
-----------------------------------------------------------------------------
*/
* {
margin: 0;
padding: 0;
@ -11,12 +13,10 @@ html, body {
background-color: $g0-obsidian;
}
body {
display: flex;
padding: 0;
width: 100%;
height: 100%;
position: absolute;
align-items: stretch;
overflow: hidden;
> #react-root {
@ -25,9 +25,5 @@ body {
position: absolute;
top: 0;
left: 0;
.container {
margin-top: 60px;
}
}
}