Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Polish Label Color Picker #12464

Merged
merged 12 commits into from
Mar 11, 2019
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ Once completed, `v2.0.0-alpha.5` can be started.
1. [12317](https://github.com/influxdata/influxdb/pull/12317): Redesign Create Scraper workflow
1. [12317](https://github.com/influxdata/influxdb/pull/12317): Show warning in Telegrafs and Scrapers lists when user has no buckets
1. [12384](https://github.com/influxdata/influxdb/pull/12384): Streamline label addition, removal, and creation from the dashboards list
1. [12464](https://github.com/influxdata/influxdb/pull/12464): Improve label color selection

## v2.0.0-alpha.4 [2019-02-21]

Expand Down
100 changes: 100 additions & 0 deletions ui/src/clockface/components/color_picker/ColorPicker.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
@import 'src/style/modules';

/*
Color Picker Widget
------------------------------------------------------------------------------
*/

$color-picker--margin: 0;

.color-picker {
display: flex;
flex-direction: column;
align-items: stretch;
width: 100%;
min-width: 160px;
}

.color-picker--swatches {
margin-bottom: $ix-marg-b;
position: relative;
padding: $color-picker--margin;
border-radius: $radius;
overflow: hidden;

&:hover {
cursor: pointer;
}
}

.color-picker--swatch {
width: 10%;
padding-bottom: 10%;
position: relative;
float: left;
opacity: 1;
transition: opacity 0.25s ease;

> span {
position: absolute;
top: $color-picker--margin;
left: $color-picker--margin;
right: $color-picker--margin;
bottom: $color-picker--margin;
}

&:after {
content: '';
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%) scale(0, 0);
width: 8px;
height: 8px;
border-radius: 50%;
background-color: $g20-white;
opacity: 0;
transition: opacity 0.25s ease, transform 0.25s ease;
box-shadow: 0 0 4px 1px rgba($g0-obsidian, 0.25);
}

&:hover {
&:after {
opacity: 1;
transform: translate(-50%, -50%) scale(1, 1);
}
}
}

.color-picker--form {
display: flex;
align-items: center;
position: relative;
}

.input.color-picker--input {
flex: 1 0 0;
margin-right: $ix-marg-a;

> input {
padding-left: $ix-marg-d;
}
}

.color-picker--selected {
pointer-events: none;
z-index: 2;
position: absolute;
top: 50%;
left: $ix-marg-c;
transform: translate(-50%, -50%);
width: 18px;
height: 18px;
border-radius: 50%;
border: $ix-border solid $g5-pepper;
transition: background-color 0.25s ease, border-color 0.25s ease;

.input:hover + & {
border-color: $g7-graphite;
}
}
184 changes: 184 additions & 0 deletions ui/src/clockface/components/color_picker/ColorPicker.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
// Libraries
import React, {Component, ChangeEvent} from 'react'
import _ from 'lodash'

// Components
import {
Button,
IconFont,
ButtonShape,
ComponentStatus,
} from '@influxdata/clockface'
import {Input} from 'src/clockface'
import Swatch from 'src/clockface/components/color_picker/ColorPickerSwatch'
import Error from 'src/clockface/components/form_layout/FormElementError'

// Constants
import {colors} from 'src/clockface/constants/colors'

// Utils
import {validateHexCode} from 'src/configuration/utils/labels'

// Styles
import 'src/clockface/components/color_picker/ColorPicker.scss'

interface PassedProps {
color: string
onChange: (color: string, status?: ComponentStatus) => void
}

interface DefaultProps {
maintainInputFocus?: boolean
testID?: string
}

type Props = PassedProps & DefaultProps

interface State {
errorMessage: string
}

export default class ColorPicker extends Component<Props, State> {
public static defaultProps: DefaultProps = {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

noice!

maintainInputFocus: false,
testID: 'color-picker',
}

constructor(props: Props) {
super(props)

this.state = {
errorMessage: null,
}
}

render() {
const {maintainInputFocus, testID, color} = this.props

return (
<div className="color-picker" data-testid={testID}>
<div className="color-picker--swatches">
{colors.map(color => (
<Swatch
key={color.name}
hex={color.hex}
name={color.name}
onClick={this.handleSwatchClick}
testID={testID}
/>
))}
</div>
<div className="color-picker--form">
<Input
customClass="color-picker--input"
placeholder="#000000"
value={color}
onChange={this.handleInputChange}
maxLength={7}
onBlur={this.handleInputBlur}
autoFocus={maintainInputFocus}
status={this.inputStatus}
testID={`${testID}--input`}
/>
{this.colorPreview}
<Button
icon={IconFont.Refresh}
shape={ButtonShape.Square}
onClick={this.handleRandomizeColor}
titleText="I'm feeling lucky"
testID={`${testID}--randomize`}
/>
</div>
{this.errorMessage}
</div>
)
}

private get inputStatus(): ComponentStatus {
const {errorMessage} = this.state

return errorMessage ? ComponentStatus.Error : ComponentStatus.Valid
}

private handleSwatchClick = (hex: string): void => {
const {onChange} = this.props

this.setState({errorMessage: null})
onChange(hex, ComponentStatus.Valid)
}

private handleInputChange = (e: ChangeEvent<HTMLInputElement>) => {
const {onChange} = this.props
const acceptedChars = [
'#',
'a',
'b',
'c',
'd',
'e',
'f',
'0',
'1',
'2',
'3',
'4',
'5',
'6',
'7',
'8',
'9',
]

const trimmedValue = e.target.value.trim()
const cleanedValue = trimmedValue
.split('')
.filter(char => acceptedChars.includes(char.toLowerCase()))
.join('')

const errorMessage = validateHexCode(cleanedValue)
const status = errorMessage ? ComponentStatus.Error : ComponentStatus.Valid

this.setState({errorMessage})
onChange(cleanedValue, status)
}

private handleInputBlur = (e: ChangeEvent<HTMLInputElement>) => {
const {maintainInputFocus} = this.props

if (maintainInputFocus) {
e.target.focus()
}
}

private handleRandomizeColor = (): void => {
const {onChange} = this.props
const {hex} = _.sample(colors)

this.setState({errorMessage: null})
onChange(hex, ComponentStatus.Valid)
}

private get errorMessage(): JSX.Element {
const {testID} = this.props
const {errorMessage} = this.state

if (errorMessage) {
return (
<div className="color-picker--error" data-testid={`${testID}--error`}>
<Error message={errorMessage} />
</div>
)
}
}

private get colorPreview(): JSX.Element {
const {color} = this.props

return (
<div
className="color-picker--selected"
style={{backgroundColor: color}}
/>
)
}
}
29 changes: 29 additions & 0 deletions ui/src/clockface/components/color_picker/ColorPickerSwatch.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// Libraries
import React, {Component} from 'react'

interface Props {
name: string
hex: string
onClick: (hex: string) => void
testID: string
}

export default class ColorPickerSwatch extends Component<Props> {
render() {
const {name, hex, testID} = this.props
return (
<div
className="color-picker--swatch"
title={name}
onClick={this.handleClick}
data-testid={`${testID}--swatch`}
>
<span style={{backgroundColor: hex}} />
</div>
)
}

private handleClick = (): void => {
this.props.onClick(this.props.hex)
}
}
Loading