Merged in fix/book-115-hidden-focus-indicators (pull request #2925)
Fix/book 115 hidden focus indicators * added focus ring to "how it works" text and close button in modal * fix(BOOK-115): added focus ring to Hotel Sidepeek close button * fix(BOOK-115): enabled selecting ancillaries with keyboard nav * fix(BOOK-115): added focus indicator to "View and print receipt" in Manage Stay * fix(BOOK-105 & BOOK-115): combined the two radio groups in payment selection to one, fixes focus indicator issue * fix(BOOK-115): added focus indicator to shortcut links * fix(BOOK-115): updated ancillaries keyboard selection * fix(BOOK-115): removed tabIndex from Link component * fix(BOOK-115): fixed single payment radio button not focusable * fix(BOOK-115): updated to onKeyDown * added id to "credit card" * removed toUpperCase() on lables * removed brackets * moved the focus indicator to the DS Button component * removed !important from ButtonLink css * changed <label> to <fieldset> and <legend> and added aria-label to PaymentOptionGroup * removed css class from sidepeek that was previously removed * reverted changes and synced Guarantee radiogroup with Payment radiogroup to use same semantics * removed duplicate label * removed old sub heading Approved-by: Erik Tiekstra
This commit is contained in:
@@ -20,3 +20,8 @@
|
||||
.listItem:last-child .link {
|
||||
border-radius: 0 0 var(--Corner-radius-md) var(--Corner-radius-md);
|
||||
}
|
||||
|
||||
.link:focus-visible {
|
||||
outline: 2px auto -webkit-focus-ring-color;
|
||||
outline-offset: -4px;
|
||||
}
|
||||
|
||||
@@ -17,6 +17,10 @@
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.item:focus-visible {
|
||||
outline: 2px auto -webkit-focus-ring-color;
|
||||
outline-offset: 1px;
|
||||
}
|
||||
.buttonWrapper {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
|
||||
@@ -146,39 +146,16 @@ export default function ConfirmationStep({
|
||||
"By adding a card you also guarantee your room booking for late arrival.",
|
||||
})}
|
||||
/>
|
||||
{savedCreditCards?.length ? (
|
||||
<SelectPaymentMethod
|
||||
paymentMethods={savedCreditCards.map((x) => ({
|
||||
...x,
|
||||
cardType: x.cardType as PaymentMethodEnum,
|
||||
}))}
|
||||
onChange={(method) => {
|
||||
trackUpdatePaymentMethod({ method })
|
||||
}}
|
||||
formName={"paymentMethod"}
|
||||
/>
|
||||
) : null}
|
||||
<PaymentOptionsGroup
|
||||
name="paymentMethod"
|
||||
label={
|
||||
savedCreditCards?.length
|
||||
? intl
|
||||
.formatMessage({
|
||||
id: "common.other",
|
||||
defaultMessage: "Other",
|
||||
})
|
||||
.toUpperCase()
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
<PaymentOption
|
||||
value={PaymentMethodEnum.card}
|
||||
label={intl.formatMessage({
|
||||
id: "common.creditCard",
|
||||
defaultMessage: "Credit card",
|
||||
})}
|
||||
/>
|
||||
</PaymentOptionsGroup>
|
||||
<SelectPaymentMethod
|
||||
paymentMethods={(savedCreditCards ?? []).map((card) => ({
|
||||
...card,
|
||||
cardType: card.cardType as PaymentMethodEnum,
|
||||
}))}
|
||||
onChange={(method) => {
|
||||
trackUpdatePaymentMethod({ method })
|
||||
}}
|
||||
formName={"paymentMethod"}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
|
||||
@@ -20,11 +20,18 @@ export default function WrappedAncillaryCard({
|
||||
|
||||
return (
|
||||
<div
|
||||
tabIndex={0}
|
||||
role="button"
|
||||
onClick={() => {
|
||||
selectAncillary(ancillary)
|
||||
trackViewAncillary(ancillary, booking)
|
||||
}}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter") {
|
||||
selectAncillary(ancillary)
|
||||
trackViewAncillary(ancillary, booking)
|
||||
}
|
||||
}}
|
||||
>
|
||||
<AncillaryCard ancillary={ancillaryWithoutDescription} />
|
||||
</div>
|
||||
|
||||
@@ -12,8 +12,6 @@ import { privacyPolicyRoutes } from "@scandic-hotels/common/constants/routes/pri
|
||||
import { formatPrice } from "@scandic-hotels/common/utils/numberFormatting"
|
||||
import { Divider } from "@scandic-hotels/design-system/Divider"
|
||||
import Checkbox from "@scandic-hotels/design-system/Form/Checkbox"
|
||||
import { PaymentOption } from "@scandic-hotels/design-system/Form/PaymentOption"
|
||||
import { PaymentOptionsGroup } from "@scandic-hotels/design-system/Form/PaymentOptionsGroup"
|
||||
import { SelectPaymentMethod } from "@scandic-hotels/design-system/Form/SelectPaymentMethod"
|
||||
import { LoadingSpinner } from "@scandic-hotels/design-system/LoadingSpinner"
|
||||
import Link from "@scandic-hotels/design-system/OldDSLink"
|
||||
@@ -155,42 +153,17 @@ export default function Form() {
|
||||
id="guarantee"
|
||||
onSubmit={methods.handleSubmit(handleGuaranteeLateArrival)}
|
||||
>
|
||||
{savedCreditCards?.length ? (
|
||||
<SelectPaymentMethod
|
||||
formName="paymentMethod"
|
||||
paymentMethods={savedCreditCards.map((x) => ({
|
||||
...x,
|
||||
cardType: x.type as PaymentMethodEnum,
|
||||
}))}
|
||||
onChange={(method) => {
|
||||
trackUpdatePaymentMethod({ method })
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
<PaymentOptionsGroup
|
||||
name="paymentMethod"
|
||||
label={
|
||||
savedCreditCards?.length
|
||||
? intl
|
||||
.formatMessage({
|
||||
id: "common.other",
|
||||
defaultMessage: "Other",
|
||||
})
|
||||
.toUpperCase()
|
||||
: undefined
|
||||
}
|
||||
<SelectPaymentMethod
|
||||
paymentMethods={(savedCreditCards ?? []).map((card) => ({
|
||||
...card,
|
||||
cardType: card.cardType as PaymentMethodEnum,
|
||||
}))}
|
||||
onChange={(method) => {
|
||||
trackUpdatePaymentMethod({ method })
|
||||
}}
|
||||
>
|
||||
<PaymentOption
|
||||
value={PaymentMethodEnum.card}
|
||||
label={intl.formatMessage({
|
||||
id: "common.creditCard",
|
||||
defaultMessage: "Credit card",
|
||||
})}
|
||||
/>
|
||||
</PaymentOptionsGroup>
|
||||
formName="paymentMethod"
|
||||
/>
|
||||
|
||||
<div className={styles.termsAndConditions}>
|
||||
<Checkbox className={styles.checkbox} name="termsAndConditions">
|
||||
<Typography variant="Body/Supporting text (caption)/smRegular">
|
||||
|
||||
@@ -17,6 +17,7 @@ import styles from "./view.module.css"
|
||||
export default function ViewAndPrintReceipt() {
|
||||
const intl = useIntl()
|
||||
const lang = useLang()
|
||||
|
||||
const canDownloadInvoice = useMyStayStore(
|
||||
(state) =>
|
||||
!state.bookedRoom.isCancelled &&
|
||||
|
||||
@@ -5,3 +5,7 @@
|
||||
gap: var(--Space-x1);
|
||||
padding: var(--Space-x05) 0;
|
||||
}
|
||||
|
||||
.download:focus-visible {
|
||||
outline: 2px solid -webkit-focus-ring-color;
|
||||
}
|
||||
|
||||
@@ -38,6 +38,10 @@
|
||||
justify-self: flex-end;
|
||||
outline: none;
|
||||
}
|
||||
.infoButton:focus-visible {
|
||||
outline: 2px auto -webkit-focus-ring-color;
|
||||
outline-offset: 1px;
|
||||
}
|
||||
|
||||
.overlay {
|
||||
align-items: center;
|
||||
|
||||
@@ -9,12 +9,9 @@ import {
|
||||
import { useWatch } from "react-hook-form"
|
||||
import { useIntl } from "react-intl"
|
||||
|
||||
import { PaymentMethodEnum } from "@scandic-hotels/common/constants/paymentMethod"
|
||||
import useSetOverflowVisibleOnRA from "@scandic-hotels/common/hooks/useSetOverflowVisibleOnRA"
|
||||
import { Button } from "@scandic-hotels/design-system/Button"
|
||||
import Checkbox from "@scandic-hotels/design-system/Form/Checkbox"
|
||||
import { PaymentOption } from "@scandic-hotels/design-system/Form/PaymentOption"
|
||||
import { PaymentOptionsGroup } from "@scandic-hotels/design-system/Form/PaymentOptionsGroup"
|
||||
import { SelectPaymentMethod } from "@scandic-hotels/design-system/Form/SelectPaymentMethod"
|
||||
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
|
||||
import { Typography } from "@scandic-hotels/design-system/Typography"
|
||||
@@ -22,6 +19,7 @@ import { trackUpdatePaymentMethod } from "@scandic-hotels/tracking/payment"
|
||||
|
||||
import styles from "./guarantee.module.css"
|
||||
|
||||
import type { PaymentMethodEnum } from "@scandic-hotels/common/constants/paymentMethod"
|
||||
import type { CreditCard } from "@scandic-hotels/trpc/types/user"
|
||||
|
||||
interface GuaranteeProps {
|
||||
@@ -112,9 +110,10 @@ export default function Guarantee({ savedCreditCards }: GuaranteeProps) {
|
||||
</DialogTrigger>
|
||||
</div>
|
||||
</Checkbox>
|
||||
{savedCreditCards?.length && guarantee ? (
|
||||
|
||||
{guarantee && (
|
||||
<SelectPaymentMethod
|
||||
paymentMethods={savedCreditCards.map((card) => ({
|
||||
paymentMethods={(savedCreditCards ?? []).map((card) => ({
|
||||
...card,
|
||||
cardType: card.cardType as PaymentMethodEnum,
|
||||
}))}
|
||||
@@ -123,30 +122,7 @@ export default function Guarantee({ savedCreditCards }: GuaranteeProps) {
|
||||
}}
|
||||
formName={"paymentMethod"}
|
||||
/>
|
||||
) : null}
|
||||
{guarantee ? (
|
||||
<PaymentOptionsGroup
|
||||
name="paymentMethod"
|
||||
label={
|
||||
savedCreditCards?.length
|
||||
? intl
|
||||
.formatMessage({
|
||||
id: "common.other",
|
||||
defaultMessage: "Other",
|
||||
})
|
||||
.toUpperCase()
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
<PaymentOption
|
||||
value={PaymentMethodEnum.card}
|
||||
label={intl.formatMessage({
|
||||
id: "common.creditCard",
|
||||
defaultMessage: "Credit card",
|
||||
})}
|
||||
/>
|
||||
</PaymentOptionsGroup>
|
||||
) : null}
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -17,6 +17,10 @@
|
||||
cursor: progress;
|
||||
}
|
||||
}
|
||||
.button:focus-visible {
|
||||
outline: 2px auto -webkit-focus-ring-color; /*color should probably be cutomized for each variant later on*/
|
||||
outline-offset: 1px;
|
||||
}
|
||||
|
||||
.size-large {
|
||||
padding: var(--Space-x2) var(--Space-x3);
|
||||
|
||||
@@ -8,3 +8,8 @@
|
||||
justify-content: center;
|
||||
gap: var(--Space-x05);
|
||||
}
|
||||
|
||||
.buttonLink:focus-visible {
|
||||
outline: 2px auto -webkit-focus-ring-color;
|
||||
outline-offset: 1px;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
import { Label, RadioGroup } from 'react-aria-components'
|
||||
import { useController, useFormContext } from 'react-hook-form'
|
||||
|
||||
import type { ReactNode } from 'react'
|
||||
import { Typography } from '../../../components/Typography'
|
||||
|
||||
@@ -11,6 +10,7 @@ interface PaymentOptionsGroupProps {
|
||||
label?: string
|
||||
children: ReactNode
|
||||
className?: string
|
||||
initalValue?: string
|
||||
onChange?: (newValue: string) => void
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ export function PaymentOptionsGroup({
|
||||
children,
|
||||
className,
|
||||
onChange,
|
||||
initalValue,
|
||||
}: PaymentOptionsGroupProps) {
|
||||
const { control } = useFormContext()
|
||||
|
||||
@@ -36,7 +37,11 @@ export function PaymentOptionsGroup({
|
||||
}
|
||||
|
||||
return (
|
||||
<RadioGroup value={value} onChange={handleChange} className={className}>
|
||||
<RadioGroup
|
||||
value={initalValue || value}
|
||||
onChange={handleChange}
|
||||
className={className}
|
||||
>
|
||||
{label ? (
|
||||
<Typography variant="Title/Overline/sm">
|
||||
<Label>{label}</Label>
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { useIntl } from 'react-intl'
|
||||
import { Label } from 'react-aria-components'
|
||||
|
||||
import { PaymentOptionsGroup } from '../PaymentOption/PaymentOptionsGroup'
|
||||
import { PaymentOption } from '../PaymentOption/PaymentOption'
|
||||
import { Typography } from '../../../components/Typography'
|
||||
|
||||
import styles from './selectPaymentMethod.module.css'
|
||||
|
||||
@@ -29,34 +31,71 @@ export function SelectPaymentMethod({
|
||||
formName,
|
||||
}: SelectPaymentMethodProps) {
|
||||
const intl = useIntl()
|
||||
const mySavedCardsLabel = intl.formatMessage({
|
||||
id: 'payment.mySavedCards',
|
||||
defaultMessage: 'My saved cards',
|
||||
})
|
||||
const mySavedCardsLabel = paymentMethods.length
|
||||
? intl.formatMessage({
|
||||
id: 'payment.mySavedCards',
|
||||
defaultMessage: 'My saved cards',
|
||||
})
|
||||
: undefined
|
||||
const otherCardLabel = paymentMethods.length
|
||||
? intl.formatMessage({
|
||||
id: 'common.other',
|
||||
defaultMessage: 'Other',
|
||||
})
|
||||
: undefined
|
||||
|
||||
const hasSavedCards = paymentMethods.length > 0
|
||||
|
||||
return (
|
||||
<section className={styles.section}>
|
||||
<PaymentOptionsGroup
|
||||
name={formName}
|
||||
label={mySavedCardsLabel}
|
||||
className={styles.paymentOptionContainer}
|
||||
onChange={onChange}
|
||||
initalValue={
|
||||
//set credit card as default checked if user has no saved cards
|
||||
!hasSavedCards ? PAYMENT_METHOD_TITLES.card : undefined
|
||||
}
|
||||
>
|
||||
{paymentMethods?.map((paymentMethods) => {
|
||||
const label =
|
||||
PAYMENT_METHOD_TITLES[paymentMethods.cardType] ||
|
||||
paymentMethods.alias ||
|
||||
paymentMethods.cardType
|
||||
<Label className="sr-only">
|
||||
{intl.formatMessage({
|
||||
id: 'enterDetails.guarantee.cardOptions',
|
||||
defaultMessage: 'Card options',
|
||||
})}
|
||||
</Label>
|
||||
{hasSavedCards ? (
|
||||
<>
|
||||
<Typography variant="Title/Overline/sm">
|
||||
<span>{mySavedCardsLabel}</span>
|
||||
</Typography>
|
||||
{paymentMethods?.map((paymentMethods) => {
|
||||
const label =
|
||||
PAYMENT_METHOD_TITLES[paymentMethods.cardType] ||
|
||||
paymentMethods.alias ||
|
||||
paymentMethods.cardType
|
||||
|
||||
return (
|
||||
<PaymentOption
|
||||
key={paymentMethods.id}
|
||||
value={paymentMethods.id as PaymentMethodEnum}
|
||||
label={label}
|
||||
cardNumber={paymentMethods.truncatedNumber}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
return (
|
||||
<PaymentOption
|
||||
key={paymentMethods.id}
|
||||
value={paymentMethods.id as PaymentMethodEnum}
|
||||
label={label}
|
||||
cardNumber={paymentMethods.truncatedNumber}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
<Typography variant="Title/Overline/sm">
|
||||
<span>{otherCardLabel}</span>
|
||||
</Typography>
|
||||
</>
|
||||
) : null}
|
||||
|
||||
<PaymentOption
|
||||
value={PAYMENT_METHOD_TITLES.card as PaymentMethodEnum}
|
||||
label={intl.formatMessage({
|
||||
id: 'common.creditCard',
|
||||
defaultMessage: 'Credit card',
|
||||
})}
|
||||
/>
|
||||
</PaymentOptionsGroup>
|
||||
</section>
|
||||
)
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import { useContext, useRef } from 'react'
|
||||
import { Dialog, Modal, ModalOverlay } from 'react-aria-components'
|
||||
|
||||
import { IconButton } from '../IconButton'
|
||||
import { MaterialIcon } from '../Icons/MaterialIcon'
|
||||
import { Typography } from '../Typography'
|
||||
|
||||
@@ -10,7 +10,6 @@ import { SidePeekContext } from './SidePeekContext'
|
||||
|
||||
import SidePeekSEO from './SidePeekSEO'
|
||||
|
||||
import { IconButton } from '../IconButton'
|
||||
import styles from './sidePeek.module.css'
|
||||
|
||||
interface SidePeekProps {
|
||||
@@ -63,8 +62,8 @@ export default function SidePeek({
|
||||
<IconButton
|
||||
theme="Black"
|
||||
style="Muted"
|
||||
onPress={onClose}
|
||||
aria-label={closeLabel}
|
||||
onPress={onClose}
|
||||
>
|
||||
<MaterialIcon
|
||||
icon="close"
|
||||
|
||||
Reference in New Issue
Block a user