-
Notifications
You must be signed in to change notification settings - Fork 3.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* 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
Showing
13 changed files
with
588 additions
and
255 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
100 changes: 100 additions & 0 deletions
100
ui/src/clockface/components/color_picker/ColorPicker.scss
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
184
ui/src/clockface/components/color_picker/ColorPicker.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
29
ui/src/clockface/components/color_picker/ColorPickerSwatch.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} | ||
} |
Oops, something went wrong.