diff --git a/.changeset/short-dolphins-worry.md b/.changeset/short-dolphins-worry.md new file mode 100644 index 0000000000..dfd98fe8cf --- /dev/null +++ b/.changeset/short-dolphins-worry.md @@ -0,0 +1,13 @@ +--- +'@adyen/adyen-web': major +--- + +Redesign with Bento design tokens.\ +Remove default placeholders, merchants can configure placeholders via the configuration object.\ +Add `showContextualElement` and `contextualText` for the form field, merchants can configure them via configuration object.\ +New spinner.\ +Phone prefix drop down contains flag icons.\ +Ideal issuer names align to the right.\ +Fix the stored card icon overlapping with the error icon.\ +Scss code refactoring.\ +Use the same syntax for 'required' error message in the Personal details and Address components. \ No newline at end of file diff --git a/packages/e2e-playwright/models/card.ts b/packages/e2e-playwright/models/card.ts index cafe5d969b..3a5edab08d 100644 --- a/packages/e2e-playwright/models/card.ts +++ b/packages/e2e-playwright/models/card.ts @@ -1,21 +1,77 @@ import { Locator, Page } from '@playwright/test'; import { USER_TYPE_DELAY } from '../tests/utils/constants'; +import LANG from '../../lib/src/language/locales/en-US.json'; + +const CARD_IFRAME_TITLE = LANG['creditCard.encryptedCardNumber.aria.iframeTitle']; +const EXPIRY_DATE_IFRAME_TITLE = LANG['creditCard.encryptedExpiryDate.aria.iframeTitle']; +const CVC_IFRAME_TITLE = LANG['creditCard.encryptedSecurityCode.aria.iframeTitle']; + +const CARD_IFRAME_LABEL = LANG['creditCard.cardNumber.label']; +const EXPIRY_DATE_IFRAME_LABEL = LANG['creditCard.expiryDate.label']; +const CVC_IFRAME_LABEL = LANG['creditCard.securityCode.label']; class Card { readonly rootElement: Locator; readonly rootElementSelector: string; + readonly cardNumberField: Locator; + readonly cardNumberErrorElement: Locator; readonly cardNumberInput: Locator; + + readonly expiryDateField: Locator; + readonly expiryDateContextualElement: Locator; readonly expiryDateInput: Locator; + readonly expiryDateIframeContextualElement: Locator; + + readonly cvcField: Locator; + readonly cvcErrorElement: Locator; + readonly cvcContextualElement: Locator; readonly cvcInput: Locator; + readonly cvcIframeContextualElement: Locator; constructor(page: Page, rootElementSelector = '.adyen-checkout__card-input') { this.rootElement = page.locator(rootElementSelector); this.rootElementSelector = rootElementSelector; - this.cardNumberInput = this.rootElement.frameLocator('[title="Iframe for card number"]').locator('input[aria-label="Card number"]'); - this.expiryDateInput = this.rootElement.frameLocator('[title="Iframe for expiry date"]').locator('input[aria-label="Expiry date"]'); - this.cvcInput = this.rootElement.frameLocator('[title="Iframe for security code"]').locator('input[aria-label="Security code"]'); + /** + * Card Number elements, in Checkout + */ + this.cardNumberField = this.rootElement.locator('.adyen-checkout__field--cardNumber'); // Holder + + this.cardNumberErrorElement = this.cardNumberField.locator('.adyen-checkout-contextual-text--error'); + + /** + * Card Number elements, in iframe + */ + const cardNumberIframe = this.rootElement.frameLocator(`[title="${CARD_IFRAME_TITLE}"]`); + this.cardNumberInput = cardNumberIframe.locator(`input[aria-label="${CARD_IFRAME_LABEL}"]`); + + /** + * Expiry Date elements, in Checkout + */ + this.expiryDateField = this.rootElement.locator('.adyen-checkout__field--expiryDate'); // Holder + this.expiryDateContextualElement = this.expiryDateField.locator('.adyen-checkout-contextual-text'); // Related contextual element + + /** + * Expiry Date elements, in iframe + */ + const expiryDateIframe = this.rootElement.frameLocator(`[title="${EXPIRY_DATE_IFRAME_TITLE}"]`); + this.expiryDateInput = expiryDateIframe.locator(`input[aria-label="${EXPIRY_DATE_IFRAME_LABEL}"]`); + this.expiryDateIframeContextualElement = expiryDateIframe.locator('.aria-context'); + + /** + * Security code elements, in Checkout + */ + this.cvcField = this.rootElement.locator('.adyen-checkout__field--securityCode'); // Holder + this.cvcErrorElement = this.cvcField.locator('.adyen-checkout-contextual-text--error'); // Related erro element + this.cvcContextualElement = this.cvcField.locator('.adyen-checkout-contextual-text'); // Related contextual element + + /** + * Security code elements, in iframe + */ + const cvcIframe = this.rootElement.frameLocator(`[title="${CVC_IFRAME_TITLE}"]`); + this.cvcInput = cvcIframe.locator(`input[aria-label="${CVC_IFRAME_LABEL}"]`); + this.cvcIframeContextualElement = cvcIframe.locator('.aria-context'); } async isComponentVisible() { @@ -28,6 +84,10 @@ class Card { await this.cardNumberInput.type(cardNumber, { delay: USER_TYPE_DELAY }); } + async deleteCardNumber() { + await this.cardNumberInput.clear(); + } + async typeExpiryDate(expiryDate: string) { await this.expiryDateInput.type(expiryDate, { delay: USER_TYPE_DELAY }); } diff --git a/packages/e2e-playwright/pages/cards/card.fixture.ts b/packages/e2e-playwright/pages/cards/card.fixture.ts index 037dfd7d2c..7bf3872420 100644 --- a/packages/e2e-playwright/pages/cards/card.fixture.ts +++ b/packages/e2e-playwright/pages/cards/card.fixture.ts @@ -7,6 +7,7 @@ import { optionalDateAndCvcMock } from '../../mocks/binLookup/binLookup.data'; type Fixture = { cardPage: CardPage; cardAvsPage: CardAvsPage; + cardNoContextualElementPage: CardPage; }; const test = base.extend({ @@ -26,6 +27,16 @@ const test = base.extend({ const cardAvsPage = new CardAvsPage(page); await cardAvsPage.goto(); await use(cardAvsPage); + }, + + cardNoContextualElementPage: async ({ page }, use) => { + await page.addInitScript({ + content: 'window.cardConfig = { showContextualElement: false}' + }); + + const cardPage = new CardPage(page); + await cardPage.goto(); + await use(cardPage); } }); diff --git a/packages/e2e-playwright/playwright-report/index.html b/packages/e2e-playwright/playwright-report/index.html index 46a92e354f..27df4ce254 100644 --- a/packages/e2e-playwright/playwright-report/index.html +++ b/packages/e2e-playwright/playwright-report/index.html @@ -7,7 +7,7 @@ Playwright Test Report - @@ -59,4 +59,4 @@ \ No newline at end of file +window.playwrightReportBase64 = "data:application/zip;base64,UEsDBBQAAAgIAFtlLVdiYdEDNwQAAO8SAAAZAAAAY2NkOGFkNzQxZTBkMGIwYzYzZGQuanNvbu1YW2/bNhT+K4RekgC27pZlDcmwpRgWYCsGNO3DYq+gJdrSQpECSSU1Uv/3HdJy5IsSuyi87SF8sEXyXD/yfKT0ZM0KSm4yK7HSNItxNgw94mbu1E2jIMusnpl/j0uiJbDIHP1jp5wp8kXVmN7Cn7RlRVJbSRBXRMJ/cvdknl403B8NYzzzw2waD/3Az2YEB6FWLxTVrj7kvKYZKpi2rJDKCdKOYaCqlUSYZUgSoscXKMcPMPscESKUlISBlCQKTFaC/w021jnkgpdFXcIE5SlWBWdW8mSyPDJDWjAQ9dyelXJal6A+WPasrBaNsSgO4p6FGePKjGg0Jj2L1yrlJoSakS86K6LhrbDKVwKCyJo22O1akwoLdVsYdd/1g7476nvBrecmoZ+EQzuKoz8tbUKJhZW4WoFUzTI0iP5MZlwQ9Cvn9zqLIy22gYRePOqyOxX8URJxvcLLZuTxDzwnBz3EdhjG2x4GA791MNHw1kwB0ste660C2/acK36eK1UljqMXkeZcqiRw/dC5OOh4ZLuuv+3Yj4cBqLGiqmDHJNa4dl1veueWqHkawaMXoa9NNxiVSLd1NyqxXLB0Y9ZEWAu6HgrKH9vZdjDZUJFKFGx+gZ6evY9ZdyDDvUB2gnnEhdqQaGdUXsiuOOx2UAPcOWFSWnf98qwb/7NW4aI188Oe0xezg/a1I3Zvrei1Ge+0vxoJ3y+Pd+fFe2AunwWtre3YQRe27Wi8pKGM1a9tdugmUQxbovA1Uzxv6xe2uPHDha1X8Rcuzm2cLQjrpzlJ74FEPn/WbvqGCNHVFbozepdj62YmgOIQVPmKKlldTokYWxMtVUB1CoZponlNcHpJ9EB/pWLmwdwdFgXuUzwlFOxdbxk5WFgDz46HOxUdBNHgYGFF/jcUViGveVlxBuF/KmQxpeT8iJKJglOWjEb7vcHpRqPYKbNezCeoc6w2Kizp8ArV9bBKbqOc0PLfLqjvra0oPCXqcIYWYvEOwPxPUT+GI0qeEdrQwwYzREHLDEEEzECE4GKfARJ0i8WcKJRSLkk2ZpcvN0T5XKJXBMZMW4WTxhBF4+n87BWSObuwDU38tpY9zDig8mz5ILOcXSDF0ZSgBv1X8zvYrG5S/WkGdHfc5WcwsP0w2CaykXfE1ccsz1HWBzv3D7f73rPzbLaHlnnbIN/RgKzYFj1ihdq63qtWqNINjR2C8zYJ7iAdnuac6yD0q1fY+u047Mz9ldYB8Ntx+I2oR4OT7riH9H+Z9P4b43JTZ63lmzmgIU179n7lo3PnIxwz0mFFmnOKpfOOp7X5xOFk5IFQXumOs2JoGOl/+l02vUcyhVeU9N68pRCf9CuKF4+imOdqn+jgFrLOVO/svfhOE4b5YuQc/PKSeEHiRW141sSctKqGE9GaYbhs6c8pWCmc5iYofZjqY5PfW8kMU0mWk+U/UEsDBBQAAAgIAFtlLVdmczPNUAEAANcCAAALAAAAcmVwb3J0Lmpzb261UkFOwzAQ/ErkcyhpUto0P+DCBW6Iw9beKKGOHcVr1KrK39lNQ1uEkLgQS7G1O56ZHfmkOiQwQKCqkzJxAGq9U9V2U2aLPFtu5cuXRT6mqm4tBlW9nqbTo1GV0tqUYDarJWYm22V6XRijzsgn6FAQMJh7+S20d4QHimBfeAuL0KNeUGA4YaAzsZx+Jb5jU1DnK7MrN3mRmxqhWMn1lqxIPTc+WpO0TpgpoQYTEeZCHykk4EwSEKV+TBr44O7FUYIWO3SMCkhM2Q/+nTm+ZmgG37Wx44b1ek7onMIfJ7StY+gyS5X2NnZ8/YEDvca9LosyVeCcp6kiabylykfSfrIQHR5kKpR4e6BmBuxVVYMNmKoBQ7RzjEAEupkGEtzIK1WBqYP4Jhax7CZVF86Knd1ISK+2sD9OjbBv+34G3Qh+dz+KxE1sInwN7l/lf77V8RNQSwECPwMUAAAICABbZS1XYmHRAzcEAADvEgAAGQAAAAAAAAAAAAAAtIEAAAAAY2NkOGFkNzQxZTBkMGIwYzYzZGQuanNvblBLAQI/AxQAAAgIAFtlLVdmczPNUAEAANcCAAALAAAAAAAAAAAAAAC0gW4EAAByZXBvcnQuanNvblBLBQYAAAAAAgACAIAAAADnBQAAAAA="; \ No newline at end of file diff --git a/packages/e2e-playwright/tests/card/card.contextualTexts.spec.ts b/packages/e2e-playwright/tests/card/card.contextualTexts.spec.ts new file mode 100644 index 0000000000..040ab82e06 --- /dev/null +++ b/packages/e2e-playwright/tests/card/card.contextualTexts.spec.ts @@ -0,0 +1,102 @@ +import { test, expect } from '../../pages/cards/card.fixture'; +import { AMEX_CARD } from '../utils/constants'; +import LANG from '../../../lib/src/language/locales/en-US.json'; + +const EXPIRY_DATE_CONTEXTUAL_TEXT = LANG['creditCard.expiryDate.contextualText']; +const CVC_CONTEXTUAL_TEXT_3_DIGITS = LANG['creditCard.securityCode.contextualText.3digits']; +const CVC_CONTEXTUAL_TEXT_4_DIGITS = LANG['creditCard.securityCode.contextualText.4digits']; +const CVC_ERROR = LANG['error.va.sf-cc-cvc.01']; + +test('#1 Should inspect the card inputs and see they have contextual elements set', async ({ cardPage }) => { + const { card, page } = cardPage; + + await card.isComponentVisible(); + + // checkout expiryDate element + await expect(card.expiryDateContextualElement).toHaveText(EXPIRY_DATE_CONTEXTUAL_TEXT); + const expiryDateAriaHidden = await card.expiryDateContextualElement.getAttribute('aria-hidden'); + await expect(expiryDateAriaHidden).toEqual('true'); + + // iframe expiryDate element + await expect(card.expiryDateIframeContextualElement).toHaveText(EXPIRY_DATE_CONTEXTUAL_TEXT); + + // checkout security code contextual element + await expect(card.cvcContextualElement).toHaveText(CVC_CONTEXTUAL_TEXT_3_DIGITS); + const cvcAriaHidden = await card.cvcContextualElement.getAttribute('aria-hidden'); + await expect(cvcAriaHidden).toEqual('true'); + + // iframe security code element + await expect(card.cvcIframeContextualElement).toHaveText(CVC_CONTEXTUAL_TEXT_3_DIGITS); + + // Type amex number and see the contextual element change in the CVC field + await card.typeCardNumber(AMEX_CARD); + + await expect(card.cvcContextualElement).toHaveText(CVC_CONTEXTUAL_TEXT_4_DIGITS); + await expect(card.cvcIframeContextualElement).toHaveText(CVC_CONTEXTUAL_TEXT_4_DIGITS); + + // Delete the card number and see the contextual element reset in the CVC field + await card.deleteCardNumber(); + + await expect(card.cvcContextualElement).toHaveText(CVC_CONTEXTUAL_TEXT_3_DIGITS); + await expect(card.cvcIframeContextualElement).toHaveText(CVC_CONTEXTUAL_TEXT_3_DIGITS); +}); + +test('#2 Should inspect the cvc input for a contextual text set, then it should be replaced by an error, then reset', async ({ cardPage }) => { + const { card, page } = cardPage; + + await card.isComponentVisible(); + + // checkout security code contextual element + await expect(card.cvcContextualElement).toHaveText(CVC_CONTEXTUAL_TEXT_3_DIGITS); + let cvcAriaHidden = await card.cvcContextualElement.getAttribute('aria-hidden'); + await expect(cvcAriaHidden).toEqual('true'); + + // error element hidden + await expect(card.cvcErrorElement).not.toBeVisible(); + + // iframe security code contextual element + await expect(card.cvcIframeContextualElement).toHaveText(CVC_CONTEXTUAL_TEXT_3_DIGITS); + + // press pay to generate errors + await cardPage.pay(); + + // checkout security code error element + await expect(card.cvcErrorElement).toBeVisible(); + await expect(card.cvcErrorElement).toHaveText(CVC_ERROR); + cvcAriaHidden = await card.cvcErrorElement.getAttribute('aria-hidden'); + await expect(cvcAriaHidden).toEqual('true'); + + // contextual element being hidden + await expect(card.cvcContextualElement).not.toBeVisible(); + + // iframe contextual (error) element + await expect(card.cvcIframeContextualElement).toHaveText(CVC_ERROR); + + // Allow default focusing after validation to happen + await page.waitForTimeout(1000); + + // type + await card.typeCvc('737'); + + // reset + await expect(card.cvcContextualElement).toBeVisible(); + await expect(card.cvcContextualElement).toHaveText(CVC_CONTEXTUAL_TEXT_3_DIGITS); + // error element hidden + await expect(card.cvcErrorElement).not.toBeVisible(); + + await expect(card.cvcIframeContextualElement).toHaveText(CVC_CONTEXTUAL_TEXT_3_DIGITS); +}); + +test('#3 Should find no contextualElements because the config says to not show them', async ({ cardNoContextualElementPage }) => { + const { card, page } = cardNoContextualElementPage; + + await card.isComponentVisible(); + + // checkout contextual elements not present + await expect(card.expiryDateContextualElement).not.toBeVisible(); + await expect(card.cvcContextualElement).not.toBeVisible(); + + // iframe contextual elements - present but without text + await expect(card.expiryDateIframeContextualElement).toHaveText(''); + await expect(card.cvcIframeContextualElement).toHaveText(''); +}); diff --git a/packages/e2e-playwright/tests/issuerList/issue-list.spec.ts b/packages/e2e-playwright/tests/issuerList/issue-list.spec.ts index cce6a5ba1b..81b5577564 100644 --- a/packages/e2e-playwright/tests/issuerList/issue-list.spec.ts +++ b/packages/e2e-playwright/tests/issuerList/issue-list.spec.ts @@ -10,7 +10,6 @@ test('should select highlighted issuer and update pay button label', async ({ is await expect(issuerList.submitButton).toHaveText('Continue to Test Issuer 4'); await expect(issuerList.highlightedIssuerButtonGroup.getByRole('button', { pressed: true })).toHaveText('Test Issuer 4'); - await expect(issuerList.selectorCombobox).toHaveValue('Select your bank'); }); test('it should be able to filter and select using the keyboard', async ({ issuerListPage }) => { diff --git a/packages/e2e-playwright/tests/utils/constants.ts b/packages/e2e-playwright/tests/utils/constants.ts index 3786a5e3d4..2b330aa8bc 100644 --- a/packages/e2e-playwright/tests/utils/constants.ts +++ b/packages/e2e-playwright/tests/utils/constants.ts @@ -1,6 +1,7 @@ export const BIN_LOOKUP_VERSION = 'v3'; export const REGULAR_TEST_CARD = '5500000000000004'; +export const AMEX_CARD = '370000000000002'; export const TEST_DATE_VALUE = '03/30'; export const TEST_CVC_VALUE = '737'; diff --git a/packages/e2e/tests/_models/Address.component.js b/packages/e2e/tests/_models/Address.component.js index 745176a27b..81ada81f53 100644 --- a/packages/e2e/tests/_models/Address.component.js +++ b/packages/e2e/tests/_models/Address.component.js @@ -10,7 +10,7 @@ export default class AddressComponent { this.postalCodeLabel = this.baseEl.find('.adyen-checkout__field--postalCode .adyen-checkout__label__text'); this.postalCodeInput = this.baseEl.find('.adyen-checkout__input--postalCode'); - this.postalCodeInputError = this.baseEl.find('.adyen-checkout__field--postalCode .adyen-checkout__error-text'); + this.postalCodeInputError = this.baseEl.find('.adyen-checkout__field--postalCode .adyen-checkout-contextual-text--error'); } async fillPostalCode(value = '') { diff --git a/packages/e2e/tests/_models/CardComponent.page.js b/packages/e2e/tests/_models/CardComponent.page.js index e7cb1fbb00..e8863ace41 100644 --- a/packages/e2e/tests/_models/CardComponent.page.js +++ b/packages/e2e/tests/_models/CardComponent.page.js @@ -38,7 +38,7 @@ export default class CardPage extends BasePage { this.numSpan = Selector(`${BASE_EL} .adyen-checkout__card__cardNumber__input`); // The that holds the error text - this.numErrorText = Selector(`${BASE_EL} .adyen-checkout__field--cardNumber .adyen-checkout__error-text`); + this.numErrorText = Selector(`${BASE_EL} .adyen-checkout__field--cardNumber .adyen-checkout-contextual-text--error`); // The el that holds the card brand logo (actually a child of this.numSpan) this.brandingIcon = Selector(`${BASE_EL} .adyen-checkout__card__cardNumber__brandIcon`); @@ -60,7 +60,7 @@ export default class CardPage extends BasePage { this.dateSpan = Selector(`${BASE_EL} .adyen-checkout__card__exp-date__input`); // The that holds the error text - this.dateErrorText = Selector(`${BASE_EL} .adyen-checkout__field__exp-date .adyen-checkout__error-text`); + this.dateErrorText = Selector(`${BASE_EL} .adyen-checkout__field__exp-date .adyen-checkout-contextual-text--error`); this.storedCardExpiryDate = Selector(`${BASE_EL} .adyen-checkout__field--storedCard .adyen-checkout__card__exp-date__input--oneclick`); @@ -81,7 +81,7 @@ export default class CardPage extends BasePage { this.cvcSpan = Selector(`${BASE_EL} .adyen-checkout__card__cvc__input`); // The that holds the error text - this.cvcErrorText = Selector(`${BASE_EL} .adyen-checkout__field__cvc .adyen-checkout__error-text`); + this.cvcErrorText = Selector(`${BASE_EL} .adyen-checkout__field__cvc .adyen-checkout-contextual-text--error`); /** * Dual branding @@ -96,18 +96,20 @@ export default class CardPage extends BasePage { this.kcpTaxNumberLabelWithFocus = Selector(`${BASE_EL} .adyen-checkout__field--kcp-taxNumber .adyen-checkout__label--focused`); this.kcpTaxNumberInput = Selector(`${BASE_EL} .adyen-checkout__field--kcp-taxNumber .adyen-checkout__card__kcp-taxNumber__input`); this.pwdSpan = Selector(`${BASE_EL} [data-cse="encryptedPassword"]`); - this.pwdErrorText = Selector(`${BASE_EL} .adyen-checkout__field--koreanAuthentication-encryptedPassword .adyen-checkout__error-text`); + this.pwdErrorText = Selector( + `${BASE_EL} .adyen-checkout__field--koreanAuthentication-encryptedPassword .adyen-checkout-contextual-text--error` + ); /** * AVS */ this.addressLabelWithFocus = Selector(`${BASE_EL} .adyen-checkout__field--street .adyen-checkout__label--focused`); - this.addressLabelErrorText = Selector(`${BASE_EL} .adyen-checkout__field--street .adyen-checkout__error-text`); + this.addressLabelErrorText = Selector(`${BASE_EL} .adyen-checkout__field--street .adyen-checkout-contextual-text--error`); this.addressLabel = Selector(`${BASE_EL} .adyen-checkout__field--street .adyen-checkout__label`); this.addressInput = Selector(`${BASE_EL} .adyen-checkout__field--street .adyen-checkout__input--street`); this.postalCodeInput = Selector(`${BASE_EL} .adyen-checkout__field--postalCode .adyen-checkout__input--postalCode`); - this.postalCodeErrorText = Selector(`${BASE_EL} .adyen-checkout__field--postalCode .adyen-checkout__error-text`); + this.postalCodeErrorText = Selector(`${BASE_EL} .adyen-checkout__field--postalCode .adyen-checkout-contextual-text--error`); this.houseNumberLabelWithFocus = Selector(`${BASE_EL} .adyen-checkout__field--houseNumberOrName .adyen-checkout__label--focused`); diff --git a/packages/e2e/tests/address/address.postalCode.test.js b/packages/e2e/tests/address/address.postalCode.test.js index a570650f7d..2976002933 100644 --- a/packages/e2e/tests/address/address.postalCode.test.js +++ b/packages/e2e/tests/address/address.postalCode.test.js @@ -16,8 +16,6 @@ test('should show error when switching from country that has valid postal code t await addressComponent.selectCountry('United States'); await addressComponent.fillPostalCode('12345'); - await t.expect(addressComponent.postalCodeInputError.exists).ok(); // error fields should always be in DOM - await addressComponent.selectCountry('Brazil'); await t.expect(addressComponent.postalCodeInputError.innerText).contains('Invalid format. Expected format: 12345678 or 12345-678'); diff --git a/packages/e2e/tests/cards/Bancontact/bancontact.visa.test.js b/packages/e2e/tests/cards/Bancontact/bancontact.visa.test.js index cc6982d069..3dafe7d7b6 100644 --- a/packages/e2e/tests/cards/Bancontact/bancontact.visa.test.js +++ b/packages/e2e/tests/cards/Bancontact/bancontact.visa.test.js @@ -72,7 +72,7 @@ test('#3 Enter card number, that we mock to co-branded bcmc/visa ' + 'then compl }); test( - '#4 Enter card number, that we mock to co-branded bcmc/visa ' + + '#4 Enter card number (co-branded bcmc/visa) ' + 'then complete expiryDate and expect comp to be valid' + 'then click Visa logo and expect comp to not be valid' + 'then click BCMC logo and expect comp to be valid again', diff --git a/packages/e2e/tests/cards/binLookup/responseAndCallbacks/binLookup.v2.test.js b/packages/e2e/tests/cards/binLookup/responseAndCallbacks/binLookup.v2.test.js index ef5a0534e4..7bf3a45998 100644 --- a/packages/e2e/tests/cards/binLookup/responseAndCallbacks/binLookup.v2.test.js +++ b/packages/e2e/tests/cards/binLookup/responseAndCallbacks/binLookup.v2.test.js @@ -19,7 +19,7 @@ const logger = RequestLogger( } ); -const errorLabel = Selector('.card-field .adyen-checkout__error-text'); +const errorLabel = Selector('.card-field .adyen-checkout-contextual-text--error'); const UNSUPPORTED_CARD = LANG['error.va.sf-cc-num.03']; @@ -29,10 +29,7 @@ const iframeSelector = getIframeSelector('.card-field iframe'); const cardUtils = cu(iframeSelector); -fixture`Testing binLookup v2 response` - .page(CARDS_URL) - .clientScripts('binLookup.clientScripts.js') - .requestHooks(logger); +fixture`Testing binLookup v2 response`.page(CARDS_URL).clientScripts('binLookup.clientScripts.js').requestHooks(logger); test('#1 Enter number of known dual branded card, ' + 'then inspect response body for expected properties ', async t => { // Start, allow time for iframes to load diff --git a/packages/e2e/tests/cards/binLookup/ui/handlingErrors/unsupportedCardErrors.test.js b/packages/e2e/tests/cards/binLookup/ui/handlingErrors/unsupportedCardErrors.test.js index 1507dde2c3..1852c0b313 100644 --- a/packages/e2e/tests/cards/binLookup/ui/handlingErrors/unsupportedCardErrors.test.js +++ b/packages/e2e/tests/cards/binLookup/ui/handlingErrors/unsupportedCardErrors.test.js @@ -38,7 +38,7 @@ test('#1 Enter number of unsupported card, ' + 'then check UI shows an error ' + await cardPage.cardUtils.fillCardNumber(t, REGULAR_TEST_CARD, 'paste'); // Test UI shows "Unsupported card" error has gone - await t.expect(cardPage.numErrorText.withExactText('').exists).ok(); + await t.expect(cardPage.numErrorText.exists).notOk(); }); test( @@ -77,7 +77,7 @@ test( await cardPage.cardUtils.fillCardNumber(t, REGULAR_TEST_CARD, 'paste'); // Test UI shows "Unsupported card" error has gone - await t.expect(cardPage.numErrorText.withExactText('').exists).ok(); + await t.expect(cardPage.numErrorText.exists).notOk(); // PAN error cleared but other errors persist await t @@ -109,7 +109,7 @@ test('#3 Enter number of unsupported card, ' + 'then check UI shows an error ' + await cardPage.cardUtils.fillCardNumber(t, UNKNOWN_VISA_CARD, 'paste'); // Test UI shows "Unsupported card" error has gone - await t.expect(cardPage.numErrorText.withExactText('').exists).ok(); + await t.expect(cardPage.numErrorText.exists).notOk(); }); test('#4 Enter number of unsupported card, ' + 'then check UI shows an error ' + 'then delete PAN & check UI error is cleared', async t => { @@ -131,5 +131,5 @@ test('#4 Enter number of unsupported card, ' + 'then check UI shows an error ' + await cardPage.cardUtils.deleteCardNumber(t); // Test UI shows "Unsupported card" error has gone - await t.expect(cardPage.numErrorText.withExactText('').exists).ok(); + await t.expect(cardPage.numErrorText.exists).notOk(); }); diff --git a/packages/e2e/tests/cards/expiryDate/minimumExpiryDate.test.js b/packages/e2e/tests/cards/expiryDate/minimumExpiryDate.test.js index 509d5ff862..f152df0a1f 100644 --- a/packages/e2e/tests/cards/expiryDate/minimumExpiryDate.test.js +++ b/packages/e2e/tests/cards/expiryDate/minimumExpiryDate.test.js @@ -5,7 +5,7 @@ import { CARDS_URL } from '../../pages'; import LANG from '../../../../lib/src/language/locales/en-US.json'; const errorHolder = Selector('.card-field .adyen-checkout__field--error'); -const errorLabel = Selector('.card-field .adyen-checkout__error-text'); +const errorLabel = Selector('.card-field .adyen-checkout-contextual-text--error'); const CARD_TOO_OLD = LANG['error.va.sf-cc-dat.01']; const CARD_TOO_FAR = LANG['error.va.sf-cc-dat.02']; @@ -70,7 +70,7 @@ test('#3 With minimumExpiryDate set - input an expiry date that is matches it & await t.expect(errorHolder.exists).notOk(); // Test UI shows no error - await t.expect(errorLabel.withExactText('').exists).ok(); + await t.expect(errorLabel.exists).notOk(); }); test('#4 With minimumExpiryDate set - input an expiry date that exceeds it (a bit) & expect no error', async t => { @@ -84,7 +84,7 @@ test('#4 With minimumExpiryDate set - input an expiry date that exceeds it (a bi await t.expect(errorHolder.exists).notOk(); // Test UI shows no error - await t.expect(errorLabel.withExactText('').exists).ok(); + await t.expect(errorLabel.exists).notOk(); }); test('#5 With minimumExpiryDate set - input an expiry date that is too far in the future, & expect the correct error ', async t => { @@ -120,7 +120,7 @@ test( await t.expect(errorHolder.exists).notOk(); // Test UI shows no error - await t.expect(errorLabel.withExactText('').exists).ok(); + await t.expect(errorLabel.exists).notOk(); // Card out of date await cardUtils.fillDate(t, '08/24', 'paste'); @@ -152,7 +152,7 @@ test( await t.expect(errorHolder.exists).notOk(); // Test UI shows no error - await t.expect(errorLabel.withExactText('').exists).ok(); + await t.expect(errorLabel.exists).notOk(); // Card out of date await cardUtils.fillDate(t, '04/10', 'paste'); diff --git a/packages/e2e/tests/issuerLists/ideal/ideal.test.js b/packages/e2e/tests/issuerLists/ideal/ideal.test.js index 1e6b7e08ab..b4a4a399b6 100644 --- a/packages/e2e/tests/issuerLists/ideal/ideal.test.js +++ b/packages/e2e/tests/issuerLists/ideal/ideal.test.js @@ -19,8 +19,9 @@ test('should make an iDeal payment', async t => { const stateData = await getComponentData(); await t.expect(stateData.paymentMethod).eql({ + checkoutAttemptId: 'do-not-track', type: 'ideal', - issuer: '1121' + issuer: '1164' }); await t.expect(stateData.clientStateDataIndicator).eql(true); @@ -34,6 +35,7 @@ test('should make an iDeal payment using a highlighted issuer', async t => { const stateData = await getComponentData(); await t.expect(stateData.paymentMethod).eql({ + checkoutAttemptId: 'do-not-track', type: 'ideal', issuer: '1121' }); diff --git a/packages/e2e/tests/storedCard/general.test.js b/packages/e2e/tests/storedCard/general.test.js index 0e2afea9ad..69e05fae3d 100644 --- a/packages/e2e/tests/storedCard/general.test.js +++ b/packages/e2e/tests/storedCard/general.test.js @@ -18,7 +18,7 @@ test('#1 Can fill out the cvc fields in the stored card and make a successful pa await t.setNativeDialogHandler(() => true); // expiry date field is readonly - await t.expect(cardPage.storedCardExpiryDate.withAttribute('readonly').exists).ok(); + await t.expect(cardPage.storedCardExpiryDate.withAttribute('disabled').exists).ok(); await cardPage.cardUtils.fillCVC(t, TEST_CVC_VALUE, 'add', 0); diff --git a/packages/lib/.storybook/preview.tsx b/packages/lib/.storybook/preview.tsx index 3465c1b47a..e59c738a52 100644 --- a/packages/lib/.storybook/preview.tsx +++ b/packages/lib/.storybook/preview.tsx @@ -1,5 +1,4 @@ import './main.css'; -import '../src/style/index.scss'; import { Preview } from '@storybook/preact'; import { DEFAULT_COUNTRY_CODE, DEFAULT_SHOPPER_LOCALE, DEFAULT_AMOUNT_VALUE } from '../storybook/config/commonConfig'; import { createCheckout } from '../storybook/helpers/create-checkout'; diff --git a/packages/lib/postcss.config.js b/packages/lib/postcss.config.js index 31ba2919a3..4b6f5d4a79 100644 --- a/packages/lib/postcss.config.js +++ b/packages/lib/postcss.config.js @@ -1,5 +1,5 @@ console.log('Using postcss plugins...'); module.exports = { - // eslint-disable-next-line @typescript-eslint/no-var-requires + // eslint-disable-next-line @typescript-eslint/no-var-requires,import/no-extraneous-dependencies plugins: [require('autoprefixer'), require('cssnano')({ preset: ['default', { colormin: false }] })] }; diff --git a/packages/lib/src/components/Ach/components/AchInput/AchInput.module.scss b/packages/lib/src/components/Ach/components/AchInput/AchInput.module.scss deleted file mode 100644 index 3ed5c9538e..0000000000 --- a/packages/lib/src/components/Ach/components/AchInput/AchInput.module.scss +++ /dev/null @@ -1,18 +0,0 @@ -@import "../../../../style/index"; - -.sf-input { - &__wrapper { - position: relative; - } - - &__wrapper *, - &__wrapper *::before, - &__wrapper *::after { - box-sizing: border-box; - } -} - -.adyen-checkout__input { - display: block; - max-height: 100px; -} diff --git a/packages/lib/src/components/Ach/components/AchInput/AchInput.scss b/packages/lib/src/components/Ach/components/AchInput/AchInput.scss index e4e03040c1..20d63f382a 100644 --- a/packages/lib/src/components/Ach/components/AchInput/AchInput.scss +++ b/packages/lib/src/components/Ach/components/AchInput/AchInput.scss @@ -4,6 +4,10 @@ .adyen-checkout__pm__holderName { margin-bottom: 0; + + .adyen-checkout__input { + max-height: 100px; + } } .adyen-checkout__fieldset__title + .adyen-checkout__ach-sf__form { @@ -11,11 +15,17 @@ } .adyen-checkout__ach-sf__form { - margin-top: 16px; + margin-top: var(--b-spacer-070); } .adyen-checkout__ach-input { .adyen-checkout__fieldset--address { - margin-top: 16px; + margin-top: var(--b-spacer-070); + } +} + +.sf-input { + &__wrapper { + position: relative; } } diff --git a/packages/lib/src/components/Ach/components/AchInput/AchInput.tsx b/packages/lib/src/components/Ach/components/AchInput/AchInput.tsx index 0b3f949488..d0596c30db 100644 --- a/packages/lib/src/components/Ach/components/AchInput/AchInput.tsx +++ b/packages/lib/src/components/Ach/components/AchInput/AchInput.tsx @@ -9,7 +9,6 @@ import LoadingWrapper from '../../../internal/LoadingWrapper/LoadingWrapper'; import defaultProps from './defaultProps'; import defaultStyles from './defaultStyles'; import useCoreContext from '../../../../core/Context/useCoreContext'; -import styles from './AchInput.module.scss'; import './AchInput.scss'; import { ACHInputDataState, ACHInputProps, ACHInputStateError, ACHInputStateValid } from './types'; import StoreDetails from '../../../internal/StoreDetails'; @@ -145,7 +144,7 @@ function AchInput(props: ACHInputProps) { onChange={handleSecuredFieldsChange} onFocus={handleFocus} render={({ setRootNode, setFocusOn }, sfpState) => ( -
+
{
{i18n.get('ach.bankAccount')}
} @@ -155,12 +154,14 @@ function AchInput(props: ACHInputProps) { label={i18n.get('ach.accountHolderNameField.title')} className={'adyen-checkout__pm__holderName'} errorMessage={!!errors.holderName && i18n.get('ach.accountHolderNameField.invalid')} + showContextualElement={props.showContextualElement} + contextualText={i18n.get('ach.accountHolderNameField.contextualText')} isValid={!!valid.holderName} name={'holderName'} > { styles: props.styles, type: props.type, forceCompat: props.forceCompat, - resources: props.resources + resources: props.resources, + placeholders: props.placeholders }; }; diff --git a/packages/lib/src/components/Ach/components/AchInput/components/AchSFInput.tsx b/packages/lib/src/components/Ach/components/AchInput/components/AchSFInput.tsx index 0ed3a6ab6b..d5b8f8c99d 100644 --- a/packages/lib/src/components/Ach/components/AchInput/components/AchSFInput.tsx +++ b/packages/lib/src/components/Ach/components/AchInput/components/AchSFInput.tsx @@ -1,8 +1,8 @@ import { h } from 'preact'; import classNames from 'classnames'; -import styles from '../AchInput.module.scss'; import Field from '../../../../internal/FormFields/Field'; import DataSfSpan from '../../../../Card/components/CardInput/components/DataSfSpan'; +import { alternativeLabelContent } from '../../../../Card/components/CardInput/components/IframeLabelAlternative'; const AchSFInput = ({ id, dataInfo, className = '', label, focused, filled, errorMessage = '', isValid = false, onFocusField, dir }) => { const capitalisedId = id.charAt(0).toUpperCase() + id.slice(1); @@ -20,7 +20,9 @@ const AchSFInput = ({ id, dataInfo, className = '', label, focused, filled, erro className={className} dir={dir} name={id} - errorVisibleToScreenReader={false} + contextVisibleToScreenReader={false} + useLabelElement={false} + renderAlternativeToLabel={alternativeLabelContent} > {}, diff --git a/packages/lib/src/components/Ach/components/AchInput/defaultStyles.ts b/packages/lib/src/components/Ach/components/AchInput/defaultStyles.ts index 7b81429129..925a636641 100644 --- a/packages/lib/src/components/Ach/components/AchInput/defaultStyles.ts +++ b/packages/lib/src/components/Ach/components/AchInput/defaultStyles.ts @@ -1,5 +1,3 @@ export default { - base: { - caretColor: '#0075FF' - } + base: {} }; diff --git a/packages/lib/src/components/Ach/components/AchInput/types.ts b/packages/lib/src/components/Ach/components/AchInput/types.ts index 259ff0a63f..0acc5576fa 100644 --- a/packages/lib/src/components/Ach/components/AchInput/types.ts +++ b/packages/lib/src/components/Ach/components/AchInput/types.ts @@ -22,9 +22,9 @@ export interface ACHInputDataState { billingAddress?: object; } -type Placeholders = { - holderName?: string; -}; +type PlaceholderKeys = 'holderName' | 'bankAccountNumber' | 'bankLocationId'; + +export type Placeholders = Partial>; export interface ACHInputProps { allowedDOMAccess?: boolean; @@ -53,6 +53,7 @@ export interface ACHInputProps { onLoad?: () => {}; payButton?: (obj) => {}; placeholders?: Placeholders; + showContextualElement?: boolean; ref?: any; resources: Resources; showPayButton?: boolean; diff --git a/packages/lib/src/components/Ach/types.ts b/packages/lib/src/components/Ach/types.ts index 1dd2121e8a..3ad178d738 100644 --- a/packages/lib/src/components/Ach/types.ts +++ b/packages/lib/src/components/Ach/types.ts @@ -1,4 +1,5 @@ import { UIElementProps } from '../types'; +import { Placeholders } from './components/AchInput/types'; export interface AchElementProps extends UIElementProps { storedPaymentMethodId?: string; @@ -7,4 +8,5 @@ export interface AchElementProps extends UIElementProps { enableStoreDetails: boolean; bankAccountNumber: string; showFormInstruction?: boolean; + placeholders?: Placeholders; } diff --git a/packages/lib/src/components/AfterPay/components/ConsentCheckboxLabel/ConsentCheckboxLabel.tsx b/packages/lib/src/components/AfterPay/components/ConsentCheckboxLabel/ConsentCheckboxLabel.tsx index 70a29f84d8..5aa68f3fbd 100644 --- a/packages/lib/src/components/AfterPay/components/ConsentCheckboxLabel/ConsentCheckboxLabel.tsx +++ b/packages/lib/src/components/AfterPay/components/ConsentCheckboxLabel/ConsentCheckboxLabel.tsx @@ -15,7 +15,7 @@ export default function ConsentCheckboxLabel(props: ConsentCheckboxLabelProps) { return ( {textBeforeLink} - + {linkText} {textAfterLink} diff --git a/packages/lib/src/components/AmazonPay/AmazonPay.scss b/packages/lib/src/components/AmazonPay/AmazonPay.scss index da34f8f224..8d38fa8f93 100644 --- a/packages/lib/src/components/AmazonPay/AmazonPay.scss +++ b/packages/lib/src/components/AmazonPay/AmazonPay.scss @@ -1,11 +1,9 @@ -@import "../../style/index"; - .adyen-checkout__amazonpay__button { margin: auto; } .adyen-checkout__amazonpay .adyen-checkout__button--ghost { display: block; - margin: $spacing-small auto 0; + margin: var(--b-spacer-040) auto 0; width: auto; } diff --git a/packages/lib/src/components/ApplePay/components/ApplePayButton.module.scss b/packages/lib/src/components/ApplePay/components/ApplePayButton.module.scss deleted file mode 100644 index 01733fc595..0000000000 --- a/packages/lib/src/components/ApplePay/components/ApplePayButton.module.scss +++ /dev/null @@ -1,117 +0,0 @@ -@supports (-webkit-appearance: -apple-pay-button) { - /* - * Combination of both classes improve the specificity, avoiding - * overwrite of the -webkit-appearence by the button native css - */ - /* stylelint-disable property-no-vendor-prefix */ - .apple-pay, - .apple-pay-button { - -webkit-appearance: -apple-pay-button; - } - /* stylelint-enable property-no-vendor-prefix */ - - .apple-pay-button { - display: inline-block; - cursor: pointer; - } - - .apple-pay-button-black { - -apple-pay-button-style: black; - } - - .apple-pay-button-white { - -apple-pay-button-style: white; - } - - .apple-pay-button-white-with-line { - -apple-pay-button-style: white-outline; - } - - /* Apple Pay Button types https://developer.apple.com/documentation/apple_pay_on_the_web/displaying_apple_pay_buttons */ - .apple-pay-button--type-plain { - -apple-pay-button-type: plain; - } - - .apple-pay-button--type-buy { - -apple-pay-button-type: buy; - } - - .apple-pay-button--type-donate { - -apple-pay-button-type: donate; - } - - .apple-pay-button--type-check-out { - -apple-pay-button-type: check-out; - } - - .apple-pay-button--type-book { - -apple-pay-button-type: book; - } - - .apple-pay-button--type-subscribe { - -apple-pay-button-type: subscribe; - } - - .apple-pay-button--type-add-money { - -apple-pay-button-type: add-money; - } - - .apple-pay-button--type-contribute { - -apple-pay-button-type: contribute; - } - - .apple-pay-button--type-order { - -apple-pay-button-type: order; - } - - .apple-pay-button--type-reload { - -apple-pay-button-type: reload; - } - - .apple-pay-button--type-rent { - -apple-pay-button-type: rent; - } - - .apple-pay-button--type-support { - -apple-pay-button-type: support; - } - - .apple-pay-button--type-tip { - -apple-pay-button-type: tip; - } - - .apple-pay-button--type-top-up { - -apple-pay-button-type: top-up; - } -} - -@supports not (-webkit-appearance: -apple-pay-button) { - .apple-pay-button { - display: inline-block; - background-size: 100% 60%; - background-repeat: no-repeat; - background-position: 50% 50%; - border-radius: 5px; - padding: 0; - box-sizing: border-box; - min-width: 200px; - min-height: 32px; - max-height: 64px; - } - - .apple-pay-button-black { - background-image: -webkit-named-image(apple-pay-logo-white); - background-color: black; - } - - .apple-pay-button-white { - background-image: -webkit-named-image(apple-pay-logo-black); - background-color: white; - } - - .apple-pay-button-white-with-line { - background-image: -webkit-named-image(apple-pay-logo-black); - background-color: white; - border: 0.5px solid black; - } -} diff --git a/packages/lib/src/components/ApplePay/components/ApplePayButton.scss b/packages/lib/src/components/ApplePay/components/ApplePayButton.scss index 22eb025147..d22f47cf75 100644 --- a/packages/lib/src/components/ApplePay/components/ApplePayButton.scss +++ b/packages/lib/src/components/ApplePay/components/ApplePayButton.scss @@ -1,8 +1,125 @@ .adyen-checkout__applepay__button { width: 240px; - height: 48px; + height: var(--b-spacer-120); } .adyen-checkout__dropin .adyen-checkout__applepay__button { width: 100%; } + +@supports (-webkit-appearance: -apple-pay-button) { + /* + * Combination of both classes improve the specificity, avoiding + * overwrite of the -webkit-appearence by the button native css + */ + /* stylelint-disable property-no-vendor-prefix */ + .apple-pay, + .apple-pay-button { + -webkit-appearance: -apple-pay-button; + } + /* stylelint-enable property-no-vendor-prefix */ + + .apple-pay-button { + display: inline-block; + cursor: pointer; + } + + .apple-pay-button-black { + -apple-pay-button-style: black; + } + + .apple-pay-button-white { + -apple-pay-button-style: white; + } + + .apple-pay-button-white-with-line { + -apple-pay-button-style: white-outline; + } + + /* Apple Pay Button types https://developer.apple.com/documentation/apple_pay_on_the_web/displaying_apple_pay_buttons */ + .apple-pay-button--type-plain { + -apple-pay-button-type: plain; + } + + .apple-pay-button--type-buy { + -apple-pay-button-type: buy; + } + + .apple-pay-button--type-donate { + -apple-pay-button-type: donate; + } + + .apple-pay-button--type-check-out { + -apple-pay-button-type: check-out; + } + + .apple-pay-button--type-book { + -apple-pay-button-type: book; + } + + .apple-pay-button--type-subscribe { + -apple-pay-button-type: subscribe; + } + + .apple-pay-button--type-add-money { + -apple-pay-button-type: add-money; + } + + .apple-pay-button--type-contribute { + -apple-pay-button-type: contribute; + } + + .apple-pay-button--type-order { + -apple-pay-button-type: order; + } + + .apple-pay-button--type-reload { + -apple-pay-button-type: reload; + } + + .apple-pay-button--type-rent { + -apple-pay-button-type: rent; + } + + .apple-pay-button--type-support { + -apple-pay-button-type: support; + } + + .apple-pay-button--type-tip { + -apple-pay-button-type: tip; + } + + .apple-pay-button--type-top-up { + -apple-pay-button-type: top-up; + } +} + +@supports not (-webkit-appearance: -apple-pay-button) { + .apple-pay-button { + display: inline-block; + background-size: 100% 60%; + background-repeat: no-repeat; + background-position: 50% 50%; + border-radius: 5px; + padding: 0; + min-width: 200px; + min-height: var(--b-spacer-100); + max-height: var(--b-spacer-140); + } + + .apple-pay-button-black { + background-image: -webkit-named-image(apple-pay-logo-white); + background-color: black; + } + + .apple-pay-button-white { + background-image: -webkit-named-image(apple-pay-logo-black); + background-color: white; + } + + .apple-pay-button-white-with-line { + background-image: -webkit-named-image(apple-pay-logo-black); + background-color: white; + border: 0.5px solid black; + } +} diff --git a/packages/lib/src/components/ApplePay/components/ApplePayButton.tsx b/packages/lib/src/components/ApplePay/components/ApplePayButton.tsx index a37e99e0d0..02de70bba2 100644 --- a/packages/lib/src/components/ApplePay/components/ApplePayButton.tsx +++ b/packages/lib/src/components/ApplePay/components/ApplePayButton.tsx @@ -1,6 +1,5 @@ import { Component, h } from 'preact'; import cx from 'classnames'; -import styles from './ApplePayButton.module.scss'; import './ApplePayButton.scss'; import Language from '../../../language/Language'; import { ApplePayButtonType } from '../types'; @@ -30,10 +29,10 @@ class ApplePayButton extends Component { 'adyen-checkout__applepay__button', `adyen-checkout__applepay__button--${buttonColor}`, `adyen-checkout__applepay__button--${buttonType}`, - [styles['apple-pay']], - [styles['apple-pay-button']], - [styles[`apple-pay-button-${buttonColor}`]], - [styles[`apple-pay-button--type-${buttonType}`]] + 'apple-pay', + 'apple-pay-button', + `apple-pay-button-${buttonColor}`, + `apple-pay-button--type-add-money` )} onClick={this.props.onClick} /> diff --git a/packages/lib/src/components/BacsDD/components/BacsInput.scss b/packages/lib/src/components/BacsDD/components/BacsInput.scss index a4833a85cf..6f2f7447aa 100644 --- a/packages/lib/src/components/BacsDD/components/BacsInput.scss +++ b/packages/lib/src/components/BacsDD/components/BacsInput.scss @@ -1,5 +1,3 @@ -@import "../../../style/index"; - .adyen-checkout__bacs { &--confirm { position: relative; @@ -17,10 +15,9 @@ .adyen-checkout__bacs--edit { position: absolute; - top: -25px; + top: 0; right: 0; cursor: pointer; - width: 20%; &-dropin { top: -50px; @@ -29,10 +26,11 @@ .adyen-checkout__bacs--edit-button { border: none; background: none; - color: $color-blue; + color: var(--b-color-label-primary); text-decoration: underline; text-align: end; cursor: pointer; + padding: 0; } } } diff --git a/packages/lib/src/components/BacsDD/components/BacsInput.tsx b/packages/lib/src/components/BacsDD/components/BacsInput.tsx index ebc70e2e91..a9f71a8d32 100644 --- a/packages/lib/src/components/BacsDD/components/BacsInput.tsx +++ b/packages/lib/src/components/BacsDD/components/BacsInput.tsx @@ -13,6 +13,7 @@ import useImage from '../../../core/Context/useImage'; import InputText from '../../internal/FormFields/InputText'; import InputEmail from '../../internal/FormFields/InputEmail'; import FormInstruction from '../../internal/FormInstruction'; +import { getErrorMessage } from '../../../utils/getErrorMessage'; const ENTER_STATE = 'enter-data'; const CONFIRM_STATE = 'confirm-data'; @@ -160,7 +161,7 @@ function BacsInput(props: BacsInputProps) {
!!value }, - shopperEmail: personalDetailsValidationRules.shopperEmail, + shopperEmail: validationRules.emailRule, default: { modes: ['blur'], validate: value => !!value && value.length > 0 diff --git a/packages/lib/src/components/BankTransfer/components/BankTransferInput/BankTransferInput.scss b/packages/lib/src/components/BankTransfer/components/BankTransferInput/BankTransferInput.scss index 18d96eb0da..f8e6ca0f04 100644 --- a/packages/lib/src/components/BankTransfer/components/BankTransferInput/BankTransferInput.scss +++ b/packages/lib/src/components/BankTransfer/components/BankTransferInput/BankTransferInput.scss @@ -1,13 +1,13 @@ -@import "../../../../style/index"; +@use "../../../../style/index"; .adyen-checkout__bankTransfer__introduction { - font-size: $font-size-small; - color: $color-black; - font-weight: 400; - margin: 0 0 $spacing-medium; + font-size: index.$font-size-small; + color: var(--b-color-label-primary); + font-weight: var(--b-text-body-font-weight); + margin: 0 0 var(--b-spacer-070); padding: 0; } .adyen-checkout__bankTransfer__emailField { - margin: 0 0 $spacing-medium; + margin: 0 0 var(--b-spacer-070); } diff --git a/packages/lib/src/components/BankTransfer/components/BankTransferInput/BankTransferInput.tsx b/packages/lib/src/components/BankTransfer/components/BankTransferInput/BankTransferInput.tsx index ad0aa9abb3..a52b68979c 100644 --- a/packages/lib/src/components/BankTransfer/components/BankTransferInput/BankTransferInput.tsx +++ b/packages/lib/src/components/BankTransfer/components/BankTransferInput/BankTransferInput.tsx @@ -5,7 +5,7 @@ import SendCopyToEmail from '../../../internal/SendCopyToEmail/SendCopyToEmail'; import { useEffect, useState } from 'preact/hooks'; import useForm from '../../../../utils/useForm'; import { BankTransferSchema } from '../../types'; -import { personalDetailsValidationRules } from '../../../internal/PersonalDetails/validate'; +import { validationRules } from '../../../../utils/Validator/defaultRules'; function BankTransferInput(props) { const { i18n } = useCoreContext(); @@ -15,7 +15,7 @@ function BankTransferInput(props) { schema: [], defaultData: props.data, rules: { - shopperEmail: personalDetailsValidationRules.shopperEmail + shopperEmail: validationRules.emailRule } }); diff --git a/packages/lib/src/components/BillDesk/BillDeskWallet.ts b/packages/lib/src/components/BillDesk/BillDeskWallet.ts index dfd6be80a7..c8283d53a0 100644 --- a/packages/lib/src/components/BillDesk/BillDeskWallet.ts +++ b/packages/lib/src/components/BillDesk/BillDeskWallet.ts @@ -6,8 +6,7 @@ class BillDeskWalletElement extends IssuerListContainer { formatProps(props) { return { ...super.formatProps(props), - showImage: false, - placeholder: 'issuerList.wallet.placeholder' + showImage: false }; } } diff --git a/packages/lib/src/components/Blik/components/BlikInput.scss b/packages/lib/src/components/Blik/components/BlikInput.scss index d07a4f9aad..51feea34cb 100644 --- a/packages/lib/src/components/Blik/components/BlikInput.scss +++ b/packages/lib/src/components/Blik/components/BlikInput.scss @@ -1,9 +1,9 @@ -@import "../../../style/index"; +@use "../../../style/index"; .adyen-checkout__blik__helper { - font-size: $font-size-medium; + font-size: index.$font-size-medium; font-weight: normal; - color: $color-black; - margin: 0 0 $spacing-medium; + color: var(--b-color-label-primary); + margin: 0 0 var(--b-spacer-070); padding: 0; } diff --git a/packages/lib/src/components/Blik/components/BlikInput.tsx b/packages/lib/src/components/Blik/components/BlikInput.tsx index e8bf548120..3e7e896392 100644 --- a/packages/lib/src/components/Blik/components/BlikInput.tsx +++ b/packages/lib/src/components/Blik/components/BlikInput.tsx @@ -12,6 +12,7 @@ import InputText from '../../internal/FormFields/InputText'; interface BlikInputProps extends UIElementProps { data?: BlikInputDataState; + placeholders?: BlikInputDataState; } interface BlikInputDataState { @@ -63,7 +64,7 @@ function BlikInput(props: BlikInputProps) { autocomplete={'off'} onInput={handleChangeFor('blikCode', 'input')} onBlur={handleChangeFor('blikCode', 'blur')} - placeholder={'123456'} + placeholder={props?.placeholders?.blikCode} inputMode={'numeric'} maxLength={6} /> diff --git a/packages/lib/src/components/Boleto/components/BoletoInput/validate.ts b/packages/lib/src/components/Boleto/components/BoletoInput/validate.ts index aeab9ffd1b..9d6d575ed9 100644 --- a/packages/lib/src/components/Boleto/components/BoletoInput/validate.ts +++ b/packages/lib/src/components/Boleto/components/BoletoInput/validate.ts @@ -1,6 +1,6 @@ import { ValidatorRules } from '../../../../utils/Validator/types'; import validateSSN from '../../../internal/SocialSecurityNumberBrazil/validate'; -import { personalDetailsValidationRules } from '../../../internal/PersonalDetails/validate'; +import { validationRules } from '../../../../utils/Validator/defaultRules'; export const boletoValidationRules: ValidatorRules = { socialSecurityNumber: { @@ -8,7 +8,7 @@ export const boletoValidationRules: ValidatorRules = { errorMessage: 'error.va.gen.02', modes: ['blur'] }, - shopperEmail: personalDetailsValidationRules.shopperEmail, + shopperEmail: validationRules.emailRule, default: { validate: value => !!value && value.length > 0, errorMessage: 'error.va.gen.02', diff --git a/packages/lib/src/components/Boleto/components/BoletoVoucherResult/BoletoVoucherResult.scss b/packages/lib/src/components/Boleto/components/BoletoVoucherResult/BoletoVoucherResult.scss index 8c256a8a5f..cb7eb582e5 100644 --- a/packages/lib/src/components/Boleto/components/BoletoVoucherResult/BoletoVoucherResult.scss +++ b/packages/lib/src/components/Boleto/components/BoletoVoucherResult/BoletoVoucherResult.scss @@ -1,8 +1,8 @@ -@import "../../../../style/index"; +@use "../../../../style/index"; .adyen-checkout__voucher-result--boletobancario .adyen-checkout__voucher-result__code { - font-size: $font-size-small; - line-height: 19px; + font-size: index.$font-size-small; + line-height: var(--b-text-caption-line-height); word-break: break-all; - padding: 24px; + padding: var(--b-spacer-090); } diff --git a/packages/lib/src/components/Card/components/CardInput/CardInput.module.scss b/packages/lib/src/components/Card/components/CardInput/CardInput.module.scss deleted file mode 100644 index 0168c3d47f..0000000000 --- a/packages/lib/src/components/Card/components/CardInput/CardInput.module.scss +++ /dev/null @@ -1,67 +0,0 @@ -@import "../../../../style/index"; - -.card-input { - &__wrapper { - position: relative; - } - - &__wrapper *, - &__wrapper *::before, - &__wrapper *::after { - box-sizing: border-box; - } - - &__icon { - border-radius: $border-radius-small; - position: absolute; - right: 10px; - margin-left: 7px; - transform: translateY(-50%); - top: 50%; - height: 18px; - width: 27px; - } - - &__form { - opacity: 1; - } - - &__spinner { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - z-index: 1; - display: none; - } - - &__spinner--active { - display: block; - } - - &__form--loading { - opacity: 0; - } -} - -.adyen-checkout__input { - display: block; - max-height: 100px; -} - -.adyen-checkout__card__cvc__input--hidden, -.adyen-checkout__card__exp-date__input--hidden { - display: none; -} - -.adyen-checkout__card__exp-cvc__exp-date__input--hidden { - justify-content: flex-end; -} - -.revolving-plan-installments { - &__disabled { - pointer-events: none; - opacity: 0.4; - } -} diff --git a/packages/lib/src/components/Card/components/CardInput/CardInput.scss b/packages/lib/src/components/Card/components/CardInput/CardInput.scss index bc3674070e..5a07c3336b 100644 --- a/packages/lib/src/components/Card/components/CardInput/CardInput.scss +++ b/packages/lib/src/components/Card/components/CardInput/CardInput.scss @@ -1,4 +1,58 @@ -@import "../../../../style/index"; +.adyen-checkout-card-input { + &__wrapper { + position: relative; + } + + &__icon { + border-radius: var(--b-input-field-static-value-border-radius); + height: 18px; + width: 27px; + margin-right: var(--b-spacer-060); + } + + &__form { + opacity: 1; + } + + &__spinner { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + z-index: 1; + display: none; + } + + &__spinner--active { + display: block; + } + + &__form--loading { + opacity: 0; + } +} + +.adyen-checkout__input { + display: block; + max-height: 100px; +} + +.adyen-checkout__card__cvc__input--hidden, +.adyen-checkout__card__exp-date__input--hidden { + display: none; +} + +.adyen-checkout__card__exp-cvc__exp-date__input--hidden { + justify-content: flex-end; +} + +.revolving-plan-installments { + &__disabled { + pointer-events: none; + opacity: 0.4; + } +} .adyen-checkout__card-input__form { transition: opacity 0.25s ease-out; @@ -9,22 +63,16 @@ } .adyen-checkout__card__cardNumber__input { - padding: 5px 8px; + padding-left: var(--b-spacer-060); } .adyen-checkout__card__exp-date__input--oneclick { - line-height: 30px; - font-weight: 400; + line-height: var(--b-text-title-line-height); + font-weight: var(--b-text-body-font-weight); text-overflow: ellipsis; overflow: hidden; white-space: nowrap; text-align: left; - -} - -.adyen-checkout__field--storedCard .adyen-checkout__input[readonly], -.adyen-checkout__field--storedCard .adyen-checkout__input[readonly]:hover{ - color: $color-black; } .adyen-checkout__field--storedCard, @@ -33,13 +81,22 @@ margin-bottom: 0; } +.adyen-checkout__store-details { + display: flex; + align-items: center; + gap: var(--b-spacer-060); + padding: var(--b-spacer-060) var(--b-spacer-070); + margin-top: var(--b-spacer-070); + border: var(--b-border-width-s) solid var(--b-color-outline-secondary); + border-radius: var(--b-border-radius-m); +} + .adyen-checkout__card__holderName, -.adyen-checkout__store-details, .adyen-checkout__card__kcp-authentication, .adyen-checkout__card__socialSecurityNumber, .adyen-checkout__installments, .adyen-checkout__card-input .adyen-checkout__fieldset--billingAddress { - margin-top: $spacing-medium; + margin-top: var(--b-spacer-070); } .adyen-checkout__card-input.adyen-checkout__card-input--loading { @@ -47,11 +104,11 @@ } .adyen-checkout__card__holderName:first-child { - margin: 0 0 $spacing-medium; + margin: 0 0 var(--b-input-field-label-margin-bottom); } /* Hide card brand icon when cardNumber is in an error state */ -.adyen-checkout__field--cardNumber .adyen-checkout__input--error .adyen-checkout__card__cardNumber__brandIcon { +.adyen-checkout__field--cardNumber .adyen-checkout__input--error + .adyen-checkout__card__cardNumber__brandIcon { display: none; } @@ -88,14 +145,11 @@ } .adyen-checkout__card__cvc__hint__wrapper { - position: absolute; - right: 0; - top: 0; height: 100%; width: 27px; display: flex; align-items: center; - margin: 0 10px; + margin-right: var(--b-spacer-060); // Card Flip animation transition: transform 0.3s cubic-bezier(0.455, 0.03, 0.515, 0.955); @@ -118,13 +172,10 @@ } .adyen-checkout__field__exp-date_hint_wrapper { - position: absolute; - right: 0; - top: 0; - bottom: 0; display: flex; align-items: center; transition: opacity 0.1s linear; + margin-right: var(--b-spacer-060); &.adyen-checkout__field__exp-date_hint_wrapper--hidden { opacity: 0; @@ -134,7 +185,6 @@ .adyen-checkout__field__exp-date_hint { width: 27px; height: 18px; - margin: 0 10px 0 0; } // Front of the card is our back @@ -159,13 +209,13 @@ } .adyen-checkout__radio_group__input-wrapper { - margin-top: 20px; + margin-top: var(--b-spacer-080); } .adyen-checkout__field--revolving-plan-installments { position: relative; top: 42px; width: 30%; - margin-left: 15px; + margin-left: var(--b-spacer-070); } } diff --git a/packages/lib/src/components/Card/components/CardInput/CardInput.test.tsx b/packages/lib/src/components/Card/components/CardInput/CardInput.test.tsx index ab788c8c03..aca5648182 100644 --- a/packages/lib/src/components/Card/components/CardInput/CardInput.test.tsx +++ b/packages/lib/src/components/Card/components/CardInput/CardInput.test.tsx @@ -115,18 +115,14 @@ describe('CardInput > holderName', () => { }); test('holderName required, valid.holderName is false, add text to make valid.holderName = true', () => { - render(); + const placeholder = { holderName: 'Joe' }; + render(); expect(valid.holderName).toBe(false); - - const placeholderText = i18n.get('creditCard.holderName.placeholder'); - - const field = screen.getByPlaceholderText(placeholderText); + const field = screen.getByPlaceholderText(placeholder.holderName); fireEvent.blur(field, { target: { value: 'joe blogs' } }); - // await waitFor(() => { expect(data.holderName).toBe('joe blogs'); expect(valid.holderName).toBe(true); - // }); }); test('holderName required, data.holderName passed into comp - valid.holderName is true', () => { diff --git a/packages/lib/src/components/Card/components/CardInput/CardInput.tsx b/packages/lib/src/components/Card/components/CardInput/CardInput.tsx index 0cd0ddbcbb..5857fd3cfe 100644 --- a/packages/lib/src/components/Card/components/CardInput/CardInput.tsx +++ b/packages/lib/src/components/Card/components/CardInput/CardInput.tsx @@ -3,7 +3,6 @@ import { useState, useEffect, useRef, useMemo, useCallback } from 'preact/hooks' import SecuredFieldsProvider from '../../../internal/SecuredFields/SFP/SecuredFieldsProvider'; import { OnChangeEventDetails, SFPState } from '../../../internal/SecuredFields/SFP/types'; import defaultProps from './defaultProps'; -import defaultStyles from './defaultStyles'; import './CardInput.scss'; import { AddressModeOptions, CardInputDataState, CardInputErrorState, CardInputProps, CardInputRef, CardInputValidState } from './types'; import { CVC_POLICY_REQUIRED, DATE_POLICY_REQUIRED } from '../../../internal/SecuredFields/lib/configuration/constants'; @@ -18,7 +17,6 @@ import { AddressData } from '../../../../types'; import Specifications from '../../../internal/Address/Specifications'; import { StoredCardFieldsWrapper } from './components/StoredCardFieldsWrapper'; import { CardFieldsWrapper } from './components/CardFieldsWrapper'; -import styles from './CardInput.module.scss'; import { getAddressHandler, getAutoJumpHandler, getFocusHandler, setFocusOnFirstField } from './handlers'; import { InstallmentsObj } from './components/Installments/Installments'; import { TouchStartEventObj } from './components/types'; @@ -434,7 +432,7 @@ const CardInput: FunctionalComponent = props => { = props => { ref={setRootNode} className={classNames({ 'adyen-checkout__card-input': true, - [styles['card-input__wrapper']]: true, + 'adyen-checkout-card-input__wrapper': true, [`adyen-checkout__card-input--${props.fundingSource ?? 'credit'}`]: true, 'adyen-checkout__card-input--loading': status === 'loading' })} diff --git a/packages/lib/src/components/Card/components/CardInput/components/AvailableBrands/AvailableBrands.scss b/packages/lib/src/components/Card/components/CardInput/components/AvailableBrands/AvailableBrands.scss index 8cdd20fe91..0ae5e615f2 100644 --- a/packages/lib/src/components/Card/components/CardInput/components/AvailableBrands/AvailableBrands.scss +++ b/packages/lib/src/components/Card/components/CardInput/components/AvailableBrands/AvailableBrands.scss @@ -1,16 +1,14 @@ -@import "../../../../../../style/index"; - .adyen-checkout__card__brands { display: flex; flex-basis: auto; flex-shrink: 1; flex-wrap: wrap; - gap: 4px; - height: 16px; + gap: var(--b-spacer-020); + height: var(--b-spacer-070); overflow: hidden; margin-top: -8px; - margin-bottom: 16px; - transition: all 0.2s ease-out; + margin-bottom: var(--b-spacer-070); + transition: all 0.3s ease-out; } .adyen-checkout__card__brands--hidden { @@ -20,25 +18,19 @@ } .adyen-checkout__card__brands img { - border-radius: 3px; - height: 16px; - width: 24px; + margin: 0; + padding: 0; + display: block; + width: 100%; + height: auto; } .adyen-checkout__card__brands__brand-wrapper { + overflow: hidden; display: inline-block; - height: 16px; - width: 24px; + height: var(--b-spacer-070); + width: var(--b-spacer-090); position: relative; + border-radius: var(--b-border-radius-s); } -.adyen-checkout__card__brands__brand-wrapper::after { - content: ""; - position: absolute; - top: 0; - width: 100%; - height: 100%; - left: 0; - border-radius: $border-radius-small; - border: 1px solid rgb(0 27 43 / 17%); -} diff --git a/packages/lib/src/components/Card/components/CardInput/components/BrandIcon.tsx b/packages/lib/src/components/Card/components/CardInput/components/BrandIcon.tsx index 10091dba20..602d4844a3 100644 --- a/packages/lib/src/components/Card/components/CardInput/components/BrandIcon.tsx +++ b/packages/lib/src/components/Card/components/CardInput/components/BrandIcon.tsx @@ -1,7 +1,6 @@ import { h } from 'preact'; import { getCardImageUrl, getFullBrandName } from '../utils'; import { BrandIconProps } from './types'; -import styles from '../CardInput.module.scss'; import useImage from '../../../../../core/Context/useImage'; export default function BrandIcon({ brand, brandsConfiguration = {} }: BrandIconProps) { @@ -14,7 +13,7 @@ export default function BrandIcon({ brand, brandsConfiguration = {} }: BrandIcon return ( {getFullBrandName(brand)} diff --git a/packages/lib/src/components/Card/components/CardInput/components/CardFields.tsx b/packages/lib/src/components/Card/components/CardInput/components/CardFields.tsx index 88ab1ed4f0..ec418b5a0c 100644 --- a/packages/lib/src/components/Card/components/CardInput/components/CardFields.tsx +++ b/packages/lib/src/components/Card/components/CardInput/components/CardFields.tsx @@ -5,7 +5,6 @@ import ExpirationDate from './ExpirationDate'; import useCoreContext from '../../../../../core/Context/useCoreContext'; import { CardFieldsProps } from './types'; import classNames from 'classnames'; -import styles from '../CardInput.module.scss'; import { BRAND_ICON_UI_EXCLUSION_LIST, DATE_POLICY_HIDDEN, @@ -30,17 +29,21 @@ export default function CardFields({ onFocusField, showBrandIcon, showBrandsUnderCardNumber, - valid + valid, + showContextualElement }: CardFieldsProps) { const { i18n } = useCoreContext(); const getError = (errors, fieldType) => { - const errorMessage = errors[fieldType] ? i18n.get(errors[fieldType]) : null; - return errorMessage; + return errors[fieldType] ? i18n.get(errors[fieldType]) : null; }; // A set of brands filtered to exclude those that can never appear in the UI const allowedBrands = brandsIcons?.filter(brandsIcons => !BRAND_ICON_UI_EXCLUSION_LIST?.includes(brandsIcons.name)); + const isAmex = brand === 'amex'; + const cvcContextualText = isAmex + ? i18n.get('creditCard.securityCode.contextualText.4digits') + : i18n.get('creditCard.securityCode.contextualText.3digits'); return (
@@ -50,7 +53,7 @@ export default function CardFields({ error={getError(errors, ENCRYPTED_CARD_NUMBER)} focused={focusedElement === ENCRYPTED_CARD_NUMBER} isValid={!!valid.encryptedCardNumber} - label={i18n.get('creditCard.numberField.title')} + label={i18n.get('creditCard.cardNumber.label')} onFocusField={onFocusField} filled={!!errors.encryptedCardNumber || !!valid.encryptedCardNumber} showBrandIcon={showBrandIcon} @@ -63,7 +66,7 @@ export default function CardFields({
{hasCVC && ( @@ -84,10 +89,12 @@ export default function CardFields({ cvcPolicy={cvcPolicy} isValid={!!valid.encryptedSecurityCode} filled={!!errors.encryptedSecurityCode || !!valid.encryptedSecurityCode} - label={i18n.get('creditCard.cvcField.title')} + label={i18n.get('creditCard.securityCode.label')} onFocusField={onFocusField} className={'adyen-checkout__field--50'} - frontCVC={brand === 'amex'} + frontCVC={isAmex} + showContextualElement={showContextualElement} + contextualText={cvcContextualText} /> )}
diff --git a/packages/lib/src/components/Card/components/CardInput/components/CardFieldsWrapper.tsx b/packages/lib/src/components/Card/components/CardInput/components/CardFieldsWrapper.tsx index 84904a73e6..ac8fcb80f7 100644 --- a/packages/lib/src/components/Card/components/CardInput/components/CardFieldsWrapper.tsx +++ b/packages/lib/src/components/Card/components/CardInput/components/CardFieldsWrapper.tsx @@ -62,6 +62,7 @@ export const CardFieldsWrapper = ({ // For CardFields > CardNumber showBrandIcon, showBrandsUnderCardNumber, + showContextualElement, // iOSFocusedField, disclaimerMessage @@ -86,6 +87,7 @@ export const CardFieldsWrapper = ({ )} @@ -159,6 +162,7 @@ export const CardFieldsWrapper = ({ specifications={partialAddressSchema} iOSFocusedField={iOSFocusedField} onAddressLookup={onAddressLookup} + showContextualElement={showContextualElement} /> )} diff --git a/packages/lib/src/components/Card/components/CardInput/components/CardHolderName.tsx b/packages/lib/src/components/Card/components/CardInput/components/CardHolderName.tsx index 114856ab96..277a650228 100644 --- a/packages/lib/src/components/Card/components/CardInput/components/CardHolderName.tsx +++ b/packages/lib/src/components/Card/components/CardInput/components/CardHolderName.tsx @@ -2,7 +2,6 @@ import { h } from 'preact'; import Field from '../../../../internal/FormFields/Field'; import useCoreContext from '../../../../../core/Context/useCoreContext'; import { CardHolderNameProps } from './types'; -import styles from '../CardInput.module.scss'; import InputText from '../../../../internal/FormFields/InputText'; export default function CardHolderName({ onBlur, onInput, placeholder, value, required, error = false, isValid, disabled }: CardHolderNameProps) { @@ -19,8 +18,8 @@ export default function CardHolderName({ onBlur, onInput, placeholder, value, re > diff --git a/packages/lib/src/components/Card/components/CardInput/components/CardNumber.test.tsx b/packages/lib/src/components/Card/components/CardInput/components/CardNumber.test.tsx index 3d1723fcd2..1c788b3ac9 100644 --- a/packages/lib/src/components/Card/components/CardInput/components/CardNumber.test.tsx +++ b/packages/lib/src/components/Card/components/CardInput/components/CardNumber.test.tsx @@ -21,7 +21,7 @@ const wrapper = mount( describe('CardNumber', () => { test('Renders a CardNumber field, with standard brand image, and no dual branding', () => { expect(wrapper.find('[data-cse="encryptedCardNumber"]')).toHaveLength(1); - expect(wrapper.find('.adyen-checkout__card__cardNumber__input .adyen-checkout__card__cardNumber__brandIcon')).toHaveLength(1); + expect(wrapper.find('.adyen-checkout__card__cardNumber__brandIcon')).toHaveLength(1); expect(wrapper.find('.adyen-checkout__card__dual-branding__buttons')).toHaveLength(0); }); diff --git a/packages/lib/src/components/Card/components/CardInput/components/CardNumber.tsx b/packages/lib/src/components/Card/components/CardInput/components/CardNumber.tsx index a1777535fb..f1649f245c 100644 --- a/packages/lib/src/components/Card/components/CardInput/components/CardNumber.tsx +++ b/packages/lib/src/components/Card/components/CardInput/components/CardNumber.tsx @@ -5,7 +5,6 @@ import DualBrandingIcon from './DualBrandingIcon/DualBrandingIcon'; import Field from '../../../../internal/FormFields/Field'; import useCoreContext from '../../../../../core/Context/useCoreContext'; import { CardNumberProps } from './types'; -import styles from '../CardInput.module.scss'; import DataSfSpan from './DataSfSpan'; import { ENCRYPTED_CARD_NUMBER } from '../../../../internal/SecuredFields/lib/configuration/constants'; import { alternativeLabelContent } from './IframeLabelAlternative'; @@ -27,7 +26,7 @@ export default function CardNumber(props: CardNumberProps) { name={ENCRYPTED_CARD_NUMBER} showValidIcon={false} i18n={i18n} - errorVisibleToScreenReader={false} // securedFields have their own, internal, aria-describedby element + contextVisibleToScreenReader={false} // securedFields have their own, internal, aria-describedby element useLabelElement={false} renderAlternativeToLabel={alternativeLabelContent} > @@ -37,15 +36,14 @@ export default function CardNumber(props: CardNumberProps) { 'adyen-checkout__input': true, 'adyen-checkout__input--large': true, 'adyen-checkout__card__cardNumber__input': true, - [styles['adyen-checkout__input']]: true, 'adyen-checkout__input--error': error, 'adyen-checkout__input--focus': props.focused, 'adyen-checkout__input--valid': isValid, 'adyen-checkout__card__cardNumber__input--noBrand': !props.showBrandIcon })} - > - {props.showBrandIcon && !dualBrandingElements && } - + > + + {props.showBrandIcon && !dualBrandingElements && } {dualBrandingElements && !error && (
diff --git a/packages/lib/src/components/Card/components/CardInput/components/Installments/Installments.tsx b/packages/lib/src/components/Card/components/CardInput/components/Installments/Installments.tsx index e797cbf873..6abb7eb9f8 100644 --- a/packages/lib/src/components/Card/components/CardInput/components/Installments/Installments.tsx +++ b/packages/lib/src/components/Card/components/CardInput/components/Installments/Installments.tsx @@ -5,7 +5,6 @@ import useCoreContext from '../../../../../../core/Context/useCoreContext'; import { InstallmentsItem, InstallmentsProps } from '../types'; import Fieldset from '../../../../../internal/FormFields/Fieldset/Fieldset'; import RadioGroup from '../../../../../internal/FormFields/RadioGroup'; -import styles from '../../CardInput.module.scss'; import Select from '../../../../../internal/FormFields/Select'; export interface InstallmentsObj { @@ -20,6 +19,7 @@ function Installments(props: InstallmentsProps) { const { i18n } = useCoreContext(); const { amount, brand, onChange, type } = props; const installmentOptions = props.installmentOptions[brand] || props.installmentOptions.card; + const readOnly = installmentOptions?.values?.length === 1; const [installmentAmount, setInstallmentAmount] = useState(installmentOptions?.preselectedValue || installmentOptions?.values[0]); const [radioBtnValue, setRadioBtnValue] = useState('onetime'); @@ -92,15 +92,11 @@ function Installments(props: InstallmentsProps) { />
diff --git a/packages/lib/src/components/Card/components/CardInput/components/KCPAuthentication.tsx b/packages/lib/src/components/Card/components/CardInput/components/KCPAuthentication.tsx index b412be8621..e99273d7bc 100644 --- a/packages/lib/src/components/Card/components/CardInput/components/KCPAuthentication.tsx +++ b/packages/lib/src/components/Card/components/CardInput/components/KCPAuthentication.tsx @@ -4,9 +4,9 @@ import classNames from 'classnames'; import Field from '../../../../internal/FormFields/Field'; import useCoreContext from '../../../../../core/Context/useCoreContext'; import { KCPProps } from './types'; -import styles from '../CardInput.module.scss'; import DataSfSpan from './DataSfSpan'; import InputTelephone from '../../../../internal/FormFields/InputTelephone'; +import { alternativeLabelContent } from './IframeLabelAlternative'; export default function KCPAuthentication(props: KCPProps) { const { i18n } = useCoreContext(); @@ -30,8 +30,7 @@ export default function KCPAuthentication(props: KCPProps) { > @@ -53,13 +53,14 @@ export default function KCPAuthentication(props: KCPProps) { isValid={props.encryptedPasswordState.valid} dir={'ltr'} name={'encryptedPassword'} + useLabelElement={false} + renderAlternativeToLabel={alternativeLabelContent} > { expect(screen.queryByText('Expiry date', { exact: false })).toBeTruthy(); // presence expect(screen.getByLabelText('Expiry date', { exact: true })).toBeTruthy(); // presence - expect(screen.getByLabelText('Expiry date', { exact: true })).toHaveAttribute('readonly', ''); // Look for cvc field elements expect(screen.getAllByText('Security code', { exact: true })).toBeTruthy(); diff --git a/packages/lib/src/components/Card/components/CardInput/components/StoredCardFields.tsx b/packages/lib/src/components/Card/components/CardInput/components/StoredCardFields.tsx index d7ed03835f..931e0eb6c5 100644 --- a/packages/lib/src/components/Card/components/CardInput/components/StoredCardFields.tsx +++ b/packages/lib/src/components/Card/components/CardInput/components/StoredCardFields.tsx @@ -16,17 +16,20 @@ export default function StoredCardFields({ focusedElement, lastFour, expiryMonth, - expiryYear + expiryYear, + showContextualElement }: StoredCardFieldsProps) { const { i18n } = useCoreContext(); const storedCardDescription = i18n.get('creditCard.storedCard.description.ariaLabel').replace('%@', lastFour); - const storedCardDescriptionSuffix = - expiryMonth && expiryYear ? ` ${i18n.get('creditCard.expiryDateField.title')} ${expiryMonth}/${expiryYear}` : ''; + const storedCardDescriptionSuffix = expiryMonth && expiryYear ? ` ${i18n.get('creditCard.expiryDate.label')} ${expiryMonth}/${expiryYear}` : ''; const ariaLabel = `${storedCardDescription}${storedCardDescriptionSuffix}`; + const isAmex = brand === 'amex'; + const cvcContextualText = isAmex + ? i18n.get('creditCard.securityCode.contextualText.4digits') + : i18n.get('creditCard.securityCode.contextualText.3digits'); const getError = (errors, fieldType) => { - const errorMessage = errors[fieldType] ? i18n.get(errors[fieldType]) : null; - return errorMessage; + return errors[fieldType] ? i18n.get(errors[fieldType]) : null; }; return ( @@ -34,7 +37,7 @@ export default function StoredCardFields({
{expiryMonth && expiryYear && ( @@ -58,11 +60,13 @@ export default function StoredCardFields({ focused={focusedElement === 'encryptedSecurityCode'} filled={!!valid.encryptedSecurityCode || !!errors.encryptedSecurityCode} isValid={!!valid.encryptedSecurityCode} - label={i18n.get('creditCard.cvcField.title')} + label={i18n.get('creditCard.securityCode.label')} onFocusField={onFocusField} {...(expiryMonth && expiryYear && { className: 'adyen-checkout__field--50' })} classNameModifiers={['storedCard']} - frontCVC={brand === 'amex'} + frontCVC={isAmex} + showContextualElement={showContextualElement} + contextualText={cvcContextualText} /> )}
diff --git a/packages/lib/src/components/Card/components/CardInput/components/StoredCardFieldsWrapper.tsx b/packages/lib/src/components/Card/components/CardInput/components/StoredCardFieldsWrapper.tsx index e2983ac708..eb5778e641 100644 --- a/packages/lib/src/components/Card/components/CardInput/components/StoredCardFieldsWrapper.tsx +++ b/packages/lib/src/components/Card/components/CardInput/components/StoredCardFieldsWrapper.tsx @@ -15,6 +15,7 @@ export const StoredCardFieldsWrapper = ({ hasInstallments, handleInstallments, showAmountsInInstallments, + showContextualElement, // props passed through from CardInput: amount, hasCVC, @@ -38,6 +39,7 @@ export const StoredCardFieldsWrapper = ({ lastFour={lastFour} expiryMonth={expiryMonth} expiryYear={expiryYear} + showContextualElement={showContextualElement} /> {hasInstallments && ( diff --git a/packages/lib/src/components/Card/components/CardInput/components/types.ts b/packages/lib/src/components/Card/components/CardInput/components/types.ts index 6b29e18b10..9c07525cf6 100644 --- a/packages/lib/src/components/Card/components/CardInput/components/types.ts +++ b/packages/lib/src/components/Card/components/CardInput/components/types.ts @@ -24,6 +24,7 @@ export interface CardFieldsProps { showBrandIcon?: boolean; showBrandsUnderCardNumber: boolean; valid?: any; + showContextualElement?: boolean; } export interface CardHolderNameProps { @@ -63,6 +64,8 @@ export interface CVCProps { isValid?: any; label?: any; onFocusField: (field: string) => void; + showContextualElement?: boolean; + contextualText?: string; } export interface CVCHintProps { @@ -88,6 +91,8 @@ export interface ExpirationDateProps { label?: string; onFocusField: (fieldName: string) => {}; expiryDatePolicy?: DatePolicyType; + showContextualElement?: boolean; + contextualText?: string; } export interface InstallmentsProps { @@ -126,11 +131,12 @@ export interface KCPProps { onFocusField: (str: string) => {}; onBlur: (event: Event) => void; onInput: (event: Event) => void; - taxNumber?: string; + // taxNumber?: string; error: boolean; isValid: boolean; value: string; disabled?: boolean; + placeholder?: string; } export type RtnType_ParamBooleanFn = (tn) => boolean; @@ -147,6 +153,8 @@ export interface StoredCardFieldsProps { lastFour?: string; onFocusField: any; valid: any; + status?: string; + showContextualElement?: boolean; } export interface SfSpanProps { diff --git a/packages/lib/src/components/Card/components/CardInput/defaultProps.ts b/packages/lib/src/components/Card/components/CardInput/defaultProps.ts index a1a45817c7..2d6b4f60ee 100644 --- a/packages/lib/src/components/Card/components/CardInput/defaultProps.ts +++ b/packages/lib/src/components/Card/components/CardInput/defaultProps.ts @@ -22,6 +22,7 @@ export default { autoFocus: true, isPayButtonPrimaryVariant: true, disableIOSArrowKeys: true, + showContextualElement: true, // Events onLoad: (): any => {}, diff --git a/packages/lib/src/components/Card/components/CardInput/defaultStyles.ts b/packages/lib/src/components/Card/components/CardInput/defaultStyles.ts index 7b81429129..925a636641 100644 --- a/packages/lib/src/components/Card/components/CardInput/defaultStyles.ts +++ b/packages/lib/src/components/Card/components/CardInput/defaultStyles.ts @@ -1,5 +1,3 @@ export default { - base: { - caretColor: '#0075FF' - } + base: {} }; diff --git a/packages/lib/src/components/Card/components/CardInput/types.ts b/packages/lib/src/components/Card/components/CardInput/types.ts index edb24ac695..21b26a944f 100644 --- a/packages/lib/src/components/Card/components/CardInput/types.ts +++ b/packages/lib/src/components/Card/components/CardInput/types.ts @@ -45,9 +45,16 @@ export interface CardInputDataState { taxNumber?: string; } -type Placeholders = { - holderName?: string; -}; +type PlaceholderKeys = + | 'holderName' + | 'cardNumber' + | 'expiryDate' + | 'expiryMonth' + | 'expiryYear' + | 'securityCodeThreeDigits' + | 'securityCodeFourDigits' + | 'password'; +export type Placeholders = Partial>; /** * Should be the subset of the props sent to CardInput that are *actually* used by CardInput @@ -119,6 +126,7 @@ export interface CardInputProps { showInstallmentAmounts?: boolean; showPayButton?: boolean; showWarnings?: boolean; + showContextualElement?: boolean; specifications?: Specifications; storedPaymentMethodId?: string; styles?: StylesObject; diff --git a/packages/lib/src/components/Card/components/CardInput/utils.ts b/packages/lib/src/components/Card/components/CardInput/utils.ts index e31754f41d..e917fc2429 100644 --- a/packages/lib/src/components/Card/components/CardInput/utils.ts +++ b/packages/lib/src/components/Card/components/CardInput/utils.ts @@ -126,6 +126,7 @@ export const extractPropsForCardFields = (props: CardInputProps) => { // Extract props for CardFields > CardNumber showBrandIcon: props.showBrandIcon, showBrandsUnderCardNumber: props.showBrandsUnderCardNumber, + showContextualElement: props.showContextualElement, // Extract props for StoredCardFields lastFour: props.lastFour, expiryMonth: props.expiryMonth, @@ -161,7 +162,9 @@ export const extractPropsForSFP = (props: CardInputProps) => { showWarnings: props.showWarnings, trimTrailingSeparator: props.trimTrailingSeparator, maskSecurityCode: props.maskSecurityCode, - resources: props.resources + resources: props.resources, + placeholders: props.placeholders, + showContextualElement: props.showContextualElement } as SFPProps; // Can't set as return type on fn or it will complain about missing, mandatory, props }; diff --git a/packages/lib/src/components/Card/types.ts b/packages/lib/src/components/Card/types.ts index c9043e6681..309e9fb957 100644 --- a/packages/lib/src/components/Card/types.ts +++ b/packages/lib/src/components/Card/types.ts @@ -12,6 +12,7 @@ import { } from '../internal/SecuredFields/lib/types'; import { CVCPolicyType, DatePolicyType } from '../internal/SecuredFields/lib/types'; import { ClickToPayConfiguration } from '../internal/ClickToPay/types'; +import { Placeholders } from './components/CardInput/types'; export interface CardElementProps extends UIElementProps { /** @@ -70,6 +71,12 @@ export interface CardElementProps extends UIElementProps { */ showFormInstruction?: boolean; + /** + * Show/hide the contextual text under each form field. The contextual text is to assist shoppers filling in the payment form. + * @defaultValue `true` + */ + showContextualElement?: boolean; + /** Show/hide the "store details" checkbox */ enableStoreDetails?: boolean; @@ -92,6 +99,8 @@ export interface CardElementProps extends UIElementProps { /** An object sent in the /paymentMethods response */ configuration?: CardConfiguration; + /** Configure placeholder text for holderName, cardNumber, expirationDate, securityCode and password. */ + placeholders?: Placeholders; /** * Called once all the card input fields have been created but are not yet ready to use. */ diff --git a/packages/lib/src/components/CashAppPay/components/CashAppComponent.scss b/packages/lib/src/components/CashAppPay/components/CashAppComponent.scss index 63aa14da61..15f8d81ffb 100644 --- a/packages/lib/src/components/CashAppPay/components/CashAppComponent.scss +++ b/packages/lib/src/components/CashAppPay/components/CashAppComponent.scss @@ -1,4 +1,4 @@ .adyen-checkout__cashapp > .adyen-checkout__store-details { margin-top: 0; - margin-bottom: 16px; + margin-bottom: var(--b-spacer-070); } \ No newline at end of file diff --git a/packages/lib/src/components/CashAppPay/components/CashAppComponent.tsx b/packages/lib/src/components/CashAppPay/components/CashAppComponent.tsx index bda4a9f6cd..fff61edfde 100644 --- a/packages/lib/src/components/CashAppPay/components/CashAppComponent.tsx +++ b/packages/lib/src/components/CashAppPay/components/CashAppComponent.tsx @@ -11,10 +11,15 @@ import './CashAppComponent.scss'; interface CashAppComponentProps { enableStoreDetails?: boolean; cashAppService: ICashAppService; + onClick(): void; + onChangeStoreDetails(data: any): void; + onAuthorize(payEventData: CashAppPayEventData): void; + onError(error: AdyenCheckoutError): void; + ref(ref: RefObject): void; } @@ -86,7 +91,7 @@ export function CashAppComponent({ }, [cashAppService, initializeCashAppSdk]); return ( -
+
{status === 'loading' && } {status !== 'loading' && enableStoreDetails && } diff --git a/packages/lib/src/components/Donation/Donation.scss b/packages/lib/src/components/Donation/Donation.scss index bb241ed43b..c571130b65 100644 --- a/packages/lib/src/components/Donation/Donation.scss +++ b/packages/lib/src/components/Donation/Donation.scss @@ -1,21 +1,21 @@ -@import '../../style/index'; +@use '../../style/index'; .adyen-checkout__adyen-giving { .adyen-checkout__status__icon { display: block; - margin: $spacing-xxxlarge auto $spacing-xlarge; + margin: var(--b-spacer-130) auto var(--b-spacer-100); } .adyen-checkout__status__text { - color: $color-black; - margin-bottom: $spacing-xxxlarge; + color: var(--b-color-label-primary); + margin-bottom: var(--b-spacer-130); text-align: center; } } .adyen-checkout__campaign { - border-radius: $border-radius-medium; - background: $color-black; + border-radius: var(--b-border-radius-m); + background: var(--b-color-label-primary); height: 227px; overflow: hidden; position: relative; @@ -30,17 +30,17 @@ } .adyen-checkout__campaign-logo { - border: 2px solid rgb(255 255 255 / 40%); - border-radius: $border-radius-small; + border: var(--b-spacer-010) solid rgb(255 255 255 / 40%); + border-radius: var(--b-border-radius-s); display: block; - height: 48px; - margin-bottom: $spacing-medium; + height: var(--b-spacer-120); + margin-bottom: var(--b-spacer-070); overflow: hidden; - width: 48px; + width: var(--b-spacer-120); } .adyen-checkout__campaign-background-image { - background-color: $color-black; + background-color: var(--b-color-label-primary); background-position: center; background-size: cover; height: 100%; @@ -69,36 +69,36 @@ .adyen-checkout__campaign-content { bottom: 0; - padding: $spacing-medium; + padding: var(--b-spacer-070); position: absolute; z-index: 2; } .adyen-checkout__campaign-title, .adyen-checkout__campaign-description { - color: $color-white; + color: var(--b-color-white); font-weight: normal; margin: 0; } .adyen-checkout__campaign-title { - font-size: $font-size-medium; - margin-bottom: $spacing-small; + font-size: index.$font-size-medium; + margin-bottom: var(--b-spacer-040); } .adyen-checkout__campaign-description { - font-size: $font-size-small; - line-height: 19px; + font-size: index.$font-size-small; + line-height: var(--b-text-caption-line-height); } .adyen-checkout__adyen-giving-actions { - margin-top: $spacing-medium; + margin-top: var(--b-spacer-070); text-align: center; } .adyen-checkout__button { &.adyen-checkout__button--donate { - margin: $spacing-medium auto $spacing-small; + margin: var(--b-spacer-070) auto var(--b-spacer-040); } &.adyen-checkout__button--decline { diff --git a/packages/lib/src/components/Dragonpay/components/DragonpayInput/DragonpayInput.tsx b/packages/lib/src/components/Dragonpay/components/DragonpayInput/DragonpayInput.tsx index fa73b13731..9a2f376eee 100644 --- a/packages/lib/src/components/Dragonpay/components/DragonpayInput/DragonpayInput.tsx +++ b/packages/lib/src/components/Dragonpay/components/DragonpayInput/DragonpayInput.tsx @@ -5,10 +5,11 @@ import Field from '../../../internal/FormFields/Field'; import getIssuerImageUrl from '../../../../utils/get-issuer-image'; import useCoreContext from '../../../../core/Context/useCoreContext'; import { DragonpayInputData, DragonpayInputIssuerItem, DragonpayInputProps } from '../../types'; -import { personalDetailsValidationRules } from '../../../internal/PersonalDetails/validate'; import InputEmail from '../../../internal/FormFields/InputEmail'; import Select from '../../../internal/FormFields/Select'; import useImage from '../../../../core/Context/useImage'; +import { validationRules } from '../../../../utils/Validator/defaultRules'; +import { getErrorMessage } from '../../../../utils/getErrorMessage'; export default function DragonpayInput(props: DragonpayInputProps) { const { i18n } = useCoreContext(); @@ -25,7 +26,7 @@ export default function DragonpayInput(props: DragonpayInputProps) { validate: issuer => isIssuerRequired() && !!issuer, modes: ['input', 'blur'] }, - shopperEmail: personalDetailsValidationRules.shopperEmail + shopperEmail: validationRules.emailRule } }); @@ -39,9 +40,9 @@ export default function DragonpayInput(props: DragonpayInputProps) { const getIssuerSelectFieldKey = type => { if (type === 'dragonpay_otc_non_banking') { - return 'dragonpay.voucher.non.bank.selectField.placeholder'; + return 'dragonpayVoucher.selectField.contextualText.nonBank'; } - return 'dragonpay.voucher.bank.selectField.placeholder'; + return 'dragonpayVoucher.selectField.contextualText.bank'; }; useEffect(() => { @@ -54,7 +55,11 @@ export default function DragonpayInput(props: DragonpayInputProps) { return (
- + { return ( - {altDescription} + {altDescription} ); }; diff --git a/packages/lib/src/components/Dropin/components/PaymentMethod/PaymentMethodItem.scss b/packages/lib/src/components/Dropin/components/PaymentMethod/PaymentMethodItem.scss index 1375bb8e27..d3b6ead911 100644 --- a/packages/lib/src/components/Dropin/components/PaymentMethod/PaymentMethodItem.scss +++ b/packages/lib/src/components/Dropin/components/PaymentMethod/PaymentMethodItem.scss @@ -1,9 +1,9 @@ -@import "../../../../style/index"; +@use "../../../../style/index"; .adyen-checkout__payment-method { position: relative; - background: $color-white; - border: 1px solid $color-gray-light; + background: var(--b-color-white); + border: var(--b-border-width-s) solid var(--b-color-separator-primary); cursor: pointer; margin-top: -1px; width: 100%; @@ -16,16 +16,16 @@ .adyen-checkout__payment-method:first-child, .adyen-checkout__payment-method--selected + .adyen-checkout__payment-method { - margin-top: 0; - border-top-left-radius: 12px; - border-top-right-radius: 12px; + margin-top: var(--b-spacer-000); + border-top-left-radius: var(--b-border-radius-m); + border-top-right-radius: var(--b-border-radius-m); } .adyen-checkout__payment-method:last-child, .adyen-checkout__payment-method--next-selected { - margin-bottom: 0; - border-bottom-left-radius: 12px; - border-bottom-right-radius: 12px; + margin-bottom: var(--b-spacer-000); + border-bottom-left-radius: var(--b-border-radius-m); + border-bottom-right-radius: var(--b-border-radius-m); } .adyen-checkout__payment-method--loading { @@ -49,29 +49,26 @@ .adyen-checkout__payment-method__header { align-items: center; - color: $color-black; + color: var(--b-color-label-primary); display: flex; flex-wrap: nowrap; justify-content: space-between; - font-weight: 400; - font-size: $font-size-medium; - - $payment-button-padding: $spacing-xsmall; - - padding: $spacing-medium - $payment-button-padding; - padding-left: $spacing-xxlarge - $payment-button-padding; - padding-right: $spacing-medium; + font-weight: var(--b-text-body-font-weight); + font-size: index.$font-size-medium; + padding: calc(var(--b-spacer-070) - var(--b-spacer-020)); + padding-left: calc(var(--b-spacer-120) - var(--b-spacer-020)); + padding-right: var(--b-spacer-070); position: relative; transition: background 0.1s ease-out; width: 100%; [dir="rtl"] & { - padding: $spacing-medium - $payment-button-padding; - padding-right: $spacing-xxlarge - $payment-button-padding; + padding: calc(var(--b-spacer-070) - var(--b-spacer-020)); + padding-right: calc(var(--b-spacer-120) - var(--b-spacer-020)); } .adyen-checkout__payment-method--standalone & { - padding: 16px; + padding: var(--b-spacer-070); } } @@ -79,35 +76,35 @@ display: flex; align-items: center; flex-shrink: 0; - margin-right: 16px; + margin-right: var(--b-spacer-070); max-width: 100%; // reset button styles border: none; background: none; cursor: pointer; - padding: $spacing-xsmall; - color: $color-black; + padding: var(--b-spacer-020); + color: var(--b-color-label-primary); font-size: 1em; - font-weight: 400; + font-weight: var(--b-text-body-font-weight); [dir="rtl"] & { - margin-right: 0; - margin-left: 16px; + margin-right: var(--b-spacer-000); + margin-left: var(--b-spacer-070); } } .adyen-checkout__payment-method__surcharge { - color: $color-gray-darker; - margin-left: 5px; + color: var(--b-color-outline-tertiary); + margin-left: var(--b-spacer-020); } .adyen-checkout__payment-method--selected { transition: margin 150ms cubic-bezier(0.4, 0, 0.2, 1) 0ms, opacity 0.3s ease-out; - background: $color-gray-lighter; - border: 1px solid $color-gray-light; - margin: 8px 0; - border-radius: $border-radius-large; + background: var(--b-color-interactive-secondary-hovered); + border: var(--b-border-width-s) solid var(--b-color-outline-secondary); + margin: var(--b-spacer-040) var(--b-spacer-000); + border-radius: var(--b-border-radius-m); cursor: default; } @@ -116,49 +113,49 @@ } .adyen-checkout__payment-method__details { - padding: 0 16px; + padding: var(--b-spacer-000) var(--b-spacer-070); position: relative; } .adyen-checkout__payment-method__details__content { - margin: 0 0 16px; + margin: var(--b-spacer-000) var(--b-spacer-000) var(--b-spacer-070); } .adyen-checkout__payment-method__image__wrapper { - height: 26px; - width: 40px; position: relative; + display: flex; + width: var(--b-spacer-110); + height: 26px; + justify-content: center; + align-items: center; + border-radius: var(--b-border-radius-s); + overflow: hidden; + box-shadow: 0 1px var(--b-spacer-010) 0 rgba(0 17 44 / 2%), 0 var(--b-spacer-010) var(--b-spacer-020) 0 rgba(0 17 44 / 4%); } -.adyen-checkout__payment-method__image__wrapper--outline::after { - content: ""; - position: absolute; - top: 0; - width: 100%; - height: 100%; - left: 0; - border-radius: $border-radius-small; - border: 1px solid rgb(0 27 43 / 17%); -} .adyen-checkout__payment-method__image { display: block; - border-radius: $border-radius-small; + border-radius: var(--b-border-radius-s); + width: 100%; + height: 100%; + overflow: hidden; + flex-shrink: 0; } .adyen-checkout__payment-method__brands { display: flex; flex-wrap: wrap; - margin: 4px 0; - height: 16px; + margin: var(--b-spacer-020) var(--b-spacer-000); + height: var(--b-spacer-070); flex-basis: auto; flex-shrink: 1; text-align: right; overflow: hidden; & .adyen-checkout__payment-method__brand-number { - color: $color-gray-darker; - font-size: 13px; + color: var(--b-color-outline-tertiary); + font-size: var(--b-text-body-font-size); } } @@ -170,23 +167,25 @@ .adyen-checkout__payment-method__brands .adyen-checkout__payment-method__image__wrapper { display: inline-block; - margin-right: 4px; - height: 16px; - width: 24px; + margin-right: var(--b-spacer-020); + height: var(--b-spacer-070); + width: var(--b-spacer-090); transition: opacity 0.2s ease-out; } .adyen-checkout__payment-method__brands .adyen-checkout__payment-method__image__wrapper:last-child { - margin: 0; + margin: var(--b-spacer-000); } .adyen-checkout__payment-method--selected .adyen-checkout__payment-method__brands .adyen-checkout__payment-method__image__wrapper { - margin-bottom: 4px; + margin-bottom: var(--b-spacer-020); } +/* todo: not working */ .adyen-checkout__payment-method__brands img { - width: 24px; - height: 16px; + width: 100%; + height: 100%; + display: block; } .adyen-checkout__payment-method__image__wrapper--disabled { @@ -196,16 +195,16 @@ /* Payment Method Radio Button */ .adyen-checkout__payment-method__radio { position: absolute; - background-color: $color-white; - border: 1px solid $color-gray-dark; + background-color: var(--b-color-white); + border: var(--b-border-width-s) solid var(--b-color-outline-secondary); border-radius: 50%; - height: 16px; - width: 16px; - left: 16px; + height: var(--b-spacer-070); + width: var(--b-spacer-070); + left: var(--b-spacer-070); transition: border-color 0.2s ease-out, box-shadow 0.2s ease-out; [dir="rtl"] & { - right: 16px; + right: var(--b-spacer-070); left: auto; } @@ -222,28 +221,28 @@ left: 0; right: 0; top: 50%; - height: 6px; - width: 6px; - background-color: $color-white; + height: var(--b-spacer-040); + width: var(--b-spacer-040); + background-color: var(--b-color-white); border-radius: 50%; transform: translateY(-50%) scale(0); transition: transform 0.3s ease-out; } .adyen-checkout__payment-method:hover:not(.adyen-checkout__payment-method--selected) .adyen-checkout__payment-method__radio { - border-color: #99a3ad; - box-shadow: 0 0 0 2px $color-gray; + border-color: var(--b-color-outline-secondary); + box-shadow: 0 0 0 var(--b-spacer-010) var(--b-color-outline-secondary); cursor: pointer; } .adyen-checkout__payment-method__radio--selected { - background-color: $color-primary; + background-color: var(--b-color-label-primary); border: 0; transition: all 0.3s ease-out; } .adyen-checkout__payment-method__radio--selected:hover { - box-shadow: 0 0 0 2px rgb(0 102 255 / 40%); + box-shadow: 0 0 0 var(--b-spacer-010) var(--b-color-grey-100)-transparent; } .adyen-checkout__payment-method__radio--selected::after { diff --git a/packages/lib/src/components/Dropin/components/PaymentMethod/PaymentMethodItem.tsx b/packages/lib/src/components/Dropin/components/PaymentMethod/PaymentMethodItem.tsx index aac90ff497..939ae241db 100644 --- a/packages/lib/src/components/Dropin/components/PaymentMethod/PaymentMethodItem.tsx +++ b/packages/lib/src/components/Dropin/components/PaymentMethod/PaymentMethodItem.tsx @@ -3,7 +3,6 @@ import classNames from 'classnames'; import PaymentMethodDetails from './PaymentMethodDetails'; import PaymentMethodIcon from './PaymentMethodIcon'; import DisableOneClickConfirmation from './DisableOneClickConfirmation'; -import styles from '../DropinComponent.module.scss'; import './PaymentMethodItem.scss'; import useCoreContext from '../../../../core/Context/useCoreContext'; import UIElement from '../../../UIElement'; @@ -73,16 +72,13 @@ class PaymentMethodItem extends Component { const paymentMethodClassnames = classNames({ 'adyen-checkout__payment-method': true, - [styles['adyen-checkout__payment-method']]: true, [`adyen-checkout__payment-method--${paymentMethod.props.type}`]: true, [`adyen-checkout__payment-method--${paymentMethod.props.fundingSource ?? 'credit'}`]: true, 'adyen-checkout__payment-method--selected': isSelected, - [styles['adyen-checkout__payment-method--selected']]: isSelected, 'adyen-checkout__payment-method--loading': isLoading, 'adyen-checkout__payment-method--disabling': isDisablingPaymentMethod, 'adyen-checkout__payment-method--confirming': this.state.showDisableStoredPaymentMethodConfirmation, 'adyen-checkout__payment-method--standalone': standalone, - [styles['adyen-checkout__payment-method--loading']]: isLoading, [paymentMethod._id]: true, [this.props.className]: true }); @@ -149,11 +145,7 @@ class PaymentMethodItem extends Component { )}
-
+
{showRemovePaymentMethodButton && ( { const { i18n } = useCoreContext(); const paymentMethodListClassnames = classNames({ - [styles['adyen-checkout__payment-methods-list']]: true, 'adyen-checkout__payment-methods-list': true, 'adyen-checkout__payment-methods-list--loading': isLoading }); diff --git a/packages/lib/src/components/Dropin/components/PaymentMethod/PaymentMethodName.scss b/packages/lib/src/components/Dropin/components/PaymentMethod/PaymentMethodName.scss index 5308420ead..f9ceb5df0d 100644 --- a/packages/lib/src/components/Dropin/components/PaymentMethod/PaymentMethodName.scss +++ b/packages/lib/src/components/Dropin/components/PaymentMethod/PaymentMethodName.scss @@ -1,4 +1,4 @@ -@import "../../../../style/index"; +@use "../../../../style/index"; .adyen-checkout__payment-method { &__name { @@ -8,12 +8,12 @@ } &__name--selected { - font-weight: 500; + font-weight: var(--b-text-body-stronger-font-weight); } &__additional-info { - font-size: $font-size-small; - color: $color-gray-darker; + font-size: index.$font-size-small; + color: var(--b-color-label-secondary); } &__name_wrapper { diff --git a/packages/lib/src/components/Dropin/components/status/Status.scss b/packages/lib/src/components/Dropin/components/status/Status.scss index cc8581df95..e480b76086 100644 --- a/packages/lib/src/components/Dropin/components/status/Status.scss +++ b/packages/lib/src/components/Dropin/components/status/Status.scss @@ -1,4 +1,4 @@ -@import "../../../../style/index"; +@use "../../../../style/index"; .adyen-checkout__status { display: flex; @@ -8,15 +8,15 @@ justify-content: center; height: 350px; margin: 0; - padding: 32px; - background-color: $color-white; - border-radius: $border-radius-medium; - border: 1px solid $color-gray; - font-size: $font-size-medium; - color: $color-black; + padding: var(--b-spacer-100); + background-color: var(--b-color-white); + border-radius: var(--b-border-radius-m); + border: var(--b-border-width-s) solid var(--b-color-outline-secondary); + font-size: index.$font-size-medium; + color: var(--b-color-label-primary); &__icon { - margin-bottom: 24px; + margin-bottom: var(--b-spacer-090); } .adyen-checkout__spinner__wrapper { diff --git a/packages/lib/src/components/Giftcard/components/GiftcardComponent.tsx b/packages/lib/src/components/Giftcard/components/GiftcardComponent.tsx index 1582107422..30c2710b13 100644 --- a/packages/lib/src/components/Giftcard/components/GiftcardComponent.tsx +++ b/packages/lib/src/components/Giftcard/components/GiftcardComponent.tsx @@ -6,7 +6,7 @@ import useCoreContext from '../../../core/Context/useCoreContext'; import { PaymentAmount } from '../../../types'; import { GIFT_CARD } from '../../internal/SecuredFields/lib/configuration/constants'; import { GiftCardFields } from './GiftcardFields'; -import { GiftcardFieldsProps } from './types'; +import { GiftcardFieldsProps, Placeholders } from './types'; interface GiftcardComponentProps { onChange: (state) => void; @@ -22,6 +22,7 @@ interface GiftcardComponentProps { pinRequired: boolean; expiryDateRequired?: boolean; fieldsLayoutComponent: FunctionComponent; + placeholders?: Placeholders; } class Giftcard extends Component { diff --git a/packages/lib/src/components/Giftcard/components/GiftcardNumberField.tsx b/packages/lib/src/components/Giftcard/components/GiftcardNumberField.tsx index a228922e42..f84eee6be8 100644 --- a/packages/lib/src/components/Giftcard/components/GiftcardNumberField.tsx +++ b/packages/lib/src/components/Giftcard/components/GiftcardNumberField.tsx @@ -3,18 +3,21 @@ import classNames from 'classnames'; import Field from '../../internal/FormFields/Field'; import { h } from 'preact'; import { GiftcardFieldProps } from './types'; +import { alternativeLabelContent } from '../../Card/components/CardInput/components/IframeLabelAlternative'; export const GiftcardNumberField = ({ i18n, classNameModifiers, sfpState, getCardErrorMessage, focusedElement, setFocusOn }: GiftcardFieldProps) => { return ( setFocusOn('encryptedCardNumber')} dir={'ltr'} name={'encryptedCardNumber'} - errorVisibleToScreenReader={false} + contextVisibleToScreenReader={false} + useLabelElement={false} + renderAlternativeToLabel={alternativeLabelContent} > { return ( setFocusOn('encryptedSecurityCode')} dir={'ltr'} name={'encryptedSecurityCode'} - errorVisibleToScreenReader={false} + contextVisibleToScreenReader={false} + useLabelElement={false} + renderAlternativeToLabel={alternativeLabelContent} > >; + export type GiftcardFieldsProps = { setRootNode: (input: HTMLElement) => void; i18n: Language; diff --git a/packages/lib/src/components/GooglePay/components/GooglePayButton.scss b/packages/lib/src/components/GooglePay/components/GooglePayButton.scss index 54575b90e7..ddbad46f3f 100644 --- a/packages/lib/src/components/GooglePay/components/GooglePayButton.scss +++ b/packages/lib/src/components/GooglePay/components/GooglePayButton.scss @@ -1,22 +1,23 @@ .adyen-checkout__paywithgoogle { - height: 48px; + height: var(--b-spacer-120); } .adyen-checkout__paywithgoogle > div > button { &, &.long, &.short { - height: 48px; + height: var(--b-spacer-120); transition: background-color 0.3s ease-out, box-shadow 0.3s ease-out; + border-radius: var(--b-border-radius-m); &:focus { - box-shadow: 0 0 0 2px #99c2ff; + box-shadow: 0 0 0 var(--b-spacer-010) #99c2ff; outline: 0; } } // Default button &.gpay-button { - padding: 15px 24px 13px; + padding: 15px var(--b-spacer-090) 13px; } } diff --git a/packages/lib/src/components/MBWay/MBWay.tsx b/packages/lib/src/components/MBWay/MBWay.tsx index efb070eb32..ae020cae9c 100644 --- a/packages/lib/src/components/MBWay/MBWay.tsx +++ b/packages/lib/src/components/MBWay/MBWay.tsx @@ -19,7 +19,7 @@ export class MBWayElement extends UIElement { phonePrefix: data.phonePrefix || '+351' // if not specified default to Portuguese country code }, placeholders: { - phoneNumber: placeholders.telephoneNumber || placeholders.phoneNumber || '932123456' + phoneNumber: placeholders.telephoneNumber || placeholders.phoneNumber } }; } diff --git a/packages/lib/src/components/MBWay/components/MBWayInput/MBWayInput.tsx b/packages/lib/src/components/MBWay/components/MBWayInput/MBWayInput.tsx index 8fd9b0367d..b194242772 100644 --- a/packages/lib/src/components/MBWay/components/MBWayInput/MBWayInput.tsx +++ b/packages/lib/src/components/MBWay/components/MBWayInput/MBWayInput.tsx @@ -3,9 +3,9 @@ import { useState, useRef } from 'preact/hooks'; import useCoreContext from '../../../../core/Context/useCoreContext'; import { MBWayInputProps } from './types'; import './MBWayInput.scss'; -import PhoneInput from '../../../internal/PhoneInputNew'; +import PhoneInput from '../../../internal/PhoneInput'; import LoadingWrapper from '../../../internal/LoadingWrapper'; -import usePhonePrefixes from '../../../internal/PhoneInputNew/usePhonePrefixes'; +import usePhonePrefixes from '../../../internal/PhoneInput/usePhonePrefixes'; function MBWayInput(props: MBWayInputProps) { const { i18n, loadingContext } = useCoreContext(); diff --git a/packages/lib/src/components/MealVoucherFR/components/MealVoucherExpiryField.tsx b/packages/lib/src/components/MealVoucherFR/components/MealVoucherExpiryField.tsx index 187231cea1..28dda18d69 100644 --- a/packages/lib/src/components/MealVoucherFR/components/MealVoucherExpiryField.tsx +++ b/packages/lib/src/components/MealVoucherFR/components/MealVoucherExpiryField.tsx @@ -1,35 +1,31 @@ import DataSfSpan from '../../Card/components/CardInput/components/DataSfSpan'; import classNames from 'classnames'; -import styles from '../../Card/components/CardInput/CardInput.module.scss'; import Field from '../../internal/FormFields/Field'; import { h } from 'preact'; import { GiftcardFieldProps } from '../../Giftcard/components/types'; +import { alternativeLabelContent } from '../../Card/components/CardInput/components/IframeLabelAlternative'; export const MealVoucherExpiryField = ({ i18n, sfpState, focusedElement, setFocusOn }: GiftcardFieldProps) => { return ( setFocusOn('encryptedExpiryDate')} dir={'ltr'} name={'encryptedExpiryDate'} - errorVisibleToScreenReader={false} + contextVisibleToScreenReader={false} + useLabelElement={false} + renderAlternativeToLabel={alternativeLabelContent} > ); diff --git a/packages/lib/src/components/MealVoucherFR/components/MealVoucherFields.tsx b/packages/lib/src/components/MealVoucherFR/components/MealVoucherFields.tsx index d7931c8afe..6f2efb813c 100644 --- a/packages/lib/src/components/MealVoucherFR/components/MealVoucherFields.tsx +++ b/packages/lib/src/components/MealVoucherFR/components/MealVoucherFields.tsx @@ -6,7 +6,7 @@ import { GiftcardNumberField } from '../../Giftcard/components/GiftcardNumberFie export const MealVoucherFields = (props: GiftcardFieldsProps) => { const { setRootNode } = props; - const pinFieldProps = { ...props, label: props.i18n.get('creditCard.cvcField.title') }; + const pinFieldProps = { ...props, label: props.i18n.get('creditCard.securityCode.label') }; return (
diff --git a/packages/lib/src/components/Oxxo/components/OxxoVoucherResult/OxxoVoucherResult.scss b/packages/lib/src/components/Oxxo/components/OxxoVoucherResult/OxxoVoucherResult.scss index 93640a62c2..1bd5578702 100644 --- a/packages/lib/src/components/Oxxo/components/OxxoVoucherResult/OxxoVoucherResult.scss +++ b/packages/lib/src/components/Oxxo/components/OxxoVoucherResult/OxxoVoucherResult.scss @@ -1,8 +1,8 @@ -@import "../../../../style/index"; +@use "../../../../style/index"; .adyen-checkout__voucher-result--oxxo .adyen-checkout__voucher-result__code { - font-size: $font-size-small; - line-height: 19px; + font-size: index.$font-size-small; + line-height: var(--b-text-caption-line-height); word-break: break-all; - padding: 24px; + padding: var(--b-spacer-090); } diff --git a/packages/lib/src/components/PayPal/Paypal.scss b/packages/lib/src/components/PayPal/Paypal.scss index 9b1551a6ef..be79b59f82 100644 --- a/packages/lib/src/components/PayPal/Paypal.scss +++ b/packages/lib/src/components/PayPal/Paypal.scss @@ -1,5 +1,3 @@ -@import "../../style/index"; - .adyen-checkout__paypal { &__buttons { position: relative; @@ -8,7 +6,7 @@ &__button { display: flex; - margin-bottom: $spacing-medium; + margin-bottom: var(--b-spacer-070); &:empty { display: none; @@ -17,7 +15,7 @@ &__status { &--pending { - margin: $spacing-medium 0; + margin: var(--b-spacer-070) 0; } &--processing { @@ -25,7 +23,7 @@ display: flex; font-size: 13px; justify-content: center; - padding: $spacing-large 0; + padding: var(--b-spacer-090) 0; } } } @@ -38,11 +36,11 @@ .adyen-checkout__payment-method { .adyen-checkout__paypal__status { &--pending { - margin: -$spacing-medium 0 38px; + margin: var(--b-spacer-070) 0 38px; } &--processing { - padding: 20px 0 65px; + padding: var(--b-spacer-080) 0 65px; } } } diff --git a/packages/lib/src/components/PayPal/components/PaypalComponent.tsx b/packages/lib/src/components/PayPal/components/PaypalComponent.tsx index 4926a8cb1c..9c7fb40c79 100644 --- a/packages/lib/src/components/PayPal/components/PaypalComponent.tsx +++ b/packages/lib/src/components/PayPal/components/PaypalComponent.tsx @@ -40,7 +40,7 @@ export default function PaypalComponent({ onApprove, onCancel, onChange, onError if (status === 'pending') { return ( -
+
diff --git a/packages/lib/src/components/QiwiWallet/QiwiWallet.tsx b/packages/lib/src/components/QiwiWallet/QiwiWallet.tsx index f560cd917c..1f5610accb 100644 --- a/packages/lib/src/components/QiwiWallet/QiwiWallet.tsx +++ b/packages/lib/src/components/QiwiWallet/QiwiWallet.tsx @@ -9,12 +9,19 @@ class QiwiWalletElement extends UIElement { public static type = 'qiwiwallet'; public static defaultProps = { + phoneNumberKey: 'mobileNumber', + phoneNumberErrorKey: 'error.va.gen.01', items: COUNTRIES.map(formatPrefixName).filter(item => item !== false), countryCode: COUNTRIES[0].code, prefixName: 'qiwiwallet.telephoneNumberPrefix' || COUNTRIES[0].id, phoneName: 'qiwiwallet.telephoneNumber' || '' }; + constructor(props) { + super(props); + this.componentRef = {}; + } + get isValid() { return !!this.state.isValid; } @@ -40,20 +47,30 @@ class QiwiWalletElement extends UIElement { }; } + public setComponentRef = ref => { + if (ref?.triggerValidation && !this.componentRef.showValidation) { + this.componentRef = { ...this.componentRef, showValidation: ref.triggerValidation }; + } + }; + render() { + const { i18n, loadingContext, showPayButton, items, selected, placeholders } = this.props; + return ( - + { - this.componentRef = ref; + this.setComponentRef(ref); }} - {...this.props} - {...this.state} - phoneLabel={'mobileNumber'} + phoneNumberKey={QiwiWalletElement.defaultProps.phoneNumberKey} + phoneNumberErrorKey={QiwiWalletElement.defaultProps.phoneNumberErrorKey} + placeholders={placeholders} + items={items} + data={{ phonePrefix: selected }} onChange={this.setState} - onSubmit={this.submit} - payButton={this.payButton} /> + + {showPayButton && this.payButton({})} ); } diff --git a/packages/lib/src/components/QiwiWallet/utils.test.ts b/packages/lib/src/components/QiwiWallet/utils.test.ts index d16849c18e..3dba58e85b 100644 --- a/packages/lib/src/components/QiwiWallet/utils.test.ts +++ b/packages/lib/src/components/QiwiWallet/utils.test.ts @@ -29,13 +29,12 @@ describe('formatPrefixName', () => { const item = { id: '+31', - code: 'NL', - name: 'Netherlands' + code: 'NL' }; test('Formats items and adjusts the name', () => { const returnedItem = formatPrefixName(item); - expect(returnedItem.name).toContain('Netherlands (+31)'); + expect(returnedItem.name).toContain('🇳🇱 +31 (NL)'); }); }); diff --git a/packages/lib/src/components/QiwiWallet/utils.ts b/packages/lib/src/components/QiwiWallet/utils.ts index f487f331bd..6a555d223f 100644 --- a/packages/lib/src/components/QiwiWallet/utils.ts +++ b/packages/lib/src/components/QiwiWallet/utils.ts @@ -1,3 +1,5 @@ +import { getFlagEmoji } from '../../utils/getFlagEmoji'; + /** * Formats and returns the passed items, adds flag string * @param item - prefix @@ -12,11 +14,12 @@ export const formatPrefixName = item => { return false; } - const flag = item.code.toUpperCase().replace(/./g, char => (String.fromCodePoint ? String.fromCodePoint(char.charCodeAt(0) + 127397) : '')); + const flag = getFlagEmoji(item.code); + return { ...item, - name: `${flag} ${item.name} (${item.id})`, - selectedOptionName: flag + name: `${flag} ${item.id} (${item.code})`, + selectedOptionName: `${flag} ${item.id}` }; }; diff --git a/packages/lib/src/components/SecuredFields/SecuredFieldsInput/SecuredFieldsInput.tsx b/packages/lib/src/components/SecuredFields/SecuredFieldsInput/SecuredFieldsInput.tsx index 18f7fba27d..5ce6b754f9 100644 --- a/packages/lib/src/components/SecuredFields/SecuredFieldsInput/SecuredFieldsInput.tsx +++ b/packages/lib/src/components/SecuredFields/SecuredFieldsInput/SecuredFieldsInput.tsx @@ -7,6 +7,7 @@ import { BinLookupResponse, CardBrandsConfiguration } from '../../Card/types'; import SFExtensions from '../../internal/SecuredFields/binLookup/extensions'; import { StylesObject } from '../../internal/SecuredFields/lib/types'; import { Resources } from '../../../core/Context/Resources'; +import { Placeholders } from '../../Card/components/CardInput/types'; interface SecuredFieldsProps { allowedDOMAccess?: boolean; @@ -33,6 +34,7 @@ interface SecuredFieldsProps { onFieldValid?: () => {}; onFocus?: (e) => {}; onLoad?: () => {}; + placeholders?: Placeholders; rootNode: HTMLElement; resources: Resources; showWarnings?: boolean; @@ -161,6 +163,7 @@ const extractPropsForSFP = (props: SecuredFieldsProps) => { styles: props.styles, trimTrailingSeparator: props.trimTrailingSeparator, type: props.type, - resources: props.resources + resources: props.resources, + placeholders: props.placeholders }; }; diff --git a/packages/lib/src/components/ThreeDS2/ThreeDS2.scss b/packages/lib/src/components/ThreeDS2/ThreeDS2.scss index e3ef3db347..b8538738c3 100644 --- a/packages/lib/src/components/ThreeDS2/ThreeDS2.scss +++ b/packages/lib/src/components/ThreeDS2/ThreeDS2.scss @@ -1,7 +1,6 @@ .adyen-checkout__threeds2__challenge-container, .adyen-checkout__threeds2__challenge { background-color: transparent; - box-sizing: border-box; display: block; position: relative; overflow: hidden; diff --git a/packages/lib/src/components/ThreeDS2/components/Challenge/challenge.scss b/packages/lib/src/components/ThreeDS2/components/Challenge/challenge.scss index 0fd9cc2fcf..83f1981dee 100644 --- a/packages/lib/src/components/ThreeDS2/components/Challenge/challenge.scss +++ b/packages/lib/src/components/ThreeDS2/components/Challenge/challenge.scss @@ -1,14 +1,12 @@ -@import "../../../../style/index"; - .adyen-checkout__threeds2-challenge-error { .adyen-checkout__status__icon { display: block; - margin: $spacing-xxxlarge auto $spacing-xlarge; + margin: var(--b-spacer-130) auto var(--b-spacer-100); } .adyen-checkout__status__text { - color: $color-red; - margin-bottom: $spacing-xxxlarge; + color: var(--b-color-label-critical); + margin-bottom: var(--b-spacer-130); text-align: center; } } diff --git a/packages/lib/src/components/Trustly/Trustly.scss b/packages/lib/src/components/Trustly/Trustly.scss index 4d74677fcb..62717e6a1d 100644 --- a/packages/lib/src/components/Trustly/Trustly.scss +++ b/packages/lib/src/components/Trustly/Trustly.scss @@ -1,19 +1,19 @@ -@import '../../style/index'; +@use '../../style/index'; .adyen-checkout-trustly { - margin-bottom: $spacing-medium; + margin-bottom: var(--b-spacer-070); &__descriptor { - margin: $spacing-none $spacing-none $spacing-xsmall; - font-size: $font-size-medium; - font-weight: 500; + margin: var(--b-spacer-000) var(--b-spacer-000) var(--b-spacer-020); + font-size: index.$font-size-medium; + font-weight: var(--b-text-body-stronger-font-weight); } &__description-list { list-style-type: disc; margin: 0; - font-size: $font-size-small; + font-size: index.$font-size-small; line-height: 1.5; - padding-left: 20px; + padding-left: var(--b-spacer-080); } } diff --git a/packages/lib/src/components/Trustly/Trustly.test.tsx b/packages/lib/src/components/Trustly/Trustly.test.tsx index d53d1e2897..47bf8d09c1 100644 --- a/packages/lib/src/components/Trustly/Trustly.test.tsx +++ b/packages/lib/src/components/Trustly/Trustly.test.tsx @@ -13,9 +13,9 @@ describe('TrustlyElement', () => { expect(await screen.findByText(/no cards, no app download, no registration/i)).toBeTruthy(); }); - test('should render redirect button by default', async () => { + test('should render redirect button if showPayButton is true', async () => { // @ts-ignore ignore - render(); + render(); expect(await screen.findByRole('button')).toHaveTextContent('Continue to trustly'); }); diff --git a/packages/lib/src/components/UIElement.scss b/packages/lib/src/components/UIElement.scss new file mode 100644 index 0000000000..822eba984d --- /dev/null +++ b/packages/lib/src/components/UIElement.scss @@ -0,0 +1,8 @@ +/* Shared css variables and styles are imported once here at the root level for all UI components. */ +@use '../style/index'; +@import '../style/design-tokens'; +@import '../style/link'; + +[class^='adyen-checkout'] { + @include index.adl-box-sizing(true); +} diff --git a/packages/lib/src/components/UIElement.tsx b/packages/lib/src/components/UIElement.tsx index a6b92c09f5..d436f8d979 100644 --- a/packages/lib/src/components/UIElement.tsx +++ b/packages/lib/src/components/UIElement.tsx @@ -11,6 +11,7 @@ import { hasOwnProperty } from '../utils/hasOwnProperty'; import DropinElement from './Dropin'; import { CoreOptions } from '../core/types'; import Core from '../core'; +import './UIElement.scss'; export class UIElement

extends BaseElement

implements IUIElement { protected componentRef: any; diff --git a/packages/lib/src/components/UPI/components/UPIComponent/UPIComponent.scss b/packages/lib/src/components/UPI/components/UPIComponent/UPIComponent.scss index 020decb940..e311267e3c 100644 --- a/packages/lib/src/components/UPI/components/UPIComponent/UPIComponent.scss +++ b/packages/lib/src/components/UPI/components/UPIComponent/UPIComponent.scss @@ -1,11 +1,11 @@ .adyen-checkout_upi-mode-selection-text { - font-weight: 400; + font-weight: var(--b-text-body-font-weight); font-size: 13px; - line-height: 19px; + line-height: var(--b-text-caption-line-height); margin-top: 0; margin-bottom: 7px; } .adyen-checkout__segmented-control--upi-margin-bottom { - margin-bottom: 16px; + margin-bottom: var(--b-spacer-070); } diff --git a/packages/lib/src/components/helpers/IssuerListContainer.tsx b/packages/lib/src/components/helpers/IssuerListContainer.tsx index 657db9df05..318e7d5dca 100644 --- a/packages/lib/src/components/helpers/IssuerListContainer.tsx +++ b/packages/lib/src/components/helpers/IssuerListContainer.tsx @@ -20,6 +20,7 @@ interface IssuerListContainerProps extends UIElementProps { showPaymentMethodItemImages?: boolean; showPayButton?: boolean; termsAndConditions?: TermsAndConditions; + showContextualElement?: boolean; } interface IssuerListData { @@ -115,6 +116,7 @@ class IssuerListContainer extends UIElement { onChange={this.setState} onSubmit={this.submit} payButton={this.payButton} + contextualText={this.props.i18n.get('issuerList.selectField.contextualText')} /> ) : ( diff --git a/packages/lib/src/components/internal/Address/Address.tsx b/packages/lib/src/components/internal/Address/Address.tsx index 2ccc87b449..adbc9124fd 100644 --- a/packages/lib/src/components/internal/Address/Address.tsx +++ b/packages/lib/src/components/internal/Address/Address.tsx @@ -18,7 +18,7 @@ import AddressSearch from './components/AddressSearch'; export default function Address(props: AddressProps) { const { i18n } = useCoreContext(); - const { label = '', requiredFields, visibility, iOSFocusedField = null } = props; + const { label = '', requiredFields, visibility, iOSFocusedField = null, showContextualElement } = props; /** An object by which to expose 'public' members to the parent UIElement */ const addressRef = useRef({}); @@ -176,6 +176,8 @@ export default function Address(props: AddressProps) { onManualAddress={onManualAddress} externalErrorMessage={searchErrorMessage} hideManualButton={showAddressFields} + showContextualElement={showContextualElement} + contextualText={i18n.get('address.search.contextualText')} /> )} {showAddressFields && ( diff --git a/packages/lib/src/components/internal/Address/components/AddressSearch.scss b/packages/lib/src/components/internal/Address/components/AddressSearch.scss index 912abca4c3..61d63a8578 100644 --- a/packages/lib/src/components/internal/Address/components/AddressSearch.scss +++ b/packages/lib/src/components/internal/Address/components/AddressSearch.scss @@ -1,13 +1,15 @@ .adyen-checkout__address-search { position: relative; + --magnifier-width: var(--b-spacer-070); + --magnifier-height: var(--b-spacer-070); .adyen-checkout__dropdown__button::after { position: absolute; - content: ""; - height: 16px; - left: 12px; - width: 16px; + content: ''; + left: var(--b-spacer-060); + height: var(--magnifier-height); + width: var(--magnifier-width); background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='none' viewBox='0 0 16 16'%3E%3Cpath fill='%23687282' d='M6.5 11.9a4.5 4.5 0 0 0 2.6-.83l2.77 2.74c.13.13.3.19.48.19.38 0 .65-.29.65-.66a.63.63 0 0 0-.19-.46l-2.75-2.73a4.4 4.4 0 0 0 .92-2.7 4.48 4.48 0 0 0-8.98 0 4.48 4.48 0 0 0 4.5 4.45Zm0-.96a3.53 3.53 0 0 1-3.53-3.49 3.52 3.52 0 0 1 7.04 0c0 1.9-1.59 3.49-3.52 3.49Z'/%3E%3C/svg%3E%0A"); background-repeat: no-repeat; background-position: center; @@ -19,7 +21,8 @@ } .adyen-checkout__filter-input { - padding-left: 24px; + position: relative; + padding-left: calc(var(--b-spacer-040) + var(--magnifier-width)); } } @@ -32,4 +35,4 @@ border: 0; padding: 0; } -} \ No newline at end of file +} diff --git a/packages/lib/src/components/internal/Address/components/AddressSearch.tsx b/packages/lib/src/components/internal/Address/components/AddressSearch.tsx index 6786f67cd6..c0aac26b77 100644 --- a/packages/lib/src/components/internal/Address/components/AddressSearch.tsx +++ b/packages/lib/src/components/internal/Address/components/AddressSearch.tsx @@ -21,9 +21,21 @@ interface AddressSearchProps { onManualAddress: any; externalErrorMessage: string; hideManualButton: boolean; + showContextualElement?: boolean; + contextualText?: string; + placeholder?: string; } -export default function AddressSearch({ onAddressLookup, onSelect, onManualAddress, externalErrorMessage, hideManualButton }: AddressSearchProps) { +export default function AddressSearch({ + onAddressLookup, + onSelect, + onManualAddress, + externalErrorMessage, + hideManualButton, + showContextualElement, + contextualText, + placeholder +}: AddressSearchProps) { const [formattedData, setFormattedData] = useState([]); const [originalData, setOriginalData] = useState([]); @@ -70,11 +82,18 @@ export default function AddressSearch({ onAddressLookup, onSelect, onManualAddre return (

- + + + { {card.isExpired && {i18n.get('ctp.cards.expiredCard')}}
- {errorMessage &&
{errorMessage}
} + {errorMessage &&
{errorMessage}
} ); }; diff --git a/packages/lib/src/components/internal/ClickToPay/components/CtPInfo/CtPInfoModal/CtPInfoModal.scss b/packages/lib/src/components/internal/ClickToPay/components/CtPInfo/CtPInfoModal/CtPInfoModal.scss index f38e61597e..e8722ccc70 100644 --- a/packages/lib/src/components/internal/ClickToPay/components/CtPInfo/CtPInfoModal/CtPInfoModal.scss +++ b/packages/lib/src/components/internal/ClickToPay/components/CtPInfo/CtPInfoModal/CtPInfoModal.scss @@ -6,25 +6,25 @@ &-title { font-weight: 700; - font-size: 20px; - line-height: 24px; + font-size: var(--b-spacer-080); + line-height: var(--b-spacer-090); padding: 0; - margin: 0 0 12px; + margin: 0 0 var(--b-spacer-060); } &-text { - font-weight: 400; + font-weight: var(--b-text-body-font-weight); font-size: 13px; - line-height: 19px; - margin-bottom: 16px; + line-height: var(--b-text-caption-line-height); + margin-bottom: var(--b-spacer-070); } &-benefits { margin-left: 0; - padding-left: 20px; + padding-left: var(--b-spacer-080); & li { - margin-bottom: 16px; + margin-bottom: var(--b-spacer-070); list-style: disc; } } @@ -38,5 +38,5 @@ // Classname modifier that align brands inside CTP modal .adyen_checkout-ctp__brand-wrapper--popup { justify-content: center; - margin-bottom: 24px; + margin-bottom: var(--b-spacer-090); } diff --git a/packages/lib/src/components/internal/ClickToPay/components/CtPLoader/CtPLoader.scss b/packages/lib/src/components/internal/ClickToPay/components/CtPLoader/CtPLoader.scss index 0e3ced8909..a0873df50c 100644 --- a/packages/lib/src/components/internal/ClickToPay/components/CtPLoader/CtPLoader.scss +++ b/packages/lib/src/components/internal/ClickToPay/components/CtPLoader/CtPLoader.scss @@ -5,8 +5,8 @@ } &-subtitle { - font-size: 16px; - line-height: 19px; + font-size: var(--b-spacer-070); + line-height: var(--b-text-caption-line-height); max-width: 280px; text-align: center; margin: 0 auto 58px; diff --git a/packages/lib/src/components/internal/ClickToPay/components/CtPLogin/CtPLogin.scss b/packages/lib/src/components/internal/ClickToPay/components/CtPLogin/CtPLogin.scss index 854ba0151c..d56e0b061f 100644 --- a/packages/lib/src/components/internal/ClickToPay/components/CtPLogin/CtPLogin.scss +++ b/packages/lib/src/components/internal/ClickToPay/components/CtPLogin/CtPLogin.scss @@ -1,5 +1,3 @@ -@import "../../../../../style/index"; - .adyen-checkout-ctp__section > .adyen-checkout__field.adyen-checkout__field--shopperLogin { - margin-bottom: 20px; + margin-bottom: var(--b-spacer-080); } diff --git a/packages/lib/src/components/internal/ClickToPay/components/CtPOneTimePassword/CtPOneTimePassword.scss b/packages/lib/src/components/internal/ClickToPay/components/CtPOneTimePassword/CtPOneTimePassword.scss index 0171a8ef30..79c2c8654d 100644 --- a/packages/lib/src/components/internal/ClickToPay/components/CtPOneTimePassword/CtPOneTimePassword.scss +++ b/packages/lib/src/components/internal/ClickToPay/components/CtPOneTimePassword/CtPOneTimePassword.scss @@ -1,6 +1,4 @@ -@import "../../../../../style/index"; - .adyen-checkout-ctp__otp-subtitle--highlighted { - color: $color-black; - font-weight: 500; + color: var(--b-color-label-primary); + font-weight: var(--b-text-body-stronger-font-weight); } diff --git a/packages/lib/src/components/internal/ClickToPay/components/CtPOneTimePassword/CtPOneTimePasswordInput/CtPOneTimePasswordInput.scss b/packages/lib/src/components/internal/ClickToPay/components/CtPOneTimePassword/CtPOneTimePasswordInput/CtPOneTimePasswordInput.scss index 922a07d3c0..127481b78a 100644 --- a/packages/lib/src/components/internal/ClickToPay/components/CtPOneTimePassword/CtPOneTimePasswordInput/CtPOneTimePasswordInput.scss +++ b/packages/lib/src/components/internal/ClickToPay/components/CtPOneTimePassword/CtPOneTimePasswordInput/CtPOneTimePasswordInput.scss @@ -1,20 +1,20 @@ -@import "../../../../../../style/index"; - .adyen-checkout-ctp__otp-resend-code { font-size: 13px; - font-weight: 400; - color: #0075ff; + font-weight: var(--b-text-body-font-weight); + color: var(--b-color-label-primary); margin-left: auto; cursor: pointer; + text-decoration: underline; + } .adyen-checkout-ctp__otp-resend-code--disabled, .adyen-checkout-ctp__otp-resend-code--confirmation { pointer-events: none; font-size: 13px; - font-weight: 400; + font-weight: var(--b-text-body-font-weight); margin-left: auto; - color: $color-gray-darker; + color: var(--b-color-grey-500); cursor: default; } @@ -23,13 +23,13 @@ align-items: center; > img { - margin-left: 4px; + margin-left: var(--b-spacer-020); } } .adyen-checkout-ctp__otp-resend-code-counter { font-size: 13px; - font-weight: 400; + font-weight: var(--b-text-body-font-weight); margin-left: auto; cursor: default; color: black; @@ -38,5 +38,5 @@ } .adyen-checkout-ctp__section > .adyen-checkout__field.adyen-checkout__field--otp { - margin-bottom: 20px; + margin-bottom: var(--b-spacer-080); } diff --git a/packages/lib/src/components/internal/ClickToPay/components/CtPSection/CtPLogoutLink.scss b/packages/lib/src/components/internal/ClickToPay/components/CtPSection/CtPLogoutLink.scss index 1f8100566d..8f7b136709 100644 --- a/packages/lib/src/components/internal/ClickToPay/components/CtPSection/CtPLogoutLink.scss +++ b/packages/lib/src/components/internal/ClickToPay/components/CtPSection/CtPLogoutLink.scss @@ -1,15 +1,14 @@ -@import "../../../../../style/index"; - .adyen-checkout-ctp__section-logout-button { font-size: 13px; - line-height: 19px; - font-weight: 400; - color: #0075ff; + line-height: var(--b-text-caption-line-height); + font-weight: var(--b-text-body-font-weight); + color: var(--b-color-label-primary); margin-left: auto; cursor: pointer; + text-decoration: underline; } .adyen-checkout-ctp__section-logout-button--disabled { pointer-events: none; - color: $color-gray-darker; + color: var(--b-color-grey-500); } diff --git a/packages/lib/src/components/internal/ClickToPay/components/CtPSection/CtPSection.scss b/packages/lib/src/components/internal/ClickToPay/components/CtPSection/CtPSection.scss index 24091c246f..4db7e7edbd 100644 --- a/packages/lib/src/components/internal/ClickToPay/components/CtPSection/CtPSection.scss +++ b/packages/lib/src/components/internal/ClickToPay/components/CtPSection/CtPSection.scss @@ -1,11 +1,9 @@ -@import "../../../../../style/index"; - .adyen-checkout-ctp__section { position: relative; background-color: white; - box-shadow: 0 8px 24px rgb(0 0 0 / 15%); - border-radius: 12px; - padding: 16px; + box-shadow: 0 var(--b-spacer-040) var(--b-spacer-090) rgb(0 0 0 / 15%); + border-radius: var(--b-spacer-060); + padding: var(--b-spacer-070); &-brand { display: flex; @@ -19,7 +17,7 @@ } .adyen-checkout__fieldset { - margin-bottom: 24px; + margin-bottom: var(--b-spacer-090); } } @@ -29,9 +27,9 @@ &-title { font-size: 17px; - font-weight: 600; + font-weight: var(--b-text-title-font-weight); line-height: 22px; - margin: 0 0 4px; + margin: 0 0 var(--b-spacer-020); padding: 0; width: auto; @@ -47,14 +45,14 @@ .adyen-checkout-ctp__section-text { font-size: 13px; - font-weight: 400; - line-height: 19px; - color: $color-gray-darker; - margin: 0 0 16px; + font-weight: var(--b-text-body-font-weight); + line-height: var(--b-text-caption-line-height); + color: var(--b-color-grey-500); + margin: 0 0 var(--b-spacer-070); } .adyen-checkout-ctp__separator { - color: $color-black; + color: var(--b-color-label-primary); font-size: 13px; - font-weight: 400; + font-weight: var(--b-text-body-font-weight); } diff --git a/packages/lib/src/components/internal/ContentSeparator/ContentSeparator.scss b/packages/lib/src/components/internal/ContentSeparator/ContentSeparator.scss index 459d104364..c77861ccc9 100644 --- a/packages/lib/src/components/internal/ContentSeparator/ContentSeparator.scss +++ b/packages/lib/src/components/internal/ContentSeparator/ContentSeparator.scss @@ -1,30 +1,26 @@ -@import "src/style/index"; - .adyen-checkout__content-separator { - margin-top: $spacing-medium; - margin-bottom: $spacing-medium; + margin-top: var(--b-spacer-070); + margin-bottom: var(--b-spacer-070); display: flex; justify-content: center; align-items: center; - color: $color-gray-darker; + color: var(--b-color-label-secondary); white-space: nowrap; font-size: 13px; - line-height: 19px; + line-height: var(--b-text-caption-line-height); &::after, &::before { - content: ""; - display: block; - background: $color-gray-light; - width: 100%; - height: 1px; + content: ''; + flex: 1; + border-bottom: 1px solid var(--b-color-grey-500); } &::after { - margin-left: 20px; + margin-left: var(--b-spacer-080); } &::before { - margin-right: 20px; + margin-right: var(--b-spacer-080); } } diff --git a/packages/lib/src/components/internal/DisclaimerMessage/DisclaimerMessage.scss b/packages/lib/src/components/internal/DisclaimerMessage/DisclaimerMessage.scss index 2a7877b7cb..4f25347648 100644 --- a/packages/lib/src/components/internal/DisclaimerMessage/DisclaimerMessage.scss +++ b/packages/lib/src/components/internal/DisclaimerMessage/DisclaimerMessage.scss @@ -1,12 +1,12 @@ -@import "../../../style/index"; +@use "../../../style/index"; .adyen-checkout-disclaimer__label { - margin-top: $spacing-medium; + margin-top: var(--b-spacer-070); padding-left: 0; display: inline-block; - line-height: 19px; - color: $color-gray-darker; - font-size: $font-size-small; + line-height: var(--b-text-caption-line-height); + color: var(--b-color-label-secondary); + font-size: index.$font-size-small; font-weight: normal; diff --git a/packages/lib/src/components/internal/DisclaimerMessage/DisclaimerMessage.tsx b/packages/lib/src/components/internal/DisclaimerMessage/DisclaimerMessage.tsx index 97bd2a715e..482d8a12df 100644 --- a/packages/lib/src/components/internal/DisclaimerMessage/DisclaimerMessage.tsx +++ b/packages/lib/src/components/internal/DisclaimerMessage/DisclaimerMessage.tsx @@ -19,7 +19,7 @@ function render(message: string, urls: Array) { {interpolateElement( message, urls.map(url => translation => ( - + {translation} )) diff --git a/packages/lib/src/components/internal/FormFields/Checkbox/Checkbox.scss b/packages/lib/src/components/internal/FormFields/Checkbox/Checkbox.scss index 6868c4d997..64935734a0 100644 --- a/packages/lib/src/components/internal/FormFields/Checkbox/Checkbox.scss +++ b/packages/lib/src/components/internal/FormFields/Checkbox/Checkbox.scss @@ -1,22 +1,24 @@ -@import "../../../../style/index"; +@use '../../../../style/index'; .adyen-checkout__checkbox { - display: block; + display: flex; + width: 100%; &__label { + flex: 1; position: relative; - padding-left: 24px; + padding-left: var(--b-spacer-090); cursor: pointer; display: inline-block; - line-height: 19px; - color: $color-black; - font-size: $font-size-small; + line-height: var(--b-text-caption-line-height); + color: var(--b-color-label-primary); + font-size: index.$font-size-small; font-weight: normal; user-select: none; - [dir="rtl"] & { + [dir='rtl'] & { padding-left: 0; - padding-right: 24px; + padding-right: var(--b-spacer-090); } } } @@ -33,67 +35,67 @@ } &::after { - border: 1px solid $color-primary; - background-color: $color-primary; + border: var(--b-border-width-s) solid var(--b-color-label-primary); + background-color: var(--b-color-label-primary); } } &:hover + .adyen-checkout__checkbox__label::after { - box-shadow: 0 0 0 2px rgb(0 102 255 / 40%); - border-color: $color-primary; + box-shadow: 0 0 0 var(--b-spacer-010) var(--b-color-outline-tertiary); + border-color: var(--b-color-label-primary); } } &:focus + .adyen-checkout__checkbox__label::after { - border: 1px solid $color-primary; - box-shadow: 0 0 0 2px $color-blue-light; + border: var(--b-border-width-s) solid var(--b-color-label-primary); + box-shadow: 0 0 0 var(--b-spacer-010) var(--b-color-outline-tertiary); } &:hover:not(:focus) + .adyen-checkout__checkbox__label::after { - border-color: #99a3ad; - box-shadow: 0 0 0 2px $color-gray; + border-color: var(--b-color-outline-tertiary); + box-shadow: 0 0 0 var(--b-spacer-010) var(--b-color-outline-secondary); } /* Check */ + .adyen-checkout__checkbox__label::before { border-left: 1px solid transparent; border-top: 1px solid transparent; - border-bottom: 2px solid $color-white; - border-right: 2px solid $color-white; - border-radius: 0 2px 1px; - content: ""; + border-bottom: var(--b-spacer-010) solid var(--b-color-white); + border-right: var(--b-spacer-010) solid var(--b-color-white); + border-radius: 0 var(--b-spacer-010) 1px; + content: ''; height: 11px; left: 1px; opacity: 0; position: absolute; - top: 2px; + top: var(--b-spacer-010); transform: rotateZ(37deg); transform-origin: 100% 100%; transition: opacity 0.2s ease-out; - width: 6px; + width: var(--b-spacer-030); z-index: 1; - [dir="rtl"] & { + [dir='rtl'] & { left: auto; - right: 8px; + right: var(--b-spacer-040); } } /* Box */ + .adyen-checkout__checkbox__label::after { - content: ""; + content: ''; position: absolute; top: 0; left: 0; - width: 16px; - height: 16px; - border-radius: $border-radius-small; - background-color: $color-white; - border: 1px solid $color-gray-dark; + width: var(--b-spacer-070); + height: var(--b-spacer-070); + border-radius: var(--b-border-radius-s); + background-color: var(--b-color-white); + border: var(--b-border-width-s) solid var(--b-color-grey-500); z-index: 0; transition: background 0.15s ease-out, border 0.05s ease-out, box-shadow 0.1s ease-out; - [dir="rtl"] & { + [dir='rtl'] & { left: auto; right: 0; } @@ -101,21 +103,33 @@ } .adyen-checkout__field--consentCheckbox { - background: $color-gray-light; - border: 1px solid $color-gray-light; - border-radius: $border-radius-medium; - padding: 14px 14px 13px; + background: var(--b-color-background-primary); + border: var(--b-border-width-s) solid var(--b-color-outline-secondary); + border-radius: var(--b-border-radius-m); + display: flex; + padding: var(--b-spacer-060) var(--b-spacer-070); + align-items: center; + + [dir='rtl'] & { + padding: var(--b-spacer-060) var(--b-spacer-070); + } + + .adyen-checkout__input-wrapper { + flex: 1; + justify-content: space-between; - [dir="rtl"] & { - padding: 14px 14px 13px; + @include index.adyen-checkout-input-wrapper-reset; } &.adyen-checkout__field--error { - border-color: $color-red; + border-color: var(--b-color-label-critical); } .adyen-checkout-input__inline-validation { - right: -5px; - top: 10px; + margin-right: 0; + + [dir='rtl'] & { + margin-left: 0; + } } } diff --git a/packages/lib/src/components/internal/FormFields/Checkbox/Checkbox.tsx b/packages/lib/src/components/internal/FormFields/Checkbox/Checkbox.tsx index df26bf0ab0..5a35a5d5da 100644 --- a/packages/lib/src/components/internal/FormFields/Checkbox/Checkbox.tsx +++ b/packages/lib/src/components/internal/FormFields/Checkbox/Checkbox.tsx @@ -1,7 +1,7 @@ import { ComponentChild, h } from 'preact'; import cx from 'classnames'; import './Checkbox.scss'; -import { ARIA_ERROR_SUFFIX } from '../../../../core/Errors/constants'; +import { ARIA_CONTEXT_SUFFIX, ARIA_ERROR_SUFFIX } from '../../../../core/Errors/constants'; interface CheckboxProps { checked?: boolean; @@ -14,19 +14,21 @@ interface CheckboxProps { className?: string; value?: string; uniqueId?: string; - addContextualElement?: boolean; + showErrorElement?: boolean; + showContextualElement?: boolean; } export default function Checkbox({ classNameModifiers = [], label, isInvalid, onChange, ...props }: CheckboxProps) { // Strip some values from props. We need to reference them but don't want to set them as attributes. - const { uniqueId: uid, addContextualElement: hasContextualElement, ...newProps } = props; + const { uniqueId: uid, showErrorElement, showContextualElement, ...newProps } = props; return (
// ); // }; - // + // ); }, [] @@ -195,7 +213,8 @@ const Field: FunctionalComponent = props => { classNameModifiers.map(m => `adyen-checkout__field--${m}`), { 'adyen-checkout__field--error': errorMessage, - 'adyen-checkout__field--valid': isValid + 'adyen-checkout__field--valid': isValid, + 'adyen-checkout__field--inactive': readOnly || disabled } )} > @@ -207,7 +226,7 @@ const Field: FunctionalComponent = props => { focused={focused} useLabelElement={useLabelElement} uniqueId={uniqueId.current} - isSecuredField={!errorVisibleToSR} + isSecuredField={!contextVisibleToSR} renderAlternativeToLabel={renderAlternativeToLabel} > {renderLabelOrAlternativeContents()} @@ -222,7 +241,8 @@ Field.defaultProps = { classNameModifiers: [], inputWrapperModifiers: [], useLabelElement: true, - addContextualElement: true, + showErrorElement: true, + showContextualElement: true, renderAlternativeToLabel: () => null }; diff --git a/packages/lib/src/components/internal/FormFields/Field/types.ts b/packages/lib/src/components/internal/FormFields/Field/types.ts index a43a2de9f3..7fa5504c78 100644 --- a/packages/lib/src/components/internal/FormFields/Field/types.ts +++ b/packages/lib/src/components/internal/FormFields/Field/types.ts @@ -7,7 +7,11 @@ export interface FieldProps { classNameModifiers?: string[]; children?: ComponentChildren; disabled?: boolean; + readOnly?: boolean; + showErrorElement?: boolean; errorMessage?: string | boolean; + showContextualElement?: boolean; + contextualText?: string; filled?: boolean; focused?: boolean; helper?: string; @@ -23,9 +27,8 @@ export interface FieldProps { dir?; showValidIcon?: boolean; useLabelElement?: boolean; - addContextualElement?: boolean; i18n?: Language; - errorVisibleToScreenReader?: boolean; + contextVisibleToScreenReader?: boolean; renderAlternativeToLabel?: (defaultWrapperProps, children, uniqueId) => any; } diff --git a/packages/lib/src/components/internal/FormFields/Fieldset/Fieldset.scss b/packages/lib/src/components/internal/FormFields/Fieldset/Fieldset.scss index 2fb76f99a4..5595f89781 100644 --- a/packages/lib/src/components/internal/FormFields/Fieldset/Fieldset.scss +++ b/packages/lib/src/components/internal/FormFields/Fieldset/Fieldset.scss @@ -1,8 +1,8 @@ -@import "../../../../style/index"; +@use "../../../../style/index"; .adyen-checkout__fieldset { display: block; - padding-bottom: 8px; + padding-bottom: var(--b-spacer-040); width: 100%; margin-inline-start: 0; margin-inline-end: 0; @@ -19,17 +19,17 @@ } .adyen-checkout__fieldset + .adyen-checkout__fieldset { - margin-top: 16px; + margin-top: var(--b-spacer-070); } .adyen-checkout__fieldset__title { - color: $color-gray-darker; + color: var(--b-color-grey-500); display: block; - font-size: $font-size-xxsmall; + font-size: index.$font-size-xxsmall; font-weight: bold; letter-spacing: 1px; margin: 0; - padding: 0 0 12px; + padding: 0 0 var(--b-spacer-060); text-transform: uppercase; } @@ -42,7 +42,7 @@ } .adyen-checkout__field-group:last-of-type .adyen-checkout__field { - @include screen-s-and-up { + @include index.screen-s-and-up { margin-bottom: 0; } @@ -56,8 +56,8 @@ } .adyen-checkout__fieldset--readonly .adyen-checkout__fieldset__fields { - color: $color-black; - font-size: $font-size-small; - line-height: 19px; + color: var(--b-color-label-primary); + font-size: index.$font-size-small; + line-height: var(--b-text-caption-line-height); margin: 0; } diff --git a/packages/lib/src/components/internal/FormFields/Fieldset/Fieldset.tsx b/packages/lib/src/components/internal/FormFields/Fieldset/Fieldset.tsx index a399458b9a..a13216504e 100644 --- a/packages/lib/src/components/internal/FormFields/Fieldset/Fieldset.tsx +++ b/packages/lib/src/components/internal/FormFields/Fieldset/Fieldset.tsx @@ -6,7 +6,7 @@ import './Fieldset.scss'; interface FieldsetProps { children: ComponentChildren; classNameModifiers: string[]; - label: string; + label?: string; readonly?: boolean; } diff --git a/packages/lib/src/components/internal/FormFields/FormFields.scss b/packages/lib/src/components/internal/FormFields/FormFields.scss index 456dcbc0a2..a20e72ef07 100644 --- a/packages/lib/src/components/internal/FormFields/FormFields.scss +++ b/packages/lib/src/components/internal/FormFields/FormFields.scss @@ -1,4 +1,30 @@ -@import "../../../style/index"; +@use '../../../style/index'; + +// Workaround to remove autofill background and text color, works on most browsers except for Chrome mobile +.adyen-checkout__filter-input:-webkit-autofill, +.adyen-checkout__filter-input:-webkit-autofill:active, +.adyen-checkout__filter-input:-webkit-autofill:hover, +.adyen-checkout__filter-input:-webkit-autofill:focus, +.adyen-checkout__filter-input:autofill, +.adyen-checkout__input:-webkit-autofill, +.adyen-checkout__input:-webkit-autofill:active, +.adyen-checkout__input:-webkit-autofill:hover, +.adyen-checkout__input:-webkit-autofill:focus, +.adyen-checkout__input:autofill { + color: var(--b-input-field-input-color) !important; + -webkit-text-fill-color: var(--b-input-field-input-color) !important; + box-shadow: 0 0 0 1000px var(--b-color-background-primary) inset !important; + background-clip: text !important; + background-color: var(--b-input-field-input-color) !important; + transition: none !important; +} + +// Additional workaround for Chrome mobile to remove autofill background and text color +.adyen-checkout__input, +.adyen-checkout__filter-input { + color: var(--b-input-field-input-color) !important; + background-clip: text !important; +} .adyen-checkout__field-wrapper { display: flex; @@ -6,80 +32,80 @@ } .adyen-checkout__field--20 { - @include screen-s-and-up { + @include index.screen-s-and-up { width: 20%; } } .adyen-checkout__field--30 { - @include screen-s-and-up { + @include index.screen-s-and-up { width: 30%; } } .adyen-checkout__field--40 { - @include screen-s-and-up { + @include index.screen-s-and-up { width: 40%; } } .adyen-checkout__field--50 { - @include screen-s-and-up { + @include index.screen-s-and-up { width: 50%; } } .adyen-checkout__field--60 { - @include screen-s-and-up { + @include index.screen-s-and-up { width: 60%; } } .adyen-checkout__field--70 { - @include screen-s-and-up { + @include index.screen-s-and-up { width: 70%; } } .adyen-checkout__field--80 { - @include screen-s-and-up { + @include index.screen-s-and-up { width: 80%; } } .adyen-checkout__field--col-70 { - @include screen-s-and-up { - width: calc(70% - 8px); + @include index.screen-s-and-up { + width: calc(70% - var(--b-spacer-040)); } } .adyen-checkout__field--col-30 { - @include screen-s-and-up { - width: calc(30% - 8px); + @include index.screen-s-and-up { + width: calc(30% - var(--b-spacer-040)); } } .adyen-checkout__field--col-50 { - @include screen-s-and-up { - width: calc(50% - 8px); + @include index.screen-s-and-up { + width: calc(50% - var(--b-spacer-040)); } } .adyen-checkout__field-wrapper > .adyen-checkout__field:first-child { - margin-right: 8px; + margin-right: var(--b-spacer-040); - [dir="rtl"] & { + [dir='rtl'] & { margin-right: 0; - margin-left: 8px; + margin-left: var(--b-spacer-040); } } .adyen-checkout__field-wrapper > .adyen-checkout__field:nth-child(2) { - margin-left: 8px; + margin-left: var(--b-spacer-040); - [dir="rtl"] & { + [dir='rtl'] & { margin-left: 0; - margin-right: 8px; + margin-right: var(--b-spacer-040); } } @@ -88,111 +114,53 @@ } .adyen-checkout__input { - box-sizing: border-box; - color: $color-black; - caret-color: $color-blue; - font-size: $font-size-medium; + flex: 1; + + // prevent overflow + min-width: 0; + color: var(--b-input-field-input-color); + font-size: index.$font-size-medium; font-family: inherit; display: block; - height: 40px; - background: $color-white; - border: 1px solid $color-gray-dark; - border-radius: $border-radius-medium; - padding: 5px 8px; - position: relative; + height: var(--b-spacer-110); + padding-left: var(--b-spacer-060); + background: inherit; outline: none; - width: 100%; transition: border 0.2s ease-out, box-shadow 0.2s ease-out; -} -.adyen-checkout__input:hover { - border-color: #99a3ad; -} - -.adyen-checkout__input:required { - box-shadow: none; -} - -.adyen-checkout__input[readonly], -.adyen-checkout__input--disabled { - background: $color-disabled; - border-color: $color-disabled; -} - -.adyen-checkout__input--disabled:hover { - border-color: $color-disabled; -} + &:required { + box-shadow: none; + } -.adyen-checkout__input-wrapper { - position: relative; - display: block; + &:disabled, + &:read-only { + color: var(--b-color-label-secondary) !important; + cursor: default; + } } -.adyen-checkout__input-wrapper--block { - display: block; +span.adyen-checkout__input { + overflow: hidden; } .adyen-checkout-input__inline-validation { - position: absolute; - width: 16px; - height: 16px; - top: 50%; - transform: translateY(-50%); - right: 14px; - - [dir="rtl"] & { - right: auto; - left: 14px; - } + width: var(--b-spacer-070); + height: var(--b-spacer-070); + margin-right: var(--b-spacer-060); - [dir="ltr"] & { - right: 14px; - left: auto; + [dir='rtl'] & { + margin-left: var(--b-spacer-060); } } .adyen-checkout-input__inline-validation--valid { - color: $color-success; + color: var(--b-color-label-success); } .adyen-checkout-input__inline-validation--invalid { - color: $color-alert; -} - -.adyen-checkout__input--valid { - border-bottom-color: $color-success; -} - -.adyen-checkout__input--error, -.adyen-checkout__input--invalid, -.adyen-checkout__input--error:hover, -.adyen-checkout__input--invalid:hover { - border-color: $color-alert; -} - -.adyen-checkout__input::placeholder { - color: $color-new-gray-darker; - font-weight: 200; + color: var(--b-color-label-critical); } .adyen-checkout__input--date { - padding-right: 30px; -} - -.adyen-checkout__input:active, -.adyen-checkout__input:focus, -.adyen-checkout__input--focus, -.adyen-checkout__input:active:hover, -.adyen-checkout__input:focus:hover, -.adyen-checkout__input--focus:hover { - border: 1px solid $color-primary; - box-shadow: 0 0 0 2px $color-blue-light; -} - -.adyen-checkout__input[readonly], -.adyen-checkout__input[readonly]:hover { - background-color: $color-gray-light; - border-color: transparent; - color: $color-gray-darker; - cursor: default; + padding: 0.5em; } diff --git a/packages/lib/src/components/internal/FormFields/InputBase.tsx b/packages/lib/src/components/internal/FormFields/InputBase.tsx index 105196363b..4a4726c99d 100644 --- a/packages/lib/src/components/internal/FormFields/InputBase.tsx +++ b/packages/lib/src/components/internal/FormFields/InputBase.tsx @@ -1,7 +1,7 @@ import { h } from 'preact'; import { MutableRef, useCallback, useEffect, useRef } from 'preact/hooks'; import classNames from 'classnames'; -import { ARIA_ERROR_SUFFIX } from '../../../core/Errors/constants'; +import { ARIA_CONTEXT_SUFFIX, ARIA_ERROR_SUFFIX } from '../../../core/Errors/constants'; import Language from '../../../language'; import './FormFields.scss'; @@ -109,7 +109,7 @@ export default function InputBase({ onCreateRef, ...props }: InputBaseProps) { readOnly={readonly} spellCheck={spellCheck} autoCorrect={autoCorrect} - aria-describedby={`${uniqueId}${ARIA_ERROR_SUFFIX}`} + aria-describedby={`${uniqueId}${isInvalid ? ARIA_ERROR_SUFFIX : ARIA_CONTEXT_SUFFIX}`} aria-invalid={isInvalid} onInput={handleInput} onBlur={handleBlur} diff --git a/packages/lib/src/components/internal/FormFields/RadioGroup/RadioGroup.scss b/packages/lib/src/components/internal/FormFields/RadioGroup/RadioGroup.scss index edb5c07c27..fb38fa44b9 100644 --- a/packages/lib/src/components/internal/FormFields/RadioGroup/RadioGroup.scss +++ b/packages/lib/src/components/internal/FormFields/RadioGroup/RadioGroup.scss @@ -1,4 +1,4 @@ -@import "../../../../style/index"; +@use "../../../../style/index"; .adyen-checkout__radio_group + .adyen-checkout-input__inline-validation { display: none; @@ -11,13 +11,13 @@ .adyen-checkout__radio_group__label { padding-bottom: 0; - padding-left: 24px; + padding-left: var(--b-spacer-090); position: relative; display: block; color: inherit; - font-size: $font-size-small; + font-size: index.$font-size-small; font-weight: normal; - line-height: 16px; + line-height: var(--b-spacer-070); overflow: visible; } @@ -28,11 +28,11 @@ .adyen-checkout__radio_group__label::before { content: ""; position: absolute; - background-color: $color-white; - border: 1px solid $color-gray-dark; + background-color: var(--b-color-white); + border: var(--b-border-width-s) solid var(--b-color-grey-500); border-radius: 50%; - height: 16px; - width: 16px; + height: var(--b-spacer-070); + width: var(--b-spacer-070); left: 0; top: 0; transition: border-color 0.2s ease-out, box-shadow 0.2s ease-out; @@ -40,7 +40,7 @@ .adyen-checkout__radio_group__label:hover::before { border-color: #99a3ad; - box-shadow: 0 0 0 2px $color-gray; + box-shadow: 0 0 0 var(--b-spacer-010) var(--b-color-outline-secondary); cursor: pointer; } @@ -51,9 +51,9 @@ margin: 0 auto; left: 5px; top: 5px; - height: 6px; - width: 6px; - background-color: $color-white; + height: var(--b-spacer-030); + width: var(--b-spacer-030); + background-color: var(--b-color-white); border-radius: 50%; transform: scale(0); transition: transform 0.2s ease-out; @@ -61,13 +61,13 @@ } .adyen-checkout__radio_group__label:hover { - border-color: $color-primary; + border-color: var(--b-color-label-primary); cursor: pointer; } .adyen-checkout__radio_group__input:checked + .adyen-checkout__radio_group__label::before, .adyen-checkout__radio_group__label--selected { - background-color: $color-primary; + background-color: var(--b-color-label-primary); border: 0; transition: all 0.2s ease-out; } @@ -77,16 +77,16 @@ } .adyen-checkout__radio_group__input:focus + .adyen-checkout__radio_group__label::before { - border-color: $color-primary; - box-shadow: 0 0 0 2px rgb(0 102 255 / 40%); + border-color: var(--b-color-label-primary); + box-shadow: 0 0 0 var(--b-spacer-010) var(--b-color-outline-tertiary); } .adyen-checkout__radio_group__input:checked + .adyen-checkout__radio_group__label:hover::before, .adyen-checkout__radio_group__input:checked:focus + .adyen-checkout__radio_group__label::before, .adyen-checkout__radio_group__input:checked:active + .adyen-checkout__radio_group__label::before { - box-shadow: 0 0 0 2px rgb(0 102 255 / 40%); + box-shadow: 0 0 0 var(--b-spacer-010) var(--b-color-outline-tertiary); } .adyen-checkout__radio_group__label.adyen-checkout__radio_group__label--invalid::before { - border: 1px solid $color-alert; + border: var(--b-border-width-s) solid var(--b-color-label-critical); } diff --git a/packages/lib/src/components/internal/FormFields/Select/Select.module.scss b/packages/lib/src/components/internal/FormFields/Select/Select.module.scss deleted file mode 100644 index 9b48520b54..0000000000 --- a/packages/lib/src/components/internal/FormFields/Select/Select.module.scss +++ /dev/null @@ -1,82 +0,0 @@ -@import "../../../../style/index"; - -.adyen-checkout__dropdown { - position: relative; -} - -.adyen-checkout__dropdown__button { - display: flex; - align-items: center; - cursor: pointer; -} - -.adyen-checkout__dropdown__button::after { - position: absolute; - content: ""; - height: 6px; - right: 16px; - width: 8px; - background-image: url("data:image/svg+xml,%3Csvg width='8' height='7' viewBox='0 0 8 7' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M3.19471 6.5646C3.59429 7.09797 4.39396 7.0986 4.79439 6.56587L7.78716 2.58424C8.28257 1.92514 7.81232 0.983398 6.98779 0.983398L1.01209 0.983398C0.188292 0.983398 -0.282154 1.92367 0.211778 2.58298L3.19471 6.5646Z' fill='%23687282'/%3E%3C/svg%3E%0A"); - background-repeat: no-repeat; - background-position: center; - - [dir="rtl"] & { - left: 16px; - right: auto; - } -} - -.adyen-checkout__dropdown__button--active::after { - transform: rotate(180deg); -} - -.adyen-checkout__filter-input { - background: $color-white; - border: 0; - caret-color: $color-blue; - color: $color-black; - font-family: inherit; - font-size: $font-size-medium; - height: 100%; - padding: 0; - width: 100%; - - &::placeholder { - color: $color-gray-dark; - font-weight: 200; - } - - &:focus, - &:active { - outline: 0; - } - - &[readonly] { - background: $color-gray-light; - border-color: transparent; - color: $color-black; - cursor: not-allowed; - } -} - -.adyen-checkout__dropdown__list { - position: absolute; - width: 100%; - background: $color-white; - list-style: none; - padding: 0; - margin: 0; - z-index: 1; - margin-bottom: 50px; - overflow-y: auto; - display: none; -} - -.adyen-checkout__dropdown__list.adyen-checkout__dropdown__list--active { - display: block; -} - -.adyen-checkout__dropdown__element { - display: flex; - align-items: center; -} diff --git a/packages/lib/src/components/internal/FormFields/Select/Select.scss b/packages/lib/src/components/internal/FormFields/Select/Select.scss index b55b343c87..3f4bca1a07 100644 --- a/packages/lib/src/components/internal/FormFields/Select/Select.scss +++ b/packages/lib/src/components/internal/FormFields/Select/Select.scss @@ -1,4 +1,4 @@ -@import "../../../../style/index"; +@use '../../../../style/index'; .adyen-checkout__dropdown__button-icon--left { flex-direction: row-reverse; @@ -6,57 +6,65 @@ > img { margin-left: 0; - margin-right: 12px; + margin-right: var(--b-spacer-060); } } .adyen-checkout__dropdown { + position: relative; max-width: 100%; width: 100%; - font-size: $font-size-medium; + font-size: index.$font-size-medium; } .adyen-checkout__dropdown__button { - padding: 7px 24px 7px 12px; - border: 1px solid $color-gray-dark; - background: $color-white; - color: $color-black; + display: flex; + align-items: center; + cursor: pointer; + border-radius: inherit; + color: var(--b-color-label-primary); + background: inherit; text-decoration: none; - border-radius: $border-radius-medium; outline: 0; - width: 100%; - font-size: $font-size-medium; - height: 40px; - line-height: 20px; + padding: 0 var(--b-spacer-060); + flex: 1; + font-size: index.$font-size-medium; + height: var(--b-spacer-110); + line-height: var(--b-spacer-080); user-select: none; transition: border 0.2s ease-out, box-shadow 0.2s ease-out; - [dir="rtl"] & { - padding: 7px 12px 7px 24px; + [dir='rtl'] & { + padding: 7px var(--b-spacer-060) 7px var(--b-spacer-090); } } -.adyen-checkout__dropdown__button:hover { - border-color: #99a3ad; +.adyen-checkout__dropdown__button::after { + position: absolute; + content: ''; + height: var(--b-spacer-030); + right: var(--b-spacer-070); + width: var(--b-spacer-040); + background-image: url('data:image/svg+xml,%3Csvg%20xmlns=%22http://www.w3.org/2000/svg%22%20width=%2216%22%20height=%2217%22%20viewBox=%220%200%2016%2017%22%20fill=%22none%22%3E%0A%20%20%3Cpath%20fill-rule=%22evenodd%22%20clip-rule=%22evenodd%22%20d=%22M4.46971%206.46967C4.7626%206.17678%205.23748%206.17678%205.53037%206.46967L8.00004%208.93934L10.4697%206.46967C10.7626%206.17678%2011.2375%206.17678%2011.5304%206.46967C11.8233%206.76256%2011.8233%207.23744%2011.5304%207.53033L8.53037%2010.5303C8.23747%2010.8232%207.7626%2010.8232%207.46971%2010.5303L4.46971%207.53033C4.17681%207.23744%204.17681%206.76256%204.46971%206.46967Z%22%20fill=%22#00112C%22/%3E%0A%3C/svg%3E'); + background-repeat: no-repeat; + background-position: center; + + [dir='rtl'] & { + left: var(--b-spacer-070); + right: auto; + } } -.adyen-checkout__dropdown__button__icon { - margin-right: 12px; - max-width: 40px; - height: 26px; - border-radius: 3px; +.adyen-checkout__dropdown__button--active::after { + transform: rotate(180deg); } -.adyen-checkout__dropdown__button--disabled { - opacity: 0.4; +.adyen-checkout__dropdown__button:hover { + border-color: #99a3ad; } -.adyen-checkout__dropdown__button--active, -.adyen-checkout__dropdown__button--active:hover, -.adyen-checkout__dropdown__button:active, -.adyen-checkout__dropdown__button:focus { - border-color: $color-primary; - box-shadow: 0 0 0 2px $color-blue-light; +.adyen-checkout__dropdown__button--disabled { + opacity: 0.4; } .adyen-checkout__dropdown__button--readonly { @@ -64,9 +72,9 @@ &--active, &:hover, &:focus { - background: $color-gray-light; + background: var(--b-color-surface-tertiary); border-color: transparent; - color: $color-black; + color: var(--b-color-label-primary); cursor: not-allowed; } } @@ -76,11 +84,18 @@ } .adyen-checkout__dropdown__button--invalid { - border-color: $color-alert; + border-color: var(--b-color-label-critical); } .adyen-checkout__dropdown__button--valid { - border-bottom-color: $color-success; + border-bottom-color: var(--b-color-label-success); +} + +.adyen-checkout__dropdown__button__icon { + margin-right: var(--b-spacer-060); + max-width: var(--b-spacer-110); + height: 26px; + border-radius: 3px; } .adyen-checkout__dropdown__button__text { @@ -93,35 +108,73 @@ } .adyen-checkout__dropdown__button__secondary-text { - margin-right: 16px; + margin-right: var(--b-spacer-070); +} + +.adyen-checkout__filter-input { + background: inherit; + border: 0; + color: var(--b-input-field-input-color); + font-family: inherit; + font-size: index.$font-size-medium; + height: 100%; + padding: 0; + width: 100%; + + &::placeholder { + color: var(--b-input-field-input-disabled-color) !important; + font-weight: var(--b-font-weight-200); + } + + &:focus, + &:active { + outline: 0; + } + + &[readonly] { + border-color: transparent; + color: var(--b-input-field-description-color) !important; + cursor: not-allowed; + } } .adyen-checkout__dropdown__list { + overflow-y: auto; + display: none; + position: absolute; z-index: 2; - border-radius: $border-radius-medium; + border-radius: var(--b-border-radius-m); max-height: 375px; - box-shadow: 0 2px 7px rgb(0 15 45 / 30%); + box-shadow: 0 var(--b-spacer-010) 7px rgb(0 15 45 / 30%); + width: 100%; + background: var(--b-color-white); + list-style: none; + padding: 0; + margin: 0 0 var(--b-spacer-120); } .adyen-checkout__dropdown__list.adyen-checkout__dropdown__list--active { - margin-top: 2px; + display: block; + margin-top: var(--b-spacer-010); } .adyen-checkout__dropdown__element { - padding: 8px; - line-height: 20px; - border: 1px solid transparent; + display: flex; + align-items: center; + padding: var(--b-spacer-040); + line-height: var(--b-spacer-080); + border: var(--b-border-width-s) solid transparent; word-break: break-word; hyphens: auto; cursor: pointer; - font-size: $font-size-small; + font-size: index.$font-size-small; outline: 0; user-select: none; transition: background 0.2s ease-out, border-color 0.2s ease-out; .adyen-checkout__icon { position: absolute; - right: 8px; + right: var(--b-spacer-040); } } @@ -149,9 +202,9 @@ } .adyen-checkout__dropdown__element__icon { - border-radius: $border-radius-small; - margin-right: 12px; - max-width: 40px; + border-radius: var(--b-border-radius-s); + margin-right: var(--b-spacer-060); + max-width: var(--b-spacer-110); max-height: 26px; } @@ -160,16 +213,16 @@ } .adyen-checkout__dropdown__element__secondary-text:not(:last-child) { - margin-right: 8px; + margin-right: var(--b-spacer-040); } .adyen-checkout__dropdown__element__flag { - margin-left: 8px; - margin-right: 10px; + margin-left: var(--b-spacer-040); + margin-right: var(--b-spacer-050); max-width: 27px; max-height: 18px; } .adyen-checkout__dropdown + .adyen-checkout-input__inline-validation { - right: 32px; + right: var(--b-spacer-100); } diff --git a/packages/lib/src/components/internal/FormFields/Select/Select.tsx b/packages/lib/src/components/internal/FormFields/Select/Select.tsx index ada51a00cf..d8505cdd1c 100644 --- a/packages/lib/src/components/internal/FormFields/Select/Select.tsx +++ b/packages/lib/src/components/internal/FormFields/Select/Select.tsx @@ -6,9 +6,8 @@ import SelectList from './components/SelectList'; import uuid from '../../../../utils/uuid'; import { keys } from './constants'; import { SelectItem, SelectProps } from './types'; -import styles from './Select.module.scss'; import './Select.scss'; -import { ARIA_ERROR_SUFFIX } from '../../../../core/Errors/constants'; +import { ARIA_CONTEXT_SUFFIX, ARIA_ERROR_SUFFIX } from '../../../../core/Errors/constants'; import { simulateFocusScroll } from '../utils'; function Select({ @@ -47,6 +46,9 @@ function Select({ const filteredItems = disableTextFilter ? items : items.filter(item => !textFilter || item.name.toLowerCase().includes(textFilter.toLowerCase())); + const suffix = isInvalid ? ARIA_ERROR_SUFFIX : ARIA_CONTEXT_SUFFIX; + const ariaDescribedBy = uniqueId ? `${uniqueId}${suffix}` : null; + const setNextActive = () => { if (!filteredItems || filteredItems.length < 1) return; const possibleNextIndex = filteredItems.findIndex(listItem => listItem === activeOption) + 1; @@ -256,12 +258,7 @@ function Select({ return (
`adyen-checkout__dropdown--${m}`) - ])} + className={cx(['adyen-checkout__dropdown', className, ...classNameModifiers.map(m => `adyen-checkout__dropdown--${m}`)])} ref={selectContainerRef} > ; countryCode?: string; showPayButton?: any; payButton?: any; @@ -180,7 +180,7 @@ class IbanInput extends Component { this.setError('holder', holderErr, this.onChange); // add callback param to force propagation of state to parent comp } - render({ placeholders, countryCode }: IbanInputProps, { data, errors, valid }) { + render({ placeholders }: IbanInputProps, { data, errors, valid }) { const { i18n } = useCoreContext(); return (
@@ -197,7 +197,7 @@ class IbanInput extends Component { { name={'ibanNumber'} className={'adyen-checkout__iban-input__iban-number'} classNameModifiers={['large']} - placeholder={'ibanNumber' in placeholders ? placeholders.ibanNumber : getIbanPlaceHolder(countryCode)} + placeholder={placeholders?.ibanNumber} value={data['ibanNumber']} onInput={this.handleIbanInput} aria-invalid={!!this.state.errors.iban} diff --git a/packages/lib/src/components/internal/IssuerList/IssuerButtonGroup/IssuerButton.scss b/packages/lib/src/components/internal/IssuerList/IssuerButtonGroup/IssuerButton.scss index de190c850c..041d239f9c 100644 --- a/packages/lib/src/components/internal/IssuerList/IssuerButtonGroup/IssuerButton.scss +++ b/packages/lib/src/components/internal/IssuerList/IssuerButtonGroup/IssuerButton.scss @@ -1,18 +1,19 @@ -@import "../../../../style/index"; +@use "../../../../style/index"; .adyen-checkout__issuer-button { - background-color: $color-white; - box-shadow: inset 0 0 0 1px $color-border-dark; + background-color: var(--b-color-white); + box-shadow: inset 0 0 0 1px var(--b-color-outline-primary); border: none; - border-radius: $border-radius-medium; - padding: $spacing-none $spacing-medium-small; + border-radius: var(--b-border-radius-m); + padding: var(--b-spacer-000) var(--b-spacer-060); cursor: pointer; flex-basis: 47%; flex-grow: 2; - height: 40px; - font-size: $font-size-small; + height: var(--b-spacer-110); + font-size: index.$font-size-small; display: flex; align-items: center; + justify-content: space-between; transition: background 0.3s ease-out, box-shadow 0.3s ease-out; &:active { @@ -21,30 +22,30 @@ &:not(&--selected):focus { outline: none; - box-shadow: inset 0 0 0 2px #99a3ad; + box-shadow: inset 0 0 0 1px #99a3ad; } &:not(&--selected):focus-visible { outline: none; - box-shadow: inset 0 0 0 2px #99a3ad; + box-shadow: inset 0 0 0 1px #99a3ad; } &:not(&--selected):hover { outline: none; - box-shadow: inset 0 0 0 2px #99a3ad; + box-shadow: inset 0 0 0 1px #99a3ad; } &--selected { - background: $color-white; - box-shadow: inset 0 0 0 2px $color-blue; - color: $color-blue; - font-weight: 500; - height: 40px; + background: var(--b-color-white); + box-shadow: inset 0 0 0 1px var(--b-color-outline-active); + color: var(--b-color-label-primary); + font-weight: var(--b-text-body-stronger-font-weight); + height: var(--b-spacer-110); transition: none; } } .adyen-checkout__issuer-button-img { max-height: 26px; - margin-right: 8px; + margin-right: var(--b-spacer-040); } diff --git a/packages/lib/src/components/internal/IssuerList/IssuerButtonGroup/IssuerButtonGroup.scss b/packages/lib/src/components/internal/IssuerList/IssuerButtonGroup/IssuerButtonGroup.scss index c911f2214d..32cf0d46a4 100644 --- a/packages/lib/src/components/internal/IssuerList/IssuerButtonGroup/IssuerButtonGroup.scss +++ b/packages/lib/src/components/internal/IssuerList/IssuerButtonGroup/IssuerButtonGroup.scss @@ -1,5 +1,5 @@ .adyen-checkout__issuer-button-group { display: flex; flex-wrap: wrap; - gap: 16px 16px; + gap: var(--b-spacer-070) var(--b-spacer-070); } diff --git a/packages/lib/src/components/internal/IssuerList/IssuerButtonGroup/IssuerButtonGroup.tsx b/packages/lib/src/components/internal/IssuerList/IssuerButtonGroup/IssuerButtonGroup.tsx index 9a0d6ea7ac..8176e67d54 100644 --- a/packages/lib/src/components/internal/IssuerList/IssuerButtonGroup/IssuerButtonGroup.tsx +++ b/packages/lib/src/components/internal/IssuerList/IssuerButtonGroup/IssuerButtonGroup.tsx @@ -24,7 +24,7 @@ const IssuerButtonGroup = ({ items = [], selectedIssuerId, onChange }: IssuerBut ); return ( -
+
{items.map(issuer => ( ))} diff --git a/packages/lib/src/components/internal/IssuerList/IssuerList.scss b/packages/lib/src/components/internal/IssuerList/IssuerList.scss index c5731cb061..68b0b230fd 100644 --- a/packages/lib/src/components/internal/IssuerList/IssuerList.scss +++ b/packages/lib/src/components/internal/IssuerList/IssuerList.scss @@ -1,7 +1,3 @@ .adyen-checkout__field--issuer-list { margin-bottom: 0; -} - -.adyen-checkout__issuer-list__termsAndConditions { - text-align: center; -} +} \ No newline at end of file diff --git a/packages/lib/src/components/internal/IssuerList/IssuerList.tsx b/packages/lib/src/components/internal/IssuerList/IssuerList.tsx index f523eb8026..2070cfeba5 100644 --- a/packages/lib/src/components/internal/IssuerList/IssuerList.tsx +++ b/packages/lib/src/components/internal/IssuerList/IssuerList.tsx @@ -26,7 +26,7 @@ const schema = ['issuer']; const validationRules: ValidatorRules = { issuer: { validate: issuer => !!issuer && issuer.length > 0, - errorMessage: 'idealIssuer.selectField.placeholder', + errorMessage: 'issuerList.selectField.contextualText', modes: ['blur'] } }; @@ -36,7 +36,7 @@ enum IssuerListInputTypes { Dropdown } -function IssuerList({ items, placeholder = 'idealIssuer.selectField.placeholder', issuer, highlightedIds = [], ...props }: IssuerListProps) { +function IssuerList({ items, placeholder, issuer, highlightedIds = [], showContextualElement, contextualText, ...props }: IssuerListProps) { const { i18n } = useCoreContext(); const { handleChangeFor, triggerValidation, data, valid, errors, isValid } = useForm({ schema, @@ -98,11 +98,18 @@ function IssuerList({ items, placeholder = 'idealIssuer.selectField.placeholder' )} - + + this.triggerValidation = triggerValidation; + + const getPhoneFieldError = useCallback( + (field: string) => { + if (errors[field]) { + const propsField = field === 'phoneNumber' ? 'phoneNumberErrorKey' : 'phonePrefixErrorKey'; + const key = props[propsField] ? props[propsField] : errors[field].errorMessage; + return i18n.get(key) ?? null; + } + return null; + }, + [errors] + ); -
-
{data.phonePrefix}
+ return ( +
+ {showPrefix && ( + + -
-
- )} -
-
- - {this.props.showPayButton && this.props.payButton({ status })} -
+ {showNumber && ( + 0} + dir={'ltr'} + i18n={i18n} + name={'phoneNumber'} + > + + + )} + ); } diff --git a/packages/lib/src/components/internal/PhoneInput/types.ts b/packages/lib/src/components/internal/PhoneInput/types.ts index b541ecaaa4..707d5b5a5f 100644 --- a/packages/lib/src/components/internal/PhoneInput/types.ts +++ b/packages/lib/src/components/internal/PhoneInput/types.ts @@ -1,37 +1,23 @@ -import Language from '../../../language/Language'; +import { DataSet, DataSetItem } from '../../../core/Services/data-set'; -export interface PhoneInputComponentProps { - onChange: (state) => void; - onValid: () => void; - payButton: () => void; - - selected: string; - items: []; - minLength: number; - prefixName: string; - phoneName: string; - showPayButton: boolean; - isValid: boolean; - i18n?: Language; - data?: { - phonePrefix: string; - phoneNumber: string; - }; +export interface PhoneInputSchema { + phoneNumber?: string; + phonePrefix?: string; } -export interface PhoneInputState { - data?: { - phonePrefix: string; - phoneNumber: string; - }; - errors?: { - phoneNumber?: boolean; - phonePrefix?: boolean; - }; - isValid?: boolean; +export interface PhoneInputProps { + items: DataSet; + requiredFields?: string[]; + data: PhoneInputSchema; + onChange: (obj) => void; + phoneNumberKey?: string; + phonePrefixErrorKey?: string; + phoneNumberErrorKey?: string; + placeholders?: PhoneInputSchema; + ref?; } -export interface PhoneInputSchema { - phoneNumber?: string; - phonePrefix?: string; +export interface PhonePrefixes { + phonePrefixes: DataSetItem[]; + loadingStatus: string; } diff --git a/packages/lib/src/components/internal/PhoneInputNew/usePhonePrefixes.ts b/packages/lib/src/components/internal/PhoneInput/usePhonePrefixes.ts similarity index 100% rename from packages/lib/src/components/internal/PhoneInputNew/usePhonePrefixes.ts rename to packages/lib/src/components/internal/PhoneInput/usePhonePrefixes.ts diff --git a/packages/lib/src/components/internal/PhoneInput/validate.ts b/packages/lib/src/components/internal/PhoneInput/validate.ts index 97e893b2b1..e4d0a095ad 100644 --- a/packages/lib/src/components/internal/PhoneInput/validate.ts +++ b/packages/lib/src/components/internal/PhoneInput/validate.ts @@ -1,3 +1,31 @@ -export const validatePhoneNumber = (phoneNumber, minLength = 3) => !!phoneNumber && phoneNumber.length >= minLength; +import { FormatRules, ValidatorRules } from '../../../utils/Validator/types'; +import { isEmpty, getFormattingRegEx } from '../../../utils/validator-utils'; -export default { validatePhoneNumber }; +// ((+351|00351|351)?)(2\d{1}|(9(3|6|2|1)))\d{7} full portuguese phone num regex + +const portugueseRegex = /\b(2\d{1}|(9(3|6|2|1)))\d{7}\b/; // match 2 + any digit + 7 digits OR 9 + 3|6|2|1 + 7 digits +const defaultRegex = /^(\d){4,}$/; // match >= 4 digits + +export const phoneValidationRules: ValidatorRules = { + phoneNumber: { + modes: ['blur'], + validate: (value, context) => { + // TODO improve this switching mechanism *if* we get any more country based regexs + const testRegex = context.state.data.phonePrefix === '+351' ? portugueseRegex : defaultRegex; + + return isEmpty(value) ? null : testRegex.test(value); + }, + errorMessage: 'invalidPhoneNumber' + }, + phonePrefix: { + modes: ['blur'], + validate: phonePrefix => !!phonePrefix, + errorMessage: 'invalidCountryCode' + } +}; + +export const phoneFormatters: FormatRules = { + phoneNumber: { + formatterFn: val => val.replace(getFormattingRegEx('^\\d', 'g'), '') + } +}; diff --git a/packages/lib/src/components/internal/PhoneInputNew/PhoneInput.scss b/packages/lib/src/components/internal/PhoneInputNew/PhoneInput.scss deleted file mode 100644 index c7ad7d13b9..0000000000 --- a/packages/lib/src/components/internal/PhoneInputNew/PhoneInput.scss +++ /dev/null @@ -1,82 +0,0 @@ -.adyen-checkout-phone-input--new { - direction: ltr; - - // inherited style (FormFields.scss) - .adyen-checkout__input-wrapper { - width: 100%; - - // inherited style (FormFields.scss) - .adyen-checkout__input { - padding: 0; - height: auto; - - &:focus-within { - border: 1px solid #0075ff; - - .adyen-checkout-dropdown--countrycode-selector { - border-right: 1px solid #0075ff; - } - } - } - - // inherited style (Select.scss) - .adyen-checkout__dropdown__button { - width: auto; - border: 0; - height: 35px; - border-top-right-radius: 0; - border-bottom-right-radius: 0; - - &::after { - box-sizing: revert; - height: 10px; - } - } - - // inherited style (Select.scss) - .adyen-checkout__dropdown__button--active { - box-shadow: none; - - &:hover { - box-shadow: none; - } - } - - // Example of better BEM naming i.e. 'adyen-checkout-input' as the Base - .adyen-checkout-input--phone-number { - height: 35px; - line-height: 35px; - min-height: 35px; - padding-bottom: 0; - padding-top: 0; - border: 1px solid transparent; - padding-left: 15px; - - &:focus-within { - border: 1px solid #0075ff; - box-shadow: 0 0 0 2px #99c2ff; - } - } - - .adyen-checkout-dropdown--countrycode-selector { - border-right: 1px solid #dce0e5; - min-width: 144px; - width: 144px; - } - - .adyen-checkout-input-holder--phone-input { - align-items: center; - display: flex; - } - - .adyen-checkout-phone-number { - align-items: center; - display: flex; - flex: 3; - } - } - - .adyen-checkout-phone-input__error-holder { - margin-top: -10px; - } -} diff --git a/packages/lib/src/components/internal/PhoneInputNew/PhoneInput.tsx b/packages/lib/src/components/internal/PhoneInputNew/PhoneInput.tsx deleted file mode 100644 index 537e9a0ae4..0000000000 --- a/packages/lib/src/components/internal/PhoneInputNew/PhoneInput.tsx +++ /dev/null @@ -1,179 +0,0 @@ -import { h } from 'preact'; -import { useCallback, useEffect, useMemo } from 'preact/hooks'; -import classNames from 'classnames'; -import Field from '../FormFields/Field'; -import useForm from '../../../utils/useForm'; -import useCoreContext from '../../../core/Context/useCoreContext'; -import './PhoneInput.scss'; -import { phoneFormatters, phoneValidationRules } from './validate'; -import { PhoneInputProps, PhoneInputSchema } from './types'; -import { ARIA_ERROR_SUFFIX } from '../../../core/Errors/constants'; -import { getUniqueId } from '../../../utils/idGenerator'; -import Select from '../FormFields/Select'; - -function PhoneInput(props: PhoneInputProps) { - const { i18n } = useCoreContext(); - - const schema = props.requiredFields || [...(props?.items?.length ? ['phonePrefix'] : []), 'phoneNumber']; - const showPrefix = schema.includes('phonePrefix') && !!props?.items?.length; - const showNumber = schema.includes('phoneNumber'); - - const { handleChangeFor, data, valid, errors, isValid, triggerValidation, setSchema } = useForm({ - i18n, - ...props, - schema, - defaultData: props.data, - rules: phoneValidationRules, - formatters: phoneFormatters - }); - - useEffect(() => { - setSchema(schema); - }, [schema.toString()]); - - // Force re-validation of the phoneNumber when data.phonePrefix changes (since the validation rules will also change) - useEffect((): void => { - if (data.phoneNumber) { - handleChangeFor('phoneNumber', 'blur')(data.phoneNumber); - } - }, [data.phonePrefix]); - - useEffect(() => { - props.onChange({ data, valid, errors, isValid }); - }, [data, valid, errors, isValid]); - - this.triggerValidation = triggerValidation; - - /** - * The element assigns a uniqueId which it uses for the \