Skip to content

Commit

Permalink
Polish Label Color Picker (#12464)
Browse files Browse the repository at this point in the history
* WIP introduce color picker component

* revert this commit

* Polish appearance of color selector

* Allow color picker to conform to its parent's size

* Replace label color dropdown with clockface color selector

* Add testID props to color picker and associated components

* Update changelog

* Fix snapshot test

* Remove temporary debugging code for color picker

* Refactor color picker to be a controlled component

* Update snapshots
  • Loading branch information
alexpaxton authored Mar 11, 2019
1 parent af9d4bf commit 1c72fb3
Show file tree
Hide file tree
Showing 13 changed files with 588 additions and 255 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,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 = {
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

0 comments on commit 1c72fb3

Please sign in to comment.