rebase from live enviornment
This commit is contained in:
@@ -0,0 +1,10 @@
|
||||
export * from './use-container-queries';
|
||||
export * from './use-local-storage-state';
|
||||
export * from './use-position-relative-to-viewport';
|
||||
export * from './use-previous';
|
||||
export * from './use-shallow-equal';
|
||||
export * from './use-throw-error';
|
||||
export * from './use-typography-props';
|
||||
export * from './use-is-mounted';
|
||||
export * from './use-spoken-message';
|
||||
export * from './use-style-props';
|
||||
@@ -0,0 +1,86 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { render, screen, act } from '@testing-library/react';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { usePositionRelativeToViewport } from '../use-position-relative-to-viewport';
|
||||
|
||||
describe( 'usePositionRelativeToViewport', () => {
|
||||
function setup() {
|
||||
const TestComponent = () => {
|
||||
const [ referenceElement, positionRelativeToViewport ] =
|
||||
usePositionRelativeToViewport();
|
||||
|
||||
return (
|
||||
<>
|
||||
{ referenceElement }
|
||||
{ positionRelativeToViewport === 'below' && (
|
||||
<p data-testid="below"></p>
|
||||
) }
|
||||
{ positionRelativeToViewport === 'visible' && (
|
||||
<p data-testid="visible"></p>
|
||||
) }
|
||||
{ positionRelativeToViewport === 'above' && (
|
||||
<p data-testid="above"></p>
|
||||
) }
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
return render( <TestComponent /> );
|
||||
}
|
||||
|
||||
it( "calls IntersectionObserver's `observe` and `unobserve` events", async () => {
|
||||
const observe = jest.fn();
|
||||
const unobserve = jest.fn();
|
||||
|
||||
// @ts-ignore
|
||||
IntersectionObserver = jest.fn( () => ( {
|
||||
observe,
|
||||
unobserve,
|
||||
} ) );
|
||||
|
||||
const { unmount } = setup();
|
||||
|
||||
expect( observe ).toHaveBeenCalled();
|
||||
unmount();
|
||||
expect( unobserve ).toHaveBeenCalled();
|
||||
} );
|
||||
|
||||
it.each`
|
||||
position | isIntersecting | top
|
||||
${ 'visible' } | ${ true } | ${ 0 }
|
||||
${ 'below' } | ${ false } | ${ 10 }
|
||||
${ 'above' } | ${ false } | ${ 0 }
|
||||
${ 'above' } | ${ false } | ${ -10 }
|
||||
`(
|
||||
"position relative to viewport is '$position' with isIntersecting=$isIntersecting and top=$top",
|
||||
( { position, isIntersecting, top } ) => {
|
||||
let intersectionObserverCallback = ( entries ) => entries;
|
||||
|
||||
// @ts-ignore
|
||||
IntersectionObserver = jest.fn( ( callback ) => {
|
||||
// @ts-ignore
|
||||
intersectionObserverCallback = callback;
|
||||
|
||||
return {
|
||||
observe: () => void null,
|
||||
unobserve: () => void null,
|
||||
};
|
||||
} );
|
||||
|
||||
setup();
|
||||
|
||||
act( () => {
|
||||
intersectionObserverCallback( [
|
||||
{ isIntersecting, boundingClientRect: { top } },
|
||||
] );
|
||||
} );
|
||||
|
||||
expect( screen.getAllByTestId( position ) ).toHaveLength( 1 );
|
||||
}
|
||||
);
|
||||
} );
|
||||
@@ -0,0 +1,98 @@
|
||||
// We need to disable the following eslint check as it's only applicable
|
||||
// to testing-library/react not `react-test-renderer` used here
|
||||
/* eslint-disable testing-library/await-async-query */
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import TestRenderer, { act } from 'react-test-renderer';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { usePrevious } from '../use-previous';
|
||||
|
||||
describe( 'usePrevious', () => {
|
||||
const TestComponent = ( { testValue, validation } ) => {
|
||||
const previousValue = usePrevious( testValue, validation );
|
||||
return (
|
||||
<div
|
||||
data-testValue={ testValue }
|
||||
data-previousValue={ previousValue }
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
let renderer;
|
||||
beforeEach( () => ( renderer = null ) );
|
||||
|
||||
it( 'should be undefined at first pass', () => {
|
||||
act( () => {
|
||||
renderer = TestRenderer.create( <TestComponent testValue={ 1 } /> );
|
||||
} );
|
||||
const testValue =
|
||||
renderer.root.findByType( 'div' ).props[ 'data-testValue' ];
|
||||
const testPreviousValue =
|
||||
renderer.root.findByType( 'div' ).props[ 'data-previousValue' ];
|
||||
|
||||
expect( testValue ).toBe( 1 );
|
||||
expect( testPreviousValue ).toBe( undefined );
|
||||
} );
|
||||
|
||||
it( 'test new and previous value', () => {
|
||||
let testValue;
|
||||
let testPreviousValue;
|
||||
act( () => {
|
||||
renderer = TestRenderer.create( <TestComponent testValue={ 1 } /> );
|
||||
} );
|
||||
|
||||
act( () => {
|
||||
renderer.update( <TestComponent testValue={ 2 } /> );
|
||||
} );
|
||||
testValue = renderer.root.findByType( 'div' ).props[ 'data-testValue' ];
|
||||
testPreviousValue =
|
||||
renderer.root.findByType( 'div' ).props[ 'data-previousValue' ];
|
||||
expect( testValue ).toBe( 2 );
|
||||
expect( testPreviousValue ).toBe( 1 );
|
||||
|
||||
act( () => {
|
||||
renderer.update( <TestComponent testValue={ 3 } /> );
|
||||
} );
|
||||
testValue = renderer.root.findByType( 'div' ).props[ 'data-testValue' ];
|
||||
testPreviousValue =
|
||||
renderer.root.findByType( 'div' ).props[ 'data-previousValue' ];
|
||||
expect( testValue ).toBe( 3 );
|
||||
expect( testPreviousValue ).toBe( 2 );
|
||||
} );
|
||||
|
||||
it( 'should not update value if validation fails', () => {
|
||||
let testValue;
|
||||
let testPreviousValue;
|
||||
act( () => {
|
||||
renderer = TestRenderer.create(
|
||||
<TestComponent testValue={ 1 } validation={ Number.isFinite } />
|
||||
);
|
||||
} );
|
||||
|
||||
act( () => {
|
||||
renderer.update(
|
||||
<TestComponent testValue="abc" validation={ Number.isFinite } />
|
||||
);
|
||||
} );
|
||||
testValue = renderer.root.findByType( 'div' ).props[ 'data-testValue' ];
|
||||
testPreviousValue =
|
||||
renderer.root.findByType( 'div' ).props[ 'data-previousValue' ];
|
||||
expect( testValue ).toBe( 'abc' );
|
||||
expect( testPreviousValue ).toBe( 1 );
|
||||
|
||||
act( () => {
|
||||
renderer.update(
|
||||
<TestComponent testValue={ 3 } validation={ Number.isFinite } />
|
||||
);
|
||||
} );
|
||||
testValue = renderer.root.findByType( 'div' ).props[ 'data-testValue' ];
|
||||
testPreviousValue =
|
||||
renderer.root.findByType( 'div' ).props[ 'data-previousValue' ];
|
||||
expect( testValue ).toBe( 3 );
|
||||
expect( testPreviousValue ).toBe( 1 );
|
||||
} );
|
||||
} );
|
||||
@@ -0,0 +1,79 @@
|
||||
// We need to disable the following eslint check as it's only applicable
|
||||
// to testing-library/react not `react-test-renderer` used here
|
||||
/* eslint-disable testing-library/await-async-query */
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import TestRenderer, { act } from 'react-test-renderer';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { useShallowEqual } from '../use-shallow-equal';
|
||||
|
||||
describe( 'useShallowEqual', () => {
|
||||
const TestComponent = ( { testValue } ) => {
|
||||
const newValue = useShallowEqual( testValue );
|
||||
return <div data-newValue={ newValue } />;
|
||||
};
|
||||
let renderer;
|
||||
beforeEach( () => ( renderer = null ) );
|
||||
it.each`
|
||||
testValueA | aType | testValueB | bType
|
||||
${ { a: 'b', foo: 'bar' } } | ${ 'object' } | ${ { foo: 'bar', a: 'b' } } | ${ 'object' }
|
||||
${ [ 'b', 'bar' ] } | ${ 'array' } | ${ [ 'b', 'bar' ] } | ${ 'array' }
|
||||
${ 1 } | ${ 'number' } | ${ 1 } | ${ 'number' }
|
||||
${ '1' } | ${ 'string' } | ${ '1' } | ${ 'string' }
|
||||
${ true } | ${ 'bool' } | ${ true } | ${ 'bool' }
|
||||
`(
|
||||
'$testValueA ($aType) and $testValueB ($bType) are expected to be equal',
|
||||
( { testValueA, testValueB } ) => {
|
||||
let testPropValue;
|
||||
act( () => {
|
||||
renderer = TestRenderer.create(
|
||||
<TestComponent testValue={ testValueA } />
|
||||
);
|
||||
} );
|
||||
testPropValue =
|
||||
renderer.root.findByType( 'div' ).props[ 'data-newValue' ];
|
||||
expect( testPropValue ).toBe( testValueA );
|
||||
// do update
|
||||
act( () => {
|
||||
renderer.update( <TestComponent testValue={ testValueB } /> );
|
||||
} );
|
||||
testPropValue =
|
||||
renderer.root.findByType( 'div' ).props[ 'data-newValue' ];
|
||||
expect( testPropValue ).toBe( testValueA );
|
||||
}
|
||||
);
|
||||
|
||||
it.each`
|
||||
testValueA | aType | testValueB | bType
|
||||
${ { a: 'b', foo: 'bar' } } | ${ 'object' } | ${ { foo: 'bar', a: 'c' } } | ${ 'object' }
|
||||
${ [ 'b', 'bar' ] } | ${ 'array' } | ${ [ 'bar', 'b' ] } | ${ 'array' }
|
||||
${ 1 } | ${ 'number' } | ${ '1' } | ${ 'string' }
|
||||
${ 1 } | ${ 'number' } | ${ 2 } | ${ 'number' }
|
||||
${ 1 } | ${ 'number' } | ${ true } | ${ 'bool' }
|
||||
${ 0 } | ${ 'number' } | ${ false } | ${ 'bool' }
|
||||
`(
|
||||
'$testValueA ($aType) and $testValueB ($bType) are expected to not be equal',
|
||||
( { testValueA, testValueB } ) => {
|
||||
let testPropValue;
|
||||
act( () => {
|
||||
renderer = TestRenderer.create(
|
||||
<TestComponent testValue={ testValueA } />
|
||||
);
|
||||
} );
|
||||
testPropValue =
|
||||
renderer.root.findByType( 'div' ).props[ 'data-newValue' ];
|
||||
expect( testPropValue ).toBe( testValueA );
|
||||
// do update
|
||||
act( () => {
|
||||
renderer.update( <TestComponent testValue={ testValueB } /> );
|
||||
} );
|
||||
testPropValue =
|
||||
renderer.root.findByType( 'div' ).props[ 'data-newValue' ];
|
||||
expect( testPropValue ).toBe( testValueB );
|
||||
}
|
||||
);
|
||||
} );
|
||||
@@ -0,0 +1,47 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { useResizeObserver } from '@wordpress/compose';
|
||||
|
||||
/**
|
||||
* Returns a resizeListener element and a class name based on its width.
|
||||
* Class names are based on the smaller of the breakpoints:
|
||||
* https://github.com/WordPress/gutenberg/tree/master/packages/viewport#usage
|
||||
* Values are also based on those breakpoints minus ~80px which is approximately
|
||||
* the left + right margin in Storefront with a font-size of 16px.
|
||||
* _Note: `useContainerQueries` will return an empty class name `` until after
|
||||
* first render_
|
||||
*
|
||||
* @return {Array} An array of {Element} `resizeListener` and {string} `className`.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```js
|
||||
* const App = () => {
|
||||
* const [ resizeListener, containerClassName ] = useContainerQueries();
|
||||
*
|
||||
* return (
|
||||
* <div className={ containerClassName }>
|
||||
* { resizeListener }
|
||||
* Your content here
|
||||
* </div>
|
||||
* );
|
||||
* };
|
||||
* ```
|
||||
*/
|
||||
export const useContainerQueries = (): [ React.ReactElement, string ] => {
|
||||
const [ resizeListener, { width } ] = useResizeObserver();
|
||||
|
||||
let className = '';
|
||||
if ( width > 700 ) {
|
||||
className = 'is-large';
|
||||
} else if ( width > 520 ) {
|
||||
className = 'is-medium';
|
||||
} else if ( width > 400 ) {
|
||||
className = 'is-small';
|
||||
} else if ( width ) {
|
||||
className = 'is-mobile';
|
||||
}
|
||||
|
||||
return [ resizeListener, className ];
|
||||
};
|
||||
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { useCallback, useEffect, useRef } from '@wordpress/element';
|
||||
|
||||
/**
|
||||
* Returns a boolean value based on whether the current component has been mounted.
|
||||
*
|
||||
* @return {boolean} If the component has been mounted.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```js
|
||||
* const App = () => {
|
||||
* const isMounted = useIsMounted();
|
||||
*
|
||||
* if ( ! isMounted() ) {
|
||||
* return null;
|
||||
* }
|
||||
*
|
||||
* return </div>;
|
||||
* };
|
||||
* ```
|
||||
*/
|
||||
|
||||
export function useIsMounted() {
|
||||
const isMounted = useRef( false );
|
||||
|
||||
useEffect( () => {
|
||||
isMounted.current = true;
|
||||
|
||||
return () => {
|
||||
isMounted.current = false;
|
||||
};
|
||||
}, [] );
|
||||
|
||||
return useCallback( () => isMounted.current, [] );
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { useEffect, useState } from '@wordpress/element';
|
||||
import type { Dispatch, SetStateAction } from 'react';
|
||||
|
||||
export const useLocalStorageState = < T >(
|
||||
key: string,
|
||||
initialValue: T
|
||||
): [ T, Dispatch< SetStateAction< T > > ] => {
|
||||
const [ state, setState ] = useState< T >( () => {
|
||||
const valueInLocalStorage = window.localStorage.getItem( key );
|
||||
if ( valueInLocalStorage ) {
|
||||
try {
|
||||
return JSON.parse( valueInLocalStorage );
|
||||
} catch {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(
|
||||
`Value for key '${ key }' could not be retrieved from localStorage because it can't be parsed.`
|
||||
);
|
||||
}
|
||||
}
|
||||
return initialValue;
|
||||
} );
|
||||
useEffect( () => {
|
||||
try {
|
||||
window.localStorage.setItem( key, JSON.stringify( state ) );
|
||||
} catch {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(
|
||||
`Value for key '${ key }' could not be saved in localStorage because it can't be converted into a string.`
|
||||
);
|
||||
}
|
||||
}, [ key, state ] );
|
||||
|
||||
return [ state, setState ];
|
||||
};
|
||||
@@ -0,0 +1,84 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { useRef, useLayoutEffect, useState } from '@wordpress/element';
|
||||
|
||||
/** @typedef {import('react')} React */
|
||||
|
||||
/** @type {React.CSSProperties} */
|
||||
const style = {
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
opacity: 0,
|
||||
pointerEvents: 'none',
|
||||
position: 'absolute',
|
||||
right: 0,
|
||||
top: 0,
|
||||
zIndex: -1,
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns an element and a string (`above`, `visible` or `below`) based on the
|
||||
* element position relative to the viewport.
|
||||
* _Note: `usePositionRelativeToViewport` will return an empty position (``)
|
||||
* until after first render_
|
||||
*
|
||||
* @return {Array} An array of {Element} `referenceElement` and {string} `positionRelativeToViewport`.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```js
|
||||
* const App = () => {
|
||||
* const [ referenceElement, positionRelativeToViewport ] = useContainerQueries();
|
||||
*
|
||||
* return (
|
||||
* <>
|
||||
* { referenceElement }
|
||||
* { positionRelativeToViewport === 'below' && <p>Reference element is below the viewport.</p> }
|
||||
* { positionRelativeToViewport === 'visible' && <p>Reference element is visible in the viewport.</p> }
|
||||
* { positionRelativeToViewport === 'above' && <p>Reference element is above the viewport.</p> }
|
||||
* </>
|
||||
* );
|
||||
* };
|
||||
* ```
|
||||
*/
|
||||
export const usePositionRelativeToViewport = () => {
|
||||
const [ positionRelativeToViewport, setPositionRelativeToViewport ] =
|
||||
useState( '' );
|
||||
const referenceElementRef = useRef( null );
|
||||
const intersectionObserver = useRef(
|
||||
new IntersectionObserver(
|
||||
( entries ) => {
|
||||
if ( entries[ 0 ].isIntersecting ) {
|
||||
setPositionRelativeToViewport( 'visible' );
|
||||
} else {
|
||||
setPositionRelativeToViewport(
|
||||
entries[ 0 ].boundingClientRect.top > 0
|
||||
? 'below'
|
||||
: 'above'
|
||||
);
|
||||
}
|
||||
},
|
||||
{ threshold: 1.0 }
|
||||
)
|
||||
);
|
||||
|
||||
useLayoutEffect( () => {
|
||||
const referenceElementNode = referenceElementRef.current;
|
||||
const observer = intersectionObserver.current;
|
||||
|
||||
if ( referenceElementNode ) {
|
||||
observer.observe( referenceElementNode );
|
||||
}
|
||||
|
||||
return () => {
|
||||
observer.unobserve( referenceElementNode );
|
||||
};
|
||||
}, [] );
|
||||
|
||||
const referenceElement = (
|
||||
<div aria-hidden={ true } ref={ referenceElementRef } style={ style } />
|
||||
);
|
||||
|
||||
return [ referenceElement, positionRelativeToViewport ];
|
||||
};
|
||||
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { useRef, useEffect } from '@wordpress/element';
|
||||
|
||||
interface Validation< T > {
|
||||
( value: T, previousValue: T | undefined ): boolean;
|
||||
}
|
||||
/**
|
||||
* Use Previous based on https://usehooks.com/useprevious/.
|
||||
*
|
||||
* @param {*} value
|
||||
* @param {Function} [validation] Function that needs to validate for the value
|
||||
* to be updated.
|
||||
*/
|
||||
export function usePrevious< T >(
|
||||
value: T,
|
||||
validation?: Validation< T >
|
||||
): T | undefined {
|
||||
const ref = useRef< T >();
|
||||
|
||||
useEffect( () => {
|
||||
if (
|
||||
ref.current !== value &&
|
||||
( ! validation || validation( value, ref.current ) )
|
||||
) {
|
||||
ref.current = value;
|
||||
}
|
||||
}, [ value, validation ] );
|
||||
|
||||
return ref.current;
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { useRef } from '@wordpress/element';
|
||||
import isShallowEqual from '@wordpress/is-shallow-equal';
|
||||
|
||||
/**
|
||||
* A custom hook that compares the provided value across renders and returns the
|
||||
* previous instance if shallow equality with previous instance exists.
|
||||
*
|
||||
* This is particularly useful when non-primitive types are used as
|
||||
* dependencies for react hooks.
|
||||
*
|
||||
* @param {*} value Value to keep the same if satisfies shallow equality.
|
||||
*
|
||||
* @return {*} The previous cached instance of the value if the current has shallow equality with it.
|
||||
*/
|
||||
export function useShallowEqual< T >( value: T ): T {
|
||||
const ref = useRef< T >( value );
|
||||
if ( ! isShallowEqual( value, ref.current ) ) {
|
||||
ref.current = value;
|
||||
}
|
||||
return ref.current;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { useEffect, renderToString } from '@wordpress/element';
|
||||
import { speak } from '@wordpress/a11y';
|
||||
|
||||
/**
|
||||
* Custom hook which announces the message with the given politeness, if a
|
||||
* valid message is provided.
|
||||
*/
|
||||
export const useSpokenMessage = (
|
||||
message: string | React.ReactNode | undefined,
|
||||
politeness: 'polite' | 'assertive' | undefined
|
||||
) => {
|
||||
const spokenMessage =
|
||||
typeof message === 'string' ? message : renderToString( message );
|
||||
|
||||
useEffect( () => {
|
||||
if ( spokenMessage ) {
|
||||
speak( spokenMessage, politeness );
|
||||
}
|
||||
}, [ spokenMessage, politeness ] );
|
||||
};
|
||||
|
||||
export default useSpokenMessage;
|
||||
@@ -0,0 +1,88 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import classnames from 'classnames';
|
||||
import { isString, isObject } from '@woocommerce/types';
|
||||
import type { Style as StyleEngineProperties } from '@wordpress/style-engine/src/types';
|
||||
import type { CSSProperties } from 'react';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { useTypographyProps } from './use-typography-props';
|
||||
import {
|
||||
getColorClassesAndStyles,
|
||||
getBorderClassesAndStyles,
|
||||
getSpacingClassesAndStyles,
|
||||
} from '../utils';
|
||||
|
||||
export type StyleProps = {
|
||||
className: string;
|
||||
style: CSSProperties;
|
||||
};
|
||||
|
||||
type BlockAttributes = Record< string, unknown > & {
|
||||
style?: StyleEngineProperties | string | undefined;
|
||||
};
|
||||
|
||||
type StyleAttributes = Record< string, unknown > & {
|
||||
style: StyleEngineProperties;
|
||||
};
|
||||
|
||||
/**
|
||||
* Parses incoming props.
|
||||
*
|
||||
* This may include style properties at the top level, or may include a nested `style` object. This ensures the expected
|
||||
* values are present and converts any string based values to objects as required.
|
||||
*/
|
||||
const parseStyleAttributes = ( rawProps: BlockAttributes ): StyleAttributes => {
|
||||
const props = isObject( rawProps )
|
||||
? rawProps
|
||||
: {
|
||||
style: {},
|
||||
};
|
||||
|
||||
let style = props.style;
|
||||
|
||||
if ( isString( style ) ) {
|
||||
style = JSON.parse( style ) || {};
|
||||
}
|
||||
|
||||
if ( ! isObject( style ) ) {
|
||||
style = {};
|
||||
}
|
||||
|
||||
return {
|
||||
...props,
|
||||
style,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the CSS class names and inline styles for a block when provided with its props/attributes.
|
||||
*
|
||||
* This hook (and its utilities) borrow functionality from the Gutenberg Block Editor package--something we don't want
|
||||
* to import on the frontend.
|
||||
*/
|
||||
export const useStyleProps = ( props: BlockAttributes ): StyleProps => {
|
||||
const styleAttributes = parseStyleAttributes( props );
|
||||
const colorProps = getColorClassesAndStyles( styleAttributes );
|
||||
const borderProps = getBorderClassesAndStyles( styleAttributes );
|
||||
const spacingProps = getSpacingClassesAndStyles( styleAttributes );
|
||||
const typographyProps = useTypographyProps( styleAttributes );
|
||||
|
||||
return {
|
||||
className: classnames(
|
||||
typographyProps.className,
|
||||
colorProps.className,
|
||||
borderProps.className,
|
||||
spacingProps.className
|
||||
),
|
||||
style: {
|
||||
...typographyProps.style,
|
||||
...colorProps.style,
|
||||
...borderProps.style,
|
||||
...spacingProps.style,
|
||||
},
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,18 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { useState, useCallback } from '@wordpress/element';
|
||||
|
||||
/**
|
||||
* Helper method for throwing an error in a React Hook.
|
||||
*
|
||||
* @see https://github.com/facebook/react/issues/14981
|
||||
*/
|
||||
export const useThrowError = (): ( ( error: Error ) => void ) => {
|
||||
const [ , setState ] = useState();
|
||||
return useCallback( ( error: Error ): void => {
|
||||
setState( () => {
|
||||
throw error;
|
||||
} );
|
||||
}, [] );
|
||||
};
|
||||
@@ -0,0 +1,45 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { isObject, isString } from '@woocommerce/types';
|
||||
import type { Style as StyleEngineProperties } from '@wordpress/style-engine/src/types';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import type { StyleProps } from './use-style-props';
|
||||
|
||||
type blockAttributes = {
|
||||
style: StyleEngineProperties;
|
||||
// String identifier for the font size preset--not an absolute value.
|
||||
fontSize?: string | undefined;
|
||||
// String identifier for the font family preset, not the actual font family.
|
||||
fontFamily?: string | undefined;
|
||||
};
|
||||
|
||||
export const useTypographyProps = ( props: blockAttributes ): StyleProps => {
|
||||
const typography = isObject( props.style.typography )
|
||||
? props.style.typography
|
||||
: {};
|
||||
const classNameFallback = isString( typography.fontFamily )
|
||||
? typography.fontFamily
|
||||
: '';
|
||||
const className = props.fontFamily
|
||||
? `has-${ props.fontFamily }-font-family`
|
||||
: classNameFallback;
|
||||
|
||||
return {
|
||||
className,
|
||||
style: {
|
||||
fontSize: props.fontSize
|
||||
? `var(--wp--preset--font-size--${ props.fontSize })`
|
||||
: typography.fontSize,
|
||||
fontStyle: typography.fontStyle,
|
||||
fontWeight: typography.fontWeight,
|
||||
letterSpacing: typography.letterSpacing,
|
||||
lineHeight: typography.lineHeight,
|
||||
textDecoration: typography.textDecoration,
|
||||
textTransform: typography.textTransform,
|
||||
},
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user