Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/CardCVCElement.res
Original file line number Diff line number Diff line change
Expand Up @@ -249,5 +249,7 @@ let make = (
height={isVaultCvcFlow ? "1.8rem" : ""}
name=TestUtils.cardCVVInputTestId
autocomplete="cc-csc"
ariaLabel={localeString.cvcTextLabel}
ariaRequired=true
/>
}
2 changes: 2 additions & 0 deletions src/CardSchemeComponent.res
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ module CoBadgeCardSchemeDropDown = {
@react.component
let make = (~eligibleCardSchemes, ~setCardBrand) => {
let loggerState = Recoil.useRecoilValueFromAtom(RecoilAtoms.loggerAtom)
let {localeString} = Recoil.useRecoilValueFromAtom(RecoilAtoms.configAtom)
<select
className="w-4"
ariaLabel={localeString.cardNetworkLabel}
onClick={_ =>
loggerState.setLogInfo(~value="CardSchemeMenu expanded", ~eventName=CARD_SCHEME_SELECTION)}
onChange={ev => {
Expand Down
1 change: 1 addition & 0 deletions src/Components/BillingNamePaymentInput.res
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ let make = (~customFieldName=None, ~requiredFields as optionalRequiredFields=?)
placeholder
className={config.appearance.innerLayout === Spaced ? "" : "!border-b-0"}
name=TestUtils.cardHolderNameInputTestId
ariaRequired=true
/>
</RenderIf>
}
1 change: 1 addition & 0 deletions src/Components/BlikCodePaymentInput.res
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ let make = () => {
inputRef=blikCodeRef
placeholder="000 000"
maxLength=7
ariaRequired=true
/>
</RenderIf>
}
14 changes: 13 additions & 1 deletion src/Components/ClickToPayNotYou.res
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ open Utils

@react.component
let make = (~setIsShowClickToPayNotYou, ~isCTPAuthenticateNotYouClicked, ~getVisaCards) => {
let {themeObj} = Recoil.useRecoilValueFromAtom(RecoilAtoms.configAtom)
let {themeObj, localeString} = Recoil.useRecoilValueFromAtom(RecoilAtoms.configAtom)
let (clickToPayConfig, setClickToPayConfig) = Recoil.useRecoilState(RecoilAtoms.clickToPayConfig)

let (identifier, setIdentifier) = React.useState(_ => "")
Expand Down Expand Up @@ -189,6 +189,8 @@ let make = (~setIsShowClickToPayNotYou, ~isCTPAuthenticateNotYouClicked, ~getVis
<div className="w-full flex space-x-2">
<div className="relative w-1/3">
<select
id="ctp-identifier-type"
ariaLabel={`${localeString.emailLabel} / ${localeString.formFieldPhoneNumberLabel}`}
value={identifierType->getIdentityType}
onChange={handleTypeChange}
className="w-full p-3 pr-10 border border-gray-300 rounded-md appearance-none">
Expand All @@ -207,6 +209,10 @@ let make = (~setIsShowClickToPayNotYou, ~isCTPAuthenticateNotYouClicked, ~getVis
{identifierType === EMAIL_ADDRESS
? <input
type_="text"
id="ctp-email"
ariaLabel={localeString.emailLabel}
ariaRequired=true
autoComplete="email"
value={identifier}
onChange={handleInputChange}
placeholder="Enter email"
Expand All @@ -216,6 +222,8 @@ let make = (~setIsShowClickToPayNotYou, ~isCTPAuthenticateNotYouClicked, ~getVis
: <div className="w-2/3 flex border border-gray-300 rounded-md overflow-hidden">
<div className="relative">
<select
id="ctp-country-code"
ariaLabel={localeString.formFieldCountryCodeRequiredLabel}
value={countryCode}
onChange={handleCountryCodeChange}
className="h-full p-3 appearance-none focus:outline-none">
Expand All @@ -239,6 +247,10 @@ let make = (~setIsShowClickToPayNotYou, ~isCTPAuthenticateNotYouClicked, ~getVis
</div>
<input
type_="tel"
id="ctp-phone"
ariaLabel={localeString.formFieldPhoneNumberLabel}
ariaRequired=true
autoComplete="tel-national"
value={identifier}
onChange={handlePhoneInputChange}
placeholder="Mobile number"
Expand Down
1 change: 1 addition & 0 deletions src/Components/DynamicFields/CardHolderNameField.res
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ module FullNameFieldInput = {
placeholder
inputRef
?autocomplete
ariaRequired={firstNameFieldConfig.isRequired || lastNameFieldConfig.isRequired}
/>
}
}
Expand Down
1 change: 1 addition & 0 deletions src/Components/DynamicFields/EmailField.res
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ module EmailInput = {
placeholder
inputRef={fieldRef}
autocomplete
ariaRequired={primaryFieldConfig.isRequired}
/>
}
}
Expand Down
1 change: 1 addition & 0 deletions src/Components/DynamicFields/GenericInputField.res
Original file line number Diff line number Diff line change
Expand Up @@ -33,5 +33,6 @@ let make = (~fieldConfig: fieldConfig) => {
inputRef={fieldRef}
?autocomplete
maxLength=?{fieldConfig.maxInputLength}
ariaRequired={fieldConfig.isRequired}
/>
}
1 change: 1 addition & 0 deletions src/Components/DynamicFields/PhoneField.res
Original file line number Diff line number Diff line change
Expand Up @@ -42,5 +42,6 @@ let make = (~fieldConfig: fieldConfig, ~isLabelHidden=false) => {
inputRef={fieldRef}
autocomplete
?maxLength
ariaRequired={fieldConfig.isRequired}
/>
}
1 change: 1 addition & 0 deletions src/Components/EmailPaymentInput.res
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ let make = () => {
inputRef=emailRef
placeholder="Eg: johndoe@gmail.com"
name=TestUtils.emailInputTestId
ariaRequired=true
/>
</RenderIf>
}
1 change: 1 addition & 0 deletions src/Components/FullNamePaymentInput.res
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ let make = (~customFieldName=None, ~optionalRequiredFields=None) => {
inputRef=nameRef
placeholder
name=TestUtils.fullNameInputTestId
ariaRequired=true
/>
</RenderIf>
}
1 change: 1 addition & 0 deletions src/Components/GiftCardNumberInput.res
Original file line number Diff line number Diff line change
Expand Up @@ -52,5 +52,6 @@ let make = () => {
placeholder={localeString.giftCardNumberPlaceholder}
maxLength=32
paymentType=Payment
ariaRequired=true
/>
}
1 change: 1 addition & 0 deletions src/Components/GiftCardPinInput.res
Original file line number Diff line number Diff line change
Expand Up @@ -52,5 +52,6 @@ let make = () => {
placeholder={localeString.giftCardPinPlaceholder}
maxLength=12
paymentType=Payment
ariaRequired=true
/>
}
38 changes: 31 additions & 7 deletions src/Components/Input.res
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ let make = (
~placeholder="",
~className="",
~inputRef,
~ariaRequired=false,
~ariaLabel=?,
~fieldId=?,
) => {
let options = Recoil.useRecoilValueFromAtom(elementOptions)
let {themeObj} = Recoil.useRecoilValueFromAtom(configAtom)
Expand Down Expand Up @@ -59,13 +62,30 @@ let make = (
""
}

let generatedId = React.useId()
let inputId = AccessibilityUtils.getControlId(~fieldId, ~preferredId=id, ~generatedId)
let hasError = errorString->AccessibilityUtils.hasOptionalText
let (
accessibleLabel,
errorId,
describedById,
ariaInvalid,
) = AccessibilityUtils.getFieldAccessibility(
~controlId=inputId,
~fieldName,
~placeholder,
~ariaLabel,
~hasError,
~isValid,
)

<div className={` flex flex-col w-full`} style={color: themeObj.colorText}>
<RenderIf condition={fieldName->String.length > 0}>
<div> {React.string(fieldName)} </div>
<label htmlFor={inputId}> {React.string(fieldName)} </label>
</RenderIf>
<div className="flex flex-row " style={direction: direction}>
<input
id
id={inputId}
style={
background: themeObj.colorBackground,
padding: themeObj.spacingUnit,
Expand All @@ -83,17 +103,21 @@ let make = (
onChange
onBlur=handleBlur
onFocus=handleFocus
ariaLabel={`Type to fill ${fieldName} input`}
ariaLabel={accessibleLabel}
ariaInvalid
ariaRequired
ariaDescribedby=?describedById
/>
<div className={`flex -ml-10 items-center`}> {rightIcon} </div>
</div>
{switch errorString {
| Some(val) =>
<RenderIf condition={val->String.length > 0}>
<div
className="py-1 text-xs text-red-600 transition-colors transition-border ease-out duration-200">
{React.string(val)}
</div>
<LiveError
text={val}
className="py-1 text-xs text-red-600 transition-colors transition-border ease-out duration-200"
id={errorId}
/>
</RenderIf>
| None => React.null
}}
Expand Down
31 changes: 27 additions & 4 deletions src/Components/InputField.res
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ let make = (
~labelClassName="",
~paymentType: option<CardThemeType.mode>=?,
~autocomplete="on",
~ariaRequired=false,
~ariaLabel=?,
~fieldId=?,
) => {
open ElementType
let (eleClassName, setEleClassName) = React.useState(_ => "input-base")
Expand Down Expand Up @@ -96,6 +99,23 @@ let make = (
""
}

let generatedId = React.useId()
let inputId = AccessibilityUtils.getControlId(~fieldId, ~preferredId=id, ~generatedId)
let hasError = errorString->AccessibilityUtils.hasOptionalText
let (
accessibleLabel,
errorId,
describedById,
ariaInvalid,
) = AccessibilityUtils.getFieldAccessibility(
~controlId=inputId,
~fieldName,
~placeholder,
~ariaLabel,
~hasError,
~isValid,
)

let isValidValue = CardUtils.getBoolOptionVal(isValid)

let (cardEmpty, cardComplete, cardInvalid, cardFocused) = React.useMemo(() => {
Expand Down Expand Up @@ -125,11 +145,11 @@ let make = (

<div className={` flex flex-col w-full`}>
<RenderIf condition={fieldName->String.length > 0}>
<div className={`${labelClassName}`}> {React.string(fieldName)} </div>
<label htmlFor={inputId} className={`${labelClassName}`}> {React.string(fieldName)} </label>
</RenderIf>
<div className="flex flex-row " style={direction: direction}>
<input
id
id={inputId}
style={
background: "transparent",
width: "-webkit-fill-available",
Expand All @@ -148,7 +168,10 @@ let make = (
onBlur=handleBlur
onFocus=handleFocus
autoComplete={autocomplete}
ariaLabel={`Type to fill ${fieldName} input`}
ariaLabel={accessibleLabel}
ariaInvalid
ariaRequired
ariaDescribedby=?describedById
/>
<div className={`flex -ml-10 items-center`}> {rightIcon} </div>
</div>
Expand All @@ -157,7 +180,7 @@ let make = (
switch errorString {
| Some(val) =>
<RenderIf condition={val->String.length > 0}>
<div className={`py-1 ${errorClases}`}> {React.string(val)} </div>
<LiveError text={val} className={`py-1 ${errorClases}`} id={errorId} />
</RenderIf>
| None => React.null
}
Expand Down
15 changes: 15 additions & 0 deletions src/Components/LiveError.res
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// A field/validation error that is both visually rendered and announced to
// screen readers (role="alert" + assertive, atomic live region).
//
// Renders only the inner alert <div>: callers keep their own conditional-render
// wrapper (the render condition is not always text-based), and pass the matching
// `id` (for aria-describedby linkage from the input), `className`, and `style`.
// `id` defaults to "" → the id attribute is omitted (byte-identical to a div with
// no id); `style` is optional and omitted when not provided.
@react.component
let make = (~text: string, ~className: string, ~style: option<JsxDOM.style>=?, ~id: string="") => {
let elementId = id == "" ? None : Some(id)
<div id=?elementId role="alert" ariaLive={#assertive} ariaAtomic=true className ?style>
{React.string(text)}
</div>
}
21 changes: 14 additions & 7 deletions src/Components/PaymentDropDownField.res
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,9 @@ let make = (
themeObj.colorBackground
}, [themeObj])
let cursorClass = !disabled ? "cursor-pointer" : "cursor-not-allowed"
let selectId = `dropdown-${fieldName->String.trim->String.replaceRegExp(%re("/\s+/g"), "-")}`
let hasError = value.errorString->String.length > 0
let ariaInvalid = AccessibilityUtils.ariaInvalid(~hasError, ~isValid=value.isValid)
<RenderIf condition={options->Array.length > 0}>
<div className="flex flex-col w-full" style={color: themeObj.colorText}>
<RenderIf
Expand All @@ -93,6 +96,7 @@ let make = (
<div className="relative">
<select
ref={dropdownRef->ReactDOM.Ref.domRef}
id={selectId}
style={
background: disabled ? disbaledBG : themeObj.colorBackground,
opacity: disabled ? "35%" : "",
Expand All @@ -105,7 +109,9 @@ let make = (
onFocus={handleFocus}
onChange=handleChange
className={`${inputClassStyles} ${inputClass} ${className} w-full appearance-none outline-none overflow-hidden whitespace-nowrap text-ellipsis ${cursorClass}`}
ariaLabel={`${fieldName} option tab`}>
ariaLabel={localeString.optionTabLabel(fieldName)}
ariaInvalid
ariaDescribedby=?{hasError ? Some(selectId ++ "-error") : None}>
{options
->Array.mapWithIndex((item: string, i) => {
<option key={Int.toString(i)} value=item> {React.string(item)} </option>
Expand Down Expand Up @@ -139,17 +145,18 @@ let make = (
}>
<Icon size=10 name={"arrow-down"} />
</div>
<RenderIf condition={value.errorString->String.length > 0}>
<div
<RenderIf condition={hasError}>
<LiveError
text={value.errorString}
className="Error pt-1"
style={
style={{
color: themeObj.colorDangerText,
fontSize: themeObj.fontSizeSm,
alignSelf: "start",
textAlign: "left",
}>
{React.string(value.errorString)}
</div>
}}
id={selectId ++ "-error"}
/>
</RenderIf>
</div>
</div>
Expand Down
Loading
Loading