index.js
236 lines
| 1 | /** |
| 2 | * External dependencies |
| 3 | */ |
| 4 | import { useState } from '@wordpress/element'; |
| 5 | import { settings as settingsIcon } from '@wordpress/icons'; |
| 6 | import { __ } from '@wordpress/i18n'; |
| 7 | import { applyFilters } from '@wordpress/hooks'; |
| 8 | import { |
| 9 | TextControl, |
| 10 | ButtonGroup, |
| 11 | Button, |
| 12 | BaseControl, |
| 13 | Tooltip, |
| 14 | } from '@wordpress/components'; |
| 15 | import useLocalStorageState from 'use-local-storage-state'; |
| 16 | |
| 17 | /** |
| 18 | * Internal dependencies |
| 19 | */ |
| 20 | import UnitPicker from '../unit-picker'; |
| 21 | import hasNumericValue from '../../utils/has-numeric-value'; |
| 22 | import getResponsivePlaceholder from '../../utils/get-responsive-placeholder'; |
| 23 | import './editor.scss'; |
| 24 | |
| 25 | export default function NumberControl( props ) { |
| 26 | const { |
| 27 | label, |
| 28 | attributeName, |
| 29 | attributes, |
| 30 | setAttributes, |
| 31 | units = [], |
| 32 | unit = 'px', |
| 33 | device, |
| 34 | presets = [], |
| 35 | min = 0, |
| 36 | max, |
| 37 | step, |
| 38 | id = attributeName, |
| 39 | defaultPlaceholder = '', |
| 40 | } = props; |
| 41 | |
| 42 | const [ isCustom, setCustom ] = useState( false ); |
| 43 | |
| 44 | const [ inputPreferences, setInputPreferences ] = useLocalStorageState( |
| 45 | 'generateblocksCustomInputs', { |
| 46 | ssr: true, |
| 47 | defaultValue: [], |
| 48 | } |
| 49 | ); |
| 50 | |
| 51 | const attributeNames = { |
| 52 | value: attributeName, |
| 53 | unit: attributeName + 'Unit', |
| 54 | }; |
| 55 | |
| 56 | if ( device && 'Desktop' !== device ) { |
| 57 | attributeNames.value += device; |
| 58 | } |
| 59 | |
| 60 | const allPresets = applyFilters( |
| 61 | 'generateblocns.editor.numberPresets', |
| 62 | presets, |
| 63 | props, |
| 64 | ); |
| 65 | |
| 66 | const presetData = allPresets.length > 0 ? allPresets[ 0 ].data : []; |
| 67 | const presetUnit = allPresets.length > 0 ? allPresets[ 0 ].unit : 'px'; |
| 68 | |
| 69 | const presetsHaveValue = presetData.length > 0 && 'object' === typeof presetData[ 0 ] |
| 70 | ? presetData.find( ( preset ) => { |
| 71 | return preset.value === attributes[ attributeNames.value ]; |
| 72 | } ) |
| 73 | : presetData.includes( attributes[ attributeNames.value ] ); |
| 74 | |
| 75 | const hasParentValue = 'Desktop' !== device && |
| 76 | getResponsivePlaceholder( attributeNames.value, attributes, device, '' ); |
| 77 | |
| 78 | const showCustom = allPresets.length === 0 || |
| 79 | ( |
| 80 | !! hasNumericValue( attributes[ attributeNames.value ] ) && |
| 81 | ! presetsHaveValue |
| 82 | ) || |
| 83 | hasParentValue || |
| 84 | isCustom || |
| 85 | inputPreferences.some( ( pref ) => pref.includes( attributeName ) ); |
| 86 | |
| 87 | return ( |
| 88 | <BaseControl |
| 89 | label={ units.length === 0 ? label : null } |
| 90 | id={ id } |
| 91 | className="gblocks-number-component" |
| 92 | > |
| 93 | { units.length > 0 && |
| 94 | <UnitPicker |
| 95 | label={ label } |
| 96 | id={ id } |
| 97 | value={ attributes[ attributeNames.unit ] || unit } |
| 98 | units={ units } |
| 99 | singleOption={ ! showCustom } |
| 100 | onClick={ ( value ) => { |
| 101 | if ( 'undefined' !== typeof attributes[ attributeNames.unit ] ) { |
| 102 | setAttributes( { |
| 103 | [ attributeNames.unit ]: value, |
| 104 | } ); |
| 105 | } |
| 106 | |
| 107 | return false; |
| 108 | } } |
| 109 | /> |
| 110 | } |
| 111 | |
| 112 | { ! showCustom && |
| 113 | <ButtonGroup className="gblocks-component-number-presets"> |
| 114 | { |
| 115 | presetData.map( ( preset, index ) => { |
| 116 | const presetValue = 'object' === typeof preset |
| 117 | ? preset.value |
| 118 | : preset; |
| 119 | |
| 120 | const presetLabel = 'object' === typeof preset |
| 121 | ? preset.label |
| 122 | : preset; |
| 123 | |
| 124 | return ( |
| 125 | <Button |
| 126 | key={ index } |
| 127 | isPrimary={ |
| 128 | presetValue === attributes[ attributeNames.value ] || |
| 129 | ( presetValue === defaultPlaceholder && ! hasNumericValue( attributes[ attributeNames.value ] ) ) |
| 130 | } |
| 131 | onClick={ () => { |
| 132 | if ( attributes[ attributeNames.value ] !== presetValue ) { |
| 133 | setAttributes( { |
| 134 | [ attributeNames.value ]: presetValue, |
| 135 | [ attributeNames.unit ]: presetUnit, |
| 136 | } ); |
| 137 | } else { |
| 138 | setAttributes( { [ attributeNames.value ]: '' } ); |
| 139 | } |
| 140 | |
| 141 | // Disable our legacy row gap option in the Grid block if this option is changed. |
| 142 | // Since 1.7.0. |
| 143 | if ( 'verticalGap' === attributeName ) { |
| 144 | setAttributes( { useLegacyRowGap: false } ); |
| 145 | } |
| 146 | } } |
| 147 | > |
| 148 | { presetLabel } |
| 149 | </Button> |
| 150 | ); |
| 151 | } ) |
| 152 | } |
| 153 | |
| 154 | <Tooltip text={ __( 'Custom', 'generateblocks' ) }> |
| 155 | <Button |
| 156 | icon={ settingsIcon } |
| 157 | onClick={ () => { |
| 158 | setCustom( true ); |
| 159 | setInputPreferences( [ ...inputPreferences, attributeName ] ); |
| 160 | } } |
| 161 | /> |
| 162 | </Tooltip> |
| 163 | </ButtonGroup> |
| 164 | } |
| 165 | |
| 166 | { showCustom && |
| 167 | <div className="gblocks-number-component__input"> |
| 168 | <TextControl |
| 169 | type="number" |
| 170 | value={ hasNumericValue( attributes[ attributeNames.value ] ) ? attributes[ attributeNames.value ] : '' } |
| 171 | placeholder={ getResponsivePlaceholder( attributeNames.value, attributes, device, defaultPlaceholder ) } |
| 172 | id={ id } |
| 173 | min={ min } |
| 174 | max={ max } |
| 175 | step={ step } |
| 176 | autoComplete="off" |
| 177 | onChange={ ( value ) => { |
| 178 | if ( min >= 0 ) { |
| 179 | // No hyphens allowed here. |
| 180 | value = value.toString().replace( /-/g, '' ); |
| 181 | } |
| 182 | |
| 183 | setAttributes( { |
| 184 | [ attributeNames.value ]: value, |
| 185 | } ); |
| 186 | } } |
| 187 | onBlur={ () => { |
| 188 | if ( |
| 189 | '' !== attributes[ attributeNames.value ] && |
| 190 | false !== attributes[ attributeNames.value ] |
| 191 | ) { |
| 192 | setAttributes( { |
| 193 | [ attributeNames.value ]: parseFloat( attributes[ attributeNames.value ] ), |
| 194 | } ); |
| 195 | } |
| 196 | |
| 197 | // Disable our legacy row gap option in the Grid block if this option is changed. |
| 198 | // Since 1.7.0. |
| 199 | if ( 'verticalGap' === attributeName ) { |
| 200 | setAttributes( { useLegacyRowGap: false } ); |
| 201 | } |
| 202 | } } |
| 203 | onClick={ ( e ) => { |
| 204 | // Make sure onBlur fires in Firefox. |
| 205 | e.currentTarget.focus(); |
| 206 | } } |
| 207 | /> |
| 208 | |
| 209 | { |
| 210 | allPresets.length > 0 && |
| 211 | ( |
| 212 | presetsHaveValue || |
| 213 | ! hasNumericValue( attributes[ attributeNames.value ] ) |
| 214 | ) && |
| 215 | ! hasParentValue && |
| 216 | <Tooltip text={ __( 'Presets', 'generateblocks' ) }> |
| 217 | <Button |
| 218 | icon={ settingsIcon } |
| 219 | onClick={ () => { |
| 220 | setCustom( false ); |
| 221 | |
| 222 | setAttributes( { |
| 223 | [ attributeNames.unit ]: presetUnit, |
| 224 | } ); |
| 225 | |
| 226 | setInputPreferences( inputPreferences.filter( ( pref ) => pref !== attributeName ) ); |
| 227 | } } |
| 228 | /> |
| 229 | </Tooltip> |
| 230 | } |
| 231 | </div> |
| 232 | } |
| 233 | </BaseControl> |
| 234 | ); |
| 235 | } |
| 236 |