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 = {
|
type Props = {
|
||||||
roomInfo: RoomInfo
|
roomInfo: RoomInfo
|
||||||
|
roomTypeCode: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Details({ roomInfo }: Props) {
|
export default function Details({ roomInfo, roomTypeCode }: Props) {
|
||||||
const intl = useIntl()
|
const intl = useIntl()
|
||||||
|
|
||||||
const { name, occupancy, roomSize } = roomInfo || {}
|
const { name, occupancy, roomSize } = roomInfo || {}
|
||||||
@@ -50,7 +51,7 @@ export default function Details({ roomInfo }: Props) {
|
|||||||
</div>
|
</div>
|
||||||
<div className={styles.roomDetails}>
|
<div className={styles.roomDetails}>
|
||||||
<Typography variant="Title/Subtitle/lg">
|
<Typography variant="Title/Subtitle/lg">
|
||||||
<h2>{name}</h2>
|
<h2 id={roomTypeCode}>{name}</h2>
|
||||||
</Typography>
|
</Typography>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -227,6 +227,7 @@ function Inner({
|
|||||||
key={product.rate}
|
key={product.rate}
|
||||||
approximateRate={approximateRate}
|
approximateRate={approximateRate}
|
||||||
bannerText={bannerText}
|
bannerText={bannerText}
|
||||||
|
roomTypeCode={roomTypeCode}
|
||||||
handleChange={() =>
|
handleChange={() =>
|
||||||
selectRate({
|
selectRate({
|
||||||
roomIndex,
|
roomIndex,
|
||||||
|
|||||||
@@ -221,6 +221,7 @@ function CorporateChequeCode({
|
|||||||
handleChange={() =>
|
handleChange={() =>
|
||||||
handleSelectRate(codeProduct.corporateCheque.rateCode)
|
handleSelectRate(codeProduct.corporateCheque.rateCode)
|
||||||
}
|
}
|
||||||
|
roomTypeCode={roomTypeCode}
|
||||||
isSelected={isSelected}
|
isSelected={isSelected}
|
||||||
id={`${roomNr}-${roomTypeCode}-${rateCode}`.replace(/\s+/g, "-")}
|
id={`${roomNr}-${roomTypeCode}-${rateCode}`.replace(/\s+/g, "-")}
|
||||||
paymentTerm={rateTitles[codeProduct.rate].paymentTerm}
|
paymentTerm={rateTitles[codeProduct.rate].paymentTerm}
|
||||||
@@ -314,6 +315,7 @@ function PublicCode({
|
|||||||
approximateRate={approximateRate}
|
approximateRate={approximateRate}
|
||||||
bannerText={bannerText}
|
bannerText={bannerText}
|
||||||
comparisonRate={comparisonRate}
|
comparisonRate={comparisonRate}
|
||||||
|
roomTypeCode={roomTypeCode}
|
||||||
handleChange={() => handleSelectRate(codeProduct.public!.rateCode)}
|
handleChange={() => handleSelectRate(codeProduct.public!.rateCode)}
|
||||||
isSelected={isSelected}
|
isSelected={isSelected}
|
||||||
id={`${roomNr}-${roomTypeCode}-${rateCode}`.replace(/\s+/g, "-")}
|
id={`${roomNr}-${roomTypeCode}-${rateCode}`.replace(/\s+/g, "-")}
|
||||||
@@ -379,6 +381,7 @@ function VoucherCode({
|
|||||||
bannerText={bannerText}
|
bannerText={bannerText}
|
||||||
handleChange={() => handleSelectRate(codeProduct.voucher.rateCode)}
|
handleChange={() => handleSelectRate(codeProduct.voucher.rateCode)}
|
||||||
isSelected={isSelected}
|
isSelected={isSelected}
|
||||||
|
roomTypeCode={roomTypeCode}
|
||||||
id={`${roomNr}-${roomTypeCode}-${rateCode}`.replace(/\s+/g, "-")}
|
id={`${roomNr}-${roomTypeCode}-${rateCode}`.replace(/\s+/g, "-")}
|
||||||
paymentTerm={rateTitles[codeProduct.rate].paymentTerm}
|
paymentTerm={rateTitles[codeProduct.rate].paymentTerm}
|
||||||
rate={{
|
rate={{
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ export default function Redemptions({
|
|||||||
actions: { selectRate },
|
actions: { selectRate },
|
||||||
selectedRates,
|
selectedRates,
|
||||||
} = useSelectRateContext()
|
} = useSelectRateContext()
|
||||||
|
const roomNr = roomIndex + 1
|
||||||
const pointsCurrency = useGetPointsCurrency()
|
const pointsCurrency = useGetPointsCurrency()
|
||||||
|
|
||||||
// TODO: Replace with context value when we have support for dropdown "Show all rates"
|
// 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}
|
paymentTerm={rateTitles[firstRedemption.rate].paymentTerm}
|
||||||
rates={rates}
|
rates={rates}
|
||||||
|
id={`${roomNr}-${roomTypeCode}`.replace(/\s+/g, "-")}
|
||||||
rateTitle={rateTitles[firstRedemption.rate].title}
|
rateTitle={rateTitles[firstRedemption.rate].title}
|
||||||
rateTermDetails={rateTermDetails}
|
rateTermDetails={rateTermDetails}
|
||||||
selectedRate={selectedRateCode}
|
selectedRate={selectedRateCode}
|
||||||
isNotEnoughPoints={notEnoughPoints}
|
isNotEnoughPoints={notEnoughPoints}
|
||||||
|
roomTypeCode={roomTypeCode}
|
||||||
notEnoughPointsText={intl.formatMessage({
|
notEnoughPointsText={intl.formatMessage({
|
||||||
id: "booking.notEnoughPoints",
|
id: "booking.notEnoughPoints",
|
||||||
defaultMessage: "Not enough points",
|
defaultMessage: "Not enough points",
|
||||||
|
|||||||
@@ -250,6 +250,7 @@ function Inner({
|
|||||||
}}
|
}}
|
||||||
isMemberRateActive={isMemberRateActive}
|
isMemberRateActive={isMemberRateActive}
|
||||||
isSelected={isSelected}
|
isSelected={isSelected}
|
||||||
|
roomTypeCode={roomTypeCode}
|
||||||
id={`${roomNr}-${roomTypeCode}-${rateCode}`.replace(/\s+/g, "-")}
|
id={`${roomNr}-${roomTypeCode}-${rateCode}`.replace(/\s+/g, "-")}
|
||||||
paymentTerm={rateTitles[product.rate].paymentTerm}
|
paymentTerm={rateTitles[product.rate].paymentTerm}
|
||||||
rateTitle={rateTitles[product.rate].title}
|
rateTitle={rateTitles[product.rate].title}
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ export function RoomListItem({
|
|||||||
images={room.roomInfo.images ?? []}
|
images={room.roomInfo.images ?? []}
|
||||||
hotelId={hotelId}
|
hotelId={hotelId}
|
||||||
/>
|
/>
|
||||||
<Details roomInfo={room.roomInfo} />
|
<Details roomInfo={room.roomInfo} roomTypeCode={room.roomTypeCode} />
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.container}>
|
<div className={styles.container}>
|
||||||
{room.status === AvailabilityEnum.NotAvailable ? (
|
{room.status === AvailabilityEnum.NotAvailable ? (
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { PropsWithChildren } from 'react'
|
|||||||
import { Radio as AriaRadio } from 'react-aria-components'
|
import { Radio as AriaRadio } from 'react-aria-components'
|
||||||
import styles from './radio.module.css'
|
import styles from './radio.module.css'
|
||||||
import { variants } from './variants'
|
import { variants } from './variants'
|
||||||
|
import { cx } from 'class-variance-authority'
|
||||||
|
|
||||||
interface RadioProps extends PropsWithChildren {
|
interface RadioProps extends PropsWithChildren {
|
||||||
value: string
|
value: string
|
||||||
@@ -22,7 +23,7 @@ export function Radio({ id, value, children, color, isDisabled }: RadioProps) {
|
|||||||
id={inputId}
|
id={inputId}
|
||||||
value={value}
|
value={value}
|
||||||
isDisabled={isDisabled}
|
isDisabled={isDisabled}
|
||||||
className={`${styles.container} ${isDisabled ? styles.disabled : ''}`}
|
className={cx(styles.container, { [styles.disabled]: isDisabled })}
|
||||||
>
|
>
|
||||||
<div className={`${styles.radio} ${classNames}`} />
|
<div className={`${styles.radio} ${classNames}`} />
|
||||||
<div>{children}</div>
|
<div>{children}</div>
|
||||||
|
|||||||
@@ -22,6 +22,11 @@
|
|||||||
border-width: 8px;
|
border-width: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.container[data-focus-visible] .radio {
|
||||||
|
outline: 2px solid var(--UI-Input-Controls-Border-Focus);
|
||||||
|
outline-offset: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
.disabled {
|
.disabled {
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ interface CampaignRateCardProps {
|
|||||||
isHighlightedRate?: boolean
|
isHighlightedRate?: boolean
|
||||||
isHighlightedRateLabel?: boolean
|
isHighlightedRateLabel?: boolean
|
||||||
approximateRate?: Rate
|
approximateRate?: Rate
|
||||||
|
roomTypeCode: string
|
||||||
handleChange: () => void
|
handleChange: () => void
|
||||||
handleTermsClick?: () => void
|
handleTermsClick?: () => void
|
||||||
rateTermDetails: RateTermDetails[]
|
rateTermDetails: RateTermDetails[]
|
||||||
@@ -35,6 +36,7 @@ export default function CampaignRateCard({
|
|||||||
rateTitle,
|
rateTitle,
|
||||||
paymentTerm,
|
paymentTerm,
|
||||||
rate,
|
rate,
|
||||||
|
roomTypeCode,
|
||||||
memberRate,
|
memberRate,
|
||||||
approximateRate,
|
approximateRate,
|
||||||
comparisonRate,
|
comparisonRate,
|
||||||
@@ -60,8 +62,8 @@ export default function CampaignRateCard({
|
|||||||
onPress={handleChange}
|
onPress={handleChange}
|
||||||
className={styles.buttonOverlay}
|
className={styles.buttonOverlay}
|
||||||
aria-pressed={isSelected}
|
aria-pressed={isSelected}
|
||||||
aria-labelledby={`${id}-title`}
|
aria-describedby={`${roomTypeCode} ${id}-title`}
|
||||||
aria-describedby={`${id}-details`}
|
aria-labelledby={`${id}-details`}
|
||||||
/>
|
/>
|
||||||
<div className={styles.banner}>
|
<div className={styles.banner}>
|
||||||
<MaterialIcon size={16} icon="sell" color="CurrentColor" />
|
<MaterialIcon size={16} icon="sell" color="CurrentColor" />
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ interface CodeRateCardProps {
|
|||||||
bannerText: string
|
bannerText: string
|
||||||
comparisonRate?: Omit<Rate, 'label'>
|
comparisonRate?: Omit<Rate, 'label'>
|
||||||
approximateRate?: Rate
|
approximateRate?: Rate
|
||||||
|
roomTypeCode: string
|
||||||
isHighlightedRate?: boolean
|
isHighlightedRate?: boolean
|
||||||
handleChange: () => void
|
handleChange: () => void
|
||||||
handleTermsClick?: () => void
|
handleTermsClick?: () => void
|
||||||
@@ -31,6 +32,7 @@ export default function CodeRateCard({
|
|||||||
rateTitle,
|
rateTitle,
|
||||||
paymentTerm,
|
paymentTerm,
|
||||||
rate,
|
rate,
|
||||||
|
roomTypeCode,
|
||||||
approximateRate,
|
approximateRate,
|
||||||
comparisonRate,
|
comparisonRate,
|
||||||
bannerText,
|
bannerText,
|
||||||
@@ -53,8 +55,8 @@ export default function CodeRateCard({
|
|||||||
onPress={handleChange}
|
onPress={handleChange}
|
||||||
className={styles.buttonOverlay}
|
className={styles.buttonOverlay}
|
||||||
aria-pressed={isSelected}
|
aria-pressed={isSelected}
|
||||||
aria-labelledby={`${id}-title`}
|
aria-describedby={`${roomTypeCode} ${id}-title`}
|
||||||
aria-describedby={`${id}-details`}
|
aria-labelledby={`${id}-details`}
|
||||||
/>
|
/>
|
||||||
<Typography variant="Label/xsBold">
|
<Typography variant="Label/xsBold">
|
||||||
<p className={styles.banner}>{bannerText}</p>
|
<p className={styles.banner}>{bannerText}</p>
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { Radio } from '../../Radio'
|
|||||||
import Modal from '../Modal'
|
import Modal from '../Modal'
|
||||||
import styles from '../rate-card.module.css'
|
import styles from '../rate-card.module.css'
|
||||||
import { variants } from '../variants'
|
import { variants } from '../variants'
|
||||||
|
import { useIntl } from 'react-intl'
|
||||||
|
|
||||||
interface PointsRateCardProps {
|
interface PointsRateCardProps {
|
||||||
rateTitle: string
|
rateTitle: string
|
||||||
@@ -19,22 +20,27 @@ interface PointsRateCardProps {
|
|||||||
isNotEnoughPoints?: boolean
|
isNotEnoughPoints?: boolean
|
||||||
notEnoughPointsText?: string
|
notEnoughPointsText?: string
|
||||||
rateTermDetails: RateTermDetails[]
|
rateTermDetails: RateTermDetails[]
|
||||||
|
id: string
|
||||||
|
roomTypeCode: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function PointsRateCard({
|
export default function PointsRateCard({
|
||||||
rateTitle,
|
rateTitle,
|
||||||
paymentTerm,
|
paymentTerm,
|
||||||
bannerText,
|
bannerText,
|
||||||
|
id,
|
||||||
rates,
|
rates,
|
||||||
selectedRate,
|
selectedRate,
|
||||||
isNotEnoughPoints,
|
isNotEnoughPoints,
|
||||||
notEnoughPointsText,
|
notEnoughPointsText,
|
||||||
onRateSelect,
|
onRateSelect,
|
||||||
|
roomTypeCode,
|
||||||
rateTermDetails,
|
rateTermDetails,
|
||||||
}: PointsRateCardProps) {
|
}: PointsRateCardProps) {
|
||||||
const classNames = variants({
|
const classNames = variants({
|
||||||
variant: 'Points',
|
variant: 'Points',
|
||||||
})
|
})
|
||||||
|
const intl = useIntl()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classNames}>
|
<div className={classNames}>
|
||||||
@@ -49,7 +55,15 @@ export default function PointsRateCard({
|
|||||||
title={rateTitle}
|
title={rateTitle}
|
||||||
subtitle={paymentTerm}
|
subtitle={paymentTerm}
|
||||||
trigger={
|
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" />
|
<MaterialIcon icon="info" size={20} color="Icon/Default" />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
}
|
}
|
||||||
@@ -85,8 +99,10 @@ export default function PointsRateCard({
|
|||||||
</header>
|
</header>
|
||||||
<div className={styles.content}>
|
<div className={styles.content}>
|
||||||
<RadioGroup
|
<RadioGroup
|
||||||
aria-label={rateTitle}
|
/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */
|
||||||
value={selectedRate}
|
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}
|
onChange={onRateSelect}
|
||||||
>
|
>
|
||||||
{rates.map((rate, index) => (
|
{rates.map((rate, index) => (
|
||||||
@@ -94,6 +110,7 @@ export default function PointsRateCard({
|
|||||||
<Radio
|
<Radio
|
||||||
value={rate.rateCode}
|
value={rate.rateCode}
|
||||||
isDisabled={rate.isDisabled || isNotEnoughPoints}
|
isDisabled={rate.isDisabled || isNotEnoughPoints}
|
||||||
|
id={`${id}-${rate.rateCode}`}
|
||||||
>
|
>
|
||||||
<div className={styles.pointsRow}>
|
<div className={styles.pointsRow}>
|
||||||
<Typography variant="Title/Subtitle/md">
|
<Typography variant="Title/Subtitle/md">
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ interface RegularRateCardProps {
|
|||||||
approximateRate?: Rate
|
approximateRate?: Rate
|
||||||
isMemberRateActive?: boolean
|
isMemberRateActive?: boolean
|
||||||
handleChange: () => void
|
handleChange: () => void
|
||||||
|
roomTypeCode: string
|
||||||
rateTermDetails: RateTermDetails[]
|
rateTermDetails: RateTermDetails[]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -32,6 +33,7 @@ export default function RegularRateCard({
|
|||||||
approximateRate,
|
approximateRate,
|
||||||
omnibusRate,
|
omnibusRate,
|
||||||
rate,
|
rate,
|
||||||
|
roomTypeCode,
|
||||||
memberRate,
|
memberRate,
|
||||||
isMemberRateActive,
|
isMemberRateActive,
|
||||||
handleChange,
|
handleChange,
|
||||||
@@ -51,8 +53,8 @@ export default function RegularRateCard({
|
|||||||
onPress={handleChange}
|
onPress={handleChange}
|
||||||
className={styles.buttonOverlay}
|
className={styles.buttonOverlay}
|
||||||
aria-pressed={isSelected}
|
aria-pressed={isSelected}
|
||||||
aria-labelledby={`${id}-title`}
|
aria-describedby={`${roomTypeCode} ${id}-title`}
|
||||||
aria-describedby={`${id}-details`}
|
aria-labelledby={`${id}-details`}
|
||||||
/>
|
/>
|
||||||
<div className={styles.container}>
|
<div className={styles.container}>
|
||||||
<Typography variant="Tag/sm">
|
<Typography variant="Tag/sm">
|
||||||
|
|||||||
Reference in New Issue
Block a user