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

Add duotone to image block #26751

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
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
Prev Previous commit
Next Next commit
Add duotone toolbar
  • Loading branch information
ajlende committed Feb 26, 2021
commit 802581e0ed0dd4ceba7bd1da05920bc286647749
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