Merge pull request #11800 from influxdata/refactor/organization-tabs

refactor(ui): Separate tab navigation from tabs in organization view
pull/11795/head
Iris Scholten 2019-02-11 16:27:21 -08:00 committed by GitHub
commit 2a45302288
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 325 additions and 24 deletions

View File

@ -129,7 +129,8 @@
------------------------------------------------------------------------------
*/
.tabbed-page-content {
.tabbed-page-content,
.tabs--contents {
.index-list--cell {
background-color: $g4-onyx;
}

View File

@ -0,0 +1,19 @@
// Libraries
import React, {SFC} from 'react'
import classnames from 'classnames'
import {Link} from 'react-router'
interface Props {
id: string
title: string
active: boolean
url: string
}
const NavigationTab: SFC<Props> = ({title, active, url}) => (
<Link className={classnames('tabs--tab', {active})} to={url}>
{title}
</Link>
)
export default NavigationTab

View File

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

View File

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

View File

@ -0,0 +1,93 @@
/*
Styles for Tabs
----------------------------------------------------------------------------
*/
@import 'src/style/modules';
.tabs {
display: flex;
align-items: stretch;
background-color: rgba($g3-castle, 0.25);
border-radius: $radius;
}
.tabs--nav {
display: flex;
flex-direction: column;
align-items: stretch;
flex: 0 1 0;
padding: $ix-marg-d;
padding-right: 0;
}
.tabs--nav-tabs {
@include no-user-select();
display: flex;
flex-direction: column;
align-items: stretch;
}
.tabs--tab {
border-radius: $radius 0 0 $radius;
font-size: $form-lg-font;
height: $nav-size;
line-height: $nav-size;
padding: 0 $form-lg-font;
color: $g11-sidewalk;
white-space: nowrap;
transition: background-color 0.25s ease, color 0.25s ease;
&:hover {
background-color: rgba($g3-castle, 0.5);
color: $g16-pearl;
cursor: pointer;
}
&.active {
background-color: $g3-castle;
color: $g18-cloud;
}
}
.page{
a:link.tabs--tab,
a:visited.tabs--tab {
border-radius: $radius 0 0 $radius;
font-size: $form-lg-font;
font-weight: 400;
height: $nav-size;
line-height: $nav-size;
padding: 0 $form-lg-font;
color: $g11-sidewalk;
white-space: nowrap;
transition: background-color 0.25s ease, color 0.25s ease;
&:hover {
background-color: rgba($g3-castle, 0.5);
color: $g16-pearl;
cursor: pointer;
}
&.active {
background-color: $g3-castle;
color: $g18-cloud;
}
}
}
.tabs--contents {
flex: 1 0 0;
background-color: $g3-castle;
border-radius: $radius;
min-height: 500px;
padding: $ix-marg-d;
}
.tabs--contents-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: $ix-marg-d;
}

View File

@ -0,0 +1,30 @@
// Libraries
import React, {Component} from 'react'
// Styles
import 'src/clockface/components/tabs/Tabs.scss'
// Components
import TabContents from 'src/clockface/components/tabs/TabContents'
import TabsNav from 'src/clockface/components/tabs/TabsNav'
import NavigationTab from 'src/clockface/components/tabs/NavigationTab'
import TabContentsHeader from 'src/clockface/components/tabs/TabContentsHeader'
interface Props {
children: JSX.Element[]
}
class Tabs extends Component<Props> {
public static TabContents = TabContents
public static Nav = TabsNav
public static Tab = NavigationTab
public static TabContentsHeader = TabContentsHeader
public render() {
const {children} = this.props
return <div className="tabs">{children}</div>
}
}
export default Tabs

View File

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

View File

@ -37,6 +37,7 @@ import Grid from 'src/clockface/components/grid_layout/Grid'
import QuestionMarkTooltip from 'src/clockface/components/tooltips/QuestionMarkTooltip'
import SpinnerContainer from 'src/clockface/components/spinners/SpinnerContainer'
import TechnoSpinner from 'src/clockface/components/spinners/TechnoSpinner'
import Tabs from './components/tabs/Tabs'
// Import Types
import {
@ -107,6 +108,7 @@ export {
Sort,
SpinnerContainer,
Stack,
Tabs,
TechnoSpinner,
WizardFullScreen,
WizardProgressHeader,

View File

@ -3,7 +3,6 @@ import React, {PureComponent, ChangeEvent} from 'react'
import _ from 'lodash'
// Components
import TabbedPageHeader from 'src/shared/components/tabbed_page/TabbedPageHeader'
import FilterList from 'src/shared/components/Filter'
import BucketList from 'src/organizations/components/BucketList'
import {PrettyBucket} from 'src/organizations/components/BucketRow'
@ -16,6 +15,7 @@ import {
OverlayTechnology,
ComponentSize,
EmptyState,
Tabs,
} from 'src/clockface'
// Actions
@ -67,7 +67,7 @@ export default class Buckets extends PureComponent<Props, State> {
return (
<>
<TabbedPageHeader>
<Tabs.TabContentsHeader>
<Input
icon={IconFont.Search}
placeholder="Filter Buckets..."
@ -82,7 +82,7 @@ export default class Buckets extends PureComponent<Props, State> {
color={ComponentColor.Primary}
onClick={this.handleOpenModal}
/>
</TabbedPageHeader>
</Tabs.TabContentsHeader>
<FilterList<PrettyBucket>
searchTerm={searchTerm}
searchKeys={['name', 'ruleString']}

View File

@ -4,7 +4,6 @@ import React, {PureComponent, ChangeEvent} from 'react'
import {connect} from 'react-redux'
// Components
import TabbedPageHeader from 'src/shared/components/tabbed_page/TabbedPageHeader'
import CollectorList from 'src/organizations/components/CollectorList'
import TelegrafExplainer from 'src/organizations/components/TelegrafExplainer'
import TelegrafInstructionsOverlay from 'src/organizations/components/TelegrafInstructionsOverlay'
@ -19,6 +18,7 @@ import {
Columns,
Input,
InputType,
Tabs,
} from 'src/clockface'
import CollectorsWizard from 'src/dataLoaders/components/collectorsWizard/CollectorsWizard'
import FilterList from 'src/shared/components/Filter'
@ -90,7 +90,7 @@ export class Collectors extends PureComponent<Props, State> {
return (
<>
<TabbedPageHeader>
<Tabs.TabContentsHeader>
<Input
icon={IconFont.Search}
placeholder="Filter telegraf configs by bucket..."
@ -101,7 +101,7 @@ export class Collectors extends PureComponent<Props, State> {
onBlur={this.handleFilterBlur}
/>
{this.createButton}
</TabbedPageHeader>
</Tabs.TabContentsHeader>
<Grid>
<Grid.Row>
<Grid.Column widthSM={Columns.Twelve}>

View File

@ -3,10 +3,9 @@ import React, {PureComponent, ChangeEvent} from 'react'
import _ from 'lodash'
// Components
import {ComponentSize, EmptyState, IconFont, Input} from 'src/clockface'
import {ComponentSize, EmptyState, IconFont, Input, Tabs} from 'src/clockface'
import MemberList from 'src/organizations/components/MemberList'
import FilterList from 'src/shared/components/Filter'
import TabbedPageHeader from 'src/shared/components/tabbed_page/TabbedPageHeader'
// Types
import {ResourceOwner} from '@influxdata/influx'
@ -32,7 +31,7 @@ export default class Members extends PureComponent<Props, State> {
return (
<>
<TabbedPageHeader>
<Tabs.TabContentsHeader>
<Input
icon={IconFont.Search}
placeholder="Filter tasks..."
@ -41,7 +40,7 @@ export default class Members extends PureComponent<Props, State> {
onChange={this.handleFilterChange}
onBlur={this.handleFilterChange}
/>
</TabbedPageHeader>
</Tabs.TabContentsHeader>
<FilterList<ResourceOwner>
list={this.props.members}
searchKeys={['name']}

View File

@ -13,10 +13,10 @@ import {
ComponentColor,
IconFont,
Input,
Tabs,
} from 'src/clockface'
import ImportDashboardOverlay from 'src/dashboards/components/ImportDashboardOverlay'
import EditLabelsOverlay from 'src/shared/components/EditLabelsOverlay'
import TabbedPageHeader from 'src/shared/components/tabbed_page/TabbedPageHeader'
// Utils
import {getDeep} from 'src/utils/wrappers'
@ -116,7 +116,7 @@ class OrgDashboardIndex extends PureComponent<Props, State> {
return (
<>
<TabbedPageHeader>
<Tabs.TabContentsHeader>
<Input
icon={IconFont.Search}
placeholder="Filter tasks..."
@ -132,7 +132,7 @@ class OrgDashboardIndex extends PureComponent<Props, State> {
text="Create Dashboard"
titleText="Create a new dashboard"
/>
</TabbedPageHeader>
</Tabs.TabContentsHeader>
<DashboardsIndexContents
dashboards={dashboards}
orgs={orgs}

View File

@ -0,0 +1,66 @@
// Libraries
import React, {PureComponent} from 'react'
import _ from 'lodash'
// Components
import {Tabs} from 'src/clockface'
// Decorators
import {ErrorHandling} from 'src/shared/decorators/errors'
interface Props {
tab: string
orgID: string
}
@ErrorHandling
class OrganizationNavigation extends PureComponent<Props> {
public render() {
const {tab, orgID} = this.props
const route = `/organizations/${orgID}`
return (
<Tabs.Nav>
<Tabs.Tab
title={'Members'}
id={'members'}
url={`${route}/members_tab`}
active={'members_tab' === tab}
/>
<Tabs.Tab
title={'Buckets'}
id={'buckets'}
url={`${route}/buckets_tab`}
active={'buckets_tab' === tab}
/>
<Tabs.Tab
title={'Dashboards'}
id={'dashboards'}
url={`${route}/dashboards_tab`}
active={'dashboards_tab' === tab}
/>
<Tabs.Tab
title={'Tasks'}
id={'tasks'}
url={`${route}/tasks_tab`}
active={'tasks_tab' === tab}
/>
<Tabs.Tab
title={'Telegraf'}
id={'telegrafs'}
url={`${route}/telegrafs_tab`}
active={'telegrafs_tab' === tab}
/>
<Tabs.Tab
title={'Scrapers'}
id={'scrapers'}
url={`${route}/scrapers_tab`}
active={'scrapers_tab' === tab}
/>
</Tabs.Nav>
)
}
}
export default OrganizationNavigation

View File

@ -0,0 +1,53 @@
// Libraries
import React, {Component, ReactElement, ReactNode} from 'react'
import {withRouter, WithRouterProps} from 'react-router'
// Components
import OrganizationNavigation from 'src/organizations/components/OrganizationNavigation'
import {Tabs} from 'src/clockface'
// Decorators
import {ErrorHandling} from 'src/shared/decorators/errors'
interface OwnProps {
name: string
avatar?: string
description?: string
children: ReactNode[] | ReactNode
activeTabUrl: string
orgID: string
}
type Props = OwnProps & WithRouterProps
@ErrorHandling
class OrganizationTabs extends Component<Props> {
constructor(props) {
super(props)
}
public render() {
return (
<Tabs>
<OrganizationNavigation
tab={this.props.activeTabUrl}
orgID={this.props.orgID}
/>
<Tabs.TabContents>{this.activeSectionComponent}</Tabs.TabContents>
</Tabs>
)
}
private get activeSectionComponent(): JSX.Element[] {
const {children, activeTabUrl} = this.props
// Using ReactElement as type to ensure children have props
return React.Children.map(children, (child: ReactElement<any>) => {
if (child.props.url === activeTabUrl) {
return child.props.children
}
})
}
}
export default withRouter<OwnProps>(OrganizationTabs)

View File

@ -6,7 +6,6 @@ import React, {PureComponent, ChangeEvent} from 'react'
import {client} from 'src/utils/api'
// Components
import TabbedPageHeader from 'src/shared/components/tabbed_page/TabbedPageHeader'
import ScraperList from 'src/organizations/components/ScraperList'
import {
Button,
@ -16,6 +15,7 @@ import {
EmptyState,
Input,
InputType,
Tabs,
} from 'src/clockface'
import DataLoadersWizard from 'src/dataLoaders/components/DataLoadersWizard'
@ -55,7 +55,7 @@ export default class Scrapers extends PureComponent<Props, State> {
return (
<>
<TabbedPageHeader>
<Tabs.TabContentsHeader>
<Input
icon={IconFont.Search}
placeholder="Filter scrapers by bucket..."
@ -66,7 +66,7 @@ export default class Scrapers extends PureComponent<Props, State> {
onBlur={this.handleFilterBlur}
/>
{this.createScraperButton}
</TabbedPageHeader>
</Tabs.TabContentsHeader>
<ScraperList
scrapers={this.configurations}
emptyState={this.emptyState}

View File

@ -27,7 +27,6 @@ import * as notifyActions from 'src/shared/actions/notifications'
// Components
import {Page} from 'src/pageLayout'
import {SpinnerContainer, TechnoSpinner} from 'src/clockface'
import TabbedPage from 'src/shared/components/tabbed_page/TabbedPage'
import TabbedPageSection from 'src/shared/components/tabbed_page/TabbedPageSection'
import Members from 'src/organizations/components/Members'
import Buckets from 'src/organizations/components/Buckets'
@ -36,6 +35,8 @@ import Collectors from 'src/organizations/components/Collectors'
import Scrapers from 'src/organizations/components/Scrapers'
import GetOrgResources from 'src/organizations/components/GetOrgResources'
import RenamablePageTitle from 'src/pageLayout/components/RenamablePageTitle'
import OrgDashboardIndex from 'src/organizations/components/OrgDashboardIndex'
import OrganizationTabs from 'src/organizations/components/OrganizationTabs'
// Types
import {AppState, Dashboard} from 'src/types/v2'
@ -51,7 +52,6 @@ import * as NotificationsActions from 'src/types/actions/notifications'
// Decorators
import {ErrorHandling} from 'src/shared/decorators/errors'
import {Task} from 'src/tasks/containers/TasksPage'
import OrgDashboardIndex from '../components/OrgDashboardIndex'
interface StateProps {
org: Organization
@ -95,10 +95,10 @@ class OrganizationView extends PureComponent<Props> {
</Page.Header>
<Page.Contents fullWidth={false} scrollable={true}>
<div className="col-xs-12">
<TabbedPage
<OrganizationTabs
name={org.name}
parentUrl={`/organizations/${org.id}`}
activeTabUrl={params.tab}
orgID={org.id}
>
<TabbedPageSection
id="org-view-tab--members"
@ -191,8 +191,8 @@ class OrganizationView extends PureComponent<Props> {
</GetOrgResources>
</TabbedPageSection>
<TabbedPageSection
id="org-view-tab--collectors"
url="collectors_tab"
id="org-view-tab--telegrafs"
url="telegrafs_tab"
title="Telegraf"
>
<GetOrgResources<Telegraf[]>
@ -265,7 +265,7 @@ class OrganizationView extends PureComponent<Props> {
}}
</GetOrgResources>
</TabbedPageSection>
</TabbedPage>
</OrganizationTabs>
</div>
</Page.Contents>
</Page>