Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improves RTL style conversion #20503

Merged
merged 8 commits into from
Mar 12, 2020
Merged
74 changes: 55 additions & 19 deletions packages/components/src/utils/rtl.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,48 +2,84 @@
* External dependencies
*/
import { css } from '@emotion/core';
import { mapKeys } from 'lodash';

const LOWER_LEFT_REGEXP = new RegExp( /-left/g );
const LOWER_RIGHT_REGEXP = new RegExp( /-right/g );
const UPPER_LEFT_REGEXP = new RegExp( /Left/g );
const UPPER_RIGHT_REGEXP = new RegExp( /Right/g );

/**
* Checks to see whether the document is set to rtl.
*
* @return {boolean} Whether document is RTL.
*/
function getRtl() {
return !! ( document && document.documentElement.dir === 'rtl' );
}

/**
* Simple hook to retrieve RTL direction value
*
* @return {boolean} Whether document is RTL.
*/
export function useRtl() {
return getRtl();
}

/**
* Flips a CSS property from left <-> right.
*
* @param {string} key The CSS property name.
*
* @return {string} The flipped CSS property name, if applicable.
*/
function getConvertedKey( key ) {
if ( key === 'left' ) {
return 'right';
}

if ( key === 'right' ) {
return 'left';
}

if ( LOWER_LEFT_REGEXP.test( key ) ) {
return key.replace( LOWER_LEFT_REGEXP, '-right' );
}

if ( LOWER_RIGHT_REGEXP.test( key ) ) {
return key.replace( LOWER_RIGHT_REGEXP, '-left' );
}

if ( UPPER_LEFT_REGEXP.test( key ) ) {
return key.replace( UPPER_LEFT_REGEXP, 'Right' );
}

if ( UPPER_RIGHT_REGEXP.test( key ) ) {
return key.replace( UPPER_RIGHT_REGEXP, 'Left' );
}

return key;
}

/**
* An incredibly basic ltr -> rtl converter for style properties
*
* @param {Object} ltrStyles
*
* @return {Object} Converted ltr -> rtl styles
*/
const convertLtrToRtl = ( ltrStyles = {} ) => {
const nextStyles = {};

for ( const key in ltrStyles ) {
const value = ltrStyles[ key ];
let nextKey = key;
if ( /left/gi.test( key ) ) {
nextKey = [ key.replace( 'left', 'right' ) ];
}
if ( /Left/gi.test( key ) ) {
nextKey = [ key.replace( 'Left', 'Right' ) ];
}
nextStyles[ nextKey ] = value;
}

return nextStyles;
export const convertLTRToRTL = ( ltrStyles = {} ) => {
return mapKeys( ltrStyles, ( _value, key ) => getConvertedKey( key ) );
};

/**
* An incredibly basic ltr -> rtl style converter for CSS objects.
* A higher-order function that create an incredibly basic ltr -> rtl style converter for CSS objects.
*
* @param {Object} ltrStyles Ltr styles. Converts and renders from ltr -> rtl styles, if applicable.
* @param {null|Object} rtlStyles Rtl styles. Renders if provided.
* @return {Object} Rendered CSS styles for Emotion's renderer
*
* @return {Function} A function to output CSS styles for Emotion's renderer
*/
export function rtl( ltrStyles = {}, rtlStyles ) {
return () => {
Expand All @@ -53,6 +89,6 @@ export function rtl( ltrStyles = {}, rtlStyles ) {
return isRtl ? css( rtlStyles ) : css( ltrStyles );
}

return isRtl ? css( convertLtrToRtl( ltrStyles ) ) : css( ltrStyles );
return isRtl ? css( convertLTRToRTL( ltrStyles ) ) : css( ltrStyles );
};
}
124 changes: 124 additions & 0 deletions packages/components/src/utils/test/rtl.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
/**
* Internal dependencies
*/
import { convertLTRToRTL } from '../rtl';

describe( 'convertLTRToRTL', () => {
it( 'converts (*)Left <-> (*)Right', () => {
const style = {
// left values
borderLeft: '10px solid red',
borderLeftColor: 'red',
borderLeftStyle: 'solid',
borderLeftWidth: 10,
borderTopLeftRadius: 10,
marginLeft: 10,
scrollMarginLeft: 10,
scrollPaddingLeft: 10,
// right values
paddingLeft: 10,
borderRight: '20px solid blue',
borderRightColor: 'blue',
borderRightStyle: 'dashed',
borderRightWidth: 20,
borderTopRightRadius: 20,
marginRight: 20,
paddingRight: 20,
scrollMarginRight: 20,
scrollPaddingRight: 20,
// edge cases
textCombineUpright: 'none',
};
const nextStyle = convertLTRToRTL( style );

expect( Object.keys( style ).length ).toBe(
Object.keys( nextStyle ).length
);

// Left -> Right
expect( nextStyle.borderRight ).toBe( '10px solid red' );
expect( nextStyle.borderRightColor ).toBe( 'red' );
expect( nextStyle.borderRightStyle ).toBe( 'solid' );
expect( nextStyle.borderRightWidth ).toBe( 10 );
expect( nextStyle.borderTopRightRadius ).toBe( 10 );
expect( nextStyle.marginRight ).toBe( 10 );
expect( nextStyle.paddingRight ).toBe( 10 );
expect( nextStyle.scrollMarginRight ).toBe( 10 );
expect( nextStyle.scrollPaddingRight ).toBe( 10 );

// Right -> Left
expect( nextStyle.borderLeft ).toBe( '20px solid blue' );
expect( nextStyle.borderLeftColor ).toBe( 'blue' );
expect( nextStyle.borderLeftStyle ).toBe( 'dashed' );
expect( nextStyle.borderLeftWidth ).toBe( 20 );
expect( nextStyle.borderTopLeftRadius ).toBe( 20 );
expect( nextStyle.marginLeft ).toBe( 20 );
expect( nextStyle.paddingLeft ).toBe( 20 );
expect( nextStyle.scrollMarginLeft ).toBe( 20 );
expect( nextStyle.scrollPaddingLeft ).toBe( 20 );

// Edge cases
expect( nextStyle.textCombineUpright ).toBe( 'none' );
} );

it( 'converts (*)left <-> (*)right', () => {
const style = {
// left values
'border-left': '10px solid red',
'border-left-color': 'red',
'border-left-style': 'solid',
'border-left-width': 10,
'border-top-left-radius': 10,
'margin-left': 10,
'padding-left': 10,
'scroll-margin-left': 10,
'scroll-padding-left': 10,
left: 10,
// right values
'border-right': '20px solid blue',
'border-right-color': 'blue',
'border-right-style': 'dashed',
'border-right-width': 20,
'border-top-right-radius': 20,
'margin-right': 20,
'padding-right': 20,
'scroll-margin-right': 20,
'scroll-padding-right': 20,
right: 20,
// edge cases
'text-combine-upright': 'none',
};
const nextStyle = convertLTRToRTL( style );

expect( Object.keys( style ).length ).toBe(
Object.keys( nextStyle ).length
);

// left -> right
expect( nextStyle[ 'border-right' ] ).toBe( '10px solid red' );
expect( nextStyle[ 'border-right-color' ] ).toBe( 'red' );
expect( nextStyle[ 'border-right-style' ] ).toBe( 'solid' );
expect( nextStyle[ 'border-right-width' ] ).toBe( 10 );
expect( nextStyle[ 'border-top-right-radius' ] ).toBe( 10 );
expect( nextStyle[ 'margin-right' ] ).toBe( 10 );
expect( nextStyle[ 'padding-right' ] ).toBe( 10 );
expect( nextStyle[ 'scroll-margin-right' ] ).toBe( 10 );
expect( nextStyle[ 'scroll-padding-right' ] ).toBe( 10 );
expect( nextStyle.right ).toBe( 10 );

// right -> left
expect( nextStyle[ 'border-left' ] ).toBe( '20px solid blue' );
expect( nextStyle[ 'border-left-color' ] ).toBe( 'blue' );
expect( nextStyle[ 'border-left-style' ] ).toBe( 'dashed' );
expect( nextStyle[ 'border-left-width' ] ).toBe( 20 );
expect( nextStyle[ 'border-top-left-radius' ] ).toBe( 20 );
expect( nextStyle[ 'margin-left' ] ).toBe( 20 );
expect( nextStyle[ 'padding-left' ] ).toBe( 20 );
expect( nextStyle[ 'scroll-margin-left' ] ).toBe( 20 );
expect( nextStyle[ 'scroll-padding-left' ] ).toBe( 20 );
expect( nextStyle.left ).toBe( 20 );

// Edge cases
expect( nextStyle[ 'text-combine-upright' ] ).toBe( 'none' );
} );
} );