Skip to content

Commit

Permalink
Custom color picker (#180)
Browse files Browse the repository at this point in the history
  • Loading branch information
swissspidy authored Feb 26, 2020
1 parent 8ce4484 commit 58045a0
Show file tree
Hide file tree
Showing 11 changed files with 542 additions and 74 deletions.
3 changes: 3 additions & 0 deletions assets/src/edit-story/components/button/icons/close.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions assets/src/edit-story/components/button/icons/eyedropper.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
14 changes: 14 additions & 0 deletions assets/src/edit-story/components/button/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ import { ReactComponent as RedoIcon } from './icons/redo.svg';
import { ReactComponent as LeftArrowIcon } from './icons/arrow_left.svg';
import { ReactComponent as RightArrowIcon } from './icons/arrow_right.svg';
import { ReactComponent as GridViewIcon } from './icons/grid_view.svg';
import { ReactComponent as CloseIcon } from './icons/close.svg';
import { ReactComponent as EyedropperIcon } from './icons/eyedropper.svg';

const Base = styled.button.attrs(({ isDisabled }) => ({
disabled: isDisabled,
Expand Down Expand Up @@ -128,6 +130,18 @@ export const GridView = (props) => (
</StyledButton>
);

export const Close = (props) => (
<StyledButton {...props}>
<CloseIcon />
</StyledButton>
);

export const Eyedropper = (props) => (
<StyledButton {...props}>
<EyedropperIcon />
</StyledButton>
);

export const ActionButton = styled.button.attrs({ type: 'button' })`
background: transparent;
border: 1px solid ${({ theme }) => theme.colors.fg.v3};
Expand Down
35 changes: 34 additions & 1 deletion assets/src/edit-story/components/button/stories/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,16 @@ import { text, boolean } from '@storybook/addon-knobs';
/**
* Internal dependencies
*/
import { Primary, Secondary, Outline, Undo, Redo, GridView } from '../';
import {
Primary,
Secondary,
Outline,
Undo,
Redo,
GridView,
Close,
Eyedropper,
} from '../';

export default {
title: 'Components/Button',
Expand Down Expand Up @@ -91,3 +100,27 @@ gridView.story = {
backgrounds: [{ name: 'dark', value: '#000', default: true }],
},
};

export const close = () => {
const isDisabled = boolean('Disabled', false);

return <Close isDisabled={isDisabled} />;
};

close.story = {
parameters: {
backgrounds: [{ name: 'dark', value: '#000', default: true }],
},
};

export const eyedropper = () => {
const isDisabled = boolean('Disabled', false);

return <Eyedropper isDisabled={isDisabled} />;
};

eyedropper.story = {
parameters: {
backgrounds: [{ name: 'dark', value: '#000', default: true }],
},
};
93 changes: 93 additions & 0 deletions assets/src/edit-story/components/colorPicker/editableHexPreview.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/*
* Copyright 2020 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

/**
* External dependencies
*/
import PropTypes from 'prop-types';
import styled from 'styled-components';
import { EditableInput } from 'react-color/lib/components/common';

/**
* WordPress dependencies
*/
import { useCallback, useLayoutEffect, useState } from '@wordpress/element';

const Preview = styled.button`
padding: 0;
margin: 0;
border: none;
background: ${({ theme }) => theme.colors.bg.v7};
color: ${({ theme }) => theme.colors.fg.v1};
`;

const inputStyles = {
/* stylelint-disable-next-line rule-empty-line-before */
input: {
fontFamily: 'monospace',
width: '60px',
},
};

function EditableHexPreview({ hex, onChange }) {
const [isEditing, setIsEditing] = useState(false);

// Handle ESC keypress to toggle input field.
const handleKeyPress = useCallback(
(evt) => {
if ('Escape' === evt.key && isEditing) {
evt.stopPropagation();
evt.preventDefault();
setIsEditing(false);
}
},
[isEditing]
);

useLayoutEffect(() => {
document.addEventListener('keydown', handleKeyPress);

return () => document.removeEventListener('keydown', handleKeyPress);
}, [handleKeyPress]);

const handleOnBlur = (evt) => {
if (!evt.currentTarget.contains(document.activeElement)) {
setIsEditing(false);
}
};

if (!isEditing) {
return <Preview onClick={() => setIsEditing(true)}>{hex}</Preview>;
}

return (
<div tabIndex={-1} onBlur={handleOnBlur}>
<EditableInput
value={hex}
onChange={onChange}
onChangeComplete={() => setIsEditing(false)}
style={inputStyles}
/>
</div>
);
}

EditableHexPreview.propTypes = {
hex: PropTypes.string,
onChange: PropTypes.func.isRequired,
};

export default EditableHexPreview;
211 changes: 211 additions & 0 deletions assets/src/edit-story/components/colorPicker/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
/*
* Copyright 2020 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

/**
* External dependencies
*/
import PropTypes from 'prop-types';
import styled from 'styled-components';
import { CustomPicker } from 'react-color';
import { Saturation, Hue, Alpha } from 'react-color/lib/components/common';
import { rgba } from 'polished';

/**
* WordPress dependencies
*/
import { __ } from '@wordpress/i18n';

/**
* Internal dependencies
*/
import { Close, Eyedropper } from '../button';
import Pointer from './pointer';
import EditableHexPreview from './editableHexPreview';

const CONTAINER_PADDING = 15;
const EYEDROPPER_ICON_SIZE = 15;
const HEADER_FOOTER_HEIGHT = 50;
const BODY_HEIGHT = 140;
const CONTROLS_WIDTH = 12;
const CONTROLS_BORDER_RADIUS = 6;

const Container = styled.div`
border-radius: 4px;
background: ${({ theme }) => theme.colors.bg.v7};
color: ${({ theme }) => theme.colors.fg.v1};
width: 240px;
font-family: ${({ theme }) => theme.fonts.body1.family};
font-style: normal;
font-weight: normal;
font-size: 12px;
user-select: none;
`;

const Header = styled.div`
height: ${HEADER_FOOTER_HEIGHT}px;
padding: ${CONTAINER_PADDING}px;
border-bottom: 1px solid ${({ theme }) => rgba(theme.colors.fg.v2, 0.2)};
position: relative;
`;

const CloseButton = styled(Close)`
opacity: 1;
font-size: 15px;
line-height: 20px;
position: absolute;
right: ${CONTAINER_PADDING}px;
top: ${CONTAINER_PADDING}px;
`;

const Body = styled.div`
padding: ${CONTAINER_PADDING}px;
padding-bottom: 0;
display: grid;
grid: 'saturation hue alpha' ${BODY_HEIGHT}px / 1fr ${CONTROLS_WIDTH}px ${CONTROLS_WIDTH}px;
grid-gap: 10px;
`;

const SaturationWrapper = styled.div`
position: relative;
width: 167px;
height: ${BODY_HEIGHT}px;
grid-area: saturation;
`;

const HueWrapper = styled.div`
position: relative;
height: ${BODY_HEIGHT}px;
width: ${CONTROLS_WIDTH}px;
grid-area: hue;
`;

const AlphaWrapper = styled.div`
position: relative;
height: ${BODY_HEIGHT}px;
width: ${CONTROLS_WIDTH}px;
background: #fff;
border-radius: ${CONTROLS_BORDER_RADIUS}px;
grid-area: alpha;
`;

const Footer = styled.div`
padding: ${CONTAINER_PADDING}px;
height: ${HEADER_FOOTER_HEIGHT}px;
font-size: ${CONTROLS_WIDTH}px;
line-height: 19px;
position: relative;
`;

const EyedropperWrapper = styled.div`
position: absolute;
left: ${CONTAINER_PADDING}px;
bottom: ${CONTAINER_PADDING}px;
`;

const EyedropperButton = styled(Eyedropper)`
line-height: ${EYEDROPPER_ICON_SIZE}px;
`;

const CurrentWrapper = styled.div`
position: absolute;
left: 0;
right: 0;
text-align: center;
bottom: ${CONTAINER_PADDING}px;
`;

const CurrentAlphaWrapper = styled.div`
position: absolute;
right: ${CONTAINER_PADDING}px;
bottom: ${CONTAINER_PADDING}px;
`;

function ColorPicker({ rgb, hsl, hsv, hex, onChange, onClose }) {
const alphaPercentage = Math.round(rgb.a * 100);

return (
<Container>
<Header>
<CloseButton
width={10}
height={10}
aria-label={__('Close', 'web-stories')}
onClick={onClose}
/>
</Header>
<Body>
<SaturationWrapper>
<Saturation
radius={`${CONTROLS_BORDER_RADIUS}px`}
pointer={() => <Pointer offset={-6} currentColor={rgb} />}
hsl={hsl}
hsv={hsv}
onChange={onChange}
/>
</SaturationWrapper>
<HueWrapper>
<Hue
direction="vertical"
width={`${CONTROLS_WIDTH}px`}
height={`${BODY_HEIGHT}px`}
radius={`${CONTROLS_BORDER_RADIUS}px`}
pointer={() => <Pointer offset={0} currentColor={rgb} />}
hsl={hsl}
onChange={onChange}
/>
</HueWrapper>
<AlphaWrapper>
<Alpha
direction="vertical"
width={`${CONTROLS_WIDTH}px`}
height={`${BODY_HEIGHT}px`}
radius={`${CONTROLS_BORDER_RADIUS}px`}
pointer={() => <Pointer offset={-3} currentColor={rgb} withAlpha />}
rgb={rgb}
hsl={hsl}
onChange={onChange}
/>
</AlphaWrapper>
</Body>
<Footer>
{/* TODO: implement (see https://github.com/google/web-stories-wp/issues/262) */}
<EyedropperWrapper>
<EyedropperButton
width={EYEDROPPER_ICON_SIZE}
height={EYEDROPPER_ICON_SIZE}
aria-label={__('Select color', 'web-stories')}
isDisabled
/>
</EyedropperWrapper>
<CurrentWrapper>
<EditableHexPreview hex={hex} onChange={onChange} />
</CurrentWrapper>
<CurrentAlphaWrapper>{alphaPercentage + '%'}</CurrentAlphaWrapper>
</Footer>
</Container>
);
}

ColorPicker.propTypes = {
onChange: PropTypes.func.isRequired,
onClose: PropTypes.func,
rgb: PropTypes.object,
hex: PropTypes.string,
hsl: PropTypes.object,
hsv: PropTypes.object,
};

export default CustomPicker(ColorPicker);
Loading

0 comments on commit 58045a0

Please sign in to comment.