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

View File

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

View File

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

View File

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

View File

@ -110,6 +110,11 @@
version "0.7.3" version "0.7.3"
resolved "https://registry.yarnpkg.com/@emotion/unitless/-/unitless-0.7.3.tgz#6310a047f12d21a1036fb031317219892440416f" 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": "@octokit/rest@^15.2.6":
version "15.18.1" version "15.18.1"
resolved "https://registry.yarnpkg.com/@octokit/rest/-/rest-15.18.1.tgz#ec7fb0f8775ef64dc095fae6635411d3fbff9b62" 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" version "4.17.13"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.13.tgz#0bdc3a6adc873d2f4e0c4bac285df91b64fc7b93" 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: longest@^1.0.1:
version "1.0.1" version "1.0.1"
resolved "https://registry.yarnpkg.com/longest/-/longest-1.0.1.tgz#30a0b2da38f73770e8294a0d22e6625ed77d0097" 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" version "1.0.2"
resolved "https://registry.yarnpkg.com/markdown-escapes/-/markdown-escapes-1.0.2.tgz#e639cbde7b99c841c0bacc8a07982873b46d2122" 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: math-random@^1.0.1:
version "1.0.4" version "1.0.4"
resolved "https://registry.yarnpkg.com/math-random/-/math-random-1.0.4.tgz#5dd6943c938548267016d4e34f057583080c514c" 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: dependencies:
prop-types "^15.5.8" 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: react-create-component-from-tag-prop@^1.4.0:
version "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" 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" prop-types "^15.6.2"
scheduler "^0.13.6" 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: read-pkg-up@^1.0.1:
version "1.0.1" version "1.0.1"
resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-1.0.1.tgz#9d63c13276c065918d57f002a57f40a1b643fb02" 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" version "0.0.3"
resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-0.0.3.tgz#1807eb4c5f81784a6354d58ea1d5024f18c6c81f" 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: tippy.js@^4.3.4:
version "4.3.4" version "4.3.4"
resolved "https://registry.yarnpkg.com/tippy.js/-/tippy.js-4.3.4.tgz#9a91fd5ce8c401f181b7adaa6b2c27f3d105f3ba" resolved "https://registry.yarnpkg.com/tippy.js/-/tippy.js-4.3.4.tgz#9a91fd5ce8c401f181b7adaa6b2c27f3d105f3ba"