Skip to content

Commit 2803133

Browse files
authored
chore(shared): Improve layout shift in payment element fallback (#6387)
1 parent 3b943aa commit 2803133

File tree

6 files changed

+134
-1
lines changed

6 files changed

+134
-1
lines changed

.changeset/fair-bars-agree.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
'@clerk/shared': patch
3+
---
4+
5+
6+
Improve layout behaviour with `<PaymentElement fallback={} />`.
7+
- Disables Stripe's loader, and promotes the usage of the `fallback` prop.

.changeset/vast-hoops-teach.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@clerk/clerk-js': patch
3+
---
4+
5+
Improve CLS when PaymentElement mounts in Checkout.

packages/clerk-js/src/ui/components/PaymentSources/AddPaymentSource.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import { handleError } from '@/ui/utils/errorHandler';
1919
import { useSubscriberTypeContext, useSubscriberTypeLocalizationRoot } from '../../contexts';
2020
import { descriptors, Flex, localizationKeys, Spinner, useAppearance, useLocalizations } from '../../customizables';
2121
import type { LocalizationKey } from '../../localization';
22+
import { PaymentElementSkeleton } from './PaymentElementSkeleton';
2223

2324
const useStripeAppearance = (node: HTMLElement | null) => {
2425
const theme = useAppearance().parsedInternalTheme;
@@ -231,7 +232,7 @@ const AddPaymentSourceForm = ({ children }: PropsWithChildren) => {
231232
})}
232233
>
233234
{children}
234-
<PaymentElement />
235+
<PaymentElement fallback={<PaymentElementSkeleton />} />
235236
<Card.Alert>{card.error}</Card.Alert>
236237
<FormButtons
237238
isDisabled={!isFormReady}
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
import type { PropsWithChildren } from 'react';
2+
3+
import { Box, Flex, Grid } from '@/ui/customizables';
4+
5+
const SkeletonLine = (props: Parameters<typeof Box>[0]) => {
6+
return (
7+
<Box
8+
sx={[
9+
t => ({
10+
height: t.space.$2,
11+
width: '100%',
12+
borderRadius: t.radii.$md,
13+
background: t.colors.$neutralAlpha100,
14+
}),
15+
props.sx,
16+
]}
17+
/>
18+
);
19+
};
20+
21+
const SkeletonInput = () => {
22+
return (
23+
<SkeletonLine
24+
sx={t => ({
25+
height: t.space.$10,
26+
width: '100%',
27+
})}
28+
/>
29+
);
30+
};
31+
32+
const LineGroup = (props: PropsWithChildren) => {
33+
return (
34+
<Flex
35+
direction='col'
36+
gap={2}
37+
>
38+
{props.children}
39+
</Flex>
40+
);
41+
};
42+
43+
const PaymentElementSkeleton = () => {
44+
return (
45+
<Box
46+
aria-label='Loading...'
47+
sx={{
48+
position: 'relative',
49+
minHeight: 0,
50+
flex: 1,
51+
overflowY: 'auto',
52+
}}
53+
>
54+
<Flex
55+
direction='col'
56+
gap={5}
57+
>
58+
<LineGroup>
59+
<SkeletonLine
60+
sx={t => ({
61+
height: t.space.$3,
62+
width: t.sizes.$24,
63+
})}
64+
/>
65+
<SkeletonInput />
66+
</LineGroup>
67+
68+
<Grid
69+
columns={2}
70+
gap={4}
71+
>
72+
<LineGroup>
73+
<SkeletonLine
74+
sx={t => ({
75+
height: t.space.$3,
76+
width: t.sizes.$20,
77+
})}
78+
/>
79+
80+
<SkeletonInput />
81+
</LineGroup>
82+
<LineGroup>
83+
<SkeletonLine
84+
sx={t => ({
85+
height: t.space.$3,
86+
width: t.sizes.$24,
87+
})}
88+
/>
89+
90+
<SkeletonInput />
91+
</LineGroup>
92+
</Grid>
93+
94+
<LineGroup>
95+
<SkeletonLine
96+
sx={t => ({
97+
height: t.space.$3,
98+
width: t.sizes.$16,
99+
})}
100+
/>
101+
102+
<SkeletonInput />
103+
</LineGroup>
104+
105+
<LineGroup>
106+
<SkeletonLine />
107+
<SkeletonLine />
108+
<SkeletonLine sx={{ width: '66.666667%' }} />
109+
</LineGroup>
110+
</Flex>
111+
</Box>
112+
);
113+
};
114+
115+
export { PaymentElementSkeleton };

packages/shared/src/react/commerce.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,7 @@ const PaymentElementInternalRoot = (props: PropsWithChildren) => {
206206
key={externalClientSecret}
207207
stripe={stripe}
208208
options={{
209+
loader: 'never',
209210
clientSecret: externalClientSecret,
210211
appearance: {
211212
variables: stripeAppearance,

packages/shared/src/react/stripe-react/index.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -429,6 +429,10 @@ const createElementComponent = (type: StripeElementType, isServer: boolean): Fun
429429
{!isReady && fallback}
430430
<div
431431
id={id}
432+
style={{
433+
height: isReady ? 'unset' : '0px',
434+
visibility: isReady ? 'visible' : 'hidden',
435+
}}
432436
className={className}
433437
ref={domNode}
434438
/>

0 commit comments

Comments
 (0)