@paypal/checkout-components 5.0.412-alpha-84b7728.0 → 5.0.412

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,255 @@
1
+ /* @flow */
2
+ /** @jsx node */
3
+
4
+ import { node, type ElementNode } from "@krakenjs/jsx-pragmatic/src";
5
+ import { PPRebrandLogo } from "@paypal/sdk-logos/src";
6
+
7
+ import type { SavedPaymentMethodsStyleInputs } from "../../zoid/saved-payment-methods/props";
8
+ import { validateSavedPaymentMethodsStyle } from "../../zoid/saved-payment-methods/util";
9
+
10
+ export type SavedPaymentMethodsServerRenderProps = {|
11
+ nonce?: string,
12
+ style?: SavedPaymentMethodsStyleInputs,
13
+ |};
14
+
15
+ export function validateSavedPaymentMethodsProps(props: {|
16
+ style?: SavedPaymentMethodsStyleInputs,
17
+ |}) {
18
+ validateSavedPaymentMethodsStyle(props?.style);
19
+ }
20
+
21
+ const DEFAULT_STYLE_CONFIG = {
22
+ root: {
23
+ backgroundColor: "transparent",
24
+ fontFamily: "Arial, sans-serif",
25
+ textColorBase: "#000000",
26
+ fontSizeBase: "16px",
27
+ primaryColor: "#000000",
28
+ },
29
+ component: {
30
+ height: "32px",
31
+ padding: "6px 9px",
32
+ borderRadius: "6px",
33
+ borderColor: "transparent",
34
+ borderWidth: "0",
35
+ },
36
+ layout: {
37
+ logo: true,
38
+ label: true,
39
+ message: true,
40
+ logoWidth: "45px",
41
+ logoLabelGap: "12px",
42
+ labelFiGap: "8px",
43
+ labelFontSize: "16px",
44
+ fiTextFontSize: "14px",
45
+ iconSize: "28px",
46
+ },
47
+ };
48
+
49
+ function getStyleConfig(style?: SavedPaymentMethodsStyleInputs): Object {
50
+ const s = style || {};
51
+ return {
52
+ root: {
53
+ ...DEFAULT_STYLE_CONFIG.root,
54
+ ...(s.root || {}),
55
+ },
56
+ component: {
57
+ ...DEFAULT_STYLE_CONFIG.component,
58
+ ...(s.component || {}),
59
+ },
60
+ layout: {
61
+ ...DEFAULT_STYLE_CONFIG.layout,
62
+ ...(s.layout || {}),
63
+ },
64
+ };
65
+ }
66
+
67
+ /**
68
+ * Server-only render (commonjs2 / web: false) — mirrors smart buttons `Buttons()`.
69
+ * Non-interactive loading shell (styles + tag skeleton) until the iframe hydrates;
70
+ * shared by zoid prerender and server SSR and must match the first client paint.
71
+ */
72
+ export function SavedPaymentMethods(
73
+ props: SavedPaymentMethodsServerRenderProps
74
+ ): ElementNode {
75
+ const { nonce, style } = props;
76
+ const styleConfig = getStyleConfig(style);
77
+ const shouldRenderMessageContainer = Boolean(styleConfig.layout.message);
78
+
79
+ return (
80
+ <div>
81
+ <style nonce={nonce}>
82
+ {`
83
+ body {
84
+ margin: 0;
85
+ }
86
+ * {
87
+ box-sizing: border-box;
88
+ }
89
+ .saved-payment-methods-container {
90
+ background-color: ${styleConfig.root.backgroundColor};
91
+ font-family: ${styleConfig.root.fontFamily};
92
+ color: ${styleConfig.root.textColorBase};
93
+ font-size: ${styleConfig.root.fontSizeBase};
94
+ display: flex;
95
+ align-items: center;
96
+ min-width: 0;
97
+ }
98
+
99
+ .spm-content {
100
+ min-width: 0;
101
+ }
102
+
103
+ .spm-label {
104
+ font-size: ${styleConfig.layout.labelFontSize};
105
+ font-weight: 600;
106
+ color: #222222;
107
+ margin-right: ${styleConfig.layout.labelFiGap};
108
+ }
109
+
110
+ .spm-logo {
111
+ width: ${styleConfig.layout.logoWidth};
112
+ margin-right: ${styleConfig.layout.logoLabelGap};
113
+ display: flex;
114
+ align-items: center;
115
+ justify-content: center;
116
+ overflow: hidden;
117
+ }
118
+
119
+ .spm-logo-frame {
120
+ aspect-ratio: 1.5;
121
+ width: 100%;
122
+ height: 100%;
123
+ min-width: 0;
124
+ min-height: 0;
125
+ display: flex;
126
+ align-items: center;
127
+ justify-content: center;
128
+ border: 1.2px solid #CCCCCC;
129
+ border-radius: 2px;
130
+ }
131
+
132
+ .spm-logo-frame img {
133
+ width: 100%;
134
+ height: 100%;
135
+ object-fit: contain;
136
+ }
137
+
138
+ #spm-app-root {
139
+ display: contents;
140
+ }
141
+
142
+ .spm-tag {
143
+ display: flex;
144
+ height: ${styleConfig.component.height};
145
+ align-items: center;
146
+ justify-content: space-between;
147
+ background-color: #F5F7FA;
148
+ padding: ${styleConfig.component.padding};
149
+ border-radius: ${styleConfig.component.borderRadius};
150
+ border-width: ${styleConfig.component.borderWidth};
151
+ border-color: ${styleConfig.component.borderColor};
152
+ border-style: solid;
153
+ gap: 8px;
154
+ min-width: 0;
155
+ max-width: 100%;
156
+ cursor: pointer;
157
+ }
158
+
159
+ .spm-tag-label {
160
+ font-size: ${styleConfig.layout.fiTextFontSize};
161
+ font-weight: 400;
162
+ color: #000000;
163
+ white-space: nowrap;
164
+ }
165
+
166
+ .spm-tag-label-fallback {
167
+ display: flex;
168
+ align-items: center;
169
+ min-width: 0;
170
+ }
171
+
172
+ .spm-tag-label-email {
173
+ flex: 1 1 auto;
174
+ min-width: 0;
175
+ overflow: hidden;
176
+ text-overflow: ellipsis;
177
+ white-space: nowrap;
178
+ display: inline-block;
179
+ }
180
+
181
+ .spm-tag-label-separator {
182
+ flex: 0 0 auto;
183
+ margin: 0 4px;
184
+ }
185
+
186
+ .spm-tag-label-pay-in-full {
187
+ flex: 0 0 auto;
188
+ white-space: nowrap;
189
+ }
190
+
191
+ .spm-tag-card-icon {
192
+ width: ${styleConfig.layout.iconSize};
193
+ border-radius: 4px;
194
+ }
195
+
196
+ .spm-tag-edit-button svg path {
197
+ fill: ${styleConfig.root.primaryColor};
198
+ }
199
+
200
+ .spm-message {
201
+ height: 35px;
202
+ margin-top: 6px;
203
+ ${
204
+ styleConfig.layout.logo
205
+ ? `
206
+ margin-left: calc(${styleConfig.layout.logoWidth} + ${styleConfig.layout.logoLabelGap});
207
+ `
208
+ : ""
209
+ }
210
+ }
211
+
212
+ .spm-tag-loading {
213
+ display: flex;
214
+ height: ${styleConfig.component.height};
215
+ align-items: center;
216
+ justify-content: space-between;
217
+ background: linear-gradient(to right, #ffffff, #F5F7FA);
218
+ padding: ${styleConfig.component.padding};
219
+ border-radius: ${styleConfig.component.borderRadius};
220
+ border-width: ${styleConfig.component.borderWidth};
221
+ border-color: ${styleConfig.component.borderColor};
222
+ border-style: solid;
223
+ gap: 8px;
224
+ min-width: 124px;
225
+ max-width: 100%;
226
+ }
227
+
228
+ .spm-tag-loading.spm-tag-loading--hidden {
229
+ display: none !important;
230
+ }
231
+ `}
232
+ </style>
233
+ <div class="saved-payment-methods-container">
234
+ <div class="spm-content">
235
+ <div class="saved-payment-methods-container">
236
+ {styleConfig.layout.logo && (
237
+ <div class="spm-logo" aria-hidden="true">
238
+ <div class="spm-logo-frame">
239
+ <PPRebrandLogo />
240
+ </div>
241
+ </div>
242
+ )}
243
+ {styleConfig.layout.label && <div class="spm-label">PayPal</div>}
244
+ <div id="spm-app-root">
245
+ <div class="spm-tag-loading" />
246
+ </div>
247
+ </div>
248
+ {shouldRenderMessageContainer && (
249
+ <div id="spm-message-root" class="spm-message" aria-hidden="true" />
250
+ )}
251
+ </div>
252
+ </div>
253
+ </div>
254
+ );
255
+ }
@@ -0,0 +1,23 @@
1
+ /* @flow */
2
+ import { describe, expect, test } from "vitest";
3
+ import { html } from "@krakenjs/jsx-pragmatic";
4
+
5
+ import { SavedPaymentMethods } from "./template";
6
+
7
+ describe("SavedPaymentMethods template (UI shell)", () => {
8
+ test("parses SSR markup into DOM with app root and loading skeleton", () => {
9
+ const markup = SavedPaymentMethods({ nonce: "test-nonce" }).render(html());
10
+
11
+ expect(typeof markup).toBe("string");
12
+ expect(markup.length).toBeGreaterThan(0);
13
+
14
+ const host = document.createElement("div");
15
+ host.innerHTML = markup;
16
+
17
+ expect(host.querySelector("style[nonce='test-nonce']")).toBeTruthy();
18
+ expect(host.querySelector(".saved-payment-methods-container")).toBeTruthy();
19
+ expect(host.querySelector("#spm-app-root")).toBeTruthy();
20
+ expect(host.querySelector(".spm-tag-loading")).toBeTruthy();
21
+ expect(host.querySelector(".spm-logo-frame")).toBeTruthy();
22
+ });
23
+ });
@@ -91,7 +91,6 @@ export function containerTemplate({
91
91
  top: 0;
92
92
  left: 0;
93
93
  width: 100%;
94
- height: 100%;
95
94
  border: none;
96
95
  }
97
96
 
@@ -4,6 +4,8 @@
4
4
  import { node, type ChildType } from "@krakenjs/jsx-pragmatic/src";
5
5
  import type { ZoidProps } from "@krakenjs/zoid/src";
6
6
 
7
+ import { SavedPaymentMethods } from "../../ui/saved-payment-methods/template";
8
+
7
9
  import { type SavedPaymentMethodsProps } from "./props";
8
10
 
9
11
  type PrerenderedSavedPaymentMethodsProps = {|
@@ -13,32 +15,20 @@ type PrerenderedSavedPaymentMethodsProps = {|
13
15
 
14
16
  export function PrerenderedSavedPaymentMethods({
15
17
  nonce,
18
+ props,
16
19
  }: // props,
17
20
  PrerenderedSavedPaymentMethodsProps): ChildType {
21
+ const { style } = props;
18
22
  return (
19
23
  <html>
20
24
  <head>
21
- <style nonce={nonce}>
22
- {`
23
- * {
24
- box-sizing: border-box;
25
- }
26
- body {
27
- margin: 0;
28
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
29
- }
30
- .saved-payment-methods-loading {
31
- display: flex;
32
- align-items: center;
33
- justify-content: center;
34
- height: 100vh;
35
- color: #999;
36
- }
37
- `}
38
- </style>
25
+ <meta charset="utf-8" />
39
26
  </head>
40
27
  <body>
41
- <div class="saved-payment-methods-loading">...</div>
28
+ <SavedPaymentMethods
29
+ nonce={typeof nonce === "string" ? nonce : undefined}
30
+ style={style}
31
+ />
42
32
  </body>
43
33
  </html>
44
34
  );
@@ -1,71 +0,0 @@
1
- /* @flow */
2
-
3
- import { describe, expect, test } from "vitest";
4
-
5
- import {
6
- BUTTON_REDESIGN_STYLE,
7
- BUTTON_REDESIGN_DISABLEMAXHEIGHT_STYLE,
8
- } from "../config";
9
-
10
- describe("BUTTON_REDESIGN_DISABLEMAXHEIGHT_STYLE config", () => {
11
- test("should include all BUTTON_REDESIGN_STYLE buckets", () => {
12
- Object.keys(BUTTON_REDESIGN_STYLE).forEach((key) => {
13
- expect(BUTTON_REDESIGN_DISABLEMAXHEIGHT_STYLE).toHaveProperty(key);
14
- });
15
- });
16
-
17
- test("should have XL_BIG bucket with correct values", () => {
18
- expect(BUTTON_REDESIGN_DISABLEMAXHEIGHT_STYLE.XL_BIG).toEqual({
19
- minHeight: 60,
20
- maxHeight: 65,
21
- gap: 6,
22
- fontSize: 20,
23
- });
24
- });
25
-
26
- test("should have XXL_SMALL bucket with correct values", () => {
27
- expect(BUTTON_REDESIGN_DISABLEMAXHEIGHT_STYLE.XXL_SMALL).toEqual({
28
- minHeight: 65,
29
- maxHeight: 70,
30
- gap: 7,
31
- fontSize: 22,
32
- });
33
- });
34
-
35
- test("should have XXL_BIG bucket with correct values", () => {
36
- expect(BUTTON_REDESIGN_DISABLEMAXHEIGHT_STYLE.XXL_BIG).toEqual({
37
- minHeight: 70,
38
- maxHeight: 75,
39
- gap: 7,
40
- fontSize: 24,
41
- });
42
- });
43
-
44
- test("should have XXXL bucket with correct values", () => {
45
- expect(BUTTON_REDESIGN_DISABLEMAXHEIGHT_STYLE.XXXL).toEqual({
46
- minHeight: 75,
47
- maxHeight: 200,
48
- gap: 8,
49
- fontSize: 26,
50
- });
51
- });
52
-
53
- test("should have no gaps in height coverage between buckets", () => {
54
- const buckets = Object.keys(BUTTON_REDESIGN_DISABLEMAXHEIGHT_STYLE)
55
- .map((key) => BUTTON_REDESIGN_DISABLEMAXHEIGHT_STYLE[key])
56
- .filter((b) => b.minHeight !== undefined && b.maxHeight !== undefined)
57
- .sort((a, b) => a.minHeight - b.minHeight);
58
-
59
- for (let i = 1; i < buckets.length; i++) {
60
- expect(buckets[i].minHeight).toBeLessThanOrEqual(
61
- buckets[i - 1].maxHeight
62
- );
63
- }
64
- });
65
-
66
- test("should have continuous height coverage up to 200", () => {
67
- const keys = Object.keys(BUTTON_REDESIGN_DISABLEMAXHEIGHT_STYLE);
68
- const lastKey = keys[keys.length - 1];
69
- expect(BUTTON_REDESIGN_DISABLEMAXHEIGHT_STYLE[lastKey].maxHeight).toBe(200);
70
- });
71
- });