feat: Port new color picker from Journals branch

This commit is contained in:
Tom Moor 2019-08-23 22:24:06 -07:00
parent 8e76c4e8f1
commit 8f2d31876d
5 changed files with 129 additions and 167 deletions

View File

@ -1,11 +1,11 @@
// @flow
import * as React from 'react';
import { observable, computed, action } from 'mobx';
import { observable } from 'mobx';
import { observer } from 'mobx-react';
import { TwitterPicker } from 'react-color';
import styled from 'styled-components';
import Flex from 'shared/components/Flex';
import { LabelText, Outline } from 'components/Input';
import { validateColorHex } from 'shared/utils/color';
import Fade from 'components/Fade';
import { LabelText } from 'components/Input';
const colors = [
'#4E5C6E',
@ -15,175 +15,92 @@ const colors = [
'#FC2D2D',
'#FFE100',
'#14CF9F',
'#00D084',
'#EE84F0',
'#2F362F',
];
type Props = {
onSelect: (color: string) => void,
onChange: (color: string) => void,
value?: string,
};
@observer
class ColorPicker extends React.Component<Props> {
@observable selectedColor: string = colors[0];
@observable customColorValue: string = '';
@observable customColorSelected: boolean;
componentWillMount() {
const { value } = this.props;
if (value && colors.includes(value)) {
this.selectedColor = value;
} else if (value) {
this.customColorSelected = true;
this.customColorValue = value.replace('#', '');
}
}
@observable isOpen: boolean = false;
node: ?HTMLElement;
componentDidMount() {
this.fireCallback();
window.addEventListener('click', this.handleClickOutside);
}
fireCallback = () => {
this.props.onSelect(
this.customColorSelected ? this.customColor : this.selectedColor
);
};
@computed
get customColor(): string {
return this.customColorValue &&
validateColorHex(`#${this.customColorValue}`)
? `#${this.customColorValue}`
: colors[0];
componentWillUnmount() {
window.removeEventListener('click', this.handleClickOutside);
}
@action
setColor = (color: string) => {
this.selectedColor = color;
this.customColorSelected = false;
this.fireCallback();
handleClose = () => {
this.isOpen = false;
};
@action
focusOnCustomColor = (event: SyntheticEvent<>) => {
this.selectedColor = '';
this.customColorSelected = true;
this.fireCallback();
handleOpen = () => {
this.isOpen = true;
};
@action
setCustomColor = (event: SyntheticEvent<HTMLElement>) => {
let target = event.target;
if (target instanceof HTMLInputElement) {
const color = target.value;
this.customColorValue = color.replace('#', '');
this.fireCallback();
handleClickOutside = (ev: SyntheticMouseEvent<>) => {
// $FlowFixMe
if (ev.target && this.node && this.node.contains(ev.target)) {
return;
}
this.handleClose();
};
render() {
return (
<Flex column>
<LabelText>Color</LabelText>
<StyledOutline justify="space-between">
<Flex>
{colors.map(color => (
<Swatch
key={color}
color={color}
active={
color === this.selectedColor && !this.customColorSelected
}
onClick={() => this.setColor(color)}
<Wrapper ref={ref => (this.node = ref)}>
<label>
<LabelText>Color</LabelText>
</label>
<Swatch
role="button"
onClick={this.isOpen ? this.handleClose : this.handleOpen}
color={this.props.value}
/>
<Floating>
{this.isOpen && (
<Fade>
<TwitterPicker
colors={colors}
color={this.props.value}
onChange={color => this.props.onChange(color.hex)}
triangle="top-right"
/>
))}
</Flex>
<Flex justify="flex-end">
<strong>Custom color:</strong>
<HexHash>#</HexHash>
<CustomColorInput
placeholder="FFFFFF"
onFocus={this.focusOnCustomColor}
onChange={this.setCustomColor}
value={this.customColorValue}
maxLength={6}
/>
<Swatch
color={this.customColor}
active={this.customColorSelected}
/>
</Flex>
</StyledOutline>
</Flex>
</Fade>
)}
</Floating>
</Wrapper>
);
}
}
type SwatchProps = {
onClick?: () => void,
color?: string,
active?: boolean,
};
const Swatch = ({ onClick, ...props }: SwatchProps) => (
<SwatchOutset onClick={onClick} {...props}>
<SwatchInset {...props} />
</SwatchOutset>
);
const SwatchOutset = styled(Flex)`
width: 24px;
height: 24px;
margin-right: 5px;
border: 2px solid ${({ active, color }) => (active ? color : 'transparent')};
border-radius: 2px;
background: ${({ color }) => color};
${({ onClick }) => onClick && `cursor: pointer;`} &:last-child {
margin-right: 0;
}
const Wrapper = styled('div')`
display: inline-block;
position: relative;
`;
const Floating = styled('div')`
position: absolute;
top: 60px;
right: 0;
z-index: 1;
`;
const SwatchInset = styled(Flex)`
width: 20px;
height: 20px;
const Swatch = styled('div')`
display: inline-block;
width: 40px;
height: 36px;
border: 1px solid ${({ active, color }) => (active ? 'white' : 'transparent')};
border-radius: 2px;
border-radius: 4px;
background: ${({ color }) => color};
`;
const StyledOutline = styled(Outline)`
padding: 5px;
flex-wrap: wrap;
strong {
font-weight: 500;
}
`;
const HexHash = styled.div`
margin-left: 12px;
padding-bottom: 0;
font-weight: 500;
user-select: none;
`;
const CustomColorInput = styled.input`
border: 0;
flex: 1;
width: 65px;
margin-right: 12px;
padding-bottom: 0;
outline: none;
background: none;
font-family: ${props => props.theme.monospaceFontFamily};
font-weight: 500;
&::placeholder {
color: ${props => props.theme.slate};
font-family: ${props => props.theme.monospaceFontFamily};
font-weight: 500;
}
`;
export default ColorPicker;

View File

@ -1,6 +1,6 @@
// @flow
import * as React from 'react';
import { withRouter, type RouterHistory } from 'react-router-dom';
import { withRouter } from 'react-router-dom';
import { observable } from 'mobx';
import { inject, observer } from 'mobx-react';
import Input from 'components/Input';
@ -13,7 +13,7 @@ import Collection from 'models/Collection';
import UiStore from 'stores/UiStore';
type Props = {
history: RouterHistory,
history: Object,
collection: Collection,
ui: UiStore,
onSubmit: () => void,
@ -23,15 +23,16 @@ type Props = {
class CollectionEdit extends React.Component<Props> {
@observable name: string;
@observable description: string = '';
@observable color: string = '';
@observable color: string = '#4E5C6E';
@observable isSaving: boolean;
componentWillMount() {
this.name = this.props.collection.name;
this.description = this.props.collection.description;
this.color = this.props.collection.color;
}
handleSubmit = async (ev: SyntheticEvent<>) => {
handleSubmit = async (ev: SyntheticEvent<*>) => {
ev.preventDefault();
this.isSaving = true;
@ -66,17 +67,21 @@ class CollectionEdit extends React.Component<Props> {
<Flex column>
<form onSubmit={this.handleSubmit}>
<HelpText>
You can edit a collections name and other details at any time,
however doing so often might confuse your team mates.
You can edit the name and other details at any time, however doing
so often might confuse your team mates.
</HelpText>
<Input
type="text"
label="Name"
onChange={this.handleNameChange}
value={this.name}
required
autoFocus
/>
<Flex>
<Input
type="text"
label="Name"
onChange={this.handleNameChange}
value={this.name}
required
autoFocus
flex
/>
&nbsp;<ColorPicker onChange={this.handleColor} value={this.color} />
</Flex>
<InputRich
id={this.props.collection.id}
label="Description"
@ -86,10 +91,6 @@ class CollectionEdit extends React.Component<Props> {
minHeight={68}
maxHeight={200}
/>
<ColorPicker
onSelect={this.handleColor}
value={this.props.collection.color}
/>
<Button
type="submit"
disabled={this.isSaving || !this.props.collection.name}

View File

@ -9,6 +9,7 @@ import Input from 'components/Input';
import InputRich from 'components/InputRich';
import ColorPicker from 'components/ColorPicker';
import HelpText from 'components/HelpText';
import Flex from 'shared/components/Flex';
import Collection from 'models/Collection';
import CollectionsStore from 'stores/CollectionsStore';
@ -25,7 +26,7 @@ type Props = {
class CollectionNew extends React.Component<Props> {
@observable name: string = '';
@observable description: string = '';
@observable color: string = '';
@observable color: string = '#4E5C6E';
@observable private: boolean = false;
@observable isSaving: boolean;
@ -77,14 +78,18 @@ class CollectionNew extends React.Component<Props> {
organized around a topic or internal team Product or Engineering for
example.
</HelpText>
<Input
type="text"
label="Name"
onChange={this.handleNameChange}
value={this.name}
required
autoFocus
/>
<Flex>
<Input
type="text"
label="Name"
onChange={this.handleNameChange}
value={this.name}
required
autoFocus
flex
/>
&nbsp;<ColorPicker onChange={this.handleColor} value={this.color} />
</Flex>
<InputRich
label="Description"
onChange={this.handleDescriptionChange}
@ -93,7 +98,6 @@ class CollectionNew extends React.Component<Props> {
minHeight={68}
maxHeight={200}
/>
<ColorPicker onSelect={this.handleColor} />
<Switch
id="private"
label="Private collection"

View File

@ -128,6 +128,7 @@
"raw-loader": "^0.5.1",
"react": "^16.8.6",
"react-avatar-editor": "^10.3.0",
"react-color": "^2.17.3",
"react-dom": "^16.8.6",
"react-dropzone": "4.2.1",
"react-helmet": "^5.2.0",

View File

@ -110,6 +110,11 @@
version "0.7.3"
resolved "https://registry.yarnpkg.com/@emotion/unitless/-/unitless-0.7.3.tgz#6310a047f12d21a1036fb031317219892440416f"
"@icons/material@^0.2.4":
version "0.2.4"
resolved "https://registry.yarnpkg.com/@icons/material/-/material-0.2.4.tgz#e90c9f71768b3736e76d7dd6783fc6c2afa88bc8"
integrity sha512-QPcGmICAPbGLGb6F/yNf/KzKqvFx8z5qx3D1yFqVAjoFmXK35EgyW+cJ57Te3CNsmzblwtzakLGFqHPqrfb4Tw==
"@octokit/rest@^15.2.6":
version "15.18.1"
resolved "https://registry.yarnpkg.com/@octokit/rest/-/rest-15.18.1.tgz#ec7fb0f8775ef64dc095fae6635411d3fbff9b62"
@ -5475,6 +5480,11 @@ lodash.sortby@^4.7.0:
version "4.17.13"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.13.tgz#0bdc3a6adc873d2f4e0c4bac285df91b64fc7b93"
lodash@^4.0.1:
version "4.17.15"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548"
integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==
longest@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/longest/-/longest-1.0.1.tgz#30a0b2da38f73770e8294a0d22e6625ed77d0097"
@ -5548,6 +5558,11 @@ markdown-escapes@^1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/markdown-escapes/-/markdown-escapes-1.0.2.tgz#e639cbde7b99c841c0bacc8a07982873b46d2122"
material-colors@^1.2.1:
version "1.2.6"
resolved "https://registry.yarnpkg.com/material-colors/-/material-colors-1.2.6.tgz#6d1958871126992ceecc72f4bcc4d8f010865f46"
integrity sha512-6qE4B9deFBIa9YSpOc9O0Sgc43zTeVYbgDT5veRKSlB2+ZuHNoVVxA1L/ckMUayV9Ay9y7Z/SZCLcGteW9i7bg==
math-random@^1.0.1:
version "1.0.4"
resolved "https://registry.yarnpkg.com/math-random/-/math-random-1.0.4.tgz#5dd6943c938548267016d4e34f057583080c514c"
@ -6866,6 +6881,18 @@ react-avatar-editor@^10.3.0:
dependencies:
prop-types "^15.5.8"
react-color@^2.17.3:
version "2.17.3"
resolved "https://registry.yarnpkg.com/react-color/-/react-color-2.17.3.tgz#b8556d744f95193468c7061d2aa19180118d4a48"
integrity sha512-1dtO8LqAVotPIChlmo6kLtFS1FP89ll8/OiA8EcFRDR+ntcK+0ukJgByuIQHRtzvigf26dV5HklnxDIvhON9VQ==
dependencies:
"@icons/material" "^0.2.4"
lodash "^4.17.11"
material-colors "^1.2.1"
prop-types "^15.5.10"
reactcss "^1.2.0"
tinycolor2 "^1.4.1"
react-create-component-from-tag-prop@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/react-create-component-from-tag-prop/-/react-create-component-from-tag-prop-1.4.0.tgz#a1b3ad06cecb185cd1ba4215f937bd03c69979a9"
@ -7017,6 +7044,13 @@ react@^16.8.6:
prop-types "^15.6.2"
scheduler "^0.13.6"
reactcss@^1.2.0:
version "1.2.3"
resolved "https://registry.yarnpkg.com/reactcss/-/reactcss-1.2.3.tgz#c00013875e557b1cf0dfd9a368a1c3dab3b548dd"
integrity sha512-KiwVUcFu1RErkI97ywr8nvx8dNOpT03rbnma0SSalTYjkrPYaEajR4a/MRt6DZ46K6arDRbWMNHF+xH7G7n/8A==
dependencies:
lodash "^4.0.1"
read-pkg-up@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-1.0.1.tgz#9d63c13276c065918d57f002a57f40a1b643fb02"
@ -8409,6 +8443,11 @@ tiny-warning@^0.0.3:
version "0.0.3"
resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-0.0.3.tgz#1807eb4c5f81784a6354d58ea1d5024f18c6c81f"
tinycolor2@^1.4.1:
version "1.4.1"
resolved "https://registry.yarnpkg.com/tinycolor2/-/tinycolor2-1.4.1.tgz#f4fad333447bc0b07d4dc8e9209d8f39a8ac77e8"
integrity sha1-9PrTM0R7wLB9TcjpIJ2POaisd+g=
tippy.js@^4.3.4:
version "4.3.4"
resolved "https://registry.yarnpkg.com/tippy.js/-/tippy.js-4.3.4.tgz#9a91fd5ce8c401f181b7adaa6b2c27f3d105f3ba"