index.js
260 lines
| 1 | /** |
| 2 | * External dependencies |
| 3 | */ |
| 4 | import { __, _x, sprintf } from '@wordpress/i18n'; |
| 5 | import { BaseControl, Button, Tooltip } from '@wordpress/components'; |
| 6 | import { link, linkOff } from '@wordpress/icons'; |
| 7 | |
| 8 | /** |
| 9 | * Internal dependencies |
| 10 | */ |
| 11 | import './editor.scss'; |
| 12 | import UnitPicker from '../unit-picker'; |
| 13 | import getResponsivePlaceholder from '../../utils/get-responsive-placeholder'; |
| 14 | |
| 15 | export default function Dimensions( props ) { |
| 16 | const { |
| 17 | setAttributes, |
| 18 | attributes, |
| 19 | label = __( 'Padding', 'generateblocks' ), |
| 20 | type = 'padding', |
| 21 | labelTop = __( 'Top', 'generateblocks' ), |
| 22 | labelRight = __( 'Right', 'generateblocks' ), |
| 23 | labelBottom = __( 'Bottom', 'generateblocks' ), |
| 24 | labelLeft = __( 'Left', 'generateblocks' ), |
| 25 | device, |
| 26 | units, |
| 27 | computedStyles = {}, |
| 28 | } = props; |
| 29 | |
| 30 | const attributeNames = { |
| 31 | top: type + 'Top', |
| 32 | right: type + 'Right', |
| 33 | bottom: type + 'Bottom', |
| 34 | left: type + 'Left', |
| 35 | unit: type + 'Unit', |
| 36 | sync: type + 'SyncUnits', |
| 37 | }; |
| 38 | |
| 39 | const labels = { |
| 40 | top: labelTop, |
| 41 | right: labelRight, |
| 42 | bottom: labelBottom, |
| 43 | left: labelLeft, |
| 44 | }; |
| 45 | |
| 46 | if ( 'borderRadius' === type ) { |
| 47 | attributeNames.top = 'borderRadiusTopLeft'; |
| 48 | attributeNames.right = 'borderRadiusTopRight'; |
| 49 | attributeNames.bottom = 'borderRadiusBottomRight'; |
| 50 | attributeNames.left = 'borderRadiusBottomLeft'; |
| 51 | |
| 52 | labels.top = _x( 'T-Left', 'short for Top Left', 'generateblocks' ); |
| 53 | labels.right = _x( 'T-Right', 'short for Top Right', 'generateblocks' ); |
| 54 | labels.bottom = _x( 'B-Right', 'short for Bottom Right', 'generateblocks' ); |
| 55 | labels.left = _x( 'B-Left', 'short for Bottom Left', 'generateblocks' ); |
| 56 | } |
| 57 | |
| 58 | if ( 'Desktop' !== device ) { |
| 59 | attributeNames.top += device; |
| 60 | attributeNames.right += device; |
| 61 | attributeNames.bottom += device; |
| 62 | attributeNames.left += device; |
| 63 | } |
| 64 | |
| 65 | const onChangeTop = ( value ) => setAttributes( { [ attributeNames.top ]: value } ); |
| 66 | const onChangeRight = ( value ) => setAttributes( { [ attributeNames.right ]: value } ); |
| 67 | const onChangeBottom = ( value ) => setAttributes( { [ attributeNames.bottom ]: value } ); |
| 68 | const onChangeLeft = ( value ) => setAttributes( { [ attributeNames.left ]: value } ); |
| 69 | const onChangeUnits = ( value ) => setAttributes( { [ [ attributeNames.unit ] ]: value } ); |
| 70 | const onChangeAll = ( value ) => setAttributes( { |
| 71 | [ attributeNames.top ]: value, |
| 72 | [ attributeNames.right ]: value, |
| 73 | [ attributeNames.bottom ]: value, |
| 74 | [ attributeNames.left ]: value, |
| 75 | } ); |
| 76 | |
| 77 | const syncUnits = ( value ) => { |
| 78 | const numbers = [ |
| 79 | attributes[ attributeNames.top ], |
| 80 | attributes[ attributeNames.right ], |
| 81 | attributes[ attributeNames.bottom ], |
| 82 | attributes[ attributeNames.left ], |
| 83 | ].filter( ( number ) => '' !== number ); |
| 84 | |
| 85 | if ( numbers.length === 0 ) { |
| 86 | setAttributes( { |
| 87 | [ value ]: ! attributes[ value ], |
| 88 | } ); |
| 89 | |
| 90 | return; |
| 91 | } |
| 92 | |
| 93 | const syncValue = Math.max.apply( null, numbers ).toString(); |
| 94 | |
| 95 | setAttributes( { |
| 96 | [ value ]: ! attributes[ value ], |
| 97 | [ attributeNames.top ]: syncValue, |
| 98 | [ attributeNames.right ]: syncValue, |
| 99 | [ attributeNames.bottom ]: syncValue, |
| 100 | [ attributeNames.left ]: syncValue, |
| 101 | } ); |
| 102 | }; |
| 103 | |
| 104 | return ( |
| 105 | <BaseControl className="components-gblocks-dimensions-control"> |
| 106 | <UnitPicker |
| 107 | label={ label } |
| 108 | value={ 'undefined' !== typeof attributes[ attributeNames.unit ] ? attributes[ attributeNames.unit ] : 'px' } |
| 109 | units={ units } |
| 110 | onClick={ ( value ) => onChangeUnits( value ) } |
| 111 | /> |
| 112 | |
| 113 | <div className="components-gblocks-dimensions-control__inputs"> |
| 114 | <div> |
| 115 | <input |
| 116 | id={ attributeNames.top } |
| 117 | className="components-gblocks-dimensions-control__number" |
| 118 | placeholder={ getResponsivePlaceholder( |
| 119 | attributeNames.top, |
| 120 | attributes, |
| 121 | device, |
| 122 | 'margin' === type && 'px' === attributes.marginUnit |
| 123 | ? computedStyles.marginTop |
| 124 | : '' |
| 125 | ) } |
| 126 | type="number" |
| 127 | onChange={ ( event ) => { |
| 128 | let value = event.target.value; |
| 129 | |
| 130 | if ( 'margin' !== type ) { |
| 131 | // No negative values allowed here. |
| 132 | value = value.toString().replace( /-/g, '' ); |
| 133 | } |
| 134 | |
| 135 | if ( attributes[ attributeNames.sync ] ) { |
| 136 | onChangeAll( value ); |
| 137 | } else { |
| 138 | onChangeTop( value ); |
| 139 | } |
| 140 | } } |
| 141 | /* translators: Dimension label (padding, margin, border) */ |
| 142 | aria-label={ sprintf( __( '%s Top', 'generateblocks' ), label ) } |
| 143 | value={ attributes[ attributeNames.top ] || '' } |
| 144 | min={ 'margin' !== type ? 0 : undefined } |
| 145 | /> |
| 146 | |
| 147 | <label htmlFor={ attributeNames.top } className="gblocks-dimensions-control__label">{ labels.top }</label> |
| 148 | </div> |
| 149 | |
| 150 | <div> |
| 151 | <input |
| 152 | id={ attributeNames.right } |
| 153 | className="components-gblocks-dimensions-control__number" |
| 154 | placeholder={ getResponsivePlaceholder( attributeNames.right, attributes, device, '' ) } |
| 155 | type="number" |
| 156 | onChange={ ( event ) => { |
| 157 | let value = event.target.value; |
| 158 | |
| 159 | if ( 'margin' !== type ) { |
| 160 | // No negative values allowed here. |
| 161 | value = value.toString().replace( /-/g, '' ); |
| 162 | } |
| 163 | |
| 164 | if ( attributes[ attributeNames.sync ] ) { |
| 165 | onChangeAll( value ); |
| 166 | } else { |
| 167 | onChangeRight( value ); |
| 168 | } |
| 169 | } } |
| 170 | /* translators: Dimension label (padding, margin, border) */ |
| 171 | aria-label={ sprintf( __( '%s Right', 'generateblocks' ), label ) } |
| 172 | value={ attributes[ attributeNames.right ] || '' } |
| 173 | min={ 'margin' !== type ? 0 : undefined } |
| 174 | /> |
| 175 | |
| 176 | <label htmlFor={ attributeNames.right } className="gblocks-dimensions-control__label">{ labels.right }</label> |
| 177 | </div> |
| 178 | |
| 179 | <div> |
| 180 | <input |
| 181 | id={ attributeNames.bottom } |
| 182 | className="components-gblocks-dimensions-control__number" |
| 183 | placeholder={ getResponsivePlaceholder( |
| 184 | attributeNames.bottom, |
| 185 | attributes, |
| 186 | device, |
| 187 | 'margin' === type && 'px' === attributes.marginUnit |
| 188 | ? computedStyles.marginBottom |
| 189 | : '' |
| 190 | ) } |
| 191 | type="number" |
| 192 | onChange={ ( event ) => { |
| 193 | let value = event.target.value; |
| 194 | |
| 195 | if ( 'margin' !== type ) { |
| 196 | // No negative values allowed here. |
| 197 | value = value.toString().replace( /-/g, '' ); |
| 198 | } |
| 199 | |
| 200 | if ( attributes[ attributeNames.sync ] ) { |
| 201 | onChangeAll( value ); |
| 202 | } else { |
| 203 | onChangeBottom( value ); |
| 204 | } |
| 205 | } } |
| 206 | /* translators: Dimension label (padding, margin, border) */ |
| 207 | aria-label={ sprintf( __( '%s Bottom', 'generateblocks' ), label ) } |
| 208 | value={ attributes[ attributeNames.bottom ] || '' } |
| 209 | min={ 'margin' !== type ? 0 : undefined } |
| 210 | /> |
| 211 | |
| 212 | <label htmlFor={ attributeNames.bottom } className="gblocks-dimensions-control__label">{ labels.bottom }</label> |
| 213 | </div> |
| 214 | |
| 215 | <div> |
| 216 | <input |
| 217 | id={ attributeNames.left } |
| 218 | className="components-gblocks-dimensions-control__number" |
| 219 | placeholder={ getResponsivePlaceholder( attributeNames.left, attributes, device, '' ) } |
| 220 | type="number" |
| 221 | onChange={ ( event ) => { |
| 222 | let value = event.target.value; |
| 223 | |
| 224 | if ( 'margin' !== type ) { |
| 225 | // No negative values allowed here. |
| 226 | value = value.toString().replace( /-/g, '' ); |
| 227 | } |
| 228 | |
| 229 | if ( attributes[ attributeNames.sync ] ) { |
| 230 | onChangeAll( value ); |
| 231 | } else { |
| 232 | onChangeLeft( value ); |
| 233 | } |
| 234 | } } |
| 235 | /* translators: Dimension label (padding, margin, border) */ |
| 236 | aria-label={ sprintf( __( '%s Left', 'generateblocks' ), label ) } |
| 237 | value={ attributes[ attributeNames.left ] || '' } |
| 238 | min={ 'margin' !== type ? 0 : undefined } |
| 239 | /> |
| 240 | |
| 241 | <label htmlFor={ attributeNames.left } className="gblocks-dimensions-control__label">{ labels.left }</label> |
| 242 | </div> |
| 243 | |
| 244 | <Tooltip text={ !! attributes[ attributeNames.sync ] ? __( 'Unlink Sides', 'generateblocks' ) : __( 'Link Sides', 'generateblocks' ) } > |
| 245 | <Button |
| 246 | className="components-gblocks-dimensions-control_sync" |
| 247 | aria-label={ !! attributes[ attributeNames.sync ] ? __( 'Unlink Sides', 'generateblocks' ) : __( 'Link Sides', 'generateblocks' ) } |
| 248 | isPrimary={ attributes[ attributeNames.sync ] ? attributes[ attributeNames.sync ] : false } |
| 249 | aria-pressed={ attributes[ attributeNames.sync ] ? attributes[ attributeNames.sync ] : false } |
| 250 | onClick={ () => syncUnits( attributeNames.sync ) } |
| 251 | isSmall |
| 252 | > |
| 253 | { !! attributes[ attributeNames.sync ] ? link : linkOff } |
| 254 | </Button> |
| 255 | </Tooltip> |
| 256 | </div> |
| 257 | </BaseControl> |
| 258 | ); |
| 259 | } |
| 260 |