Merged in feat/STAY-140-stepper-accesible (pull request #3432)

feat(STAY-140): make Stepper more accessible

* feat(STAY-140): make Stepper more accessible

* fix(STAY-140): add aria-valuetext


Approved-by: Bianca Widstam
This commit is contained in:
Matilda Landström
2026-01-15 12:32:09 +00:00
parent 4b67ffa7fd
commit dd4826dd99
6 changed files with 98 additions and 33 deletions

View File

@@ -4,7 +4,7 @@ import { useIntl } from "react-intl"
import { Divider } from "@scandic-hotels/design-system/Divider" import { Divider } from "@scandic-hotels/design-system/Divider"
import { Radio } from "@scandic-hotels/design-system/Radio" import { Radio } from "@scandic-hotels/design-system/Radio"
import Stepper from "@scandic-hotels/design-system/Stepper" import { Stepper } from "@scandic-hotels/design-system/Stepper"
import { Typography } from "@scandic-hotels/design-system/Typography" import { Typography } from "@scandic-hotels/design-system/Typography"
import styles from "./paymentOption.module.css" import styles from "./paymentOption.module.css"
@@ -116,26 +116,39 @@ function InnerPaymentOption({
)} )}
</div> </div>
{selected && ( {selected && (
<Stepper <>
count={quantity} <span id="stepper-quantity-label" className="sr-only">
handleOnIncrease={handleOnIncrease} {intl.formatMessage({
handleOnDecrease={handleOnDecrease} id: "ancillaries.label.quantity",
disableDecrease={quantity <= 0} defaultMessage: "Quantity",
disableIncrease={hasReachedMax || quantity >= MAX_NR_ITEMS} })}
disabledMessage={ </span>
hasReachedMax <Stepper
? intl.formatMessage({ count={quantity}
id: "myPages.myStay.ancillaries.reachedMaxPointsStepperMessage", label={intl.formatMessage({
defaultMessage: id: "ancillaries.label.quantity",
"Youve reached your points limit and cant add more items with points.", defaultMessage: "Quantity",
}) })}
: intl.formatMessage({ ariaLabelledBy="stepper-quantity-label"
id: "myPages.myStay.ancillaries.reachedMaxItemsStepperMessage", handleOnIncrease={handleOnIncrease}
defaultMessage: handleOnDecrease={handleOnDecrease}
"Maximum quantity reached for this item.", disableDecrease={quantity <= 0}
}) disableIncrease={hasReachedMax || quantity >= MAX_NR_ITEMS}
} disabledMessage={
/> hasReachedMax
? intl.formatMessage({
id: "myPages.myStay.ancillaries.reachedMaxPointsStepperMessage",
defaultMessage:
"Youve reached your points limit and cant add more items with points.",
})
: intl.formatMessage({
id: "myPages.myStay.ancillaries.reachedMaxItemsStepperMessage",
defaultMessage:
"Maximum quantity reached for this item.",
})
}
/>
</>
)} )}
</div> </div>
</div> </div>

View File

@@ -1,4 +1,3 @@
import { type ReactNode } from "react"
import { RadioGroup } from "react-aria-components" import { RadioGroup } from "react-aria-components"
import { useFormContext } from "react-hook-form" import { useFormContext } from "react-hook-form"
import { useIntl } from "react-intl" import { useIntl } from "react-intl"
@@ -19,6 +18,8 @@ import { NotEnoughPointsBanner, PaymentOption } from "./PaymentOption"
import styles from "./selectQuantityStep.module.css" import styles from "./selectQuantityStep.module.css"
import type { ReactNode } from "react"
import type { import type {
InnerSelectQuantityStepProps, InnerSelectQuantityStepProps,
SelectQuantityStepProps, SelectQuantityStepProps,

View File

@@ -3,7 +3,7 @@
import { useFormContext } from "react-hook-form" import { useFormContext } from "react-hook-form"
import { useIntl } from "react-intl" import { useIntl } from "react-intl"
import Stepper from "@scandic-hotels/design-system/Stepper" import { Stepper } from "@scandic-hotels/design-system/Stepper"
import { Typography } from "@scandic-hotels/design-system/Typography" import { Typography } from "@scandic-hotels/design-system/Typography"
import styles from "./adult-selector.module.css" import styles from "./adult-selector.module.css"
@@ -42,10 +42,12 @@ export default function AdultSelector({
variant="Body/Supporting text (caption)/smBold" variant="Body/Supporting text (caption)/smBold"
className={styles.label} className={styles.label}
> >
<p>{adultsLabel}</p> <p id="stepper-adults-label">{adultsLabel}</p>
</Typography> </Typography>
<Stepper <Stepper
count={currentAdults} count={currentAdults}
label={adultsLabel}
ariaLabelledBy="stepper-adults-label"
handleOnDecrease={decreaseAdultsCount} handleOnDecrease={decreaseAdultsCount}
handleOnIncrease={increaseAdultsCount} handleOnIncrease={increaseAdultsCount}
disableDecrease={currentAdults == 1} disableDecrease={currentAdults == 1}

View File

@@ -4,7 +4,7 @@ import { useRef } from "react"
import { useFormContext } from "react-hook-form" import { useFormContext } from "react-hook-form"
import { useIntl } from "react-intl" import { useIntl } from "react-intl"
import Stepper from "@scandic-hotels/design-system/Stepper" import { Stepper } from "@scandic-hotels/design-system/Stepper"
import { Typography } from "@scandic-hotels/design-system/Typography" import { Typography } from "@scandic-hotels/design-system/Typography"
import ChildInfoSelector from "./ChildInfoSelector" import ChildInfoSelector from "./ChildInfoSelector"
@@ -61,9 +61,11 @@ export default function ChildSelector({
variant="Body/Supporting text (caption)/smBold" variant="Body/Supporting text (caption)/smBold"
className={styles.label} className={styles.label}
> >
<p>{childrenLabel}</p> <p id="stepper-children-label">{childrenLabel}</p>
</Typography> </Typography>
<Stepper <Stepper
label={childrenLabel}
ariaLabelledBy="stepper-children-label"
count={currentChildren.length} count={currentChildren.length}
handleOnDecrease={() => { handleOnDecrease={() => {
decreaseChildrenCount(roomIndex) decreaseChildrenCount(roomIndex)

View File

@@ -1,3 +1,4 @@
import { useIntl } from "react-intl"
import { IconButton } from "../IconButton" import { IconButton } from "../IconButton"
import { Tooltip } from "../Tooltip" import { Tooltip } from "../Tooltip"
import { Typography } from "../Typography" import { Typography } from "../Typography"
@@ -6,6 +7,8 @@ import styles from "./stepper.module.css"
type StepperProps = { type StepperProps = {
count: number count: number
label: string
ariaLabelledBy: string
handleOnIncrease: () => void handleOnIncrease: () => void
handleOnDecrease: () => void handleOnDecrease: () => void
disableIncrease: boolean disableIncrease: boolean
@@ -13,28 +16,56 @@ type StepperProps = {
disabledMessage?: string disabledMessage?: string
} }
export default function Stepper({ export function Stepper({
count, count,
label,
ariaLabelledBy,
handleOnIncrease, handleOnIncrease,
handleOnDecrease, handleOnDecrease,
disableIncrease, disableIncrease,
disableDecrease, disableDecrease,
disabledMessage, disabledMessage,
}: StepperProps) { }: StepperProps) {
const intl = useIntl()
return ( return (
<div className={styles.counterContainer}> <div
role="group"
aria-labelledby={ariaLabelledBy}
className={styles.counterContainer}
>
<IconButton <IconButton
type="button"
className={styles.counterBtn} className={styles.counterBtn}
onPress={handleOnDecrease} onPress={handleOnDecrease}
variant="Elevated" variant="Elevated"
isDisabled={disableDecrease} isDisabled={disableDecrease}
iconName="remove" iconName="remove"
aria-label={intl.formatMessage(
{
id: "designSystem.stepper.ariaLabel.decrease",
defaultMessage: "Decrease {label}",
},
{ label }
)}
aria-valuetext={intl.formatMessage(
{
id: "designSystem.stepper.ariaLabel.currentCount",
defaultMessage: "Current {label} {count}",
},
{ label, count }
)}
/> />
<div className={styles.countDisplay}> <Typography variant="Body/Paragraph/mdRegular">
<Typography variant="Body/Paragraph/mdRegular"> <span
<p>{count}</p> aria-live="polite"
</Typography> aria-atomic="true"
</div> className={styles.countDisplay}
>
{count}
</span>
</Typography>
<Tooltip <Tooltip
text={disabledMessage} text={disabledMessage}
isVisible={Boolean(disabledMessage && disableIncrease)} isVisible={Boolean(disabledMessage && disableIncrease)}
@@ -43,11 +74,26 @@ export default function Stepper({
isTouchable isTouchable
> >
<IconButton <IconButton
type="button"
className={styles.counterBtn} className={styles.counterBtn}
onPress={handleOnIncrease} onPress={handleOnIncrease}
variant="Elevated" variant="Elevated"
isDisabled={disableIncrease} isDisabled={disableIncrease}
iconName="add" iconName="add"
aria-label={intl.formatMessage(
{
id: "designSystem.stepper.ariaLabel.increase",
defaultMessage: "Increase {label}",
},
{ label }
)}
aria-valuetext={intl.formatMessage(
{
id: "designSystem.stepper.ariaLabel.currentCount",
defaultMessage: "Current {label} {count}",
},
{ label, count }
)}
/> />
</Tooltip> </Tooltip>
</div> </div>

View File

@@ -5,6 +5,7 @@
gap: var(--Space-x1); gap: var(--Space-x1);
color: var(--Text-Interactive-Default); color: var(--Text-Interactive-Default);
} }
.counterBtn { .counterBtn {
width: 40px; width: 40px;
height: 40px; height: 40px;