Skip to content

Commit 23c931e

Browse files
alexcarpenteraelioxpanteliselefjacekradko
authoredMar 14, 2025··
feat(clerk-js, types): <Drawer /> component (#5337)
Co-authored-by: Keiran Flanigan <keiran@aeliox.com> Co-authored-by: panteliselef <panteliselef@outlook.com> Co-authored-by: Jacek <jacek@clerk.dev>
1 parent 286d1b3 commit 23c931e

30 files changed

+1068
-705
lines changed
 

‎.changeset/lucky-cobras-cough.md

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
'@clerk/clerk-js': patch
3+
'@clerk/types': patch
4+
---
5+
6+
Introduce `<Drawer />` component and update commerce components implementations to make use of it.

‎packages/clerk-js/bundlewatch.config.json

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
{
22
"files": [
3-
{ "path": "./dist/clerk.js", "maxSize": "573kB" },
3+
{ "path": "./dist/clerk.js", "maxSize": "575kB" },
44
{ "path": "./dist/clerk.browser.js", "maxSize": "78kB" },
55
{ "path": "./dist/clerk.headless.js", "maxSize": "50KB" },
6-
{ "path": "./dist/ui-common*.js", "maxSize": "92KB" },
7-
{ "path": "./dist/vendors*.js", "maxSize": "28.5KB" },
6+
{ "path": "./dist/ui-common*.js", "maxSize": "93KB" },
7+
{ "path": "./dist/vendors*.js", "maxSize": "30KB" },
88
{ "path": "./dist/coinbase*.js", "maxSize": "35.5KB" },
99
{ "path": "./dist/createorganization*.js", "maxSize": "5KB" },
1010
{ "path": "./dist/impersonationfab*.js", "maxSize": "5KB" },

‎packages/clerk-js/src/ui/common/CommerceBlade.tsx

-72
This file was deleted.

‎packages/clerk-js/src/ui/common/index.ts

-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
export * from './BlockButtons';
22
export * from './CalloutWithAction';
3-
export * from './CommerceBlade';
43
export * from './constants';
54
export * from './EmailLinkStatusCard';
65
export * from './EmailLinkVerify';

‎packages/clerk-js/src/ui/components/Checkout/Checkout.tsx

+18-7
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import type { __experimental_CheckoutProps } from '@clerk/types';
22

3-
import { CommerceBlade } from '../../common';
3+
import { PROFILE_CARD_SCROLLBOX_ID } from '../../constants';
44
import { useCheckoutContext, withCoreUserGuard } from '../../contexts';
55
import { Flow } from '../../customizables';
6+
import { Drawer } from '../../elements';
67
import { Route, Switch } from '../../router';
78
import { CheckoutPage } from './CheckoutPage';
89

@@ -21,14 +22,24 @@ export const __experimental_Checkout = (props: __experimental_CheckoutProps) =>
2122
};
2223

2324
const AuthenticatedRoutes = withCoreUserGuard((props: __experimental_CheckoutProps) => {
24-
const { mode = 'mounted', isShowingBlade = false } = useCheckoutContext();
25+
const { mode = 'mounted', isOpen = false, setIsOpen = () => {} } = useCheckoutContext();
2526

2627
return (
27-
<CommerceBlade
28-
isOpen={isShowingBlade}
29-
isFullscreen={mode === 'mounted'}
28+
<Drawer.Root
29+
open={isOpen}
30+
onOpenChange={setIsOpen}
31+
strategy={mode === 'mounted' ? 'fixed' : 'absolute'}
32+
portalProps={{
33+
id: mode === 'modal' ? PROFILE_CARD_SCROLLBOX_ID : undefined,
34+
}}
3035
>
31-
<CheckoutPage {...props} />
32-
</CommerceBlade>
36+
<Drawer.Overlay />
37+
<Drawer.Content>
38+
<Drawer.Header title='Checkout' />
39+
<Drawer.Body>
40+
<CheckoutPage {...props} />
41+
</Drawer.Body>
42+
</Drawer.Content>
43+
</Drawer.Root>
3344
);
3445
});

‎packages/clerk-js/src/ui/components/Checkout/CheckoutComplete.tsx

+18-21
Original file line numberDiff line numberDiff line change
@@ -4,34 +4,30 @@ import { useCheckoutContext } from '../../contexts';
44
import { Box, Button, Col, Flex, Icon, Text } from '../../customizables';
55
import { LineItems } from '../../elements';
66
import { Check } from '../../icons';
7-
import type { ThemableCssProp } from '../../styledSystem';
87

9-
export const CheckoutComplete = ({
10-
checkout,
11-
sx,
12-
}: {
13-
checkout: __experimental_CommerceCheckoutResource;
14-
sx?: ThemableCssProp;
15-
}) => {
16-
const { handleCloseBlade = () => {} } = useCheckoutContext();
8+
export const CheckoutComplete = ({ checkout }: { checkout: __experimental_CommerceCheckoutResource }) => {
9+
const { setIsOpen } = useCheckoutContext();
10+
11+
const handleClose = () => {
12+
if (setIsOpen) {
13+
setIsOpen(false);
14+
}
15+
};
1716

1817
return (
1918
<Col
20-
sx={[
21-
t => ({
22-
width: '100%',
23-
padding: t.space.$4,
24-
}),
25-
sx,
26-
]}
19+
sx={{
20+
flex: 1,
21+
}}
2722
>
2823
<Col
2924
align='center'
3025
justify='center'
3126
gap={8}
32-
sx={{
27+
sx={t => ({
3328
flex: 1,
34-
}}
29+
paddingBlock: t.space.$4,
30+
})}
3531
>
3632
<SuccessCircle />
3733

@@ -48,14 +44,15 @@ export const CheckoutComplete = ({
4844
</Text>
4945
</Col>
5046
</Col>
47+
5148
<Col
5249
gap={2}
5350
sx={t => ({
54-
flex: 0,
55-
paddingTop: t.space.$4,
51+
padding: t.space.$4,
5652
borderTopWidth: t.borderWidths.$normal,
5753
borderTopStyle: t.borderStyles.$solid,
5854
borderTopColor: t.colors.$neutralAlpha100,
55+
position: 'relative',
5956
})}
6057
>
6158
<LineItems.Root>
@@ -90,7 +87,7 @@ export const CheckoutComplete = ({
9087
width: '100%',
9188
marginTop: t.space.$2,
9289
})}
93-
onClick={handleCloseBlade}
90+
onClick={handleClose}
9491
>
9592
{/* TODO(@COMMERCE): needs localization */}
9693
Continue

‎packages/clerk-js/src/ui/components/Checkout/CheckoutPage.tsx

+5-56
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,10 @@ import type { Stripe } from '@stripe/stripe-js';
88
import { loadStripe } from '@stripe/stripe-js';
99
import { useEffect, useRef, useState } from 'react';
1010

11-
import { useCheckoutContext, useEnvironment } from '../../contexts';
12-
import { Alert, Box, Button, Col, Flex, Heading, Icon, Spinner } from '../../customizables';
11+
import { useEnvironment } from '../../contexts';
12+
import { Alert, Col, Flex, Spinner } from '../../customizables';
1313
import { LineItems } from '../../elements';
1414
import { useCheckout } from '../../hooks';
15-
import { Close } from '../../icons';
1615
import { CheckoutComplete } from './CheckoutComplete';
1716
import { CheckoutForm } from './CheckoutForm';
1817

@@ -44,8 +43,6 @@ export const CheckoutPage = (props: __experimental_CheckoutProps) => {
4443

4544
return (
4645
<>
47-
<CheckoutHeader title='Checkout' />
48-
4946
{isLoading ? (
5047
<Flex
5148
align='center'
@@ -64,24 +61,13 @@ export const CheckoutPage = (props: __experimental_CheckoutProps) => {
6461
<Alert colorScheme='danger'>There was a problem, please try again later.</Alert>
6562
</Flex>
6663
) : checkout.status === 'completed' ? (
67-
<CheckoutComplete
68-
checkout={checkout}
69-
sx={t => ({ height: `calc(100% - ${t.space.$12})` })}
70-
/>
64+
<CheckoutComplete checkout={checkout} />
7165
) : (
72-
<Box
73-
sx={t => ({
74-
overflowY: 'auto',
75-
/* minus the height of the header */
76-
height: `calc(100% - ${t.space.$12})`,
77-
overflowX: 'hidden',
78-
})}
79-
>
66+
<>
8067
<Col
8168
gap={3}
8269
sx={t => ({
8370
padding: t.space.$4,
84-
backgroundColor: t.colors.$neutralAlpha25,
8571
borderBottomWidth: t.borderWidths.$normal,
8672
borderBottomStyle: t.borderStyles.$solid,
8773
borderBottomColor: t.colors.$neutralAlpha100,
@@ -105,49 +91,12 @@ export const CheckoutPage = (props: __experimental_CheckoutProps) => {
10591
/>
10692
</Elements>
10793
)}
108-
</Box>
94+
</>
10995
)}
11096
</>
11197
);
11298
};
11399

114-
const CheckoutHeader = ({ title }: { title: string }) => {
115-
const { handleCloseBlade = () => {} } = useCheckoutContext();
116-
117-
return (
118-
<Flex
119-
align='center'
120-
justify='between'
121-
gap={2}
122-
sx={t => ({
123-
position: 'sticky',
124-
top: 0,
125-
width: '100%',
126-
height: t.space.$12,
127-
paddingInline: `${t.space.$5} ${t.space.$2}`,
128-
borderBottomWidth: t.borderWidths.$normal,
129-
borderBottomStyle: t.borderStyles.$solid,
130-
borderBottomColor: t.colors.$neutralAlpha100,
131-
})}
132-
>
133-
<Heading textVariant='h2'>{title}</Heading>
134-
<Button
135-
variant='ghost'
136-
onClick={handleCloseBlade}
137-
sx={t => ({
138-
color: t.colors.$neutralAlpha400,
139-
padding: t.space.$2,
140-
})}
141-
>
142-
<Icon
143-
icon={Close}
144-
size='md'
145-
/>
146-
</Button>
147-
</Flex>
148-
);
149-
};
150-
151100
// TODO(@COMMERCE): needs localization
152101
const CheckoutPlanRows = ({
153102
plan,

‎packages/clerk-js/src/ui/components/PricingTable/PlanCard.tsx

+313-233
Large diffs are not rendered by default.

‎packages/clerk-js/src/ui/components/PricingTable/PlanDetailBlade.tsx

-243
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
import type { __experimental_CommercePlanResource } from '@clerk/types';
2+
import * as React from 'react';
3+
4+
import { Alert, Box, Button, Col, Flex, Heading, Text } from '../../customizables';
5+
import { Drawer } from '../../elements';
6+
import type { PlanPeriod } from './PlanCard';
7+
import { PlanCardFeaturesList, PlanCardHeader } from './PlanCard';
8+
9+
type DrawerRootProps = React.ComponentProps<typeof Drawer.Root>;
10+
11+
type PlanDetailDrawerProps = {
12+
isOpen: DrawerRootProps['open'];
13+
setIsOpen: DrawerRootProps['onOpenChange'];
14+
portalProps?: DrawerRootProps['portalProps'];
15+
strategy: DrawerRootProps['strategy'];
16+
plan?: __experimental_CommercePlanResource;
17+
planPeriod: PlanPeriod;
18+
setPlanPeriod: (p: PlanPeriod) => void;
19+
};
20+
21+
export function PlanDetailDrawer({
22+
isOpen,
23+
setIsOpen,
24+
portalProps,
25+
strategy,
26+
plan,
27+
planPeriod,
28+
setPlanPeriod,
29+
}: PlanDetailDrawerProps) {
30+
if (!plan) {
31+
return null;
32+
}
33+
const hasFeatures = plan.features.length > 0;
34+
return (
35+
<Drawer.Root
36+
open={isOpen}
37+
onOpenChange={setIsOpen}
38+
strategy={strategy}
39+
portalProps={portalProps}
40+
>
41+
<Drawer.Overlay />
42+
<Drawer.Content>
43+
<Drawer.Header
44+
sx={t =>
45+
!hasFeatures
46+
? {
47+
flex: 1,
48+
borderBottomWidth: 0,
49+
background: t.colors.$colorBackground,
50+
}
51+
: null
52+
}
53+
>
54+
<PlanCardHeader
55+
plan={plan}
56+
planPeriod={planPeriod}
57+
setPlanPeriod={setPlanPeriod}
58+
closeSlot={<Drawer.Close />}
59+
/>
60+
</Drawer.Header>
61+
{hasFeatures ? (
62+
<Drawer.Body>
63+
<Box
64+
sx={t => ({
65+
padding: t.space.$4,
66+
})}
67+
>
68+
<PlanCardFeaturesList plan={plan} />
69+
</Box>
70+
</Drawer.Body>
71+
) : null}
72+
<CancelFooter
73+
plan={plan}
74+
handleClose={() => setIsOpen(false)}
75+
/>
76+
</Drawer.Content>
77+
</Drawer.Root>
78+
);
79+
}
80+
81+
const CancelFooter = ({ plan }: { plan: __experimental_CommercePlanResource; handleClose: () => void }) => {
82+
// const { __experimental_commerce } = useClerk();
83+
const [showConfirmation, setShowConfirmation] = React.useState(false);
84+
const [isSubmitting, setIsSubmitting] = React.useState(false);
85+
const [hasError, setHasError] = React.useState(false);
86+
87+
const cancelSubscription = async () => {
88+
setHasError(false);
89+
setIsSubmitting(true);
90+
91+
// TODO: we need to get a handle on the subscription object in order to cancel it,
92+
// but this method doesn't exist yet.
93+
//
94+
// await subscription.cancel().then(() => {
95+
// setIsSubmitting(false);
96+
// handleClose();
97+
// }).catch(() => { setHasError(true); setIsSubmitting(false); });
98+
};
99+
100+
// TODO: remove when we can hook up cancel button
101+
// return null;
102+
103+
return (
104+
<Drawer.Footer>
105+
{showConfirmation ? (
106+
<Col gap={8}>
107+
<Heading textVariant='h3'>Cancel {plan.name} Subscription?</Heading>
108+
<Text colorScheme='secondary'>
109+
You can keep using &ldquo;{plan.name}&rdquo; features until [DATE], after which you will no longer have
110+
access.
111+
</Text>
112+
{hasError && (
113+
<Alert colorScheme='danger'>There was a problem canceling your subscription, please try again.</Alert>
114+
)}
115+
<Flex
116+
gap={3}
117+
justify='end'
118+
>
119+
{!isSubmitting && (
120+
<Button
121+
variant='ghost'
122+
size='sm'
123+
textVariant='buttonLarge'
124+
onClick={() => {
125+
setHasError(false);
126+
setShowConfirmation(false);
127+
}}
128+
>
129+
Keep Subscription
130+
</Button>
131+
)}
132+
<Button
133+
variant='solid'
134+
colorScheme='danger'
135+
size='sm'
136+
textVariant='buttonLarge'
137+
isLoading={isSubmitting}
138+
onClick={cancelSubscription}
139+
>
140+
Cancel Subscription
141+
</Button>
142+
</Flex>
143+
</Col>
144+
) : (
145+
<Button
146+
variant='bordered'
147+
colorScheme='secondary'
148+
size='sm'
149+
textVariant='buttonLarge'
150+
sx={{
151+
width: '100%',
152+
}}
153+
onClick={() => setShowConfirmation(true)}
154+
>
155+
Cancel Subscription
156+
</Button>
157+
)}
158+
</Drawer.Footer>
159+
);
160+
};

‎packages/clerk-js/src/ui/components/PricingTable/PricingTable.tsx

+16-8
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,20 @@ import { useClerk } from '@clerk/shared/react';
22
import type { __experimental_CommercePlanResource, __experimental_PricingTableProps } from '@clerk/types';
33
import { useState } from 'react';
44

5+
import { PROFILE_CARD_SCROLLBOX_ID } from '../../constants';
56
import { __experimental_CheckoutContext, usePricingTableContext } from '../../contexts';
67
import { Box, descriptors } from '../../customizables';
78
import { useFetch } from '../../hooks';
89
import { InternalThemeProvider } from '../../styledSystem';
910
import { __experimental_Checkout } from '../Checkout';
11+
import type { PlanPeriod } from './PlanCard';
1012
import { PlanCard } from './PlanCard';
11-
import { PlanDetailBlade } from './PlanDetailBlade';
13+
import { PlanDetailDrawer } from './PlanDetailDrawer';
1214

1315
export const __experimental_PricingTable = (props: __experimental_PricingTableProps) => {
1416
const { __experimental_commerce } = useClerk();
1517
const { mode = 'mounted' } = usePricingTableContext();
16-
const [planPeriod, setPlanPeriod] = useState('month');
18+
const [planPeriod, setPlanPeriod] = useState<PlanPeriod>('month');
1719
const [selectedPlan, setSelectedPlan] = useState<__experimental_CommercePlanResource>();
1820
const [showCheckout, setShowCheckout] = useState(false);
1921
const [showPlanDetail, setShowPlanDetail] = useState(false);
@@ -57,8 +59,8 @@ export const __experimental_PricingTable = (props: __experimental_PricingTablePr
5759
<PlanCard
5860
key={plan.id}
5961
plan={plan}
60-
period={planPeriod}
61-
setPeriod={setPlanPeriod}
62+
planPeriod={planPeriod}
63+
setPlanPeriod={setPlanPeriod}
6264
onSelect={selectPlan}
6365
props={props}
6466
isCompact={isCompact}
@@ -69,8 +71,8 @@ export const __experimental_PricingTable = (props: __experimental_PricingTablePr
6971
value={{
7072
componentName: 'Checkout',
7173
mode,
72-
isShowingBlade: showCheckout,
73-
handleCloseBlade: () => setShowCheckout(false),
74+
isOpen: showCheckout,
75+
setIsOpen: setShowCheckout,
7476
}}
7577
>
7678
{/*TODO: Used by InvisibleRootBox, can we simplify? */}
@@ -81,10 +83,16 @@ export const __experimental_PricingTable = (props: __experimental_PricingTablePr
8183
/>
8284
</div>
8385
</__experimental_CheckoutContext.Provider>
84-
<PlanDetailBlade
86+
<PlanDetailDrawer
8587
isOpen={showPlanDetail}
86-
handleClose={() => setShowPlanDetail(false)}
88+
setIsOpen={setShowPlanDetail}
8789
plan={selectedPlan}
90+
planPeriod={planPeriod}
91+
setPlanPeriod={setPlanPeriod}
92+
strategy={mode === 'mounted' ? 'fixed' : 'absolute'}
93+
portalProps={{
94+
id: mode === 'modal' ? PROFILE_CARD_SCROLLBOX_ID : undefined,
95+
}}
8896
/>
8997
</InternalThemeProvider>
9098
);

‎packages/clerk-js/src/ui/constants.ts

+2
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,5 @@ export const USER_BUTTON_ITEM_ID = {
1313
MANAGE_ACCOUNT: 'manageAccount',
1414
SIGN_OUT: 'signOut',
1515
};
16+
17+
export const PROFILE_CARD_SCROLLBOX_ID = 'clerk-profileCardScrollBox';

‎packages/clerk-js/src/ui/customizables/elementDescriptors.ts

+9
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,14 @@ export const APPEARANCE_KEYS = containsAllElementsConfigKeys([
9292
'dividerText',
9393
'dividerLine',
9494

95+
'drawerBackdrop',
96+
'drawerContent',
97+
'drawerHeader',
98+
'drawerTitle',
99+
'drawerBody',
100+
'drawerFooter',
101+
'drawerClose',
102+
95103
'formHeader',
96104
'formHeaderTitle',
97105
'formHeaderSubtitle',
@@ -221,6 +229,7 @@ export const APPEARANCE_KEYS = containsAllElementsConfigKeys([
221229
'planCardDescription',
222230
'planCardAvatarBadgeContainer',
223231
'planCardAvatar',
232+
'planCardBadge',
224233
'planCardFeatures',
225234
'planCardFeaturesList',
226235
'planCardFeaturesListItem',

‎packages/clerk-js/src/ui/elements/Avatar.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ const InitialsAvatarFallback = (props: { initials: string }) => {
107107
return (
108108
<Text
109109
as='span'
110-
sx={{ ...common.centeredFlex('inline-flex'), width: '100%' }}
110+
sx={t => ({ ...common.centeredFlex('inline-flex'), width: '100%', color: t.colors.$colorText })}
111111
>
112112
{initials}
113113
</Text>

‎packages/clerk-js/src/ui/elements/Disclosure.tsx

+6-6
Original file line numberDiff line numberDiff line change
@@ -123,17 +123,17 @@ interface ContentProps {
123123

124124
const Content = React.forwardRef<HTMLDivElement, ContentProps>(({ children }, ref) => {
125125
const context = React.useContext(DisclosureContext);
126+
const prefersReducedMotion = usePrefersReducedMotion();
127+
const { animations: layoutAnimations } = useAppearance().parsedLayout;
126128
if (!context) {
127129
throw new Error('Disclosure.Content must be used within Disclosure.Root');
128130
}
129131
const { isOpen, id } = context;
130-
const prefersReducedMotion = usePrefersReducedMotion();
131-
const { animations: appearanceAnimations } = useAppearance().parsedLayout;
132+
const isMotionSafe = !prefersReducedMotion && layoutAnimations === true;
132133
const animation: ThemableCssProp = t => ({
133-
transition:
134-
appearanceAnimations && !prefersReducedMotion
135-
? `grid-template-rows ${t.transitionDuration.$slower} ${t.transitionTiming.$slowBezier}`
136-
: 'none',
134+
transition: isMotionSafe
135+
? `grid-template-rows ${t.transitionDuration.$slower} ${t.transitionTiming.$slowBezier}`
136+
: 'none',
137137
});
138138

139139
return (

‎packages/clerk-js/src/ui/elements/Drawer.tsx

+416
Large diffs are not rendered by default.

‎packages/clerk-js/src/ui/elements/Modal.tsx

+1
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ export const Modal = withFloatingTree((props: ModalProps) => {
5454
nodeId={nodeId}
5555
context={context}
5656
isOpen={isOpen}
57+
outsideElementsInert
5758
>
5859
<ModalContext.Provider value={modalCtx}>
5960
<Flex

‎packages/clerk-js/src/ui/elements/Popover.tsx

+16-1
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,26 @@ type PopoverProps = PropsWithChildren<{
88
nodeId?: string;
99
isOpen?: boolean;
1010
initialFocus?: number | React.MutableRefObject<HTMLElement | null>;
11+
/**
12+
* Determines whether outside elements are inert when modal is enabled. This enables pointer modality without a backdrop.
13+
* @default false
14+
*/
15+
outsideElementsInert?: boolean;
1116
order?: Array<'reference' | 'floating' | 'content'>;
1217
portal?: boolean;
1318
}>;
1419

1520
export const Popover = (props: PopoverProps) => {
16-
const { context, initialFocus, order = ['reference', 'content'], nodeId, isOpen, portal = true, children } = props;
21+
const {
22+
context,
23+
initialFocus,
24+
outsideElementsInert = false,
25+
order = ['reference', 'content'],
26+
nodeId,
27+
isOpen,
28+
portal = true,
29+
children,
30+
} = props;
1731

1832
if (portal) {
1933
return (
@@ -23,6 +37,7 @@ export const Popover = (props: PopoverProps) => {
2337
<FloatingFocusManager
2438
context={context}
2539
initialFocus={initialFocus}
40+
outsideElementsInert={outsideElementsInert}
2641
order={order}
2742
>
2843
<>{children}</>

‎packages/clerk-js/src/ui/elements/ProfileCard/ProfileCardContent.tsx

+2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import React from 'react';
22

3+
import { PROFILE_CARD_SCROLLBOX_ID } from '../../constants';
34
import { Col, descriptors } from '../../customizables';
45
import { useRouter } from '../../router';
56
import { common, mqu } from '../../styledSystem';
@@ -42,6 +43,7 @@ export const ProfileCardContent = (props: ProfileCardContentProps) => {
4243
borderColor: t.colors.$neutralAlpha50,
4344
boxShadow: t.shadows.$cardContentShadow,
4445
})}
46+
id={PROFILE_CARD_SCROLLBOX_ID}
4547
>
4648
<Col
4749
elementDescriptor={descriptors.pageScrollBox}

‎packages/clerk-js/src/ui/elements/index.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,9 @@ export * from './ClipboardInput';
1212
export * from './CodeControl';
1313
export * from './contexts';
1414
export * from './DevModeNotice';
15-
export * from './Divider';
1615
export * from './Disclosure';
16+
export * from './Divider';
17+
export * from './Drawer';
1718
export * from './ErrorCard';
1819
export * from './Form';
1920
export * from './FormattedPhoneNumber';
@@ -46,6 +47,7 @@ export * from './ProfileCard';
4647
export * from './ReversibleContainer';
4748
export * from './RootBox';
4849
export * from './Section';
50+
export * from './SegmentedControl';
4951
export * from './Select';
5052
export * from './SuccessPage';
5153
export * from './Tabs';
@@ -58,4 +60,3 @@ export * from './UserPreview';
5860
export * from './VerificationCodeCard';
5961
export * from './VerificationLinkCard';
6062
export * from './withAvatarShimmer';
61-
export * from './SegmentedControl';

‎packages/clerk-js/src/ui/foundations/defaultFoundations.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { colors } from './colors';
33
import { opacity } from './opacity';
44
import { shadows } from './shadows';
55
import { radii, sizes, space } from './sizes';
6-
import { transitionDuration, transitionProperty, transitionTiming } from './transitions';
6+
import { transitionDuration, transitionDurationValues, transitionProperty, transitionTiming } from './transitions';
77
import { fonts, fontSizes, fontStyles, fontWeights, letterSpacings, lineHeights } from './typography';
88
import { zIndices } from './zIndices';
99

@@ -22,6 +22,7 @@ const defaultInternalThemeFoundations = Object.freeze({
2222
transitionProperty,
2323
transitionTiming,
2424
transitionDuration,
25+
transitionDurationValues,
2526
opacity,
2627
borderStyles,
2728
borderWidths,
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,22 @@
1-
const transitionDuration = Object.freeze({
2-
slowest: '600ms',
3-
slower: '280ms',
4-
slow: '200ms',
5-
fast: '120ms',
6-
focusRing: '200ms',
7-
controls: '100ms',
8-
textField: '450ms',
1+
const transitionDurationValues = Object.freeze({
2+
slowest: 600,
3+
slower: 280,
4+
slow: 200,
5+
fast: 120,
6+
focusRing: 200,
7+
controls: 100,
8+
textField: 450,
99
} as const);
1010

11+
const toMs = (value: number) => `${value}ms`;
12+
13+
const transitionDuration = Object.freeze(
14+
Object.fromEntries(Object.entries(transitionDurationValues).map(([key, value]) => [key, toMs(value)])) as Record<
15+
keyof typeof transitionDurationValues,
16+
string
17+
>,
18+
);
19+
1120
const transitionProperty = Object.freeze({
1221
common: 'background-color,background,border-color,color,fill,stroke,opacity,box-shadow,transform',
1322
} as const);
@@ -18,4 +27,4 @@ const transitionTiming = Object.freeze({
1827
slowBezier: 'cubic-bezier(0.16, 1, 0.3, 1)',
1928
} as const);
2029

21-
export { transitionDuration, transitionTiming, transitionProperty };
30+
export { transitionDuration, transitionTiming, transitionProperty, transitionDurationValues };
+9-8
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,22 @@
1+
export * from './useCheckout';
2+
export * from './useClerkModalStateParams';
3+
export * from './useClipboard';
4+
export * from './useDebounce';
15
export * from './useDelayedVisibility';
2-
export * from './useWindowEventListener';
36
export * from './useEmailLink';
4-
export * from './useClipboard';
57
export * from './useEnabledThirdPartyProviders';
8+
export * from './useEnterpriseSSOLink';
69
export * from './useFetch';
710
export * from './useInView';
811
export * from './useLoadingStatus';
12+
export * from './useLocalStorage';
13+
export * from './useNavigateToFlowStart';
914
export * from './usePassword';
1015
export * from './usePasswordComplexity';
1116
export * from './usePopover';
1217
export * from './usePrefersReducedMotion';
13-
export * from './useLocalStorage';
1418
export * from './useResizeObserver';
1519
export * from './useSafeState';
20+
export * from './useScrollLock';
1621
export * from './useSearchInput';
17-
export * from './useDebounce';
18-
export * from './useClerkModalStateParams';
19-
export * from './useNavigateToFlowStart';
20-
export * from './useEnterpriseSSOLink';
21-
export * from './useCheckout';
22+
export * from './useWindowEventListener';

‎packages/clerk-js/src/ui/polishedAppearance.ts

+4
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,10 @@ export const polishedAppearance: Appearance = {
221221
borderWidth: 0,
222222
boxShadow: `${theme.shadows.$cardBoxShadow}, ${BORDER_SHADOW_LENGTH} ${theme.colors.$neutralAlpha100}`,
223223
},
224+
drawerContent: {
225+
borderWidth: 0,
226+
boxShadow: `${theme.shadows.$cardBoxShadow}, ${BORDER_SHADOW_LENGTH} ${theme.colors.$neutralAlpha100}`,
227+
},
224228
popoverBox: {
225229
borderWidth: 0,
226230
boxShadow: `${theme.shadows.$cardBoxShadow}, ${BORDER_SHADOW_LENGTH} ${theme.colors.$neutralAlpha100}`,

‎packages/clerk-js/src/ui/primitives/Dd.tsx

+9-5
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
import React from 'react';
22

3-
export const Dd = React.forwardRef<
4-
HTMLDListElement,
5-
React.DetailedHTMLProps<React.HTMLAttributes<HTMLDListElement>, HTMLDListElement>
6-
>((props, ref) => {
3+
import type { PrimitiveProps } from '../styledSystem';
4+
import type { BoxProps } from './Box';
5+
import { Box } from './Box';
6+
7+
export type DdProps = PrimitiveProps<'dd'> & Omit<BoxProps, 'as'>;
8+
9+
export const Dd = React.forwardRef<HTMLTableCellElement, DdProps>((props, ref) => {
710
return (
8-
<dd
11+
<Box
12+
as='dd'
913
{...props}
1014
ref={ref}
1115
/>

‎packages/clerk-js/src/ui/primitives/Dl.tsx

+9-5
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
import React from 'react';
22

3-
export const Dl = React.forwardRef<
4-
HTMLDListElement,
5-
React.DetailedHTMLProps<React.HTMLAttributes<HTMLDListElement>, HTMLDListElement>
6-
>((props, ref) => {
3+
import type { PrimitiveProps } from '../styledSystem';
4+
import type { BoxProps } from './Box';
5+
import { Box } from './Box';
6+
7+
export type DlProps = PrimitiveProps<'dl'> & Omit<BoxProps, 'as'>;
8+
9+
export const Dl = React.forwardRef<HTMLTableCellElement, DlProps>((props, ref) => {
710
return (
8-
<dl
11+
<Box
12+
as='dl'
913
{...props}
1014
ref={ref}
1115
/>

‎packages/clerk-js/src/ui/primitives/Dt.tsx

+9-5
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
import React from 'react';
22

3-
export const Dt = React.forwardRef<
4-
HTMLDListElement,
5-
React.DetailedHTMLProps<React.HTMLAttributes<HTMLDListElement>, HTMLDListElement>
6-
>((props, ref) => {
3+
import type { PrimitiveProps } from '../styledSystem';
4+
import type { BoxProps } from './Box';
5+
import { Box } from './Box';
6+
7+
export type DtProps = PrimitiveProps<'dt'> & Omit<BoxProps, 'as'>;
8+
9+
export const Dt = React.forwardRef<HTMLTableCellElement, DtProps>((props, ref) => {
710
return (
8-
<dt
11+
<Box
12+
as='dt'
913
{...props}
1014
ref={ref}
1115
/>

‎packages/clerk-js/src/ui/styledSystem/animations.ts

+1-15
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ const outAnimation = keyframes`
9292
transform: translateY(0px);
9393
max-height: 6rem;
9494
visibility: visible;
95-
}
95+
}
9696
100% {
9797
opacity: 0;
9898
transform: translateY(5px);
@@ -128,18 +128,6 @@ const navbarSlideIn = keyframes`
128128
100% {opacity: 1; transform: translateX(0);}
129129
`;
130130

131-
const drawerSlideIn = keyframes`
132-
0% { opacity: 0; translate: 100% 0; }
133-
10% { opacity: 1; }
134-
100% { opacity: 1; translate: 0; }
135-
`;
136-
137-
const drawerSlideOut = keyframes`
138-
0% { opacity: 1; translate: 0; }
139-
90% { opacity: 1; }
140-
100% { opacity: 0; translate: 100% 0; }
141-
`;
142-
143131
export const animations = {
144132
spinning,
145133
dropdownSlideInScaleAndFade,
@@ -155,6 +143,4 @@ export const animations = {
155143
inDelayAnimation,
156144
outAnimation,
157145
notificationAnimation,
158-
drawerSlideIn,
159-
drawerSlideOut,
160146
};

‎packages/clerk-js/src/ui/types.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -108,8 +108,8 @@ export type __experimental_PricingTableCtx = __experimental_PricingTableProps &
108108
export type __experimental_CheckoutCtx = __experimental_CheckoutProps & {
109109
componentName: 'Checkout';
110110
mode?: ComponentMode;
111-
isShowingBlade?: boolean;
112-
handleCloseBlade?: () => void;
111+
isOpen?: boolean;
112+
setIsOpen?: (open: boolean) => void;
113113
};
114114

115115
export type AvailableComponentCtx =

‎packages/types/src/appearance.ts

+10-1
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,14 @@ export type ElementsConfig = {
210210
dividerText: WithOptions;
211211
dividerLine: WithOptions;
212212

213+
drawerBackdrop: WithOptions;
214+
drawerContent: WithOptions;
215+
drawerHeader: WithOptions;
216+
drawerTitle: WithOptions;
217+
drawerBody: WithOptions;
218+
drawerFooter: WithOptions;
219+
drawerClose: WithOptions;
220+
213221
formHeader: WithOptions<never, ErrorState>;
214222
formHeaderTitle: WithOptions<never, ErrorState>;
215223
formHeaderSubtitle: WithOptions<never, ErrorState>;
@@ -347,10 +355,11 @@ export type ElementsConfig = {
347355
planCardHeader: WithOptions;
348356
planCardAvatarBadgeContainer: WithOptions;
349357
planCardAvatar: WithOptions;
358+
planCardBadge: WithOptions;
350359
planCardTitle: WithOptions;
351360
planCardDescription: WithOptions;
352361
planCardFeatures: WithOptions;
353-
planCardFeaturesList: WithOptions;
362+
planCardFeaturesList: WithOptions<string>;
354363
planCardFeaturesListItem: WithOptions<string>;
355364
planCardAction: WithOptions;
356365
planCardPeriodToggle: WithOptions;

0 commit comments

Comments
 (0)
Please sign in to comment.