Skip to content

Commit

Permalink
Merge pull request #194 from vendidero/block-support
Browse files Browse the repository at this point in the history
Block support
  • Loading branch information
dennisnissle authored Oct 24, 2023
2 parents 2298117 + 5f79c7d commit d64f2c1
Show file tree
Hide file tree
Showing 153 changed files with 40,886 additions and 28,759 deletions.
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,10 @@ Thumbs.db
/tests/e2e-tests/config/local-*.json

/build
/deploy
/release

# i18n json files
/i18n/languages/*.json

# Logs
/logs
Expand Down
81 changes: 81 additions & 0 deletions assets/js/base/components/formatted-monetary-amount/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/**
* External dependencies
*/
import NumberFormat from 'react-number-format';
import classNames from 'classnames';

/**
* Formats currency data into the expected format for NumberFormat.
*/
const currencyToNumberFormat = ( currency ) => {
return {
thousandSeparator: currency?.thousandSeparator,
decimalSeparator: currency?.decimalSeparator,
fixedDecimalScale: true,
prefix: currency?.prefix,
suffix: currency?.suffix,
isNumericString: true,
};
};

/**
* FormattedMonetaryAmount component.
*
* Takes a price and returns a formatted price using the NumberFormat component.
*/
const FormattedMonetaryAmount = ( {
className,
value: rawValue,
currency,
onValueChange,
displayType = 'text',
...props
}) => {
const value =
typeof rawValue === 'string' ? parseInt( rawValue, 10 ) : rawValue;

if ( ! Number.isFinite( value ) ) {
return null;
}

const priceValue = value / 10 ** currency.minorUnit;

if ( ! Number.isFinite( priceValue ) ) {
return null;
}

const classes = classNames(
'wc-block-formatted-money-amount',
'wc-block-components-formatted-money-amount',
className
);
const decimalScale = props.decimalScale ?? currency?.minorUnit;
const numberFormatProps = {
...props,
...currencyToNumberFormat( currency ),
decimalScale,
value: undefined,
currency: undefined,
onValueChange: undefined,
};

// Wrapper for NumberFormat onValueChange which handles subunit conversion.
const onValueChangeWrapper = onValueChange
? ( values ) => {
const minorUnitValue = +values.value * 10 ** currency.minorUnit;
onValueChange( minorUnitValue );
}
: () => void 0;

return (
<NumberFormat
className={ classes }
displayType={ displayType }
{ ...numberFormatProps }
value={ priceValue }
onValueChange={ onValueChangeWrapper }
/>
);
};

export default FormattedMonetaryAmount;
4 changes: 4 additions & 0 deletions assets/js/base/components/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export * from './noninteractive';
export * from './radio-control';
export * from './radio-control-accordion';
export * from './formatted-monetary-amount';
93 changes: 93 additions & 0 deletions assets/js/base/components/noninteractive/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/**
* External dependencies
*/
import { useRef, useLayoutEffect } from '@wordpress/element';
import { focus } from '@wordpress/dom';
import { useDebouncedCallback } from 'use-debounce';

/**
* Names of control nodes which need to be disabled.
*/
const FOCUSABLE_NODE_NAMES = [
'BUTTON',
'FIELDSET',
'INPUT',
'OPTGROUP',
'OPTION',
'SELECT',
'TEXTAREA',
'A',
];

/**
* Noninteractive component
*
* Makes children elements Noninteractive, preventing both mouse and keyboard events without affecting how the elements
* appear visually. Used for previews.
*
* Based on the <Disabled> component in WordPress.
*
* @see https://github.com/WordPress/gutenberg/blob/trunk/packages/components/src/disabled/index.js
*/
const Noninteractive = ( {
children,
style = {},
...props
}) => {
const node = useRef();

const disableFocus = () => {
if ( node.current ) {
focus.focusable.find( node.current ).forEach( ( focusable ) => {
if ( FOCUSABLE_NODE_NAMES.includes( focusable.nodeName ) ) {
focusable.setAttribute( 'tabindex', '-1' );
}
if ( focusable.hasAttribute( 'contenteditable' ) ) {
focusable.setAttribute( 'contenteditable', 'false' );
}
} );
}
};

// Debounce re-disable since disabling process itself will incur additional mutations which should be ignored.
const debounced = useDebouncedCallback( disableFocus, 0, {
leading: true,
} );

useLayoutEffect( () => {
let observer;
disableFocus();
if ( node.current ) {
observer = new window.MutationObserver( debounced );
observer.observe( node.current, {
childList: true,
attributes: true,
subtree: true,
} );
}
return () => {
if ( observer ) {
observer.disconnect();
}
debounced.cancel();
};
}, [ debounced ] );

return (
<div
ref={ node }
aria-disabled="true"
style={ {
userSelect: 'none',
pointerEvents: 'none',
cursor: 'normal',
...style,
} }
{ ...props }
>
{ children }
</div>
);
};

export default Noninteractive;
73 changes: 73 additions & 0 deletions assets/js/base/components/radio-control-accordion/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/**
* External dependencies
*/
import classnames from 'classnames';
import { withInstanceId } from '@wordpress/compose';

/**
* Internal dependencies
*/
import RadioControlOption from '../radio-control/option';

const RadioControlAccordion = ( {
className,
instanceId,
id,
selected,
onChange,
options,
} ) => {
const radioControlId = id || instanceId;

if ( ! options.length ) {
return null;
}
return (
<div
className={ classnames(
'wc-block-components-radio-control',
className
) }
>
{ options.map( ( option ) => {
const hasOptionContent =
typeof option === 'object' && 'content' in option;
const checked = option.value === selected;
return (
<div
className="wc-block-components-radio-control-accordion-option"
key={ option.value }
>
<RadioControlOption
name={ `radio-control-${ radioControlId }` }
checked={ checked }
option={ option }
onChange={ ( value ) => {
onChange( value );
if ( typeof option.onChange === 'function' ) {
option.onChange( value );
}
} }
/>
{ hasOptionContent && checked && (
<div
className={ classnames(
'wc-block-components-radio-control-accordion-content',
{
'wc-block-components-radio-control-accordion-content-hide':
! checked,
}
) }
>
{ option.content }
</div>
) }
</div>
);
} ) }
</div>
);
};

export default withInstanceId( RadioControlAccordion );
export { RadioControlAccordion };
55 changes: 55 additions & 0 deletions assets/js/base/components/radio-control/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/**
* External dependencies
*/
import classnames from 'classnames';
import { useInstanceId } from '@wordpress/compose';
/**
* Internal dependencies
*/
import RadioControlOption from './option';
import './style.scss';

const RadioControl = ( {
className,
id,
selected,
onChange,
options,
disabled,
} ) => {
const instanceId = useInstanceId( RadioControl );
const radioControlId = id || instanceId;

if ( ! options.length ) {
return null;
}

return (
<div
className={ classnames(
'wc-block-components-radio-control',
className
) }
>
{ options.map( ( option ) => (
<RadioControlOption
key={ `${ radioControlId }-${ option.value }` }
name={ `radio-control-${ radioControlId }` }
checked={ option.value === selected }
option={ option }
onChange={ ( value ) => {
onChange( value );
if ( typeof option.onChange === 'function' ) {
option.onChange( value );
}
} }
disabled={ disabled }
/>
) ) }
</div>
);
};

export default RadioControl;
export { default as RadioControlOption } from './option';
export { default as RadioControlOptionLayout } from './option-layout';
52 changes: 52 additions & 0 deletions assets/js/base/components/radio-control/option-layout.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
const OptionLayout = ( {
label,
secondaryLabel,
description,
secondaryDescription,
id,
} ) => {
return (
<div className="wc-block-components-radio-control__option-layout">
<div className="wc-block-components-radio-control__label-group">
{ label && (
<span
id={ id && `${ id }__label` }
className="wc-block-components-radio-control__label"
>
{ label }
</span>
) }
{ secondaryLabel && (
<span
id={ id && `${ id }__secondary-label` }
className="wc-block-components-radio-control__secondary-label"
>
{ secondaryLabel }
</span>
) }
</div>
{ ( description || secondaryDescription ) && (
<div className="wc-block-components-radio-control__description-group">
{ description && (
<span
id={ id && `${ id }__description` }
className="wc-block-components-radio-control__description"
>
{ description }
</span>
) }
{ secondaryDescription && (
<span
id={ id && `${ id }__secondary-description` }
className="wc-block-components-radio-control__secondary-description"
>
{ secondaryDescription }
</span>
) }
</div>
) }
</div>
);
};

export default OptionLayout;
Loading

0 comments on commit d64f2c1

Please sign in to comment.