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

@W-14338017@ Fix: accessibility focus for my account pages and promo code #1625

Merged
merged 18 commits into from
Dec 21, 2023
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/template-retail-react-app/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
- Add correct keyboard interaction behavior for variation attribute radio buttons [#1587](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/1587)
- Change radio refinements (for example, filtering by Price) from radio inputs to styled buttons [#1605](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/1605)
- Update search refinements ARIA labels to include "add/remove filter" [#1607](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/1607)
- Improve focus behavior on my account pages, address forms, and promo codes [#1625](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/1625)

### Other features

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
* SPDX-License-Identifier: BSD-3-Clause
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause
*/
import React from 'react'
import React, {useEffect, useRef} from 'react'
import {useIntl} from 'react-intl'
import PropTypes from 'prop-types'
import {
Grid,
Expand All @@ -19,9 +20,24 @@ import {useCurrentCustomer} from '@salesforce/retail-react-app/app/hooks/use-cur
const AddressFields = ({form, prefix = ''}) => {
const {data: customer} = useCurrentCustomer()
const fields = useAddressFields({form, prefix})
const intl = useIntl()

const addressFormRef = useRef()
useEffect(() => {
// Focus on the form when the component mounts for accessibility
addressFormRef?.current?.focus()
}, [])

return (
<Stack spacing={5}>
<Stack
spacing={5}
aria-label={intl.formatMessage({
id: 'use_address_fields.label.address_form',
defaultMessage: 'Address Form'
})}
tabIndex="0"
ref={addressFormRef}
>
<SimpleGrid columns={[1, 1, 2]} gap={5}>
<Field {...fields.firstName} />
<Field {...fields.lastName} />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ const PromoCodeFields = ({form, prefix = '', ...props}) => {
const code = form.watch('code')

return (
<Box {...props}>
<Box aria-labelledby="code-feedback" {...props}>
<Field inputProps={{flex: 1, mr: 2}} {...fields.code}>
<Button
type="submit"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ export const ToggleCard = ({
fontSize="lg"
lineHeight="30px"
color={disabled && !editing && 'gray.600'}
tabIndex="0"
>
{title}
</Heading>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause
*/

import React, {useState} from 'react'
import React, {useEffect, useRef, useState} from 'react'
import {defineMessage, FormattedMessage, useIntl} from 'react-intl'
import PropTypes from 'prop-types'

Expand Down Expand Up @@ -147,6 +147,12 @@ const AccountAddresses = () => {
const showToast = useToast()
const form = useForm()

const headingRef = useRef()
useEffect(() => {
// Focus the 'Addresses' header when the component mounts for accessibility
headingRef?.current?.focus()
}, [])

const hasAddresses = addresses?.length > 0
const showError = () => {
showToast({
Expand Down Expand Up @@ -216,6 +222,8 @@ const AccountAddresses = () => {
status: 'success',
isClosable: true
})
// Move focus to header after we successfully remove address
headingRef?.current?.focus()
}
}
)
Expand All @@ -234,12 +242,13 @@ const AccountAddresses = () => {
} else {
setSelectedAddressId(undefined)
setIsEditing(!isEditing)
headingRef?.current?.focus()
}
}

return (
<Stack spacing={4} data-testid="account-addresses-page">
<Heading as="h1" fontSize="2xl">
<Heading as="h1" fontSize="2xl" tabIndex="0" ref={headingRef}>
<FormattedMessage
defaultMessage="Addresses"
id="account_addresses.title.addresses"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause
*/

import React, {useEffect} from 'react'
import React, {useEffect, useRef} from 'react'
import {FormattedMessage, FormattedNumber, useIntl} from 'react-intl'
import {useLocation} from 'react-router'
import {
Expand Down Expand Up @@ -99,14 +99,20 @@ const AccountOrderHistory = () => {

const pageUrls = usePageUrls({total: paging.total, limit})

const headingRef = useRef()
useEffect(() => {
// Focus the 'Order History' header when the component mounts for accessibility
headingRef?.current?.focus()
}, [])

useEffect(() => {
window.scrollTo(0, 0)
}, [customer, searchParams.offset])

return (
<Stack spacing={4} data-testid="account-order-history-page">
<Stack>
<Heading as="h1" fontSize="2xl">
<Heading as="h1" fontSize="2xl" tabIndex="0" ref={headingRef}>
joeluong-sfcc marked this conversation as resolved.
Show resolved Hide resolved
<FormattedMessage
defaultMessage="Order History"
id="account_order_history.title.order_history"
Expand Down
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similarly, we are missing handling the focus when users click the Cancel or Save buttons on the form opened by clicking the 'Edit' button in the Account Details page.

<FormActionButtons onCancel={() => setIsEditing(false)} />

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There wasn't a straightforward way to maintain a reference to the edit buttons since the edit buttons disappear from the DOM when in edit mode and get re-rendered after the form is closed, so for now I set the focus to the Profile/Password heading when you click the Cancel/Save buttons in edit mode

Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause
*/

import React, {useEffect, useState} from 'react'
import React, {useEffect, useRef, useState} from 'react'
import {FormattedMessage, useIntl} from 'react-intl'
import {
Alert,
Expand Down Expand Up @@ -335,9 +335,15 @@ const PasswordCard = () => {
}

const AccountDetail = () => {
const headingRef = useRef()
useEffect(() => {
// Focus the 'Account Details' header when the component mounts for accessibility
headingRef?.current?.focus()
}, [])

return (
<Stack data-testid="account-detail-page" spacing={6}>
<Heading as="h1" fontSize="24px">
<Heading as="h1" fontSize="24px" tabIndex="0" ref={headingRef}>
<FormattedMessage
defaultMessage="Account Details"
id="account_detail.title.account_details"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* SPDX-License-Identifier: BSD-3-Clause
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause
*/
import React, {useState} from 'react'
import React, {useState, useEffect, useRef} from 'react'
import {Stack, Heading} from '@chakra-ui/layout'
import {FormattedMessage, useIntl} from 'react-intl'
import {Box, Flex, Skeleton} from '@salesforce/retail-react-app/app/components/shared/ui'
Expand All @@ -30,6 +30,12 @@ const AccountWishlist = () => {
const {formatMessage} = useIntl()
const toast = useToast()

const headingRef = useRef()
useEffect(() => {
// Focus the 'Wishlist' header when the component mounts for accessibility
headingRef?.current?.focus()
}, [])

const [selectedItem, setSelectedItem] = useState(undefined)
const [isWishlistItemLoading, setWishlistItemLoading] = useState(false)

Expand Down Expand Up @@ -112,7 +118,7 @@ const AccountWishlist = () => {

return (
<Stack spacing={4} data-testid="account-wishlist-page">
<Heading as="h1" fontSize="2xl">
<Heading as="h1" fontSize="2xl" tabIndex="0" ref={headingRef}>
<FormattedMessage defaultMessage="Wishlist" id="account_wishlist.title.wishlist" />
</Heading>

Expand Down Expand Up @@ -184,6 +190,8 @@ const AccountWishlist = () => {
secondaryActions={
<WishlistSecondaryButtonGroup
productListItemId={item.id}
// Focus to 'Wishlist' header after remove for accessibility
focusElementOnRemove={headingRef}
onClick={handleSecondaryAction}
/>
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,11 @@ export const REMOVE_WISHLIST_ITEM_CONFIRMATION_DIALOG_CONFIG = {
* Renders secondary actions on a product-item card in the form of a button group.
* Represents other actions you want the user to perform with the product-item (eg.: Remove or Edit)
*/
const WishlistSecondaryButtonGroup = ({productListItemId, onClick = noop}) => {
const WishlistSecondaryButtonGroup = ({
productListItemId,
focusElementOnRemove,
onClick = noop
}) => {
const variant = useItemVariant()
const {data: customer} = useCurrentCustomer()
const {data: wishList} = useWishList()
Expand All @@ -57,6 +61,9 @@ const WishlistSecondaryButtonGroup = ({productListItemId, onClick = noop}) => {

const showRemoveItemConfirmation = () => {
modalProps.onOpen()
// After we remove an item from the wishlist
// we need to place focus to the next logical place for accessibility
focusElementOnRemove?.current?.focus()
joeluong-sfcc marked this conversation as resolved.
Show resolved Hide resolved
}

const deleteCustomerProductListItem = useShopperCustomersMutation(
Expand Down Expand Up @@ -118,6 +125,7 @@ const WishlistSecondaryButtonGroup = ({productListItemId, onClick = noop}) => {

WishlistSecondaryButtonGroup.propTypes = {
productListItemId: PropTypes.string,
focusElementOnRemove: PropTypes.object,
onClick: PropTypes.func
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -329,7 +329,7 @@ test('Can proceed through checkout steps as guest', async () => {
await user.type(screen.getByLabelText(/first name/i), 'Tester')
await user.type(screen.getByLabelText(/last name/i), 'McTesting')
await user.type(screen.getByLabelText(/phone/i), '(727) 555-1234')
await user.type(screen.getByLabelText(/address/i), '123 Main St')
await user.type(screen.getAllByLabelText(/address/i)[0], '123 Main St')
await user.type(screen.getByLabelText(/city/i), 'Tampa')
await user.selectOptions(screen.getByLabelText(/state/i), ['FL'])
await user.type(screen.getByLabelText(/zip code/i), '33610')
Expand Down Expand Up @@ -564,7 +564,7 @@ test('Can add address during checkout as a registered customer', async () => {
await user.type(screen.getByLabelText(/last name/i), 'McTester')
await user.type(screen.getByLabelText(/phone/i), '7275551234')
await user.selectOptions(screen.getByLabelText(/country/i), ['US'])
await user.type(screen.getByLabelText(/address/i), 'Tropicana Field')
await user.type(screen.getAllByLabelText(/address/i)[0], 'Tropicana Field')
await user.type(screen.getByLabelText(/city/i), 'Tampa')
await user.selectOptions(screen.getByLabelText(/state/i), ['FL'])
await user.type(screen.getByLabelText(/zip code/i), '33712')
Expand Down
joeluong-sfcc marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -195,19 +195,35 @@ const ShippingAddressSelection = ({
form.reset({...address})
}

const headingText = formatMessage({
defaultMessage: 'Shipping Address',
id: 'shipping_address.title.shipping_address'
})
const shippingAddressHeading = Array.from(document.querySelectorAll('h2')).find(
(element) => element.textContent === headingText
)

const removeSavedAddress = async (addressId) => {
if (addressId === selectedAddressId) {
setSelectedAddressId(undefined)
setIsEditingAddress(false)
form.reset({addressId: ''})
}

await removeCustomerAddress.mutateAsync({
parameters: {
customerId: customer.customerId,
addressName: addressId
await removeCustomerAddress.mutateAsync(
{
parameters: {
customerId: customer.customerId,
addressName: addressId
}
},
{
onSuccess: () => {
// Focus on header after successful remove for accessibility
shippingAddressHeading?.focus()
}
}
})
)
}

// Opens/closes the 'add address' form. Notice that when toggling either state,
Expand All @@ -221,6 +237,8 @@ const ShippingAddressSelection = ({
setSelectedAddressId(undefined)
form.reset({addressId: ''})
setIsEditingAddress(!isEditingAddress)
// Set focus to header after canceling out of form
shippingAddressHeading?.focus()
}

form.trigger()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2965,6 +2965,12 @@
"value": "Address"
}
],
"use_address_fields.label.address_form": [
{
"type": 0,
"value": "Address Form"
}
],
"use_address_fields.label.city": [
{
"type": 0,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2965,6 +2965,12 @@
"value": "Address"
}
],
"use_address_fields.label.address_form": [
{
"type": 0,
"value": "Address Form"
}
],
"use_address_fields.label.city": [
{
"type": 0,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6317,6 +6317,20 @@
"value": "]"
}
],
"use_address_fields.label.address_form": [
{
"type": 0,
"value": "["
},
{
"type": 0,
"value": "Ȧḓḓřḗḗşş Ƒǿǿřḿ"
},
{
"type": 0,
"value": "]"
}
],
"use_address_fields.label.city": [
{
"type": 0,
Expand Down
3 changes: 3 additions & 0 deletions packages/template-retail-react-app/translations/en-GB.json
Original file line number Diff line number Diff line change
Expand Up @@ -1270,6 +1270,9 @@
"use_address_fields.label.address": {
"defaultMessage": "Address"
},
"use_address_fields.label.address_form": {
"defaultMessage": "Address Form"
},
"use_address_fields.label.city": {
"defaultMessage": "City"
},
Expand Down
3 changes: 3 additions & 0 deletions packages/template-retail-react-app/translations/en-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -1270,6 +1270,9 @@
"use_address_fields.label.address": {
"defaultMessage": "Address"
},
"use_address_fields.label.address_form": {
"defaultMessage": "Address Form"
},
"use_address_fields.label.city": {
"defaultMessage": "City"
},
Expand Down