Merged in fix/BOOK-119-accessibility-redemption-radiogroup (pull request #3172)

fix(BOOK-119): reward nights accessible radiogroup

* fix(BOOK-119): reward nights accessible radiogroup

* fix(BOOK-119): pr comment focus

* fix(BOOK-119): added roomtype name to the radiogroup for accessibility improvment


Approved-by: Matilda Haneling
Approved-by: Erik Tiekstra
This commit is contained in:
Bianca Widstam
2025-11-20 13:28:21 +00:00
parent b1d7fbad88
commit ebd6e1dc2c
12 changed files with 51 additions and 13 deletions

View File

@@ -11,9 +11,10 @@ import type { RoomInfo } from "../../../../../../../contexts/SelectRate/types"
type Props = {
roomInfo: RoomInfo
roomTypeCode: string
}
export default function Details({ roomInfo }: Props) {
export default function Details({ roomInfo, roomTypeCode }: Props) {
const intl = useIntl()
const { name, occupancy, roomSize } = roomInfo || {}
@@ -50,7 +51,7 @@ export default function Details({ roomInfo }: Props) {
</div>
<div className={styles.roomDetails}>
<Typography variant="Title/Subtitle/lg">
<h2>{name}</h2>
<h2 id={roomTypeCode}>{name}</h2>
</Typography>
</div>
</>

View File

@@ -227,6 +227,7 @@ function Inner({
key={product.rate}
approximateRate={approximateRate}
bannerText={bannerText}
roomTypeCode={roomTypeCode}
handleChange={() =>
selectRate({
roomIndex,

View File

@@ -221,6 +221,7 @@ function CorporateChequeCode({
handleChange={() =>
handleSelectRate(codeProduct.corporateCheque.rateCode)
}
roomTypeCode={roomTypeCode}
isSelected={isSelected}
id={`${roomNr}-${roomTypeCode}-${rateCode}`.replace(/\s+/g, "-")}
paymentTerm={rateTitles[codeProduct.rate].paymentTerm}
@@ -314,6 +315,7 @@ function PublicCode({
approximateRate={approximateRate}
bannerText={bannerText}
comparisonRate={comparisonRate}
roomTypeCode={roomTypeCode}
handleChange={() => handleSelectRate(codeProduct.public!.rateCode)}
isSelected={isSelected}
id={`${roomNr}-${roomTypeCode}-${rateCode}`.replace(/\s+/g, "-")}
@@ -379,6 +381,7 @@ function VoucherCode({
bannerText={bannerText}
handleChange={() => handleSelectRate(codeProduct.voucher.rateCode)}
isSelected={isSelected}
roomTypeCode={roomTypeCode}
id={`${roomNr}-${roomTypeCode}-${rateCode}`.replace(/\s+/g, "-")}
paymentTerm={rateTitles[codeProduct.rate].paymentTerm}
rate={{

View File

@@ -34,6 +34,7 @@ export default function Redemptions({
actions: { selectRate },
selectedRates,
} = useSelectRateContext()
const roomNr = roomIndex + 1
const pointsCurrency = useGetPointsCurrency()
// TODO: Replace with context value when we have support for dropdown "Show all rates"
@@ -125,10 +126,12 @@ export default function Redemptions({
}}
paymentTerm={rateTitles[firstRedemption.rate].paymentTerm}
rates={rates}
id={`${roomNr}-${roomTypeCode}`.replace(/\s+/g, "-")}
rateTitle={rateTitles[firstRedemption.rate].title}
rateTermDetails={rateTermDetails}
selectedRate={selectedRateCode}
isNotEnoughPoints={notEnoughPoints}
roomTypeCode={roomTypeCode}
notEnoughPointsText={intl.formatMessage({
id: "booking.notEnoughPoints",
defaultMessage: "Not enough points",

View File

@@ -250,6 +250,7 @@ function Inner({
}}
isMemberRateActive={isMemberRateActive}
isSelected={isSelected}
roomTypeCode={roomTypeCode}
id={`${roomNr}-${roomTypeCode}-${rateCode}`.replace(/\s+/g, "-")}
paymentTerm={rateTitles[product.rate].paymentTerm}
rateTitle={rateTitles[product.rate].title}

View File

@@ -47,7 +47,7 @@ export function RoomListItem({
images={room.roomInfo.images ?? []}
hotelId={hotelId}
/>
<Details roomInfo={room.roomInfo} />
<Details roomInfo={room.roomInfo} roomTypeCode={room.roomTypeCode} />
</div>
<div className={styles.container}>
{room.status === AvailabilityEnum.NotAvailable ? (

View File

@@ -2,6 +2,7 @@ import { PropsWithChildren } from 'react'
import { Radio as AriaRadio } from 'react-aria-components'
import styles from './radio.module.css'
import { variants } from './variants'
import { cx } from 'class-variance-authority'
interface RadioProps extends PropsWithChildren {
value: string
@@ -22,7 +23,7 @@ export function Radio({ id, value, children, color, isDisabled }: RadioProps) {
id={inputId}
value={value}
isDisabled={isDisabled}
className={`${styles.container} ${isDisabled ? styles.disabled : ''}`}
className={cx(styles.container, { [styles.disabled]: isDisabled })}
>
<div className={`${styles.radio} ${classNames}`} />
<div>{children}</div>

View File

@@ -22,6 +22,11 @@
border-width: 8px;
}
.container[data-focus-visible] .radio {
outline: 2px solid var(--UI-Input-Controls-Border-Focus);
outline-offset: 2px;
}
.disabled {
opacity: 0.5;
cursor: not-allowed;

View File

@@ -24,6 +24,7 @@ interface CampaignRateCardProps {
isHighlightedRate?: boolean
isHighlightedRateLabel?: boolean
approximateRate?: Rate
roomTypeCode: string
handleChange: () => void
handleTermsClick?: () => void
rateTermDetails: RateTermDetails[]
@@ -35,6 +36,7 @@ export default function CampaignRateCard({
rateTitle,
paymentTerm,
rate,
roomTypeCode,
memberRate,
approximateRate,
comparisonRate,
@@ -60,8 +62,8 @@ export default function CampaignRateCard({
onPress={handleChange}
className={styles.buttonOverlay}
aria-pressed={isSelected}
aria-labelledby={`${id}-title`}
aria-describedby={`${id}-details`}
aria-describedby={`${roomTypeCode} ${id}-title`}
aria-labelledby={`${id}-details`}
/>
<div className={styles.banner}>
<MaterialIcon size={16} icon="sell" color="CurrentColor" />

View File

@@ -19,6 +19,7 @@ interface CodeRateCardProps {
bannerText: string
comparisonRate?: Omit<Rate, 'label'>
approximateRate?: Rate
roomTypeCode: string
isHighlightedRate?: boolean
handleChange: () => void
handleTermsClick?: () => void
@@ -31,6 +32,7 @@ export default function CodeRateCard({
rateTitle,
paymentTerm,
rate,
roomTypeCode,
approximateRate,
comparisonRate,
bannerText,
@@ -53,8 +55,8 @@ export default function CodeRateCard({
onPress={handleChange}
className={styles.buttonOverlay}
aria-pressed={isSelected}
aria-labelledby={`${id}-title`}
aria-describedby={`${id}-details`}
aria-describedby={`${roomTypeCode} ${id}-title`}
aria-labelledby={`${id}-details`}
/>
<Typography variant="Label/xsBold">
<p className={styles.banner}>{bannerText}</p>

View File

@@ -8,6 +8,7 @@ import { Radio } from '../../Radio'
import Modal from '../Modal'
import styles from '../rate-card.module.css'
import { variants } from '../variants'
import { useIntl } from 'react-intl'
interface PointsRateCardProps {
rateTitle: string
@@ -19,22 +20,27 @@ interface PointsRateCardProps {
isNotEnoughPoints?: boolean
notEnoughPointsText?: string
rateTermDetails: RateTermDetails[]
id: string
roomTypeCode: string
}
export default function PointsRateCard({
rateTitle,
paymentTerm,
bannerText,
id,
rates,
selectedRate,
isNotEnoughPoints,
notEnoughPointsText,
onRateSelect,
roomTypeCode,
rateTermDetails,
}: PointsRateCardProps) {
const classNames = variants({
variant: 'Points',
})
const intl = useIntl()
return (
<div className={classNames}>
@@ -49,7 +55,15 @@ export default function PointsRateCard({
title={rateTitle}
subtitle={paymentTerm}
trigger={
<IconButton theme="Black" style="Muted" wrapping>
<IconButton
theme="Black"
style="Muted"
wrapping
aria-label={intl.formatMessage({
id: 'selectRate.rateCard.openReservationPolicy',
defaultMessage: 'Open reservation policy',
})}
>
<MaterialIcon icon="info" size={20} color="Icon/Default" />
</IconButton>
}
@@ -85,8 +99,10 @@ export default function PointsRateCard({
</header>
<div className={styles.content}>
<RadioGroup
aria-label={rateTitle}
value={selectedRate}
/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */
aria-label={`${rateTitle} / ${paymentTerm}`}
aria-describedby={roomTypeCode}
value={selectedRate ? selectedRate : null} // default to null for focus to land on the first radio on tab, undefined set tabIndex to -1
onChange={onRateSelect}
>
{rates.map((rate, index) => (
@@ -94,6 +110,7 @@ export default function PointsRateCard({
<Radio
value={rate.rateCode}
isDisabled={rate.isDisabled || isNotEnoughPoints}
id={`${id}-${rate.rateCode}`}
>
<div className={styles.pointsRow}>
<Typography variant="Title/Subtitle/md">

View File

@@ -21,6 +21,7 @@ interface RegularRateCardProps {
approximateRate?: Rate
isMemberRateActive?: boolean
handleChange: () => void
roomTypeCode: string
rateTermDetails: RateTermDetails[]
}
@@ -32,6 +33,7 @@ export default function RegularRateCard({
approximateRate,
omnibusRate,
rate,
roomTypeCode,
memberRate,
isMemberRateActive,
handleChange,
@@ -51,8 +53,8 @@ export default function RegularRateCard({
onPress={handleChange}
className={styles.buttonOverlay}
aria-pressed={isSelected}
aria-labelledby={`${id}-title`}
aria-describedby={`${id}-details`}
aria-describedby={`${roomTypeCode} ${id}-title`}
aria-labelledby={`${id}-details`}
/>
<div className={styles.container}>
<Typography variant="Tag/sm">