PluginProbe ʕ •ᴥ•ʔ
GenerateBlocks / 1.7.1
GenerateBlocks v1.7.1
trunk 1.0 1.0.1 1.0.2 1.1.0 1.1.1 1.1.2 1.2.0 1.3.0 1.3.1 1.3.2 1.3.3 1.3.4 1.3.5 1.4.0 1.4.1 1.4.2 1.4.3 1.4.4 1.5.0 1.5.1 1.5.2 1.5.3 1.5.4 1.6.0 1.7.0 1.7.1 1.7.2 1.7.3 1.8.0 1.8.1 1.8.2 1.8.3 1.9.0 1.9.1 2.0.0 2.0.1 2.0.2 2.1.0 2.1.1 2.1.2 2.2.0 2.2.1 2.3.0
generateblocks / src / components / unit-control / index.js
generateblocks / src / components / unit-control Last commit date
editor.scss 3 years ago index.js 3 years ago
index.js
239 lines
1 /**
2 * External dependencies
3 */
4 import { useEffect, useState, useRef, Fragment } from '@wordpress/element';
5 import { TextControl, BaseControl, ButtonGroup, Button, SlotFillProvider, Popover } from '@wordpress/components';
6 import classnames from 'classnames';
7
8 /**
9 * Internal dependencies
10 */
11
12 import './editor.scss';
13 import getDeviceType from '../../utils/get-device-type';
14
15 export default function UnitControl( props ) {
16 const {
17 label,
18 units = [ 'px', 'em', '%', 'rem' ],
19 min = 0,
20 max,
21 step,
22 id,
23 disabled = false,
24 overrideValue = null,
25 overrideAction = () => null,
26 onChange,
27 value,
28 desktopValue,
29 tabletValue,
30 presets = [],
31 help = '',
32 } = props;
33
34 const device = getDeviceType();
35 const [ unitValue, setUnitValue ] = useState( '' );
36 const [ numericValue, setNumericValue ] = useState( '' );
37 const [ placeholderValue, setPlaceholderValue ] = useState( '' );
38 const [ showPresets, setShowPresets ] = useState( false );
39 const isMounted = useRef( false );
40 const wrapperRef = useRef( false );
41 const inputRef = useRef( false );
42
43 const splitValues = ( values ) => {
44 const unitRegex = units.join( '|' );
45 const splitRegex = new RegExp( `(${ unitRegex })` );
46
47 return values
48 ? values.split( splitRegex ).filter( ( singleValue ) => '' !== singleValue )
49 : [];
50 };
51
52 const getNumericValue = ( values ) => values.length > 0 ? values[ 0 ] : '';
53 const getUnitValue = ( values ) => values.length > 1 ? values[ 1 ] : units[ 0 ];
54 const startsWithNumber = ( number ) => /^\d/.test( number );
55 const desktopValues = splitValues( desktopValue );
56 const tabletValues = splitValues( tabletValue );
57
58 const setPlaceholders = () => {
59 if ( ! value ) {
60 // Set desktop value as placeholder.
61 if ( ! tabletValue ) {
62 if (
63 'Tablet' === device ||
64 (
65 'Mobile' === device &&
66 ( desktopValue || overrideValue )
67 )
68 ) {
69 const overridePlaceholder = splitValues( overrideValue );
70 setPlaceholderValue( getNumericValue( desktopValues.length ? desktopValues : overridePlaceholder ) );
71 setUnitValue( getUnitValue( desktopValues.length ? desktopValues : overridePlaceholder ) );
72 }
73 }
74
75 // Set tablet value as placeholder.
76 if (
77 'Mobile' === device &&
78 tabletValue
79 ) {
80 setPlaceholderValue( getNumericValue( tabletValues ) );
81 setUnitValue( getUnitValue( tabletValues ) );
82 }
83 }
84 };
85
86 // Split the number and unit into two values.
87 useEffect( () => {
88 const newValue = overrideValue && disabled ? overrideValue : value;
89
90 // Split our values if we're starting with a number.
91 if ( startsWithNumber( newValue ) ) {
92 const values = splitValues( newValue );
93
94 setNumericValue( getNumericValue( values ) );
95 setUnitValue( getUnitValue( values ) );
96 } else {
97 setNumericValue( newValue );
98 setUnitValue( '' );
99 }
100
101 // Set the device placeholders and switch the units to match
102 // their parent device value if no device-specific value exists.
103 setPlaceholders();
104 }, [ device, value, overrideValue ] );
105
106 useEffect( () => {
107 // Don't run this on first render.
108 if ( ! isMounted.current ) {
109 isMounted.current = true;
110 return;
111 }
112
113 const hasOverride = !! overrideValue && !! disabled;
114
115 const fullValue = startsWithNumber( numericValue )
116 ? numericValue + unitValue
117 : numericValue;
118
119 // Clear the placeholder if the units don't match.
120 if ( ! fullValue ) {
121 const deviceValues = {
122 Tablet: desktopValues,
123 Mobile: tabletValues,
124 };
125
126 if ( device in deviceValues ) {
127 if ( unitValue !== getUnitValue( deviceValues[ device ] ) ) {
128 setPlaceholderValue( '' );
129 } else {
130 setPlaceholders();
131 }
132 }
133 }
134
135 if ( ! hasOverride && fullValue !== value ) {
136 onChange( fullValue );
137 }
138 }, [ numericValue, unitValue ] );
139
140 const useOutsideClick = ( ref, callback ) => {
141 useEffect( () => {
142 const handleClickOutside = ( evt ) => {
143 if ( ref.current && ! ref.current.contains( evt.target ) ) {
144 callback(); //Do what you want to handle in the callback
145 }
146 };
147
148 document.addEventListener( 'mousedown', handleClickOutside );
149
150 return () => {
151 document.removeEventListener( 'mousedown', handleClickOutside );
152 };
153 } );
154 };
155
156 useOutsideClick( wrapperRef, () => setShowPresets( false ) );
157
158 const doesPresetUnitMatch = Object.keys( presets ).some( ( k ) => {
159 return presets[ k ]?.value.includes( unitValue );
160 } );
161
162 return (
163 <SlotFillProvider>
164 <BaseControl
165 label={ label }
166 help={ help }
167 id={ id }
168 className={ classnames( {
169 'gblocks-unit-control': true,
170 'gblocks-unit-control__disabled': !! disabled,
171 } ) }
172 >
173 <div className="gblocks-unit-control__input" ref={ wrapperRef }>
174 <TextControl
175 type="text"
176 value={ numericValue }
177 placeholder={ placeholderValue }
178 id={ id }
179 min={ min }
180 max={ max }
181 step={ step }
182 autoComplete="off"
183 disabled={ disabled }
184 onChange={ ( newValue ) => setNumericValue( newValue ) }
185 onFocus={ () => setShowPresets( true ) }
186 ref={ inputRef }
187 />
188
189 { !! overrideAction && <div className="gblocks-unit-control__override-action">{ overrideAction() } </div> }
190
191 { (
192 startsWithNumber( numericValue ) ||
193 (
194 ! numericValue &&
195 ( ! placeholderValue || startsWithNumber( placeholderValue ) )
196 )
197 ) &&
198 <span className="gblocks-unit-control__unit-select">
199 <select
200 value={ unitValue }
201 disabled={ disabled || 1 === units.length }
202 onChange={ ( e ) => setUnitValue( e.target.value ) }
203 >
204 { units.map( ( unitOption ) => <option key={ unitOption } value={ unitOption }>{ unitOption }</option> ) }
205 </select>
206 </span>
207 }
208
209 { !! presets.length && !! showPresets && doesPresetUnitMatch &&
210 <Popover focusOnMount={ false } className="gblocks-unit-control__popover">
211 <ButtonGroup className="gblocks-flex-button-group">
212 {
213 Object.values( presets ).map( ( preset, index ) => {
214 return (
215 <Fragment key={ 'sizing-preset-' + index }>
216 <Button
217 isPrimary={ preset.value === value }
218 onClick={ () => {
219 onChange( preset.value );
220 inputRef.current.focus();
221 } }
222 >
223 { preset.label }
224 </Button>
225 </Fragment>
226 );
227 } )
228 }
229 </ButtonGroup>
230 </Popover>
231 }
232
233 <Popover.Slot />
234 </div>
235 </BaseControl>
236 </SlotFillProvider>
237 );
238 }
239