From 6f7f05bc7b47b97522b5eb60a37dde40ee7dca64 Mon Sep 17 00:00:00 2001 From: madhusudhand Date: Thu, 3 Nov 2022 12:40:51 +0530 Subject: [PATCH] add UI tools to set/update block shadow --- packages/block-library/src/button/block.json | 1 + .../components/global-styles/block-preview.js | 71 ++++++ .../components/global-styles/context-menu.js | 11 + .../src/components/global-styles/hooks.js | 5 + .../global-styles/screen-shadows.js | 103 ++++++++ .../components/global-styles/shadow-utils.js | 51 ++++ .../components/global-styles/shadows-panel.js | 221 ++++++++++++++++++ .../src/components/global-styles/ui.js | 5 + 8 files changed, 468 insertions(+) create mode 100644 packages/edit-site/src/components/global-styles/block-preview.js create mode 100644 packages/edit-site/src/components/global-styles/screen-shadows.js create mode 100644 packages/edit-site/src/components/global-styles/shadow-utils.js create mode 100644 packages/edit-site/src/components/global-styles/shadows-panel.js diff --git a/packages/block-library/src/button/block.json b/packages/block-library/src/button/block.json index 6076db9b8feec3..b58bf31e8080d6 100644 --- a/packages/block-library/src/button/block.json +++ b/packages/block-library/src/button/block.json @@ -66,6 +66,7 @@ "text": true } }, + "shadow": true, "typography": { "fontSize": true, "lineHeight": true, diff --git a/packages/edit-site/src/components/global-styles/block-preview.js b/packages/edit-site/src/components/global-styles/block-preview.js new file mode 100644 index 00000000000000..159104112a769c --- /dev/null +++ b/packages/edit-site/src/components/global-styles/block-preview.js @@ -0,0 +1,71 @@ +/** + * WordPress dependencies + */ +import { + __unstableIframe as Iframe, + __unstableEditorStyles as EditorStyles, +} from '@wordpress/block-editor'; +import { useMemo } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import { useGlobalStylesOutput } from './use-global-styles-output'; +import { useStyle } from './hooks'; + +// const normalizedWidth = 248; +const normalizedHeight = 152; + + +// // NOTE: This is a very early prototype component to show preview of a block +// it is created for the purpose of previewing box-shadow +// and will eventually be replaced when https://github.com/WordPress/gutenberg/issues/42919 is complete +const BlockPreview = ( { label, name } ) => { + const [ styles ] = useGlobalStylesOutput(); + const editorStyles = useMemo( () => { + if ( styles ) { + return [ + ...styles, + { + css: 'html{overflow:hidden}body{min-width: 0;padding: 0;border: none}', + isGlobalStyles: true, + } + ]; + } + + return styles; + }, [ styles ] ); + + const wrapperStyles = { + position: 'absolute', + top: '0', + height: '100%', + width: '100%', + display: 'flex', + alignItems: 'center', + justifyContent: 'center' + }; + + // get the styles to render the preview + let blockStyles = { padding: '0.5rem 1rem', border: '1px solid' }; + const [ shadow ] = useStyle( 'shadow', name ); + blockStyles = { ...blockStyles, boxShadow: shadow }; + + return <> + + +} + +export default BlockPreview; diff --git a/packages/edit-site/src/components/global-styles/context-menu.js b/packages/edit-site/src/components/global-styles/context-menu.js index 19a252763ff7ea..6f838b27596e53 100644 --- a/packages/edit-site/src/components/global-styles/context-menu.js +++ b/packages/edit-site/src/components/global-styles/context-menu.js @@ -10,6 +10,7 @@ import { __ } from '@wordpress/i18n'; */ import { useHasBorderPanel } from './border-panel'; import { useHasColorPanel } from './color-utils'; +import { useHasShadowPanel } from './shadows-panel'; import { useHasDimensionsPanel } from './dimensions-panel'; import { useHasTypographyPanel } from './typography-panel'; import { NavigationButtonAsItem } from './navigation-button'; @@ -17,6 +18,7 @@ import { NavigationButtonAsItem } from './navigation-button'; function ContextMenu( { name, parentMenu = '' } ) { const hasTypographyPanel = useHasTypographyPanel( name ); const hasColorPanel = useHasColorPanel( name ); + const hasShadowPanel = useHasShadowPanel( name ); const hasBorderPanel = useHasBorderPanel( name ); const hasDimensionsPanel = useHasDimensionsPanel( name ); const hasLayoutPanel = hasBorderPanel || hasDimensionsPanel; @@ -41,6 +43,15 @@ function ContextMenu( { name, parentMenu = '' } ) { { __( 'Colors' ) } ) } + { hasShadowPanel && ( + + { __( 'Shadows' ) } + + ) } { hasLayoutPanel && ( { if ( ! STYLE_PROPERTY[ styleName ].support ) { return; diff --git a/packages/edit-site/src/components/global-styles/screen-shadows.js b/packages/edit-site/src/components/global-styles/screen-shadows.js new file mode 100644 index 00000000000000..b767b5234b228b --- /dev/null +++ b/packages/edit-site/src/components/global-styles/screen-shadows.js @@ -0,0 +1,103 @@ +/** + * External dependencies + */ +import styled from '@emotion/styled'; + +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; +import { + __experimentalVStack as VStack, + Button, + Panel, +} from '@wordpress/components'; +import { plus } from '@wordpress/icons'; + +/** + * Internal dependencies + */ +import ScreenHeader from './header'; +import { useStyle } from './hooks'; +import Subtitle from './subtitle'; +import BlockPreview from './block-preview'; +import { getShadowString, parseShadowString } from './shadow-utils'; +import { ShadowPanel } from './shadows-panel'; + +const ShadowsContainer = styled.div` + // NOTE: this is a temporary component to add left and right borders to Panel + // if the Panel is the finalized component, then it needs to be updated to have borders on all sides based on a prop + // and this component will be removed. + & .components-panel__body { + border-left: 1px solid #e0e0e0; + border-right: 1px solid #e0e0e0; + } +`; + +function ScreenShadows( { name } ) { + const [ shadow, setShadow ] = useStyle( 'shadow', name ); + // if given shadow is an array, convert to string + const shadows = parseShadowString([].concat(shadow).flat().join(', ')); + + function handleShadowChange(newShadow, index) { + const shadowsCopy = [...shadows]; + shadowsCopy[index] = newShadow; + const shadowString = getShadowString(shadowsCopy); + setShadow(shadowString); + } + + function handleShadowDelete(index) { + const shadowsCopy = [...shadows]; + shadowsCopy.splice(index, 1); + const shadowString = getShadowString(shadowsCopy); + setShadow(shadowString); + } + + function addNewShadow() { + const newShadow = parseShadowString('5px 5px 0 0 var(--wp--preset--color--primary)').shift(); + handleShadowChange(newShadow, shadows.length); + } + + return ( + <> + + +
+ + + + { __( 'Preview' ) } + + + + + { __( 'Shadows' ) } + + + + { shadows.map((shadowObj, index) => + handleShadowChange(s, index)} + onDelete={() => handleShadowDelete(index)} /> + ) + } + + + + + + + +
+ + ); +} + +export default ScreenShadows; diff --git a/packages/edit-site/src/components/global-styles/shadow-utils.js b/packages/edit-site/src/components/global-styles/shadow-utils.js new file mode 100644 index 00000000000000..bcb20dbee928d5 --- /dev/null +++ b/packages/edit-site/src/components/global-styles/shadow-utils.js @@ -0,0 +1,51 @@ + +const SHADOWS_REG = /,(?![^\(]*\))/ +const SHADOW_PARTS_REG = /\s(?![^(]*\))/ + +export function parseShadowString(shadowStr) { + try { + const shadows = shadowStr.split(SHADOWS_REG).map(parseSingleShadow); + return shadows.filter(shadow => !!shadow); + } catch (error) { + return []; + } +} + +function parseSingleShadow(shadow) { + shadow = shadow.trim(); + + // check if the string is valid value for box-shadow + // this will be extended to parse presets, but for now returning any invalid string + const isValidShadow = CSS.supports('box-shadow', shadow); + if ( !isValidShadow ) return null; + + const parts = shadow.split(SHADOW_PARTS_REG); + // check if shadow had inset + const inset = parts.includes('inset'); + // check for the color right to left as per the CSS spec + const color = parts.reverse().filter(p => CSS.supports('color', p)).shift() + // remove inset and color to capture remaining values + const [offsetX, offsetY, blur, spread] = parts.filter(p => p!=='inset' && p!==color).reverse() + + return { + offsetX, + offsetY, + blur, + spread, + color, + inset, + } +} + +export function getShadowString(shadows) { + if ( !Array.isArray(shadows) ) return '' + + return shadows.map(shadow => { + let str = `${shadow.offsetX || 0} ${shadow.offsetY || 0}`; + str += shadow.blur ? ` ${shadow.blur}` : ''; + str += shadow.spread ? ` ${shadow.spread}` : ''; + str += shadow.color ? ` ${shadow.color}` : ''; + str += shadow.inset ? ` inset` : ''; + return str; + }).join(', ') +} diff --git a/packages/edit-site/src/components/global-styles/shadows-panel.js b/packages/edit-site/src/components/global-styles/shadows-panel.js new file mode 100644 index 00000000000000..d29984d4518caf --- /dev/null +++ b/packages/edit-site/src/components/global-styles/shadows-panel.js @@ -0,0 +1,221 @@ +/** + * WordPress dependencies + */ + import { __ } from '@wordpress/i18n'; + import { + __experimentalHStack as HStack, + __experimentalVStack as VStack, + __experimentalUnitControl as UnitControl, + __experimentalGrid as Grid, + __experimentalParseQuantityAndUnitFromRawValue as parseQuantityAndUnitFromRawValue, + __experimentalUseCustomUnits as useCustomUnits, + __experimentalDropdownContentWrapper as DropdownContentWrapper, + BaseControl, + RangeControl, + FlexItem, + ColorIndicator, + Button, + PanelBody, + PanelRow, + ToggleControl, + Dropdown, + } from '@wordpress/components'; +import { more, trash } from '@wordpress/icons'; +import { useState } from '@wordpress/element'; +import { + __experimentalColorGradientControl as ColorGradientControl, + __experimentalUseMultipleOriginColorsAndGradients as useMultipleOriginColorsAndGradients +} from '@wordpress/block-editor'; + +/** + * Internal dependencies + */ +import { getSupportedGlobalStylesPanels } from './hooks'; + + +export function useHasShadowPanel( name ) { + const supports = getSupportedGlobalStylesPanels( name ); + return name && supports.includes( 'shadow' ); +} + +export function ShadowPanel( { name, shadow, onChange, isPreset, onDelete } ) { + const panelTitle = name || __( 'Custom shadow' ); + const panelIcon = isPreset ? more : null; + + function handleValueChange(property, value) { + onChange({...shadow, [property]: value}); + } + + return ( + + + + handleValueChange('offsetX', value)} /> + handleValueChange('offsetY', value)} /> + handleValueChange('blur', value)} /> + handleValueChange('spread', value)} /> + + + handleValueChange('color', value) } /> + handleValueChange('inset', value) } + /> + + + + + + + ); +} + +function ShadowInputControl({label, value, min, max, onChange}) { + const [input, setInput] = useState(value || '0px'); + const [minValue, setMinValue] = useState(min === undefined ? -50 : min); + const [maxValue, setMaxValue] = useState(max === undefined ? 50 : max); + + const unit = parseQuantityAndUnitFromRawValue(input)[ 1 ] || 'px'; + const units = useCustomUnits( { + availableUnits: [ 'px', 'em', 'rem' ], // useSetting( 'spacing.units' ) || + } ); + const unitConfig = units && units.find( ( item ) => item.value === unit ); + const step = unitConfig?.step || 1; + + const handleInputChange = (newInput) => { + setInput(newInput); + onChange(newInput); + } + + const handleSliderChange = ( next ) => { + const newInput = next !== undefined ? `${ next }${ unit }` : '0px'; + handleInputChange(newInput); + } + + const handleUnitChange = (unit) => { + // setMinValue() + // setMaxValue() + } + + return <> + + { label } + + + + + + +} + +const renderToggle = + ( settings ) => + ( { onToggle, isOpen } ) => { + const { colorValue, label } = settings; + + const toggleProps = { + onClick: onToggle, + className: 'block-editor-panel-color-gradient-settings__dropdown is-open', + 'aria-expanded': isOpen, + }; + + return ( + + ); + }; + +const ShadowColorDropdown = ({label, colorValue, onChange}) => { + + const { + colors, + gradients, + disableCustomColors, + disableCustomGradients, + } = useMultipleOriginColorsAndGradients(); + + const setting = { + colorValue, + label, + onColorChange: onChange, + // onGradientChange: onChange, + }; + const enableAlpha = false; + + const controlProps = { + clearable: false, + colorValue: setting.colorValue, + colors, + disableCustomColors, + disableCustomGradients, + enableAlpha, + gradientValue: setting.gradientValue, + gradients, + label: setting.label, + onColorChange: setting.onColorChange, + onGradientChange: setting.onGradientChange, + showTitle: false, + __experimentalHasMultipleOrigins: true, + __experimentalIsRenderedInSidebar: true, + ...setting, + }; + + const popoverProps = { + placement: 'left-start', + offset: 36, + shift: true, + }; + + return ( + +
+ +
+
+ ) } + /> +}; + +const LabeledColorIndicator = ( { colorValue, label } ) => ( + + + + { label } + + +); diff --git a/packages/edit-site/src/components/global-styles/ui.js b/packages/edit-site/src/components/global-styles/ui.js index 6eeada1e67c61d..842e8c80de7cfd 100644 --- a/packages/edit-site/src/components/global-styles/ui.js +++ b/packages/edit-site/src/components/global-styles/ui.js @@ -22,6 +22,7 @@ import ScreenTextColor from './screen-text-color'; import ScreenLinkColor from './screen-link-color'; import ScreenHeadingColor from './screen-heading-color'; import ScreenButtonColor from './screen-button-color'; +import ScreenShadows from './screen-shadows'; import ScreenLayout from './screen-layout'; import ScreenStyleVariations from './screen-style-variations'; @@ -108,6 +109,10 @@ function ContextScreens( { name } ) { + + + +