Skip to content

Commit

Permalink
Add AnglePicker Component; Add useDragging hook
Browse files Browse the repository at this point in the history
  • Loading branch information
jorgefilipecosta committed Jan 17, 2020
1 parent c8a1eef commit b82f147
Show file tree
Hide file tree
Showing 8 changed files with 257 additions and 0 deletions.
98 changes: 98 additions & 0 deletions packages/components/src/angle-picker/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
/**
* WordPress dependencies
*/
import { useRef } from '@wordpress/element';
import { useInstanceId, __experimentalUseDragging as useDragging } from '@wordpress/compose';
import { __ } from '@wordpress/i18n';

/**
* Internal dependencies
*/
import BaseControl from '../base-control';

function getAngle( centerX, centerY, pointX, pointY ) {
const y = pointY - centerY;
const x = pointX - centerX;

const angleInRadians = Math.atan2( y, x );
const angleInDeg = Math.round( angleInRadians * ( 180 / Math.PI ) ) + 90;
if ( angleInDeg < 0 ) {
return 360 + angleInDeg;
}
return angleInDeg;
}

const AngleCircle = ( { value, onChange } ) => {
const angleCircleRef = useRef();
const angleCircleCenter = useRef();

const setAngleCircleCenter = () => {
const rect = angleCircleRef.current.getBoundingClientRect();
angleCircleCenter.current = {
x: rect.x + ( rect.width / 2 ),
y: rect.y + ( rect.height / 2 ),
};
};

const changeAngleToPosition = ( event ) => {
const { x: centerX, y: centerY } = angleCircleCenter.current;
onChange( getAngle( centerX, centerY, event.clientX, event.clientY ) );
};

const { startDrag, isDragging } = useDragging( {
onDragStart: ( event ) => {
setAngleCircleCenter();
changeAngleToPosition( event );
},
onDragMove: changeAngleToPosition,
onDragEnd: changeAngleToPosition,
} );
return (
/* eslint-disable jsx-a11y/no-static-element-interactions */
<div
ref={ angleCircleRef }
onMouseDown={ startDrag }
className="components-angle-picker__angle-circle"
style={ isDragging ? { cursor: 'grabbing' } : undefined }
>
<div
style={ value ? { transform: `rotate(${ value }deg)` } : undefined }
className="components-angle-picker__angle-circle-indicator-wrapper"
>
<span className="components-angle-picker__angle-circle-indicator" />
</div>
</div>
/* eslint-enable jsx-a11y/no-static-element-interactions */
);
};

export default function AnglePicker( { value, onChange, label = __( 'Angle' ) } ) {
const instanceId = useInstanceId( AnglePicker );
const inputId = `components-custom-gradient-picker__angle-picker-${ instanceId }`;
return (
<BaseControl
label={ label }
id={ inputId }
className="components-angle-picker"
>
<AngleCircle value={ value } onChange={ onChange } />
<input
className="components-angle-picker__input-field"
type="number"
id={ inputId }
onChange={ ( event ) => {
const unprocessedValue = event.target.value;
const inputValue = unprocessedValue !== '' ?
parseInt( event.target.value, 10 ) :
0;
onChange( inputValue );
} }
value={ value }
min={ 0 }
max={ 360 }
step="1"
/>
</BaseControl>
);
}

23 changes: 23 additions & 0 deletions packages/components/src/angle-picker/stories/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@

/**
* WordPress dependencies
*/
import { useState } from '@wordpress/element';

/**
* Internal dependencies
*/
import AnglePicker from '../';

export default { title: 'Components|AnglePicker', component: AnglePicker };

const AnglePickerWithState = () => {
const [ angle, setAngle ] = useState();
return (
<AnglePicker value={ angle } onChange={ setAngle } />
);
};

export const _default = () => {
return ( <AnglePickerWithState /> );
};
42 changes: 42 additions & 0 deletions packages/components/src/angle-picker/style.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
.components-angle-picker {
width: 50%;
&.components-base-control .components-base-control__label {
display: block;
}
}

.components-angle-picker__input-field {
width: calc(100% - #{$icon-button-size});
max-width: 100px;
}

.components-angle-picker__angle-circle {
width: $icon-button-size - ( 2 * $grid-size-small );
height: $icon-button-size - ( 2 * $grid-size-small );
border: 2px solid $dark-gray-500;
border-radius: 50%;
float: left;
margin-right: $grid-size-small;
cursor: grab;
}

.components-angle-picker__angle-circle-indicator-wrapper {
position: relative;
width: 100%;
height: 100%;
}

.components-angle-picker__angle-circle-indicator {
width: 1px;
height: 1px;
border-radius: 50%;
border: 3px solid $dark-gray-500;
display: block;
position: absolute;
top: -($icon-button-size - (2 * $grid-size-small)) / 2;
bottom: 0;
left: 0;
right: 0;
margin: auto;
background: $dark-gray-500;
}
1 change: 1 addition & 0 deletions packages/components/src/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Components
export * from './primitives';
export { default as Animate } from './animate';
export { default as __experimentalAnglePicker } from './angle-picker';
export { default as Autocomplete } from './autocomplete';
export { default as BaseControl } from './base-control';
export { default as Button } from './button';
Expand Down
1 change: 1 addition & 0 deletions packages/components/src/style.scss
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
@import "./animate/style.scss";
@import "./angle-picker/style.scss";
@import "./autocomplete/style.scss";
@import "./base-control/style.scss";
@import "./button-group/style.scss";
Expand Down
53 changes: 53 additions & 0 deletions packages/compose/src/hooks/use-dragging/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/**
* WordPress dependencies
*/
import { useEffect, useCallback, useState, useRef } from '@wordpress/element';

export default function useDragging( { onDragStart, onDragMove, onDragEnd } ) {
const [ isDragging, setIsDragging ] = useState( false );

const eventsRef = useRef( {
onDragStart,
onDragMove,
onDragEnd,
} );
useEffect(
() => {
eventsRef.current.onDragStart = onDragStart;
eventsRef.current.onDragMove = onDragMove;
eventsRef.current.onDragEnd = onDragEnd;
},
[ onDragStart, onDragMove, onDragEnd ]
);

const startDrag = useCallback( ( ...args ) => {
if ( eventsRef.current.onDragEnd ) {
eventsRef.current.onDragStart( ...args );
}
setIsDragging( true );
} );
const onMouseMove = useCallback( ( ...args ) => ( eventsRef.current.onDragMove && eventsRef.current.onDragMove( ...args ) ) );
const endDrag = useCallback( ( ...args ) => {
if ( eventsRef.current.onDragEnd ) {
eventsRef.current.onDragEnd( ...args );
}
setIsDragging( false );
} );

useEffect( () => {
if ( isDragging ) {
document.addEventListener( 'mousemove', onMouseMove );
document.addEventListener( 'mouseup', endDrag );
}

return () => {
document.removeEventListener( 'mousemove', onMouseMove );
document.removeEventListener( 'mouseup', endDrag );
};
}, [ isDragging ] );
return {
startDrag,
endDrag,
isDragging,
};
}
1 change: 1 addition & 0 deletions packages/compose/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export { default as withSafeTimeout } from './higher-order/with-safe-timeout';
export { default as withState } from './higher-order/with-state';

// Hooks
export { default as __experimentalUseDragging } from './hooks/use-dragging';
export { default as useInstanceId } from './hooks/use-instance-id';
export { default as useKeyboardShortcut } from './hooks/use-keyboard-shortcut';
export { default as useMediaQuery } from './hooks/use-media-query';
Expand Down
38 changes: 38 additions & 0 deletions storybook/test/__snapshots__/index.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -5618,3 +5618,41 @@ Array [
</div>,
]
`;

exports[`Storyshots Components|AnglePicker Default 1`] = `
<div
className="components-base-control components-angle-picker"
>
<div
className="components-base-control__field"
>
<label
className="components-base-control__label"
htmlFor="components-custom-gradient-picker__angle-picker-0"
>
Angle
</label>
<div
className="components-angle-picker__angle-circle"
onMouseDown={[Function]}
>
<div
className="components-angle-picker__angle-circle-indicator-wrapper"
>
<span
className="components-angle-picker__angle-circle-indicator"
/>
</div>
</div>
<input
className="components-angle-picker__input-field"
id="components-custom-gradient-picker__angle-picker-0"
max={360}
min={0}
onChange={[Function]}
step="1"
type="number"
/>
</div>
</div>
`;

0 comments on commit b82f147

Please sign in to comment.