diff --git a/src/blocks/donate/edit/FrequencyBasedLayout.tsx b/src/blocks/donate/edit/FrequencyBasedLayout.tsx index 39b463707..1bf6c3cae 100644 --- a/src/blocks/donate/edit/FrequencyBasedLayout.tsx +++ b/src/blocks/donate/edit/FrequencyBasedLayout.tsx @@ -14,9 +14,10 @@ import { RichText } from '@wordpress/block-editor'; * Internal dependencies */ import { AmountValueInput } from './components'; -import { getColorForContrast, getFrequencyLabel } from '../utils'; +import { getColorForContrast, getFrequencyLabel, getFrequencyLabelWithAmount } from '../utils'; import { FREQUENCIES } from '../consts'; import type { ComponentProps, DonationFrequencySlug } from '../types'; +import { updateBlockClassName, getFormData } from '../view' const FrequencyBasedLayout = ( props: { isTiered: boolean } & ComponentProps ) => { // Unique identifier to prevent collisions with other Donate blocks' labels. @@ -26,10 +27,25 @@ const FrequencyBasedLayout = ( props: { isTiered: boolean } & ComponentProps ) = const formRef = useRef< HTMLFormElement >( null ); + // Add event listeners to the form. + useEffect( () => { + if ( formRef.current !== null ) { + const parentElement = formRef.current.closest('.wpbnbd'); + if ( parentElement ) { + updateBlockClassName( parentElement, getFormData( formRef.current )); + } + formRef.current.addEventListener('change', () => { + if ( formRef.current !== null && parentElement ) { + updateBlockClassName( parentElement, getFormData( formRef.current )); + } + }) + } + }, [] ); + // Update selected frequency when available frequencies change. useEffect( () => { if ( formRef.current ) { - const formValues = Object.fromEntries( new FormData( formRef.current ) ); + const formValues = getFormData( formRef.current ); if ( ! formValues.donation_frequency && formRef.current.elements ) { const frequencyRadioInput = formRef.current.elements[ 0 ]; if ( frequencyRadioInput instanceof HTMLInputElement ) { @@ -92,10 +108,17 @@ const FrequencyBasedLayout = ( props: { isTiered: boolean } & ComponentProps ) = // This code is fired on tab select and updates aria elements, tabindex states, and radio buttons const displayAmount = ( amount: number ) => amount.toFixed( 2 ).replace( /\.?0*$/, '' ); + const renderFormHeader = () => { + return ! rendersSingleFrequency && ( +
{ availableFrequencies.map( renderTab ) }
+ ) + } + const rendersSingleFrequency = availableFrequencies.length === 1 + const renderUntieredForm = () => (
-
{ availableFrequencies.map( renderTab ) }
+ {renderFormHeader()} { availableFrequencies.map( frequencySlug => { const untieredAmount = amounts[ frequencySlug ][ 3 ]; return ( @@ -112,6 +135,7 @@ const FrequencyBasedLayout = ( props: { isTiered: boolean } & ComponentProps ) = htmlFor={ 'newspack-' + frequencySlug + '-' + uid + '-untiered-input' } > { __( 'Donation amount', 'newspack-blocks' ) } + { getFrequencyLabel( frequencySlug, true ) }
{ settings.currencySymbol } @@ -140,7 +164,7 @@ const FrequencyBasedLayout = ( props: { isTiered: boolean } & ComponentProps ) = >
@@ -157,7 +181,7 @@ const FrequencyBasedLayout = ( props: { isTiered: boolean } & ComponentProps ) = const renderTieredForm = () => (
-
{ availableFrequencies.map( renderTab ) }
+ { renderFormHeader()} { availableFrequencies.map( frequencySlug => (
{ isOtherTier ? ( <>
{ settings.currencySymbol } @@ -208,6 +234,11 @@ const FrequencyBasedLayout = ( props: { isTiered: boolean } & ComponentProps ) =
); } ) } + { rendersSingleFrequency ? ( +
+ {getFrequencyLabel( frequencySlug, true )} +
+ ) : null }
) ) } diff --git a/src/blocks/donate/edit/TierBasedLayout.tsx b/src/blocks/donate/edit/TierBasedLayout.tsx index 768f56bcb..48670f0f7 100644 --- a/src/blocks/donate/edit/TierBasedLayout.tsx +++ b/src/blocks/donate/edit/TierBasedLayout.tsx @@ -10,7 +10,7 @@ import classNames from 'classnames'; * Internal dependencies */ import type { ComponentProps, DonateBlockAttributes, TierBasedOptionValue } from '../types'; -import { getColorForContrast, getFrequencyLabel } from '../utils'; +import { getColorForContrast, getFrequencyLabelWithAmount } from '../utils'; import { FREQUENCIES, DISABLED_IN_TIERS_BASED_LAYOUT_TIER_INDEX } from '../consts'; const TierBasedLayout = ( props: ComponentProps ) => { @@ -44,22 +44,24 @@ const TierBasedLayout = ( props: ComponentProps ) => { return (
e.preventDefault() }> -
- { availableFrequencies.map( frequencySlug => { - const isActive = currentFrequency === frequencySlug; - return ( - - ); - } ) } -
+ { availableFrequencies.length > 1 && ( +
+ { availableFrequencies.map( frequencySlug => { + const isActive = currentFrequency === frequencySlug; + return ( + + ); + } ) } +
+ )}
{ displayedAmounts.map( ( amount, index ) => { const recommendLabel = attributes.tiersBasedOptions[ index ].recommendLabel || ''; @@ -94,7 +96,7 @@ const TierBasedLayout = ( props: ComponentProps ) => {
diff --git a/src/blocks/donate/frequency-based/style.scss b/src/blocks/donate/frequency-based/style.scss index 87e3fbb55..be28a4748 100644 --- a/src/blocks/donate/frequency-based/style.scss +++ b/src/blocks/donate/frequency-based/style.scss @@ -235,3 +235,11 @@ } } } + +.wp-block-newspack-blocks-donate { + &__frequency-label { + display: flex; + align-items: center; + padding-left: 8px; + } +} diff --git a/src/blocks/donate/frontend/class-newspack-blocks-donate-renderer-frequency-based.php b/src/blocks/donate/frontend/class-newspack-blocks-donate-renderer-frequency-based.php index 999278b6c..df08a1707 100644 --- a/src/blocks/donate/frontend/class-newspack-blocks-donate-renderer-frequency-based.php +++ b/src/blocks/donate/frontend/class-newspack-blocks-donate-renderer-frequency-based.php @@ -101,6 +101,33 @@ private static function render_footer( $attributes ) { return ob_get_clean(); } + /** + * Render the form header. + * + * @param array $configuration The donations settings. + * @param string $uid ID of the block. + */ + private static function render_form_header( $configuration, $uid ) { + ?> + +
+ $frequency_name ) : ?> + + +
+ +
- -
- $frequency_name ) : ?> - - -
- + $frequency_name ) : ?> -untiered-input' > +
@@ -206,14 +228,8 @@ class="tiered "
-
- $frequency_name ) : ?> - - -
- + $frequency_name ) : ?> -
' @@ -246,6 +262,7 @@ class='odl' for='newspack-tier--other-input' > +
@@ -279,6 +296,14 @@ class='tier-select-label tier-label' ?>
+ +
+
+ + +
+
+
diff --git a/src/blocks/donate/frontend/class-newspack-blocks-donate-renderer-tiers-based.php b/src/blocks/donate/frontend/class-newspack-blocks-donate-renderer-tiers-based.php index 50c72ea7e..aeebee906 100644 --- a/src/blocks/donate/frontend/class-newspack-blocks-donate-renderer-tiers-based.php +++ b/src/blocks/donate/frontend/class-newspack-blocks-donate-renderer-tiers-based.php @@ -136,16 +136,22 @@ class=""
-
- $frequency_name ) : ?> - - -
+ 1 ) : ?> +
+ $frequency_name ) : ?> + + +
+ +
$amount ) : ?> { + // eslint-disable-next-line no-nested-ternary + return frequencySlug === 'once' + ? hideOnceLabel + ? '' + : __( ' once', 'newspack-blocks' ) + : sprintf( + // Translators: %s is the frequency (e.g. per month, per year). + _x( ' per %s', 'per `Frequency`', 'newspack-blocks' ), + frequencySlug + ); +} + +export const getFrequencyLabelWithAmount = ( amount: number, frequencySlug: DonationFrequencySlug, hideOnceLabel = false @@ -88,17 +104,7 @@ export const getFrequencyLabel = ( const formattedAmount = ( amount || 0 ).toFixed( 2 ).replace( /\.?0*$/, '' ); - const frequency = - // eslint-disable-next-line no-nested-ternary - frequencySlug === 'once' - ? hideOnceLabel - ? '' - : __( ' once', 'newspack-blocks' ) - : sprintf( - // Translators: %s is the frequency (e.g. per month, per year). - _x( ' per %s', 'per `Frequency`', 'newspack-blocks' ), - frequencySlug - ); + const frequency = getFrequencyLabel(frequencySlug, hideOnceLabel) return template .replace( 'AMOUNT_PLACEHOLDER', formattedAmount ) diff --git a/src/blocks/donate/view.js b/src/blocks/donate/view.js deleted file mode 100644 index b6093c865..000000000 --- a/src/blocks/donate/view.js +++ /dev/null @@ -1,5 +0,0 @@ -/** - * Style dependencies - */ - -import './styles/view.scss'; diff --git a/src/blocks/donate/view.ts b/src/blocks/donate/view.ts new file mode 100644 index 000000000..89cccb589 --- /dev/null +++ b/src/blocks/donate/view.ts @@ -0,0 +1,53 @@ +/** + * Style dependencies + */ +import { isString } from 'lodash'; +import './styles/view.scss'; + +type FormValues = { [key: string]: string }; + +const CLASS_NAME_BASE = 'wpbnbd'; +const selectedClassNameBase = `${CLASS_NAME_BASE}--selected`; + +const donateBlocks = Array.from( + document.querySelectorAll(`.${CLASS_NAME_BASE}`) +); + +export const updateBlockClassName = ( + element: Element, + formValues: FormValues +) => { + const amountKey = `donation_value_${formValues.donation_frequency}`; + if (amountKey) { + Array.from(element.classList).forEach(className => { + if (className.startsWith(selectedClassNameBase)) { + element.classList.remove(className); + } + }); + element.classList.add( + `${selectedClassNameBase}-${formValues[amountKey]}` + ); + } +}; + +export const getFormData: (form: HTMLFormElement) => FormValues = form => { + const formData = new FormData(form); + const formValues: FormValues = {}; + formData.forEach((value, key) => { + if (isString(key) && isString(value)) { + formValues[key] = value; + } + }); + return formValues; +}; + +donateBlocks.forEach(element => { + const form = element.querySelector('form'); + if (!form) { + return; + } + updateBlockClassName(element, getFormData(form)); + form.addEventListener('change', () => + updateBlockClassName(element, getFormData(form)) + ); +}); diff --git a/webpack.config.js b/webpack.config.js index 2ffeffbaf..828557cd7 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -33,8 +33,15 @@ const blocks = fs // Helps split up each block into its own folder view script const viewBlocksScripts = blocks.reduce( ( viewBlocks, block ) => { - const viewScriptPath = path.join( __dirname, 'src', 'blocks', block, 'view.js' ); - if ( fs.existsSync( viewScriptPath ) ) { + const pathToBlock = [__dirname, 'src', 'blocks', block] + let viewScriptPath = path.join( ...pathToBlock, 'view.js' ); + let fileExists = fs.existsSync( viewScriptPath ) + if (!fileExists) { + // Try TS. + viewScriptPath = path.join( ...pathToBlock, 'view.ts' ); + fileExists = fs.existsSync( viewScriptPath ) + } + if ( fileExists ) { viewBlocks[ block + '/view' ] = viewScriptPath; } return viewBlocks;