Skip to content

Commit 1ac1466

Browse files
author
Yu Long
authored
feat: added responsive behavior on input fields for card and stored card (#3583)
* feat: added responsive behavior on input fields for card and stored card fields * refactor: extract `.adyen-checkout__field-group` to Address.scss, use `Fieldset` for CardFields.tsx * refactor: rollback card expiration date and cvc fields * refactor: added fieldset-fields-layout mixin
1 parent 398a9e1 commit 1ac1466

14 files changed

Lines changed: 116 additions & 37 deletions

File tree

.changeset/wide-mammals-repair.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@adyen/adyen-web': patch
3+
---
4+
5+
Stacked the expiration date and security code fields on smaller screens to enhance layout and usability.

packages/lib/.storybook/main.css

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ button {
2121

2222
.storybook-status-container {
2323
align-items: center;
24-
background-color: #ffffff;
24+
background-color: #fff;
2525
border: 1px solid #c9cdd3;
2626
border-radius: 8px;
2727
color: #00112c;
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
@use 'styles/index';
2+
3+
.adyen-checkout__card__exp-cvc {
4+
@include index.fieldset-fields-layout;
5+
6+
@include index.screen-s-and-up {
7+
.adyen-checkout__field {
8+
margin-bottom: 0;
9+
}
10+
}
11+
}

packages/lib/src/components/Card/components/CardInput/components/CardFields.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
ENCRYPTED_SECURITY_CODE
1414
} from '../../../../internal/SecuredFields/lib/constants';
1515
import AvailableBrands from './AvailableBrands';
16+
import './CardFields.scss';
1617

1718
export default function CardFields({
1819
brand,
@@ -60,33 +61,33 @@ export default function CardFields({
6061
<AvailableBrands activeBrand={brand} brands={allowedBrands} />
6162

6263
<div
63-
className={classNames('adyen-checkout__card__exp-cvc adyen-checkout__field-wrapper', {
64+
className={classNames('adyen-checkout__card__exp-cvc', {
6465
'adyen-checkout__card__exp-cvc__exp-date__input--hidden': expiryDatePolicy === DATE_POLICY_HIDDEN
6566
})}
6667
>
6768
<ExpirationDate
69+
classNameModifiers={['col-50']}
6870
error={getError(errors, ENCRYPTED_EXPIRY_DATE)}
6971
focused={focusedElement === ENCRYPTED_EXPIRY_DATE}
7072
isValid={!!valid.encryptedExpiryMonth && !!valid.encryptedExpiryYear}
7173
filled={!!errors.encryptedExpiryDate || !!valid.encryptedExpiryYear}
7274
label={i18n.get('creditCard.expiryDate.label')}
7375
onFocusField={onFocusField}
74-
className={'adyen-checkout__field--50'}
7576
expiryDatePolicy={expiryDatePolicy}
7677
showContextualElement={showContextualElement}
7778
contextualText={i18n.get('creditCard.expiryDate.contextualText')}
7879
/>
7980

8081
{hasCVC && (
8182
<CVC
83+
classNameModifiers={['col-50']}
8284
error={getError(errors, ENCRYPTED_SECURITY_CODE)}
8385
focused={focusedElement === ENCRYPTED_SECURITY_CODE}
8486
cvcPolicy={cvcPolicy}
8587
isValid={!!valid.encryptedSecurityCode}
8688
filled={!!errors.encryptedSecurityCode || !!valid.encryptedSecurityCode}
8789
label={i18n.get('creditCard.securityCode.label')}
8890
onFocusField={onFocusField}
89-
className={'adyen-checkout__field--50'}
9091
frontCVC={isAmex}
9192
showContextualElement={showContextualElement}
9293
contextualText={cvcContextualText}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { h } from 'preact';
2+
import { render, screen } from '@testing-library/preact';
3+
import ExpirationDate from './ExpirationDate';
4+
import { CoreProvider } from '../../../../../core/Context/CoreProvider';
5+
import { DATE_POLICY_REQUIRED } from '../../../../internal/SecuredFields/lib/constants';
6+
7+
const renderWithCoreContext = ui => {
8+
return render(
9+
<CoreProvider i18n={globalThis.i18n} resources={globalThis.resources} loadingContext="test">
10+
{ui}
11+
</CoreProvider>
12+
);
13+
};
14+
15+
describe('ExpirationDate', () => {
16+
const defaultProps = {
17+
label: 'Expiry date',
18+
onFocusField: jest.fn(),
19+
expiryDatePolicy: DATE_POLICY_REQUIRED,
20+
focused: false,
21+
filled: false,
22+
error: '',
23+
isValid: false,
24+
showContextualElement: false,
25+
contextualText: 'MM/YY'
26+
};
27+
28+
test('should apply classNameModifiers to the Field component', () => {
29+
const modifiers = ['custom-modifier-1', 'custom-modifier-2'];
30+
renderWithCoreContext(<ExpirationDate {...defaultProps} classNameModifiers={modifiers} />);
31+
32+
const fieldElement = screen.getByTestId('form-field');
33+
expect(fieldElement).toHaveClass('adyen-checkout__field--custom-modifier-1', 'adyen-checkout__field--custom-modifier-2');
34+
expect(fieldElement).toHaveClass('adyen-checkout__field--expiryDate');
35+
});
36+
});

packages/lib/src/components/Card/components/CardInput/components/ExpirationDate.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ export default function ExpirationDate(props: ExpirationDateProps) {
2121
filled,
2222
onFocusField,
2323
className = '',
24+
classNameModifiers = [],
2425
error = '',
2526
isValid = false,
2627
expiryDatePolicy = DATE_POLICY_REQUIRED,
@@ -47,7 +48,7 @@ export default function ExpirationDate(props: ExpirationDateProps) {
4748
return (
4849
<Field
4950
label={fieldLabel}
50-
classNameModifiers={['expiryDate']}
51+
classNameModifiers={[...classNameModifiers, 'expiryDate']}
5152
className={fieldClassnames}
5253
focused={focused}
5354
filled={filled}

packages/lib/src/components/Card/components/CardInput/components/StoredCardFields.tsx

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { useCoreContext } from '../../../../../core/Context/CoreProvider';
55
import { StoredCardFieldsProps } from './types';
66
import { ENCRYPTED_SECURITY_CODE } from '../../../../internal/SecuredFields/lib/constants';
77
import InputText from '../../../../internal/FormFields/InputText';
8+
import Fieldset from '../../../../internal/FormFields/Fieldset';
89

910
export default function StoredCardFields({
1011
brand,
@@ -34,12 +35,11 @@ export default function StoredCardFields({
3435

3536
return (
3637
<div className="adyen-checkout__card__form adyen-checkout__card__form--oneClick" aria-label={ariaLabel}>
37-
<div className="adyen-checkout__card__exp-cvc adyen-checkout__field-wrapper">
38+
<Fieldset classNamesFields={['adyen-checkout__card__exp-cvc']}>
3839
{expiryMonth && expiryYear && (
3940
<Field
4041
label={i18n.get('creditCard.expiryDate.label')}
41-
className="adyen-checkout__field--50"
42-
classNameModifiers={['storedCard']}
42+
classNameModifiers={['col-50', 'storedCard']}
4343
name={'expiryDateField'}
4444
disabled
4545
>
@@ -62,14 +62,13 @@ export default function StoredCardFields({
6262
isValid={!!valid.encryptedSecurityCode}
6363
label={i18n.get('creditCard.securityCode.label')}
6464
onFocusField={onFocusField}
65-
{...(expiryMonth && expiryYear && { className: 'adyen-checkout__field--50' })}
66-
classNameModifiers={['storedCard']}
65+
classNameModifiers={[...(expiryMonth && expiryYear ? ['col-50', 'storedCard'] : ['storedCard'])]}
6766
frontCVC={isAmex}
6867
showContextualElement={showContextualElement}
6968
contextualText={cvcContextualText}
7069
/>
7170
)}
72-
</div>
71+
</Fieldset>
7372
</div>
7473
);
7574
}

packages/lib/src/components/Card/components/CardInput/components/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ export interface DualBrandingIconProps {
8787

8888
export interface ExpirationDateProps {
8989
className?: string;
90+
classNameModifiers?: string[];
9091
error?: string;
9192
filled?: boolean;
9293
focused?: boolean;
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
@use 'styles/index';
2+
@import 'styles/variable-generator';
3+
4+
.adyen-checkout__fieldset--address {
5+
.adyen-checkout__field-group {
6+
display: flex;
7+
flex-wrap: wrap;
8+
justify-content: space-between;
9+
width: 100%;
10+
}
11+
12+
.adyen-checkout__field-group:last-of-type .adyen-checkout__field {
13+
@include index.screen-s-and-up {
14+
margin-bottom: 0;
15+
}
16+
17+
&:last-of-type {
18+
margin-bottom: 0;
19+
}
20+
}
21+
}

packages/lib/src/components/internal/Address/Address.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { getMaxLengthByFieldAndCountry } from '../../../utils/validator-utils';
1414
import { useCoreContext } from '../../../core/Context/CoreProvider';
1515
import AddressSearch from './components/AddressSearch';
1616
import { ComponentMethodsRef } from '../UIElement/types';
17+
import './Address.scss';
1718

1819
export default function Address(props: AddressProps) {
1920
const { i18n } = useCoreContext();
@@ -185,7 +186,7 @@ export default function Address(props: AddressProps) {
185186

186187
return (
187188
<Fragment>
188-
<Fieldset classNameModifiers={[label || 'address']} label={label}>
189+
<Fieldset classNameModifiers={[label, 'address']} label={label}>
189190
{showAddressSearch && (
190191
<AddressSearch
191192
onAddressLookup={props.onAddressLookup}

0 commit comments

Comments
 (0)