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:
@@ -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>
|
||||
</>
|
||||
|
||||
@@ -227,6 +227,7 @@ function Inner({
|
||||
key={product.rate}
|
||||
approximateRate={approximateRate}
|
||||
bannerText={bannerText}
|
||||
roomTypeCode={roomTypeCode}
|
||||
handleChange={() =>
|
||||
selectRate({
|
||||
roomIndex,
|
||||
|
||||
@@ -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={{
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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 ? (
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user