Skip to content

Commit 87b1a57

Browse files
authored
Merge pull request #823 from paypal/feature/ppcpsdk-4152-card-fields-components
Created card fields components
2 parents e6a3ca2 + 11ac4f0 commit 87b1a57

18 files changed

+679
-3
lines changed

.changeset/cold-regions-rescue.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@paypal/react-paypal-js": minor
3+
---
4+
5+
Created V6 React Card Field components for each field type

.changeset/quick-chefs-occur.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@paypal/paypal-js": minor
3+
---
4+
5+
Updated V6 card fields types to include session and field destroy methods

packages/paypal-js/types/v6/components/card-fields.d.ts

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,27 @@ export type CardFieldOptions = {
135135

136136
export type StateType = "canceled" | "failed" | "succeeded";
137137

138+
export type CardFieldComponent = HTMLElement & {
139+
/**
140+
* Use this method to destroy the Card Field component and clean up any associated resources. After calling this method, the Card Field component will no longer be usable.
141+
*
142+
* @example
143+
* ```typescript
144+
* const numberField = cardFieldsInstance.createCardFieldsComponent({
145+
type: "number",
146+
placeholder: "Enter a number",
147+
});
148+
document
149+
.querySelector("#paypal-card-fields-number-container")
150+
.appendChild(numberField);
151+
152+
// Later, when you want to clean up:
153+
numberField.destroy();
154+
* ```
155+
*/
156+
destroy(): void;
157+
};
158+
138159
type BaseCardFieldsSession = {
139160
/**
140161
* Use this method to create and configure individual Card Field components.
@@ -153,7 +174,7 @@ type BaseCardFieldsSession = {
153174
.appendChild(numberField);
154175
* ```
155176
*/
156-
createCardFieldsComponent: (config: CardFieldOptions) => HTMLElement;
177+
createCardFieldsComponent: (config: CardFieldOptions) => CardFieldComponent;
157178
/**
158179
* Use this method to register event listeners and set callbacks for them.
159180
*
@@ -187,6 +208,15 @@ type BaseCardFieldsSession = {
187208
* ```
188209
*/
189210
update: (options: UpdateOptions) => void;
211+
/**
212+
* Use this method to destroy the Card Fields session instance and clean up any associated resources. After calling this method, the Card Fields session instance will no longer be usable.
213+
*
214+
* @example
215+
* ```typescript
216+
* cardFieldsInstance.destroy();
217+
* ```
218+
*/
219+
destroy: () => void;
190220
};
191221

192222
export type OneTimePaymentFlowResponse = {
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import React from "react";
2+
import { render } from "@testing-library/react";
3+
4+
import { PayPalCardCvvField } from "./PayPalCardCvvField";
5+
import { PayPalCardField } from "./PayPalCardField";
6+
7+
jest.mock("./PayPalCardField", () => ({
8+
PayPalCardField: jest.fn(() => <div data-testid="mock-card-field" />),
9+
}));
10+
11+
const mockPayPalCardField = PayPalCardField as jest.MockedFunction<
12+
typeof PayPalCardField
13+
>;
14+
15+
const fieldType = "cvv";
16+
17+
describe("PayPalCardCvvField", () => {
18+
beforeEach(() => {
19+
jest.clearAllMocks();
20+
});
21+
22+
it("Should pass type='cvv' to PayPalCardField", () => {
23+
render(<PayPalCardCvvField />);
24+
expect(mockPayPalCardField.mock.calls[0][0]).toEqual(
25+
expect.objectContaining({ type: fieldType }),
26+
);
27+
});
28+
29+
it("should render PayPalCardField with the correct props", () => {
30+
const testProps = {
31+
placeholder: "Enter CVV",
32+
containerClassName: "test-container",
33+
style: {
34+
input: {
35+
background: "lightgray",
36+
},
37+
},
38+
};
39+
40+
render(<PayPalCardCvvField {...testProps} />);
41+
42+
expect(mockPayPalCardField.mock.calls[0][0]).toEqual(
43+
expect.objectContaining({
44+
type: fieldType,
45+
...testProps,
46+
}),
47+
);
48+
});
49+
});
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import React from "react";
2+
3+
import { PayPalCardField } from "./PayPalCardField";
4+
5+
import type { CardFieldOptions } from "../types";
6+
import type { PayPalCardFieldsProvider } from "./PayPalCardFieldsProvider";
7+
8+
type PayPalCardCvvFieldProps = Omit<CardFieldOptions, "type"> & {
9+
containerStyles?: React.CSSProperties;
10+
containerClassName?: string;
11+
};
12+
13+
/**
14+
* `PayPalCardCvvField` is a component that renders a CVV field using the PayPal Card Fields SDK. It must be used within a {@link PayPalCardFieldsProvider} component.
15+
*
16+
* @example
17+
* // Basic usage creating a CVV field
18+
* <PayPalCardCvvField
19+
* placeholder="Enter CVV"
20+
* containerStyles={{ height: "3rem", marginBottom: "1rem" }}
21+
* />
22+
*/
23+
export const PayPalCardCvvField = ({
24+
containerStyles,
25+
containerClassName,
26+
placeholder,
27+
label,
28+
style,
29+
ariaDescription,
30+
ariaLabel,
31+
ariaInvalidErrorMessage,
32+
}: PayPalCardCvvFieldProps): JSX.Element | null => {
33+
return (
34+
<PayPalCardField
35+
type="cvv"
36+
containerStyles={containerStyles}
37+
containerClassName={containerClassName}
38+
placeholder={placeholder}
39+
label={label}
40+
style={style}
41+
ariaDescription={ariaDescription}
42+
ariaLabel={ariaLabel}
43+
ariaInvalidErrorMessage={ariaInvalidErrorMessage}
44+
/>
45+
);
46+
};
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import React from "react";
2+
import { render } from "@testing-library/react";
3+
4+
import { PayPalCardExpiryField } from "./PayPalCardExpiryField";
5+
import { PayPalCardField } from "./PayPalCardField";
6+
7+
jest.mock("./PayPalCardField", () => ({
8+
PayPalCardField: jest.fn(() => <div data-testid="mock-card-field" />),
9+
}));
10+
11+
const mockPayPalCardField = PayPalCardField as jest.MockedFunction<
12+
typeof PayPalCardField
13+
>;
14+
15+
const fieldType = "expiry";
16+
17+
describe("PayPalCardExpiryField", () => {
18+
beforeEach(() => {
19+
jest.clearAllMocks();
20+
});
21+
22+
it("Should pass type='expiry' to PayPalCardField", () => {
23+
render(<PayPalCardExpiryField />);
24+
expect(mockPayPalCardField.mock.calls[0][0]).toEqual(
25+
expect.objectContaining({ type: fieldType }),
26+
);
27+
});
28+
29+
it("should render PayPalCardField with the correct props", () => {
30+
const testProps = {
31+
placeholder: "Enter expiry date",
32+
containerClassName: "test-container",
33+
style: {
34+
input: {
35+
background: "lightgray",
36+
},
37+
},
38+
};
39+
40+
render(<PayPalCardExpiryField {...testProps} />);
41+
42+
expect(mockPayPalCardField.mock.calls[0][0]).toEqual(
43+
expect.objectContaining({
44+
type: fieldType,
45+
...testProps,
46+
}),
47+
);
48+
});
49+
});
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import React from "react";
2+
3+
import { PayPalCardField } from "./PayPalCardField";
4+
5+
import type { CardFieldOptions } from "../types";
6+
import type { PayPalCardFieldsProvider } from "./PayPalCardFieldsProvider";
7+
8+
type PayPalCardExpiryFieldProps = Omit<CardFieldOptions, "type"> & {
9+
containerStyles?: React.CSSProperties;
10+
containerClassName?: string;
11+
};
12+
13+
/**
14+
* `PayPalCardExpiryField` is a component that renders an expiry field using the PayPal Card Fields SDK. It must be used within a {@link PayPalCardFieldsProvider} component.
15+
*
16+
* @example
17+
* // Basic usage creating an expiry field
18+
* <PayPalCardExpiryField
19+
* placeholder="Enter an expiry date"
20+
* containerStyles={{ height: "3rem", marginBottom: "1rem" }}
21+
* />
22+
*/
23+
export const PayPalCardExpiryField = ({
24+
containerStyles,
25+
containerClassName,
26+
placeholder,
27+
label,
28+
style,
29+
ariaDescription,
30+
ariaLabel,
31+
ariaInvalidErrorMessage,
32+
}: PayPalCardExpiryFieldProps): JSX.Element | null => {
33+
return (
34+
<PayPalCardField
35+
type="expiry"
36+
containerStyles={containerStyles}
37+
containerClassName={containerClassName}
38+
placeholder={placeholder}
39+
label={label}
40+
style={style}
41+
ariaDescription={ariaDescription}
42+
ariaLabel={ariaLabel}
43+
ariaInvalidErrorMessage={ariaInvalidErrorMessage}
44+
/>
45+
);
46+
};

0 commit comments

Comments
 (0)