From b82f1478dc51b36cd7da06ec51a35a97044f5198 Mon Sep 17 00:00:00 2001 From: Jorge Date: Tue, 14 Jan 2020 15:14:03 +0000 Subject: [PATCH] Add AnglePicker Component; Add useDragging hook --- packages/components/src/angle-picker/index.js | 98 +++++++++++++++++++ .../src/angle-picker/stories/index.js | 23 +++++ .../components/src/angle-picker/style.scss | 42 ++++++++ packages/components/src/index.js | 1 + packages/components/src/style.scss | 1 + .../compose/src/hooks/use-dragging/index.js | 53 ++++++++++ packages/compose/src/index.js | 1 + storybook/test/__snapshots__/index.js.snap | 38 +++++++ 8 files changed, 257 insertions(+) create mode 100644 packages/components/src/angle-picker/index.js create mode 100644 packages/components/src/angle-picker/stories/index.js create mode 100644 packages/components/src/angle-picker/style.scss create mode 100644 packages/compose/src/hooks/use-dragging/index.js diff --git a/packages/components/src/angle-picker/index.js b/packages/components/src/angle-picker/index.js new file mode 100644 index 0000000000000..c6faf2ad200d7 --- /dev/null +++ b/packages/components/src/angle-picker/index.js @@ -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 */ +
+
+ +
+
+ /* 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 ( + + + { + const unprocessedValue = event.target.value; + const inputValue = unprocessedValue !== '' ? + parseInt( event.target.value, 10 ) : + 0; + onChange( inputValue ); + } } + value={ value } + min={ 0 } + max={ 360 } + step="1" + /> + + ); +} + diff --git a/packages/components/src/angle-picker/stories/index.js b/packages/components/src/angle-picker/stories/index.js new file mode 100644 index 0000000000000..445ef54907b16 --- /dev/null +++ b/packages/components/src/angle-picker/stories/index.js @@ -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 ( + + ); +}; + +export const _default = () => { + return ( ); +}; diff --git a/packages/components/src/angle-picker/style.scss b/packages/components/src/angle-picker/style.scss new file mode 100644 index 0000000000000..c1fdd0c53bc74 --- /dev/null +++ b/packages/components/src/angle-picker/style.scss @@ -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; +} diff --git a/packages/components/src/index.js b/packages/components/src/index.js index d59d33984f16c..13df1cffea680 100644 --- a/packages/components/src/index.js +++ b/packages/components/src/index.js @@ -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'; diff --git a/packages/components/src/style.scss b/packages/components/src/style.scss index f99717ec65d84..a4728386ed030 100644 --- a/packages/components/src/style.scss +++ b/packages/components/src/style.scss @@ -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"; diff --git a/packages/compose/src/hooks/use-dragging/index.js b/packages/compose/src/hooks/use-dragging/index.js new file mode 100644 index 0000000000000..94794df07dbf5 --- /dev/null +++ b/packages/compose/src/hooks/use-dragging/index.js @@ -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, + }; +} diff --git a/packages/compose/src/index.js b/packages/compose/src/index.js index 5fce542d28e0e..3a1aabde92cfe 100644 --- a/packages/compose/src/index.js +++ b/packages/compose/src/index.js @@ -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'; diff --git a/storybook/test/__snapshots__/index.js.snap b/storybook/test/__snapshots__/index.js.snap index 801fd215521b4..59af6af753a19 100644 --- a/storybook/test/__snapshots__/index.js.snap +++ b/storybook/test/__snapshots__/index.js.snap @@ -5618,3 +5618,41 @@ Array [ , ] `; + +exports[`Storyshots Components|AnglePicker Default 1`] = ` +
+
+ +
+
+ +
+
+ +
+
+`;