Skip to content

Commit

Permalink
feat: accessibility improvements to the donate block tabs (#1622)
Browse files Browse the repository at this point in the history
  • Loading branch information
laurelfulford authored Dec 12, 2023
1 parent 229d456 commit 115e9fb
Show file tree
Hide file tree
Showing 6 changed files with 250 additions and 131 deletions.
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

0 comments on commit 115e9fb

Please sign in to comment.