diff --git a/ui/src/authorizations/components/BucketsSelector.tsx b/ui/src/authorizations/components/BucketsSelector.tsx index 9b54e08097..bd2e8f7908 100644 --- a/ui/src/authorizations/components/BucketsSelector.tsx +++ b/ui/src/authorizations/components/BucketsSelector.tsx @@ -2,7 +2,7 @@ import React, {PureComponent} from 'react' // Components -import BuilderCard from 'src/timeMachine/components/builderCard/BuilderCard' +import SelectorList from 'src/shared/components/selectorList/SelectorList' import BucketsTabBody from 'src/authorizations/components/BucketsTabBody' import {BucketTab} from 'src/authorizations/utils/permissions' import BucketsTabSelector from 'src/authorizations/components/BucketsTabSelector' @@ -78,8 +78,8 @@ class BucketsSelector extends PureComponent { ) case BucketTab.Scoped: return ( - - + +
{ />
-
+ -
+ ) } } diff --git a/ui/src/shared/components/selectorList/SelectorList.scss b/ui/src/shared/components/selectorList/SelectorList.scss new file mode 100644 index 0000000000..3f78179baf --- /dev/null +++ b/ui/src/shared/components/selectorList/SelectorList.scss @@ -0,0 +1,170 @@ +$selector-list--width: 228px; +$selector-list--margin: $ix-marg-b; +$selector-list--header-height: 30px; +$selector-list--header-margin: $ix-marg-b + $ix-border; + +.selector-list--list { + display: flex; + flex: 1 1 0; + flex-wrap: nowrap; + height: 100%; + + > * { + margin-left: $selector-list--margin / 2; + margin-right: $selector-list--margin / 2; + + &:first-child { + margin-left: 0; + } + + &:last-child { + margin-right: 0; + } + } +} + +.selector-list { + border-radius: $radius; + background-color: $g4-onyx; + overflow: hidden; + display: flex; + flex-direction: column; + align-items: stretch; + position: relative; + flex: 0 0 $selector-list--width; +} + +.selector-list--header { + flex: 0 0 $selector-list--header-height; + background-color: $g5-pepper; + display: flex; + align-items: center; + justify-content: space-between; +} + +.selector-list--title { + display: inline-block; + margin: 0 $selector-list--header-margin; + font-weight: 900; + font-size: 13px; + text-transform: uppercase; + letter-spacing: 0; + color: $g11-sidewalk; +} + +.selector-list--delete { + flex: 0 0 $selector-list--header-height; + height: $selector-list--header-height; + position: relative; + + &:before, + &:after { + content: ''; + position: absolute; + top: 50%; + left: 50%; + width: 14px; + height: 2px; + border-radius: 1px; + background-color: $g9-mountain; + transition: background-color 0.25s ease; + } + + &:before { + transform: translate(-50%,-50%) rotate(45deg); + } + &:after { + transform: translate(-50%,-50%) rotate(-45deg); + } + + &:hover { + cursor: pointer; + + &:before, + &:after { + background-color: $c-dreamsicle; + } + } +} + +.selector-list--hamburger { + flex: 0 0 ($selector-list--header-height / 2); + height: $ix-border; + border-radius: $ix-border / 2; + background-color: $g7-graphite; + margin-left: $selector-list--header-margin; + position: relative; + transition: background-color 0.25s ease; + + &:before, + &:after { + content: ''; + position: absolute; + top: 0; + width: 100%; + height: 100%; + background-color: $g7-graphite; + border-radius: $ix-border / 2; + transition: background-color 0.25s ease; + } + + &:before { + transform: translateY(-200%); + } + + &:after { + transform: translateY(200%); + } +} + +.selector-list--draggable { + display: flex; + align-items: center; + height: $selector-list--header-height; + flex: 1 0 0; + + .selector-list--title { + margin-left: $ix-marg-b; + transition: color 0.25s ease; + } + + &:hover { + cursor: grab; + .selector-list--title { + color: $g15-platinum; + } + .selector-list--hamburger, + .selector-list--hamburger:before, + .selector-list--hamburger:after { + background-color: $g15-platinum; + } + } +} + +.selector-list--menu { + border-bottom: $ix-border solid $g5-pepper; + padding: $ix-marg-b; +} + +.selector-list--body { + flex: 1 1 0; + overflow: hidden; +} + +.selector-list--empty { + display: flex; + flex-direction: column; + text-align: center; + align-items: center; + justify-content: center; + align-content: center; + color: $g9-mountain; + + > * { + margin: $ix-marg-a 0; + } +} + +.selector-list--contents { + padding: $ix-marg-b; +} \ No newline at end of file diff --git a/ui/src/shared/components/selectorList/SelectorList.tsx b/ui/src/shared/components/selectorList/SelectorList.tsx new file mode 100644 index 0000000000..a11f4b4bd9 --- /dev/null +++ b/ui/src/shared/components/selectorList/SelectorList.tsx @@ -0,0 +1,37 @@ +// Libraries +import React, {PureComponent} from 'react' +import classnames from 'classnames' + +// Components +import SelectorListHeader from 'src/shared/components/selectorList/SelectorListHeader' +import SelectorListMenu from 'src/shared/components/selectorList/SelectorListMenu' +import SelectorListBody from 'src/shared/components/selectorList/SelectorListBody' +import SelectorListEmpty from 'src/shared/components/selectorList/SelectorListEmpty' + +interface Props { + testID: string + className?: string +} + +export default class SelectorList extends PureComponent { + public static Header = SelectorListHeader + public static Menu = SelectorListMenu + public static Body = SelectorListBody + public static Empty = SelectorListEmpty + + public static defaultProps = { + testID: 'selector-list', + } + + public render() { + const {children, testID, className} = this.props + + const classname = classnames('selector-list', {[`${className}`]: className}) + + return ( +
+ {children} +
+ ) + } +} diff --git a/ui/src/shared/components/selectorList/SelectorListBody.tsx b/ui/src/shared/components/selectorList/SelectorListBody.tsx new file mode 100644 index 0000000000..b52a59e0d8 --- /dev/null +++ b/ui/src/shared/components/selectorList/SelectorListBody.tsx @@ -0,0 +1,51 @@ +// Libraries +import React, {PureComponent, ReactNode} from 'react' + +// Components +import {DapperScrollbars} from '@influxdata/clockface' + +interface Props { + scrollable: boolean + addPadding: boolean + testID: string +} + +export default class SelectorListBody extends PureComponent { + public static defaultProps = { + scrollable: true, + addPadding: true, + testID: 'selector-list--body', + } + + public render() { + const {scrollable, testID} = this.props + + if (scrollable) { + return ( + + {this.children} + + ) + } + + return ( +
+ {this.children} +
+ ) + } + + private get children(): JSX.Element | ReactNode { + const {addPadding, children} = this.props + + if (addPadding) { + return
{children}
+ } + + return children + } +} diff --git a/ui/src/shared/components/selectorList/SelectorListEmpty.tsx b/ui/src/shared/components/selectorList/SelectorListEmpty.tsx new file mode 100644 index 0000000000..8008e919ba --- /dev/null +++ b/ui/src/shared/components/selectorList/SelectorListEmpty.tsx @@ -0,0 +1,25 @@ +// Libraries +import React, {PureComponent} from 'react' + +interface Props { + testID: string +} + +export default class SelectorListEmpty extends PureComponent { + public static defaultProps = { + testID: 'selector-list--empty', + } + + public render() { + const {testID, children} = this.props + + return ( +
+ {children} +
+ ) + } +} diff --git a/ui/src/shared/components/selectorList/SelectorListHeader.tsx b/ui/src/shared/components/selectorList/SelectorListHeader.tsx new file mode 100644 index 0000000000..56e1acfea7 --- /dev/null +++ b/ui/src/shared/components/selectorList/SelectorListHeader.tsx @@ -0,0 +1,50 @@ +// Libraries +import React, {PureComponent} from 'react' + +interface Props { + title: string + testID: string + onDelete?: () => void + onDragStart?: () => void +} + +export default class SelectorListHeader extends PureComponent { + public static defaultProps = { + testID: 'selector-list--header', + } + + public render() { + const {testID, children} = this.props + + return ( +
+ {this.title} + {children} + {this.deleteButton} +
+ ) + } + + private get title(): JSX.Element { + const {onDragStart, title} = this.props + + if (onDragStart) { + return ( +
+
+

{title}

+
+ ) + } + + return

{title}

+ } + + private get deleteButton(): JSX.Element | undefined { + const {onDelete} = this.props + + if (onDelete) { + return
+ } + } +} diff --git a/ui/src/shared/components/selectorList/SelectorListMenu.tsx b/ui/src/shared/components/selectorList/SelectorListMenu.tsx new file mode 100644 index 0000000000..a8816c3dd4 --- /dev/null +++ b/ui/src/shared/components/selectorList/SelectorListMenu.tsx @@ -0,0 +1,21 @@ +// Libraries +import React, {PureComponent} from 'react' + +interface Props { + testID: string +} + +export default class SelectorListMenu extends PureComponent { + public static defaultProps = { + testID: 'selector-list--menu', + } + + public render() { + const {testID, children} = this.props + return ( +
+ {children} +
+ ) + } +} diff --git a/ui/src/style/chronograf.scss b/ui/src/style/chronograf.scss index a49a9b47c8..9cc876d5a1 100644 --- a/ui/src/style/chronograf.scss +++ b/ui/src/style/chronograf.scss @@ -52,6 +52,7 @@ @import 'src/shared/components/SearchableDropdown.scss'; @import 'src/shared/components/BoxTooltip.scss'; @import 'src/shared/components/dropdown_auto_refresh/AutoRefreshDropdown.scss'; +@import 'src/shared/components/selectorList/SelectorList.scss'; @import 'src/shared/components/CodeSnippet.scss'; @import 'src/buckets/components/Retention.scss'; @import 'src/telegrafs/components/TelegrafConfigOverlay.scss';