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;
}
}
192 changes: 192 additions & 0 deletions ui/src/clockface/components/color_picker/ColorPicker.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
// 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 {
selectedHex: string
onSelect: (hex: string, status?: ComponentStatus) => void
}

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

type Props = PassedProps & DefaultProps

interface State {
inputValue: string
status: 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 = {
inputValue: this.props.selectedHex || '',
status: null,
}
}

componentDidUpdate(prevProps) {
Copy link
Contributor

Choose a reason for hiding this comment

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

should prevProps be typed?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

YES

if (prevProps.selectedHex !== this.props.selectedHex) {
this.setState({inputValue: this.props.selectedHex})
Copy link
Contributor

Choose a reason for hiding this comment

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

is it necessary to keep track of the hex value in state? it seems like this will be creating two sources of truth? could all the places that state instead call the onSelect() prop?

}
}

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

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={inputValue}
onChange={this.handleInputChange}
maxLength={7}
onBlur={this.handleInputBlur}
autoFocus={maintainInputFocus}
status={this.inputStatus}
testID={`${testID}--input`}
/>
{this.selectedColor}
<Button
icon={IconFont.Refresh}
shape={ButtonShape.Square}
onClick={this.handleRandomizeColor}
titleText="I'm feeling lucky"
testID={`${testID}--randomize`}
/>
</div>
{this.errorMessage}
</div>
)
}

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

this.setState({inputValue: hex})
onSelect(hex, ComponentStatus.Valid)
}

private get inputStatus(): ComponentStatus {
return this.state.status ? ComponentStatus.Error : ComponentStatus.Valid
}

private handleInputChange = (e: ChangeEvent<HTMLInputElement>) => {
const {onSelect} = 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 inputValue = trimmedValue
.split('')
.filter(char => acceptedChars.includes(char.toLowerCase()))
.join('')

const status = validateHexCode(inputValue)
const validity =
status === null ? ComponentStatus.Valid : ComponentStatus.Error

onSelect(inputValue, validity)
this.setState({inputValue, status})
}

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

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

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

this.setState({inputValue: hex})
onSelect(hex, ComponentStatus.Valid)
}

private get selectedColor(): JSX.Element {
const {inputValue} = this.state

return (
<div
className="color-picker--selected"
style={{backgroundColor: inputValue}}
/>
)
}

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

if (status) {
return (
<div className="color-picker--error" data-testid={`${testID}--error`}>
<Error message={status} />
</div>
)
}
}
}
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