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

Donate Block: Accessibility improvements #1622

Merged
merged 10 commits into from
Dec 12, 2023
32 changes: 26 additions & 6 deletions src/blocks/donate/edit/FrequencyBasedLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import classNames from 'classnames';
* WordPress dependencies
*/
import { __ } from '@wordpress/i18n';
import { useMemo, useEffect, useRef } from '@wordpress/element';
import { useMemo, useEffect, useRef, useState } from '@wordpress/element';
import { SelectControl } from '@wordpress/components';
import { RichText } from '@wordpress/block-editor';

Expand Down Expand Up @@ -68,29 +68,48 @@ const FrequencyBasedLayout = ( props: { isTiered: boolean } & ComponentProps ) =
const isRenderingStripePaymentForm =
window.newspack_blocks_data?.is_rendering_stripe_payment_form;

const [ selectedFrequency, setSelectedFrequency ] = useState( attributes.defaultFrequency );

const renderFrequencySelect = ( frequencySlug: DonationFrequencySlug ) => (
<>
<input
type="radio"
value={ frequencySlug }
id={ `newspack-donate-${ frequencySlug }-${ uid }` }
name="donation_frequency"
defaultChecked={ frequencySlug === attributes.defaultFrequency }
checked={ frequencySlug === selectedFrequency }
onChange={ evt => setSelectedFrequency( evt.target.value as 'once' | 'month' | 'year' ) }
/>
<label
htmlFor={ 'newspack-donate-' + frequencySlug + '-' + uid }
className="wpbnbd__button freq-label"
>
<label htmlFor={ 'newspack-donate-' + frequencySlug + '-' + uid }>
{ FREQUENCIES[ frequencySlug ] }
</label>
</>
);

const renderTab = ( frequencySlug: DonationFrequencySlug ) => (
<button
key={ frequencySlug }
role="tab"
className={ classNames( 'wpbnbd__button freq-label', {
'wpbnbd__button--active': frequencySlug === selectedFrequency,
} ) }
id={ `tab-newspack-donate-${ frequencySlug }-${ uid }` }
onClick={ evt => {
evt.preventDefault();
setSelectedFrequency( frequencySlug );
} }
>
{ FREQUENCIES[ frequencySlug ] }
</button>
);

// 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 renderUntieredForm = () => (
<div className="wp-block-newspack-blocks-donate__options">
<div className="wp-block-newspack-blocks-donate__frequencies frequencies">
<div className="tab-container">{ availableFrequencies.map( renderTab ) }</div>
{ availableFrequencies.map( frequencySlug => (
<div
className="wp-block-newspack-blocks-donate__frequency frequency"
Expand Down Expand Up @@ -123,6 +142,7 @@ const FrequencyBasedLayout = ( props: { isTiered: boolean } & ComponentProps ) =
const renderTieredForm = () => (
<div className="wp-block-newspack-blocks-donate__options">
<div className="wp-block-newspack-blocks-donate__frequencies frequencies">
<div className="tab-container">{ availableFrequencies.map( renderTab ) }</div>
{ availableFrequencies.map( frequencySlug => (
<div
className="wp-block-newspack-blocks-donate__frequency frequency"
Expand Down
62 changes: 62 additions & 0 deletions src/blocks/donate/frequency-based/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,12 +68,74 @@ const handleOtherValue = ( container: HTMLElement ) => {
} );
};

const addAccessibleTabs = ( container: HTMLElement ) => {
// Get the block's tabs, panels, and radio buttons associated with donation frequency.
const tabList = container.querySelectorAll(
'div[role="tablist"] [role="tab"]'
) as NodeListOf< HTMLElement >;
const panels = container.querySelectorAll( 'div[role="tabpanel"]' ) as NodeListOf< HTMLElement >;
const radioButtons = container.querySelectorAll(
'input[type="radio"][name="donation_frequency"]'
) as NodeListOf< HTMLInputElement >;
// Figure out which radio button is currently selected.
const checkedRadioId =
Array.from( radioButtons ).find( ( radio: HTMLInputElement ) => radio.checked )?.id || null;

// Set the tab associated to the radio button to selected.
if ( checkedRadioId ) {
const selectedTabId = `tab-${ checkedRadioId }`;
document.getElementById( selectedTabId )?.setAttribute( 'aria-selected', 'true' );
}

// Add a click event listener to each tab.
tabList.forEach( ( tab: HTMLElement ) => {
tab.addEventListener( 'click', function () {
selectTab( tab, tabList, radioButtons, panels );
} );
} );
};

const selectTab = (
tab: HTMLElement,
tabList: NodeListOf< HTMLElement >,
radioButtons: NodeListOf< HTMLInputElement >,
panels: NodeListOf< HTMLElement >
) => {
// Loop through tabs and set them as selected or not selected:
tabList.forEach( ( thisTab: HTMLElement ) => {
if ( tab === thisTab ) {
thisTab.setAttribute( 'aria-selected', 'true' );
thisTab.classList.add( 'wpbnbd__button--active' );
} else {
thisTab.setAttribute( 'aria-selected', 'false' );
thisTab.classList.remove( 'wpbnbd__button--active' );
}
} );

// Update the underlying radio button.
const tabId = tab.id || '';
const frequencyId = tabId.replace( 'tab-', '' );
radioButtons.forEach( ( radio: HTMLInputElement ) => {
radio.checked = frequencyId === radio.id;
} );

// Loop through the panels and set tabindex 0 on the selected panel; remove it from others.
panels.forEach( ( panel: HTMLElement ) => {
if ( tab.getAttribute( 'aria-controls' ) === panel.id ) {
panel.setAttribute( 'tabindex', '0' );
} else {
panel.removeAttribute( 'tabindex' );
}
} );
};

export const processFrequencyBasedElements = ( parentEl = document ) => {
const elements = parentEl.querySelectorAll(
'.wpbnbd--frequency-based'
) as NodeListOf< HTMLElement >;
elements.forEach( container => {
handleOtherValue( container );
addAccessibleTabs( container );
} );
};

Expand Down
97 changes: 32 additions & 65 deletions src/blocks/donate/frequency-based/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,16 @@
position: relative;
width: 100%;

// Hide some radio buttons absolutely.
input[type='radio'] {
left: -99999em;
position: absolute;
left: -99999em;

// And hide the ones being replaced by tabs completely.
&[name='donation_frequency'],
&[name='donation_frequency'] + label {
display: none;
}
}

input[readonly] {
Expand Down Expand Up @@ -46,8 +53,9 @@
opacity: 0.5;
}
}
label.wpbnbd {
.wpbnbd {
&__button {
font-size: 16px;
text-transform: uppercase;
}
}
Expand Down Expand Up @@ -191,76 +199,35 @@
}
}

.wpbnbd .frequencies {
position: relative;
font-size: variables.$font__size-xs;
@include mixins.media( mobile ) {
font-size: variables.$font__size-sm;
.wpbnbd {
.frequencies {
position: relative;
font-size: variables.$font__size-xs;
@include mixins.media( mobile ) {
font-size: variables.$font__size-sm;
}
}

padding-top: 2.65em;
}

.wpbnbd .frequency {
.freq-label {
position: absolute;
top: 0;
left: 0;
border-width: 0 0 1px;
.frequency input[type='radio']:checked ~ .tiers {
display: flex;
}

input[type='radio'] {
&:checked {
+ .freq-label {
color: inherit;
border-bottom-color: transparent;

&:hover {
background: colors.$color__background-body;
}
}
.tab-container {
display: flex;
.freq-label {
border-radius: 0;
margin: 0;

~ .tiers {
display: flex;
&.wpbnbd__button--active {
color: colors.$color__text-main;
}
}

&:focus + .freq-label {
text-decoration: underline;
text-decoration-style: dotted;
text-decoration-thickness: 1px;
}
}

&:nth-of-type( 3 ) .freq-label {
left: 66.66%;
}
}

.wpbnbd-frequencies {
&--2 {
&.wpbnbd .frequency {
.freq-label {
width: 50%;
}
&:nth-of-type( 2 ) .freq-label {
border-left-width: 1px;
left: 50%;
top: 0;
&:focus {
outline: 0;
text-decoration: underline;
text-decoration-style: dotted;
text-decoration-thickness: 1px;
}
}
}
&--3 {
&.wpbnbd .frequency {
.freq-label {
width: 33.33%;
}
&:nth-of-type( 2 ) .freq-label {
border-left-width: 1px;
border-right-width: 1px;
left: 33.33%;
top: 0;
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,16 +38,41 @@ private static function render_frequency_selection( $frequency_slug, $frequency_
);
?>
/>
<label
for='newspack-donate-<?php echo esc_attr( $frequency_slug . '-' . $uid ); ?>'
class='wpbnbd__button freq-label'
>
<label for='newspack-donate-<?php echo esc_attr( $frequency_slug . '-' . $uid ); ?>'>
<?php echo esc_html( $frequency_name ); ?>
</label>
<?php
return ob_get_clean();
}

/**
* Renders the frequency selection of the donation form in accessible tabs.
*
* @param string $frequency_slug Frequency slug.
* @param string $frequency_name Frequency name.
* @param number $uid Unique ID.
* @param array $configuration The donations settings.
*
* @return string
*/
private static function render_frequency_tab( $frequency_slug, $frequency_name, $uid, $configuration ) {
ob_start();
?>
<button
role='tab'
type='button'
aria-controls='tab-panel-<?php echo esc_attr( $frequency_slug . '-' . $uid ); ?>'
class="wpbnbd__button freq-label<?php echo esc_attr( $frequency_slug === $configuration['defaultFrequency'] ? ' wpbnbd__button--active' : '' ); ?>"
data-tab-id="<?php echo esc_attr( $frequency_slug . '-' . $uid ); ?>"
id="tab-newspack-donate-<?php echo esc_attr( $frequency_slug . '-' . $uid ); ?>"
<?php echo "aria-selected='" . ( $frequency_slug === $configuration['defaultFrequency'] ? 'true' : 'false' ) . "'"; ?>
>
<?php echo esc_html( $frequency_name ); ?>
</button>
<?php
return ob_get_clean();
}

/**
* Renders the footer of the donation form.
*
Expand Down Expand Up @@ -108,12 +133,25 @@ class="untiered <?php echo esc_html( $configuration['container_classnames'] ); ?
<?php echo self::render_hidden_form_inputs( $attributes ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
<div class='wp-block-newspack-blocks-donate__options'>
<div class='wp-block-newspack-blocks-donate__frequencies frequencies'>

<div role='tablist' class='tab-container'>
<?php foreach ( $configuration['frequencies'] as $frequency_slug => $frequency_name ) : ?>
<?php echo self::render_frequency_tab( $frequency_slug, $frequency_name, $uid, $configuration ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
<?php endforeach; ?>
</div>

<?php foreach ( $configuration['frequencies'] as $frequency_slug => $frequency_name ) : ?>
<?php
$formatted_amount = $configuration['amounts'][ $frequency_slug ][3];
?>

<div class='wp-block-newspack-blocks-donate__frequency frequency'>
<div
class='wp-block-newspack-blocks-donate__frequency frequency'
id='tab-panel-<?php echo esc_attr( $frequency_slug . '-' . $uid ); ?>'
role='tabpanel'
aria-labelledby='tab-newspack-donate-<?php echo esc_attr( $frequency_slug . '-' . $uid ); ?>'
<?php ( $frequency_slug === $configuration['defaultFrequency'] ? 'tabindex="0"' : '' ); ?>
>
<?php echo self::render_frequency_selection( $frequency_slug, $frequency_name, $uid, $configuration ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
<div class='input-container'>
<label
Expand Down Expand Up @@ -157,9 +195,21 @@ class="tiered <?php echo esc_html( $configuration['container_classnames'] ); ?>"
<?php echo self::render_hidden_form_inputs( $attributes ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
<div class='wp-block-newspack-blocks-donate__options'>
<div class='wp-block-newspack-blocks-donate__frequencies frequencies'>
<div role='tablist' class='tab-container'>
<?php foreach ( $configuration['frequencies'] as $frequency_slug => $frequency_name ) : ?>
<?php echo self::render_frequency_tab( $frequency_slug, $frequency_name, $uid, $configuration ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
<?php endforeach; ?>
</div>

<?php foreach ( $configuration['frequencies'] as $frequency_slug => $frequency_name ) : ?>

<div class='wp-block-newspack-blocks-donate__frequency frequency'>
<div
class='wp-block-newspack-blocks-donate__frequency frequency'
id='tab-panel-<?php echo esc_attr( $frequency_slug . '-' . $uid ); ?>'
role='tabpanel'
aria-labelledby='tab-newspack-donate-<?php echo esc_attr( $frequency_slug . '-' . $uid ); ?>'
<?php ( $frequency_slug === $configuration['defaultFrequency'] ? 'tabindex="0"' : '' ); ?>
>
<?php echo self::render_frequency_selection( $frequency_slug, $frequency_name, $uid, $configuration ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
<div class='wp-block-newspack-blocks-donate__tiers tiers'>
<?php foreach ( $suggested_amounts[ $frequency_slug ] as $index => $amount ) : ?>
Expand Down
Loading