Skip to content

Commit

Permalink
Add duotone toolbar
Browse files Browse the repository at this point in the history
  • Loading branch information
ajlende committed Feb 26, 2021
1 parent aa66ab4 commit 802581e
Show file tree
Hide file tree
Showing 13 changed files with 628 additions and 34 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/**
* WordPress dependencies
*/
import { __experimentalCustomGradientBar as CustomGradientBar } from '@wordpress/components';

/**
* Internal dependencies
*/
import {
getColorStopsFromValues,
getCustomDuotoneIdFromColorStops,
getGradientFromValues,
getValuesFromColorStops,
} from './utils';

const PLACEHOLDER_VALUES = {
r: [ 0.2, 0.8 ],
g: [ 0.2, 0.8 ],
b: [ 0.2, 0.8 ],
};

export default function CustomDuotoneBar( { value, onChange } ) {
const hasGradient = !! value?.values;
const values = hasGradient ? value.values : PLACEHOLDER_VALUES;
const background = getGradientFromValues( values );
const controlPoints = getColorStopsFromValues( values );
return (
<div className="components-custom-duotone-picker">
<CustomGradientBar
disableInserter
disableAlpha
background={ background }
hasGradient={ hasGradient }
value={ controlPoints }
onChange={ ( newColorStops ) => {
const newDuotone = {
id: getCustomDuotoneIdFromColorStops( newColorStops ),
values: getValuesFromColorStops( newColorStops ),
};
onChange( newDuotone );
} }
/>
</div>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
/**
* WordPress dependencies
*/
import { Button, ColorPalette, Icon } from '@wordpress/components';
import { useMemo, useState } from '@wordpress/element';
import { __ } from '@wordpress/i18n';
import { swatch } from '@wordpress/icons';

/**
* Internal dependencies
*/
import Swatch from './duotone-swatch';
import {
getCustomDuotoneIdFromHexColors,
getDefaultColors,
getHexColorsFromValues,
getValuesFromColors,
} from './utils';

function CustomColorOption( { label, value, colors, onChange } ) {
const [ isOpen, setIsOpen ] = useState( false );
const icon = value ? <Swatch fill={ value } /> : <Icon icon={ swatch } />;
return (
<>
<Button
className="block-editor-duotone-toolbar__color-button"
icon={ icon }
onClick={ () => setIsOpen( ( prev ) => ! prev ) }
>
{ label }
</Button>
{ isOpen && (
<ColorPalette
colors={ colors }
value={ value }
clearable={ false }
onChange={ onChange }
/>
) }
</>
);
}

function CustomColorPicker( { colors, palette, onChange } ) {
const [ defaultDark, defaultLight ] = useMemo(
() => getDefaultColors( palette ),
[ palette ]
);

return (
<div className="block-editor-duotone-toolbar__custom-colors">
<CustomColorOption
label={ __( 'Shadows' ) }
value={ colors[ 0 ] }
colors={ palette }
onChange={ ( newColor ) => {
const newColors = colors.slice();
newColors[ 0 ] = newColor;
if ( ! newColors[ 0 ] ) {
newColors[ 0 ] = defaultDark;
}
if ( ! newColors[ 1 ] ) {
newColors[ 1 ] = defaultLight;
}
onChange( newColors );
} }
/>
<CustomColorOption
label={ __( 'Highlights' ) }
value={ colors[ 1 ] }
colors={ palette }
onChange={ ( newColor ) => {
const newColors = colors.slice();
newColors[ 1 ] = newColor;
if ( ! newColors[ 0 ] ) {
newColors[ 0 ] = defaultDark;
}
if ( ! newColors[ 1 ] ) {
newColors[ 1 ] = defaultLight;
}
onChange( newColors );
} }
/>
</div>
);
}

function CustomDuotonePicker( { colorPalette, value, onChange } ) {
return (
<CustomColorPicker
colors={ getHexColorsFromValues( value?.values ) }
palette={ colorPalette }
onChange={ ( newColors ) =>
onChange(
newColors.length >= 2
? {
values: getValuesFromColors( newColors ),
id: getCustomDuotoneIdFromHexColors(
newColors
),
}
: undefined
)
}
/>
);
}

export default CustomDuotonePicker;
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
/**
* WordPress dependencies
*/
import {
CircularOptionPicker,
Popover,
MenuGroup,
} from '@wordpress/components';
import { __, sprintf } from '@wordpress/i18n';

/**
* Internal dependencies
*/
import CustomDuotoneBar from './custom-duotone-bar';
import CustomDuotonePicker from './custom-duotone-picker';
import { getGradientFromCSSColors, getValuesFromColors } from './utils';

function DuotonePickerPopover( {
value,
onChange,
onToggle,
duotonePalette,
colorPalette,
} ) {
return (
<Popover
className="block-editor-duotone-toolbar__popover"
headerTitle={ __( 'Duotone' ) }
onFocusOutside={ onToggle }
>
<MenuGroup label={ __( 'Duotone' ) }>
<CircularOptionPicker
options={ duotonePalette.map( ( option ) => {
const isSelected = option.slug === value?.slug;
const style = {
background: getGradientFromCSSColors(
option.colors,
'135deg'
),
color: 'transparent',
};
const code = sprintf(
// translators: %s: duotone code e.g: "dark-grayscale" or "7f7f7f-ffffff".
__( 'Duotone code: %s' ),
option.slug
);
const label = sprintf(
// translators: %s: The name of the option e.g: "Dark grayscale".
__( 'Duotone: %s' ),
option.name
);

return (
<CircularOptionPicker.Option
key={ option.slug }
value={ option.slug }
isSelected={ isSelected }
tooltipText={ option.name ?? code }
style={ style }
onClick={ () => {
const newValue = {
values: getValuesFromColors(
option.colors
),
id: `duotone-filter-${ option.slug }`,
};
onChange(
isSelected ? undefined : newValue
);
} }
aria-label={ option.name ? label : code }
/>
);
} ) }
actions={
<CircularOptionPicker.ButtonAction
onClick={ () => onChange( undefined ) }
>
{ __( 'Clear' ) }
</CircularOptionPicker.ButtonAction>
}
>
<CustomDuotoneBar value={ value } onChange={ onChange } />
<CustomDuotonePicker
colorPalette={ colorPalette }
value={ value }
onChange={ onChange }
/>
</CircularOptionPicker>
</MenuGroup>
<div className="block-editor-duotone-toolbar__description">
{ __(
'The duotone filter creates a two-color version of your image, where you choose the colors.'
) }
</div>
</Popover>
);
}

export default DuotonePickerPopover;
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
function DuotoneSwatch( { fill } ) {
return (
<span
className="block-editor-duotone-toolbar__swatch"
style={ { background: fill } }
/>
);
}

export default DuotoneSwatch;
64 changes: 64 additions & 0 deletions packages/block-editor/src/components/duotone-toolbar/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/**
* WordPress dependencies
*/
import { ToolbarButton } from '@wordpress/components';
import { useState } from '@wordpress/element';
import { __ } from '@wordpress/i18n';
import { swatch } from '@wordpress/icons';
import { DOWN } from '@wordpress/keycodes';

/**
* Internal dependencies
*/
import DuotonePickerPopover from './duotone-picker-popover';
import Swatch from './duotone-swatch';
import { getGradientFromValues } from './utils';

function DuotoneToolbar( { value, onChange, duotonePalette, colorPalette } ) {
const [ isOpen, setIsOpen ] = useState( false );
const onToggle = () => {
setIsOpen( ( prev ) => ! prev );
};
const openOnArrowDown = ( event ) => {
if ( ! isOpen && event.keyCode === DOWN ) {
event.preventDefault();
event.stopPropagation();
onToggle();
}
};
return (
<>
<ToolbarButton
showTooltip
onClick={ onToggle }
aria-haspopup="true"
aria-expanded={ isOpen }
onKeyDown={ openOnArrowDown }
label={ __( 'Apply duotone filter' ) }
icon={
value ? (
<Swatch
fill={ getGradientFromValues(
value.values,
'135deg'
) }
/>
) : (
swatch
)
}
/>
{ isOpen && (
<DuotonePickerPopover
value={ value }
onChange={ onChange }
onToggle={ onToggle }
duotonePalette={ duotonePalette }
colorPalette={ colorPalette }
/>
) }
</>
);
}

export default DuotoneToolbar;
59 changes: 59 additions & 0 deletions packages/block-editor/src/components/duotone-toolbar/style.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
.block-editor-duotone-toolbar__swatch {
width: 18px;
height: 18px;
border-radius: 50%;
color: transparent;
background: transparent;

// Regular border doesn't seem to work in the toolbar button, but pseudo-selector border does.
&::after {
content: "";
display: block;
width: 100%;
height: 100%;
border: $border-width solid rgba(0, 0, 0, 0.2);
border-radius: 50%;
}
}

.components-button.has-icon.has-text .block-editor-duotone-toolbar__swatch {
margin-right: $grid-unit;
}

.block-editor-duotone-toolbar__popover {
.components-popover__content {
border: $border-width solid $gray-900;
min-width: 214px;
}

.components-circular-option-picker,
.block-editor-duotone-toolbar__description {
padding: $grid-unit-15;
}

.components-menu-group__label {
padding: $grid-unit-15 $grid-unit-15 0 $grid-unit-15;
}

.components-menu-group__label,
.block-editor-duotone-toolbar__custom-colors,
.block-editor-duotone-toolbar__color-button {
width: 100%;
}

.block-editor-duotone-toolbar__description {
border-top: $border-width solid $gray-900;
color: $gray-700;
}
}

.block-editor-duotone-toolbar__popover > .components-popover__content {
// Matches 8 swatches in width.
width: 334px;
}

// Better align the popover under the swatch.
// @todo: when the positioning for popovers gets refactored, this can presumably be removed.
.block-editor-duotone-toolbar__popover:not([data-y-axis="middle"][data-x-axis="right"]) > .components-popover__content {
margin-left: -14px;
}
Loading

0 comments on commit 802581e

Please sign in to comment.