Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Custom color picker #180

Merged
merged 41 commits into from
Feb 26, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
c5c2977
Begin with color picker
swissspidy Jan 31, 2020
44bcfba
Simplify onClick handlers
swissspidy Feb 1, 2020
6fce6c8
css cleanup
swissspidy Feb 1, 2020
08ce0ac
Fix passing numbers and strings
swissspidy Feb 1, 2020
8420fff
In-line Mode component
swissspidy Feb 1, 2020
7a6b481
Simplify
swissspidy Feb 1, 2020
ad87220
Some restructuring
swissspidy Feb 2, 2020
749331d
Updatee color panels to use new components
swissspidy Feb 2, 2020
d64cd8b
Add stories for new buttons
swissspidy Feb 2, 2020
d6afad9
few more changes and stories
swissspidy Feb 2, 2020
ecf16aa
Merge branch 'master' into add/color-picker
swissspidy Feb 10, 2020
44b8508
Remove gradient buttons for now
swissspidy Feb 10, 2020
f9e714d
styling improvements
swissspidy Feb 10, 2020
9e38445
make hex input field esc-able
swissspidy Feb 10, 2020
2e7a859
Remove ColorInput in favor of #271
swissspidy Feb 10, 2020
3b734fa
Add explicit font-size
swissspidy Feb 10, 2020
0b592d0
various styling improvements
swissspidy Feb 10, 2020
1e03471
Fix color picker component
Feb 13, 2020
74e84b9
Merge branch 'master' into add/color-picker
swissspidy Feb 13, 2020
f451d73
Remove now obsolete file
swissspidy Feb 13, 2020
fb91666
Remove now obsolete knob
swissspidy Feb 13, 2020
0724732
Remove now obsolete files
swissspidy Feb 13, 2020
38583af
Optimzie svgs, just in case
swissspidy Feb 13, 2020
e58dec4
Remove duplicate style
swissspidy Feb 13, 2020
62f3fb2
Extract some variables (DRY)
swissspidy Feb 13, 2020
61d815c
Prettier
swissspidy Feb 13, 2020
f4adb4d
Extract another var
swissspidy Feb 13, 2020
c48acb5
Remove unneeded comment
swissspidy Feb 13, 2020
6a2e75f
Pass size to pointer
swissspidy Feb 13, 2020
5114db3
Merge branch 'master' into add/color-picker
swissspidy Feb 17, 2020
d8fcc3e
Add workaround for onBlur
swissspidy Feb 17, 2020
91fa275
Merge branch 'master' into add/color-picker
swissspidy Feb 25, 2020
e14eac3
Use theme color in story
swissspidy Feb 25, 2020
73cde6f
Update pointers
swissspidy Feb 25, 2020
7883332
Lint fixes
swissspidy Feb 25, 2020
09a4dd4
Disable text selection within color picker component
swissspidy Feb 25, 2020
759081f
Merging color with alpha for alpha picker pointer
Feb 25, 2020
dff50be
Fix rgba to rgb
Feb 25, 2020
7b41a54
Fully fix rgb(a) interface for pointer
Feb 25, 2020
c12e024
Only pass relevant props to components
swissspidy Feb 26, 2020
7ed8b41
Use attr method to apply background color to pointers as inline style
swissspidy Feb 26, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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};
swissspidy marked this conversation as resolved.
Show resolved Hide resolved
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);
barklund marked this conversation as resolved.
Show resolved Hide resolved

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