Skip to content
Next Next commit
Refactor AnglePickerControl styles
  • Loading branch information
Jon Q committed Jul 13, 2020
commit 3ab4a3461b8edf60ee1562f3f1ab59f5979078af
93 changes: 93 additions & 0 deletions packages/components/src/angle-picker-control/angle-circle.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/**
* WordPress dependencies
*/
import { useEffect, useRef } from '@wordpress/element';
import { __experimentalUseDragging as useDragging } from '@wordpress/compose';

/**
* Internal dependencies
*/
import {
CircleRoot,
CircleIndicatorWrapper,
CircleIndicator,
} from './styles/angle-picker-control-styles';

function AngleCircle( { value, onChange, ...props } ) {
const angleCircleRef = useRef();
const angleCircleCenter = useRef();
const previousCursorValue = 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;
// Prevent (drag) mouse events from selecting and accidentally
// triggering actions from other elements.
event.preventDefault();

onChange( getAngle( centerX, centerY, event.clientX, event.clientY ) );
};

const { startDrag, isDragging } = useDragging( {
onDragStart: ( event ) => {
setAngleCircleCenter();
changeAngleToPosition( event );
},
onDragMove: changeAngleToPosition,
onDragEnd: changeAngleToPosition,
} );

useEffect( () => {
if ( isDragging ) {
if ( previousCursorValue.current === undefined ) {
previousCursorValue.current = document.body.style.cursor;
}
document.body.style.cursor = 'grabbing';
} else {
document.body.style.cursor = previousCursorValue.current || null;
previousCursorValue.current = undefined;
}
}, [ isDragging ] );

return (
/* eslint-disable jsx-a11y/no-static-element-interactions */
<CircleRoot
ref={ angleCircleRef }
onMouseDown={ startDrag }
className="components-angle-picker-control__angle-circle"
style={ isDragging ? { cursor: 'grabbing' } : undefined }
{ ...props }
>
<CircleIndicatorWrapper
style={
value ? { transform: `rotate(${ value }deg)` } : undefined
}
className="components-angle-picker-control__angle-circle-indicator-wrapper"
>
<CircleIndicator className="components-angle-picker-control__angle-circle-indicator" />
</CircleIndicatorWrapper>
</CircleRoot>
/* eslint-enable jsx-a11y/no-static-element-interactions */
);
}

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;
}

export default AngleCircle;
150 changes: 54 additions & 96 deletions packages/components/src/angle-picker-control/index.js
Original file line number Diff line number Diff line change
@@ -1,116 +1,74 @@
/**
* External dependencies
*/
import classnames from 'classnames';

/**
* WordPress dependencies
*/
import { useRef } from '@wordpress/element';
import {
useInstanceId,
__experimentalUseDragging as useDragging,
} from '@wordpress/compose';
import { __ } from '@wordpress/i18n';
import { useInstanceId } from '@wordpress/compose';

/**
* 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, ...props } ) => {
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;
// Prevent (drag) mouse events from selecting and accidentally
// triggering actions from other elements.
event.preventDefault();

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-control__angle-circle"
style={ isDragging ? { cursor: 'grabbing' } : undefined }
{ ...props }
>
<div
style={
value ? { transform: `rotate(${ value }deg)` } : undefined
}
className="components-angle-picker-control__angle-circle-indicator-wrapper"
>
<span className="components-angle-picker-control__angle-circle-indicator" />
</div>
</div>
/* eslint-enable jsx-a11y/no-static-element-interactions */
);
};
import { FlexBlock } from '../flex';
import NumberControl from '../number-control';
import AngleCircle from './angle-circle';
import {
Root,
NumberControlWrapper,
} from './styles/angle-picker-control-styles';

export default function AnglePickerControl( {
className,
id: idProp,
value,
onChange,
label = __( 'Angle' ),
label,
...props
} ) {
const instanceId = useInstanceId( AnglePickerControl );
const inputId = `components-angle-picker-control__input-${ instanceId }`;
const instanceId = useInstanceId(
AnglePickerControl,
'components-angle-picker-control__input'
);
const id = idProp || instanceId;

const handleOnNumberChange = ( unprocessedValue ) => {
const inputValue =
unprocessedValue !== '' ? parseInt( unprocessedValue, 10 ) : 0;
onChange( inputValue );
};

const classes = classnames( 'components-angle-picker-control', className );

return (
<BaseControl
className={ classes }
id={ id }
label={ label }
id={ inputId }
className="components-angle-picker-control"
{ ...props }
>
<AngleCircle
value={ value }
onChange={ onChange }
aria-hidden="true"
/>
<input
className="components-angle-picker-control__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"
/>
<Root gap={ 3 }>
<NumberControlWrapper>
<NumberControl
className="components-angle-picker-control__input-field"
id={ id }
max={ 360 }
min={ 0 }
onChange={ handleOnNumberChange }
step="1"
value={ value }
/>
</NumberControlWrapper>
<FlexBlock>
<AngleCircle
aria-hidden="true"
value={ value }
onChange={ onChange }
/>
</FlexBlock>
</Root>
</BaseControl>
);
}
43 changes: 0 additions & 43 deletions packages/components/src/angle-picker-control/style.scss

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/**
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you think we should have a new styles folder? Even though you're using css-in-js it's still the equivalent of style.scss which in many components I checked ( didn't check them all :) ) are in the root folder. So it seems to me we could keep it consistent. What do you think?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point! It may no longer be necessary. I originally introduced this structure to make it distinctly different from the style.scss during the early proposal/experimental phase.

I'm happy to keep the style.js files at the root level. It's the same convention I've followed in another React component library project:

https://github.com/ItsJonQ/g2/tree/master/packages/components/src/Button

* External dependencies
*/
import styled from '@emotion/styled';

/**
* Internal dependencies
*/
import { Flex, FlexItem } from '../../flex';
import { color } from '../../utils/style-mixins';

const CIRCLE_SIZE = 30;

export const Root = styled( Flex )`
max-width: 200px;
`;

export const NumberControlWrapper = styled( FlexItem )`
width: 80px;
`;

export const CircleRoot = styled.div`
border-radius: 50%;
border: 1px solid ${ color( 'ui.borderLight' ) };
box-sizing: border-box;
cursor: grab;
float: left;
height: ${ CIRCLE_SIZE }px;
width: ${ CIRCLE_SIZE }px;
`;

export const CircleIndicatorWrapper = styled.div`
box-sizing: border-box;
position: relative;
width: 100%;
height: 100%;
`;

export const CircleIndicator = styled.div`
background: ${ color( 'ui.border' ) };
border-radius: 50%;
border: 3px solid ${ color( 'ui.border' ) };
bottom: 0;
box-sizing: border-box;
display: block;
height: 1px;
left: 0;
margin: auto;
position: absolute;
right: 0;
top: -${ CIRCLE_SIZE / 2 }px;
width: 1px;
`;
1 change: 0 additions & 1 deletion packages/components/src/style.scss
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
@import "./animate/style.scss";
@import "./angle-picker-control/style.scss";
@import "./autocomplete/style.scss";
@import "./base-control/style.scss";
@import "./button-group/style.scss";
Expand Down