Skip to content

Commit

Permalink
Add Gradient Type and Angle Picker to the custom gradient UI
Browse files Browse the repository at this point in the history
  • Loading branch information
jorgefilipecosta committed Feb 7, 2020
1 parent b33c959 commit 8827cd5
Show file tree
Hide file tree
Showing 8 changed files with 297 additions and 23 deletions.
5 changes: 5 additions & 0 deletions packages/components/src/custom-gradient-picker/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,8 @@ export const COLOR_POPOVER_PROPS = {
className: 'components-custom-gradient-picker__color-picker-popover',
position: 'top',
};
export const DEFAULT_LINEAR_GRADIENT_ANGLE = 180;
export const HORIZONTAL_GRADIENT_ORIENTATION = {
type: 'angular',
value: 90,
};
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* External dependencies
*/
import gradientParser from 'gradient-parser';

import { some } from 'lodash';
import classnames from 'classnames';

Expand All @@ -19,7 +19,6 @@ import ColorPicker from '../color-picker';
import Dropdown from '../dropdown';
import ControlPoints from './control-points';
import {
DEFAULT_GRADIENT,
INSERT_POINT_WIDTH,
COLOR_POPOVER_PROPS,
MINIMUM_DISTANCE_BETWEEN_POINTS,
Expand All @@ -30,6 +29,8 @@ import {
getGradientWithColorStopAdded,
getHorizontalRelativeGradientPosition,
getMarkerPoints,
getGradientParsed,
getLinearGradientRepresentationOfARadial,
} from './utils';

function InsertPoint( {
Expand Down Expand Up @@ -142,20 +143,10 @@ function customGradientBarReducer( state, action ) {
}
const customGradientBarReducerInitialState = { id: 'IDLE' };

export default function CustomGradientPicker( { value, onChange } ) {
let hasGradient = !! value;
// gradientAST will contain the gradient AST as parsed by gradient-parser npm module.
// More information of its structure available at.
let gradientAST;
let gradientValueUsed;
try {
gradientAST = gradientParser.parse( value || DEFAULT_GRADIENT )[ 0 ];
gradientValueUsed = value || DEFAULT_GRADIENT;
} catch ( error ) {
hasGradient = false;
gradientAST = gradientParser.parse( DEFAULT_GRADIENT )[ 0 ];
gradientValueUsed = DEFAULT_GRADIENT;
}
export default function CustomGradientBar( { value, onChange } ) {
const { gradientAST, gradientValue, hasGradient } = getGradientParsed(
value
);

const onGradientStructureChange = ( newGradientStructure ) => {
onChange( serializeGradient( newGradientStructure ) );
Expand Down Expand Up @@ -204,13 +195,21 @@ export default function CustomGradientPicker( { value, onChange } ) {
return (
<div
ref={ gradientPickerDomRef }
className={ classnames( 'components-custom-gradient-picker', {
'has-gradient': hasGradient,
} ) }
className={ classnames(
'components-custom-gradient-picker__gradient-bar',
{ 'has-gradient': hasGradient }
) }
onMouseEnter={ onMouseEnterAndMove }
onMouseMove={ onMouseEnterAndMove }
// On radial gradients the bar should display a linear gradient.
// On radial gradients the bar represents a slice of the gradient from the center until the outside.
style={ {
background: gradientValueUsed,
background:
gradientAST.type === 'radial-gradient'
? getLinearGradientRepresentationOfARadial(
gradientAST
)
: gradientValue,
} }
onMouseLeave={ onMouseLeave }
>
Expand Down
73 changes: 73 additions & 0 deletions packages/components/src/custom-gradient-picker/icons.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/**
* WordPress dependencies
*/
import { withInstanceId } from '@wordpress/compose';
import {
SVG,
G,
Rect,
Defs,
RadialGradient,
LinearGradient,
Stop,
} from '@wordpress/primitives';

/**
* Internal dependencies
*/
export const LinearGradientIcon = withInstanceId( ( { instanceId } ) => {
const linerGradientId = `linear-gradient-${ instanceId }`;
return (
<SVG width="20" height="20" xmlns="http://www.w3.org/2000/svg">
<Defs>
<LinearGradient
y2="0"
x2="0.5"
y1="1"
x1="0.5"
id={ linerGradientId }
>
<Stop offset="0" stopColor="#000000" />
<Stop offset="1" stopColor="#fff" />
</LinearGradient>
</Defs>
<G>
<Rect
fill={ `url(#${ linerGradientId })` }
height="20"
width="20"
y="-1"
x="-1"
/>
</G>
</SVG>
);
} );

export const RadialGradientIcon = withInstanceId( ( { instanceId } ) => {
const radialGradientId = `radial-gradient-${ instanceId }`;
return (
<SVG width="20" height="20" xmlns="http://www.w3.org/2000/svg">
<Defs>
<RadialGradient
cy="0.5"
cx="0.5"
spreadMethod="pad"
id={ radialGradientId }
>
<Stop offset="0" stopColor="#fff" />
<Stop offset="1" stopColor="#000000" />
</RadialGradient>
</Defs>
<G>
<Rect
fill={ `url(#${ radialGradientId })` }
height="20"
width="20"
y="-1"
x="-1"
/>
</G>
</SVG>
);
} );
119 changes: 119 additions & 0 deletions packages/components/src/custom-gradient-picker/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
/**
* External dependencies
*/
import { get, omit } from 'lodash';

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

/**
* Internal dependencies
*/
import AnglePicker from '../angle-picker';
import { LinearGradientIcon, RadialGradientIcon } from './icons';
import CustomGradientBar from './custom-gradient-bar';
import BaseControl from '../base-control';
import { getGradientParsed } from './utils';
import { serializeGradient } from './serializer';
import ToolbarGroup from '../toolbar-group';
import {
DEFAULT_LINEAR_GRADIENT_ANGLE,
HORIZONTAL_GRADIENT_ORIENTATION,
} from './constants';

const GradientAnglePicker = ( { gradientAST, hasGradient, onChange } ) => {
const angle = get(
gradientAST,
[ 'orientation', 'value' ],
DEFAULT_LINEAR_GRADIENT_ANGLE
);
const onAngleChange = ( newAngle ) => {
onChange(
serializeGradient( {
...gradientAST,
orientation: {
type: 'angular',
value: newAngle,
},
} )
);
};
return (
<AnglePicker
value={ hasGradient ? angle : '' }
onChange={ onAngleChange }
/>
);
};

const GradientTypePicker = ( { gradientAST, hasGradient, onChange } ) => {
const { type } = gradientAST;
const onSetLinearGradient = () => {
onChange(
serializeGradient( {
...gradientAST,
...( gradientAST.orientation
? {}
: { orientation: HORIZONTAL_GRADIENT_ORIENTATION } ),
type: 'linear-gradient',
} )
);
};

const onSetRadialGradient = () => {
onChange(
serializeGradient( {
...omit( gradientAST, [ 'orientation' ] ),
type: 'radial-gradient',
} )
);
};

return (
<BaseControl className="components-custom-gradient-picker__type-picker">
<BaseControl.VisualLabel>{ __( 'Type' ) }</BaseControl.VisualLabel>
<ToolbarGroup
controls={ [
{
icon: <LinearGradientIcon />,
title: 'Linear Gradient',
isActive: hasGradient && type === 'linear-gradient',
onClick: onSetLinearGradient,
},
{
icon: <RadialGradientIcon />,
title: 'Radial Gradient',
isActive: hasGradient && type === 'radial-gradient',
onClick: onSetRadialGradient,
},
] }
/>
</BaseControl>
);
};

export default function CustomGradientPicker( { value, onChange } ) {
const { gradientAST, hasGradient } = getGradientParsed( value );
const { type } = gradientAST;
return (
<div className="components-custom-gradient-picker">
<CustomGradientBar value={ value } onChange={ onChange } />
<div className="components-custom-gradient-picker__ui-line">
<GradientTypePicker
gradientAST={ gradientAST }
hasGradient={ hasGradient }
onChange={ onChange }
/>
{ type === 'linear-gradient' && (
<GradientAnglePicker
gradientAST={ gradientAST }
hasGradient={ hasGradient }
onChange={ onChange }
/>
) }
</div>
</div>
);
}
32 changes: 30 additions & 2 deletions packages/components/src/custom-gradient-picker/style.scss
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
$components-custom-gradient-picker__padding: 3px; // 24px container, 18px handles inside, that leaves 6px padding, half of which is 3.

.components-custom-gradient-picker:not(.has-gradient) {
.components-custom-gradient-picker__gradient-bar:not(.has-gradient) {
opacity: 0.4;
}

.components-custom-gradient-picker {
.components-custom-gradient-picker__gradient-bar {
width: 100%;
height: $icon-button-size-small;
border-radius: $icon-button-size-small;
Expand Down Expand Up @@ -55,3 +55,31 @@ $components-custom-gradient-picker__padding: 3px; // 24px container, 18px handle
.components-custom-gradient-picker__inserter {
width: 100%;
}

.components-custom-gradient-picker__liner-gradient-indicator {
display: inline-block;
flex: 0 auto;
width: 20px;
height: 20px;
}

.components-custom-gradient-picker__ui-line {
display: flex;
justify-content: space-between;
}

.components-custom-gradient-picker .components-custom-gradient-picker__ui-line {
.components-base-control.components-custom-gradient-picker__angle-picker,
.components-base-control.components-custom-gradient-picker__type-picker {
margin-bottom: 0;
}
}

.components-custom-gradient-picker__angle-picker-field {
display: block;
width: 100%;
}

.components-custom-gradient-picker__angle-picker {
width: 50%;
}
34 changes: 34 additions & 0 deletions packages/components/src/custom-gradient-picker/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,22 @@
* External dependencies
*/
import { findIndex, map, some } from 'lodash';
import gradientParser from 'gradient-parser';

/**
* Internal dependencies
*/
import {
DEFAULT_GRADIENT,
INSERT_POINT_WIDTH,
MINIMUM_ABSOLUTE_LEFT_POSITION,
MINIMUM_DISTANCE_BETWEEN_POINTS,
HORIZONTAL_GRADIENT_ORIENTATION,
} from './constants';
import {
serializeGradientColor,
serializeGradientPosition,
serializeGradient,
} from './serializer';

function tinyColorRgbToGradientColorStop( { r, g, b, a } ) {
Expand Down Expand Up @@ -227,3 +231,33 @@ export function getMarkerPoints( gradientAST ) {
};
} );
}

export function getLinearGradientRepresentationOfARadial( gradientAST ) {
return serializeGradient( {
type: 'linear-gradient',
orientation: HORIZONTAL_GRADIENT_ORIENTATION,
colorStops: gradientAST.colorStops,
} );
}

export function getGradientParsed( value ) {
let hasGradient = !! value;
// gradientAST will contain the gradient AST as parsed by gradient-parser npm module.
// More information of its structure available at https://www.npmjs.com/package/gradient-parser#ast.
let gradientAST;
let gradientValue;
try {
gradientAST = gradientParser.parse( value || DEFAULT_GRADIENT )[ 0 ];
gradientValue = value || DEFAULT_GRADIENT;
} catch ( error ) {
hasGradient = false;
gradientAST = gradientParser.parse( DEFAULT_GRADIENT )[ 0 ];
gradientValue = DEFAULT_GRADIENT;
}

return {
hasGradient,
gradientAST,
gradientValue,
};
}
6 changes: 6 additions & 0 deletions packages/primitives/src/svg/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@ export const G = ( props ) => createElement( 'g', props );
export const Path = ( props ) => createElement( 'path', props );
export const Polygon = ( props ) => createElement( 'polygon', props );
export const Rect = ( props ) => createElement( 'rect', props );
export const Defs = ( props ) => createElement( 'defs', props );
export const RadialGradient = ( props ) =>
createElement( 'radialGradient', props );
export const LinearGradient = ( props ) =>
createElement( 'linearGradient', props );
export const Stop = ( props ) => createElement( 'stop', props );

export const SVG = ( { className, isPressed, ...props } ) => {
const appliedProps = {
Expand Down
Loading

0 comments on commit 8827cd5

Please sign in to comment.