feat: Port new color picker from Journals branch
This commit is contained in:
parent
8e76c4e8f1
commit
8f2d31876d
|
@ -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;
|
||||
|
|
|
@ -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 collection’s 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
|
||||
/>
|
||||
<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}
|
||||
|
|
|
@ -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
|
||||
/>
|
||||
<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"
|
||||
|
|
|
@ -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",
|
||||
|
|
39
yarn.lock
39
yarn.lock
|
@ -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"
|
||||
|
|
Reference in New Issue