diff --git a/.changeset/lucky-cobras-cough.md b/.changeset/lucky-cobras-cough.md
new file mode 100644
index 00000000000..b4bc1768757
--- /dev/null
+++ b/.changeset/lucky-cobras-cough.md
@@ -0,0 +1,6 @@
+---
+'@clerk/clerk-js': patch
+'@clerk/types': patch
+---
+
+Introduce `` component and update commerce components implementations to make use of it.
diff --git a/packages/clerk-js/bundlewatch.config.json b/packages/clerk-js/bundlewatch.config.json
index 338aceb7db7..50d50a6ce75 100644
--- a/packages/clerk-js/bundlewatch.config.json
+++ b/packages/clerk-js/bundlewatch.config.json
@@ -1,10 +1,10 @@
{
"files": [
- { "path": "./dist/clerk.js", "maxSize": "573kB" },
+ { "path": "./dist/clerk.js", "maxSize": "575kB" },
{ "path": "./dist/clerk.browser.js", "maxSize": "78kB" },
{ "path": "./dist/clerk.headless.js", "maxSize": "50KB" },
- { "path": "./dist/ui-common*.js", "maxSize": "92KB" },
- { "path": "./dist/vendors*.js", "maxSize": "28.5KB" },
+ { "path": "./dist/ui-common*.js", "maxSize": "93KB" },
+ { "path": "./dist/vendors*.js", "maxSize": "30KB" },
{ "path": "./dist/coinbase*.js", "maxSize": "35.5KB" },
{ "path": "./dist/createorganization*.js", "maxSize": "5KB" },
{ "path": "./dist/impersonationfab*.js", "maxSize": "5KB" },
diff --git a/packages/clerk-js/src/ui/common/CommerceBlade.tsx b/packages/clerk-js/src/ui/common/CommerceBlade.tsx
deleted file mode 100644
index 360e9a55b09..00000000000
--- a/packages/clerk-js/src/ui/common/CommerceBlade.tsx
+++ /dev/null
@@ -1,72 +0,0 @@
-import { useEffect, useState } from 'react';
-
-import { Box } from '../customizables';
-import { animations } from '../styledSystem';
-
-interface CommerceBladeProps {
- isOpen: boolean;
- isFullscreen?: boolean;
- children: React.ReactNode;
-}
-
-export const CommerceBlade = ({ isOpen, isFullscreen, children }: CommerceBladeProps) => {
- const [mounted, setMounted] = useState(false);
-
- useEffect(() => {
- if (isOpen) {
- setMounted(true);
- return;
- } else {
- const timer = setTimeout(() => {
- setMounted(false);
- }, 280);
- return () => clearTimeout(timer);
- }
- }, [isOpen, mounted]);
-
- if (!mounted && !isOpen) {
- return null;
- }
-
- return (
-
- {children}
-
- );
-};
-
-const CommerceBladeContent = ({ isOpen, isFullscreen, children }: CommerceBladeProps) => {
- return (
- <>
- ({
- position: 'absolute',
- zIndex: t.zIndices.$modal,
- inset: 0,
- backgroundColor: t.colors.$whiteAlpha300,
- animation: `${isOpen ? animations.fadeIn : animations.fadeOut} ${t.transitionDuration.$slower} ${t.transitionTiming.$common}`,
- })}
- />
- ({
- position: isFullscreen ? 'fixed' : 'absolute',
- width: t.sizes.$100,
- inset: isFullscreen ? t.space.$3 : 0,
- insetInlineStart: 'auto',
- overflow: 'hidden',
- backgroundColor: t.colors.$colorBackground,
- borderRadius: `${t.radii.$xl} ${isFullscreen ? t.radii.$xl : 0} ${isFullscreen ? t.radii.$xl : 0} ${t.radii.$xl}`,
- boxShadow:
- '0px 0px 0px 1px rgba(25, 28, 33, 0.06), 0px 15px 35px -5px rgba(25, 28, 33, 0.20), 0px 5px 15px 0px rgba(0, 0, 0, 0.08)',
- zIndex: t.zIndices.$modal,
- animation: `${isOpen ? animations.drawerSlideIn : animations.drawerSlideOut} ${t.transitionDuration.$slower} ${t.transitionTiming.$slowBezier}`,
- })}
- >
- {children}
-
- >
- );
-};
diff --git a/packages/clerk-js/src/ui/common/index.ts b/packages/clerk-js/src/ui/common/index.ts
index 4641ea459bf..bea7cf9e442 100644
--- a/packages/clerk-js/src/ui/common/index.ts
+++ b/packages/clerk-js/src/ui/common/index.ts
@@ -1,6 +1,5 @@
export * from './BlockButtons';
export * from './CalloutWithAction';
-export * from './CommerceBlade';
export * from './constants';
export * from './EmailLinkStatusCard';
export * from './EmailLinkVerify';
diff --git a/packages/clerk-js/src/ui/components/Checkout/Checkout.tsx b/packages/clerk-js/src/ui/components/Checkout/Checkout.tsx
index 81ad97b6828..848a2edc0da 100644
--- a/packages/clerk-js/src/ui/components/Checkout/Checkout.tsx
+++ b/packages/clerk-js/src/ui/components/Checkout/Checkout.tsx
@@ -1,8 +1,9 @@
import type { __experimental_CheckoutProps } from '@clerk/types';
-import { CommerceBlade } from '../../common';
+import { PROFILE_CARD_SCROLLBOX_ID } from '../../constants';
import { useCheckoutContext, withCoreUserGuard } from '../../contexts';
import { Flow } from '../../customizables';
+import { Drawer } from '../../elements';
import { Route, Switch } from '../../router';
import { CheckoutPage } from './CheckoutPage';
@@ -21,14 +22,24 @@ export const __experimental_Checkout = (props: __experimental_CheckoutProps) =>
};
const AuthenticatedRoutes = withCoreUserGuard((props: __experimental_CheckoutProps) => {
- const { mode = 'mounted', isShowingBlade = false } = useCheckoutContext();
+ const { mode = 'mounted', isOpen = false, setIsOpen = () => {} } = useCheckoutContext();
return (
-
-
-
+
+
+
+
+
+
+
+
);
});
diff --git a/packages/clerk-js/src/ui/components/Checkout/CheckoutComplete.tsx b/packages/clerk-js/src/ui/components/Checkout/CheckoutComplete.tsx
index 13a43e04fba..00b9d5cb7ab 100644
--- a/packages/clerk-js/src/ui/components/Checkout/CheckoutComplete.tsx
+++ b/packages/clerk-js/src/ui/components/Checkout/CheckoutComplete.tsx
@@ -4,34 +4,30 @@ import { useCheckoutContext } from '../../contexts';
import { Box, Button, Col, Flex, Icon, Text } from '../../customizables';
import { LineItems } from '../../elements';
import { Check } from '../../icons';
-import type { ThemableCssProp } from '../../styledSystem';
-export const CheckoutComplete = ({
- checkout,
- sx,
-}: {
- checkout: __experimental_CommerceCheckoutResource;
- sx?: ThemableCssProp;
-}) => {
- const { handleCloseBlade = () => {} } = useCheckoutContext();
+export const CheckoutComplete = ({ checkout }: { checkout: __experimental_CommerceCheckoutResource }) => {
+ const { setIsOpen } = useCheckoutContext();
+
+ const handleClose = () => {
+ if (setIsOpen) {
+ setIsOpen(false);
+ }
+ };
return (
({
- width: '100%',
- padding: t.space.$4,
- }),
- sx,
- ]}
+ sx={{
+ flex: 1,
+ }}
>
({
flex: 1,
- }}
+ paddingBlock: t.space.$4,
+ })}
>
@@ -48,14 +44,15 @@ export const CheckoutComplete = ({
+
({
- flex: 0,
- paddingTop: t.space.$4,
+ padding: t.space.$4,
borderTopWidth: t.borderWidths.$normal,
borderTopStyle: t.borderStyles.$solid,
borderTopColor: t.colors.$neutralAlpha100,
+ position: 'relative',
})}
>
@@ -90,7 +87,7 @@ export const CheckoutComplete = ({
width: '100%',
marginTop: t.space.$2,
})}
- onClick={handleCloseBlade}
+ onClick={handleClose}
>
{/* TODO(@COMMERCE): needs localization */}
Continue
diff --git a/packages/clerk-js/src/ui/components/Checkout/CheckoutPage.tsx b/packages/clerk-js/src/ui/components/Checkout/CheckoutPage.tsx
index e1087cc7e22..f44fb7666f6 100644
--- a/packages/clerk-js/src/ui/components/Checkout/CheckoutPage.tsx
+++ b/packages/clerk-js/src/ui/components/Checkout/CheckoutPage.tsx
@@ -8,11 +8,10 @@ import type { Stripe } from '@stripe/stripe-js';
import { loadStripe } from '@stripe/stripe-js';
import { useEffect, useRef, useState } from 'react';
-import { useCheckoutContext, useEnvironment } from '../../contexts';
-import { Alert, Box, Button, Col, Flex, Heading, Icon, Spinner } from '../../customizables';
+import { useEnvironment } from '../../contexts';
+import { Alert, Col, Flex, Spinner } from '../../customizables';
import { LineItems } from '../../elements';
import { useCheckout } from '../../hooks';
-import { Close } from '../../icons';
import { CheckoutComplete } from './CheckoutComplete';
import { CheckoutForm } from './CheckoutForm';
@@ -44,8 +43,6 @@ export const CheckoutPage = (props: __experimental_CheckoutProps) => {
return (
<>
-
-
{isLoading ? (
{
There was a problem, please try again later.
) : checkout.status === 'completed' ? (
- ({ height: `calc(100% - ${t.space.$12})` })}
- />
+
) : (
- ({
- overflowY: 'auto',
- /* minus the height of the header */
- height: `calc(100% - ${t.space.$12})`,
- overflowX: 'hidden',
- })}
- >
+ <>
({
padding: t.space.$4,
- backgroundColor: t.colors.$neutralAlpha25,
borderBottomWidth: t.borderWidths.$normal,
borderBottomStyle: t.borderStyles.$solid,
borderBottomColor: t.colors.$neutralAlpha100,
@@ -105,49 +91,12 @@ export const CheckoutPage = (props: __experimental_CheckoutProps) => {
/>
)}
-
+ >
)}
>
);
};
-const CheckoutHeader = ({ title }: { title: string }) => {
- const { handleCloseBlade = () => {} } = useCheckoutContext();
-
- return (
- ({
- position: 'sticky',
- top: 0,
- width: '100%',
- height: t.space.$12,
- paddingInline: `${t.space.$5} ${t.space.$2}`,
- borderBottomWidth: t.borderWidths.$normal,
- borderBottomStyle: t.borderStyles.$solid,
- borderBottomColor: t.colors.$neutralAlpha100,
- })}
- >
- {title}
-
-
- );
-};
-
// TODO(@COMMERCE): needs localization
const CheckoutPlanRows = ({
plan,
diff --git a/packages/clerk-js/src/ui/components/PricingTable/PlanCard.tsx b/packages/clerk-js/src/ui/components/PricingTable/PlanCard.tsx
index f786d447c38..bc5aa512e21 100644
--- a/packages/clerk-js/src/ui/components/PricingTable/PlanCard.tsx
+++ b/packages/clerk-js/src/ui/components/PricingTable/PlanCard.tsx
@@ -23,10 +23,16 @@ import type { ThemableCssProp } from '../../styledSystem';
import { common } from '../../styledSystem';
import { colors } from '../../utils';
+export type PlanPeriod = 'month' | 'annual';
+
+/* -------------------------------------------------------------------------------------------------
+ * PlanCard
+ * -----------------------------------------------------------------------------------------------*/
+
interface PlanCardProps {
plan: __experimental_CommercePlanResource;
- period: string;
- setPeriod: (k: string) => void;
+ planPeriod: PlanPeriod;
+ setPlanPeriod: (p: PlanPeriod) => void;
onSelect: (plan: __experimental_CommercePlanResource) => void;
isCompact?: boolean;
props: __experimental_PricingTableProps;
@@ -34,37 +40,11 @@ interface PlanCardProps {
export function PlanCard(props: PlanCardProps) {
const clerk = useClerk();
- const { plan, period, setPeriod, onSelect, props: pricingTableProps, isCompact = false } = props;
- const {
- id,
- slug,
- name,
- description,
- avatarUrl,
- features,
- isActiveForPayer,
- hasBaseFee,
- currencySymbol,
- amountFormatted,
- annualMonthlyAmountFormatted,
- } = plan;
+ const { plan, planPeriod, setPlanPeriod, onSelect, props: pricingTableProps, isCompact = false } = props;
const { ctaPosition = 'top', collapseFeatures = false } = pricingTableProps;
- const [showAllFeatures, setShowAllFeatures] = React.useState(false);
+ const { id, slug, isActiveForPayer, features } = plan;
const totalFeatures = features.length;
const hasFeatures = totalFeatures > 0;
- const canToggleFeatures = isCompact && totalFeatures > 3;
- const prefersReducedMotion = usePrefersReducedMotion();
- const { animations: appearanceAnimations } = useAppearance().parsedLayout;
- const planCardFeePeriodNoticeAnimation: ThemableCssProp = t => ({
- transition:
- appearanceAnimations && !prefersReducedMotion
- ? `grid-template-rows ${t.transitionDuration.$slower} ${t.transitionTiming.$slowBezier}`
- : 'none',
- });
-
- const toggleFeatures = () => {
- setShowAllFeatures(prev => !prev);
- };
return (
- ({
- padding: isCompact ? t.space.$3 : t.space.$4,
- })}
- >
- {avatarUrl || isActiveForPayer ? (
- ({
- display: 'flex',
- alignItems: 'start',
- justifyContent: 'space-between',
- gap: t.space.$3,
- marginBlockEnd: t.space.$3,
- })}
- >
- {avatarUrl ? (
- 40}
- title={name}
- initials={name[0]}
- rounded={false}
- imageUrl={avatarUrl}
- />
- ) : null}
- {isActiveForPayer ? (
-
- ) : null}
-
- ) : null}
-
- {name}
-
- {!isCompact && description ? (
-
- {description}
-
- ) : null}
- ({
- marginTop: isCompact ? t.space.$2 : t.space.$3,
- columnGap: t.space.$1x5,
- })}
- >
- {hasBaseFee ? (
- <>
-
- {currencySymbol}
- {period === 'month' ? amountFormatted : annualMonthlyAmountFormatted}
-
- ({
- textTransform: 'lowercase',
- ':before': {
- content: '"/"',
- marginInlineEnd: t.space.$1,
- },
- })}
- localizationKey={localizationKeys('__experimental_commerce.month')}
- />
- ({
- width: '100%',
- display: 'grid',
- gridTemplateRows: period === 'annual' ? '1fr' : '0fr',
- }),
- planCardFeePeriodNoticeAnimation,
- ]}
- // @ts-ignore - Needed until React 19 support
- inert={period !== 'annual' ? 'true' : undefined}
- >
-
- ({
- width: '100%',
- display: 'flex',
- alignItems: 'center',
- columnGap: t.space.$1,
- })}
- >
- {' '}
-
-
-
-
- >
- ) : (
-
- )}
-
- {hasBaseFee ? (
- ({
- display: 'flex',
- marginTop: t.space.$3,
- })}
- >
-
- Monthly
- Annually
-
-
- ) : null}
-
+
{!collapseFeatures && hasFeatures ? (
- ({
- display: 'grid',
- flex: '1',
- rowGap: isCompact ? t.space.$2 : t.space.$3,
- })}
- >
- {features.slice(0, showAllFeatures ? totalFeatures : 3).map(feature => (
-
-
-
- {feature.description || feature.name}
-
-
- ))}
-
- {canToggleFeatures && (
- ({
- marginBlockStart: t.space.$2,
- gap: t.space.$1,
- })}
- >
-
- {showAllFeatures ? 'Hide features' : 'See all features'}
-
- )}
+
) : null}
void;
+ closeSlot?: React.ReactNode;
+}
+
+export const PlanCardHeader = React.forwardRef((props, ref) => {
+ const prefersReducedMotion = usePrefersReducedMotion();
+ const { animations: layoutAnimations } = useAppearance().parsedLayout;
+ const { plan, isCompact, planPeriod, setPlanPeriod, closeSlot } = props;
+ const { name, avatarUrl, isActiveForPayer } = plan;
+ const isMotionSafe = !prefersReducedMotion && layoutAnimations === true;
+ const planCardFeePeriodNoticeAnimation: ThemableCssProp = t => ({
+ transition: isMotionSafe
+ ? `grid-template-rows ${t.transitionDuration.$slower} ${t.transitionTiming.$slowBezier}`
+ : 'none',
+ });
+ return (
+ ({
+ width: '100%',
+ padding: isCompact ? t.space.$3 : t.space.$4,
+ })}
+ >
+ {avatarUrl || isActiveForPayer || closeSlot ? (
+ ({
+ marginBlockEnd: t.space.$3,
+ ...(!avatarUrl && !isActiveForPayer
+ ? {
+ float: 'right',
+ }
+ : {
+ display: 'grid',
+ gridTemplateColumns: 'repeat(2, minmax(0,1fr))',
+ alignItems: 'start',
+ justifyContent: 'space-between',
+ gap: t.space.$3,
+ }),
+ })}
+ >
+ {avatarUrl ? (
+ 40}
+ title={name}
+ initials={name[0]}
+ rounded={false}
+ imageUrl={avatarUrl}
+ sx={{
+ gridRowStart: 1,
+ }}
+ />
+ ) : null}
+
+ {isActiveForPayer ? (
+
+ ) : null}
+ {closeSlot}
+
+
+ ) : null}
+
+ {plan.name}
+
+ {!isCompact && plan.description ? (
+
+ {plan.description}
+
+ ) : null}
+ ({
+ marginTop: isCompact ? t.space.$2 : t.space.$3,
+ columnGap: t.space.$1x5,
+ })}
+ >
+ {plan.hasBaseFee ? (
+ <>
+
+ {plan.currencySymbol}
+ {planPeriod === 'month' ? plan.amountFormatted : plan.annualMonthlyAmountFormatted}
+
+ ({
+ textTransform: 'lowercase',
+ ':before': {
+ content: '"/"',
+ marginInlineEnd: t.space.$1,
+ },
+ })}
+ localizationKey={localizationKeys('__experimental_commerce.month')}
+ />
+ ({
+ width: '100%',
+ display: 'grid',
+ gridTemplateRows: planPeriod === 'annual' ? '1fr' : '0fr',
+ }),
+ planCardFeePeriodNoticeAnimation,
+ ]}
+ // @ts-ignore - Needed until React 19 support
+ inert={planPeriod !== 'annual' ? 'true' : undefined}
+ >
+
+ ({
+ width: '100%',
+ display: 'flex',
+ alignItems: 'center',
+ columnGap: t.space.$1,
+ })}
+ >
+ {' '}
+
+
+
+
+ >
+ ) : (
+
+ )}
+
+ {plan.hasBaseFee ? (
+ ({
+ display: 'flex',
+ marginTop: t.space.$3,
+ })}
+ >
+ setPlanPeriod(value as PlanPeriod)}
+ >
+ Monthly
+ Annually
+
+
+ ) : null}
+
+ );
+});
+
+/* -------------------------------------------------------------------------------------------------
+ * PlanCardFeaturesList
+ * -----------------------------------------------------------------------------------------------*/
+
+interface PlanCardFeaturesListProps {
+ plan: __experimental_CommercePlanResource;
+ isCompact?: boolean;
+}
+
+export const PlanCardFeaturesList = React.forwardRef((props, ref) => {
+ const { plan, isCompact } = props;
+ const totalFeatures = plan.features.length;
+ const [showAllFeatures, setShowAllFeatures] = React.useState(false);
+ const canToggleFeatures = isCompact && totalFeatures > 3;
+ const toggleFeatures = () => {
+ setShowAllFeatures(prev => !prev);
+ };
+ return (
+ ({
+ display: 'grid',
+ flex: '1',
+ rowGap: isCompact ? t.space.$2 : t.space.$3,
+ })}
+ >
+ ({
+ display: 'grid',
+ flex: '1',
+ rowGap: isCompact ? t.space.$2 : t.space.$3,
+ })}
+ >
+ {plan.features.slice(0, showAllFeatures ? totalFeatures : 3).map(feature => (
+ ({
+ display: 'flex',
+ alignItems: 'baseline',
+ gap: t.space.$2,
+ })}
+ >
+ ({
+ transform: `translateY(${t.space.$0x25})`,
+ })}
+ />
+ {feature.description || feature.name}
+
+ ))}
+
+ {canToggleFeatures && (
+ ({
+ marginBlockStart: t.space.$2,
+ gap: t.space.$1,
+ })}
+ >
+
+ {showAllFeatures ? 'Hide features' : 'See all features'}
+
+ )}
+
+ );
+});
+
function ReversibleContainer(props: React.PropsWithChildren<{ reverse?: boolean }>) {
const { children, reverse } = props;
return <>{reverse ? React.Children.toArray(children).reverse() : children}>;
diff --git a/packages/clerk-js/src/ui/components/PricingTable/PlanDetailBlade.tsx b/packages/clerk-js/src/ui/components/PricingTable/PlanDetailBlade.tsx
deleted file mode 100644
index 3191ff06b05..00000000000
--- a/packages/clerk-js/src/ui/components/PricingTable/PlanDetailBlade.tsx
+++ /dev/null
@@ -1,243 +0,0 @@
-import type { __experimental_CommercePlanResource } from '@clerk/types';
-import { useState } from 'react';
-
-import { CommerceBlade } from '../../common';
-import { Alert, Button, Col, Flex, Heading, Icon, localizationKeys, Text } from '../../customizables';
-import { Avatar } from '../../elements';
-import { Close } from '../../icons';
-
-interface PlanDetailBladeProps {
- isOpen: boolean;
- handleClose: () => void;
- plan?: __experimental_CommercePlanResource;
-}
-
-export const PlanDetailBlade = ({ isOpen, handleClose, plan }: PlanDetailBladeProps) => {
- if (!plan) {
- return null;
- }
-
- return (
-
-
- ({
- flex: 0,
- padding: t.space.$4,
- borderBottomWidth: t.borderWidths.$normal,
- borderBottomStyle: t.borderStyles.$solid,
- borderBottomColor: t.colors.$neutralAlpha100,
- backgroundImage: `linear-gradient(to top, ${t.colors.$neutralAlpha50} 30%, ${t.colors.$colorBackground} 100%)`,
- })}
- >
-
- 40}
- title={plan.name}
- initials={plan.name[0]}
- rounded={false}
- imageUrl={plan.avatarUrl}
- />
-
- {plan.name}
- {plan.hasBaseFee ? (
-
-
- {plan.currencySymbol}
- {plan.amountFormatted}
-
-
-
- /
-
-
-
-
- ) : (
-
- )}
-
-
- {plan.description}
-
-
-
- ({
- flex: 1,
- padding: t.space.$4,
- overflowY: 'auto',
- overflowX: 'hidden',
- })}
- >
-
- Available features
-
- {plan.features.map(feature => (
-
- 24}
- title={feature.name}
- rounded={false}
- imageUrl={feature.avatarUrl}
- />
-
- {feature.name}
-
- {feature.description}
-
-
-
- ))}
-
-
-
-
-
- );
-};
-
-const CancelFooter = ({ plan }: { plan: __experimental_CommercePlanResource; handleClose: () => void }) => {
- // const { __experimental_commerce } = useClerk();
- const [showConfirmation, setShowConfirmation] = useState(false);
- const [isSubmitting, setIsSubmitting] = useState(false);
- const [hasError, setHasError] = useState(false);
-
- const cancelSubscription = async () => {
- setHasError(false);
- setIsSubmitting(true);
-
- // TODO: we need to get a handle on the subscription object in order to cancel it,
- // but this method doesn't exist yet.
- //
- // await subscription.cancel().then(() => {
- // setIsSubmitting(false);
- // handleClose();
- // }).catch(() => { setHasError(true); setIsSubmitting(false); });
- };
-
- // TODO: remove when we can hook up cancel button
- // return null;
-
- return (
- ({
- flex: 0,
- padding: t.space.$4,
- borderTopWidth: t.borderWidths.$normal,
- borderTopStyle: t.borderStyles.$solid,
- borderTopColor: t.colors.$neutralAlpha100,
- backgroundColor: t.colors.$neutralAlpha50,
- })}
- >
- {showConfirmation ? (
-
- Cancel {plan.name} Subscription?
-
- You can keep using “{plan.name}” features until [DATE], after which you will no longer have
- access.
-
- {hasError && (
- There was a problem canceling your subscription, please try again.
- )}
-
- {!isSubmitting && (
-
- )}
-
-
-
- ) : (
-
- )}
-
- );
-};
diff --git a/packages/clerk-js/src/ui/components/PricingTable/PlanDetailDrawer.tsx b/packages/clerk-js/src/ui/components/PricingTable/PlanDetailDrawer.tsx
new file mode 100644
index 00000000000..abf44033e4f
--- /dev/null
+++ b/packages/clerk-js/src/ui/components/PricingTable/PlanDetailDrawer.tsx
@@ -0,0 +1,160 @@
+import type { __experimental_CommercePlanResource } from '@clerk/types';
+import * as React from 'react';
+
+import { Alert, Box, Button, Col, Flex, Heading, Text } from '../../customizables';
+import { Drawer } from '../../elements';
+import type { PlanPeriod } from './PlanCard';
+import { PlanCardFeaturesList, PlanCardHeader } from './PlanCard';
+
+type DrawerRootProps = React.ComponentProps;
+
+type PlanDetailDrawerProps = {
+ isOpen: DrawerRootProps['open'];
+ setIsOpen: DrawerRootProps['onOpenChange'];
+ portalProps?: DrawerRootProps['portalProps'];
+ strategy: DrawerRootProps['strategy'];
+ plan?: __experimental_CommercePlanResource;
+ planPeriod: PlanPeriod;
+ setPlanPeriod: (p: PlanPeriod) => void;
+};
+
+export function PlanDetailDrawer({
+ isOpen,
+ setIsOpen,
+ portalProps,
+ strategy,
+ plan,
+ planPeriod,
+ setPlanPeriod,
+}: PlanDetailDrawerProps) {
+ if (!plan) {
+ return null;
+ }
+ const hasFeatures = plan.features.length > 0;
+ return (
+
+
+
+
+ !hasFeatures
+ ? {
+ flex: 1,
+ borderBottomWidth: 0,
+ background: t.colors.$colorBackground,
+ }
+ : null
+ }
+ >
+ }
+ />
+
+ {hasFeatures ? (
+
+ ({
+ padding: t.space.$4,
+ })}
+ >
+
+
+
+ ) : null}
+ setIsOpen(false)}
+ />
+
+
+ );
+}
+
+const CancelFooter = ({ plan }: { plan: __experimental_CommercePlanResource; handleClose: () => void }) => {
+ // const { __experimental_commerce } = useClerk();
+ const [showConfirmation, setShowConfirmation] = React.useState(false);
+ const [isSubmitting, setIsSubmitting] = React.useState(false);
+ const [hasError, setHasError] = React.useState(false);
+
+ const cancelSubscription = async () => {
+ setHasError(false);
+ setIsSubmitting(true);
+
+ // TODO: we need to get a handle on the subscription object in order to cancel it,
+ // but this method doesn't exist yet.
+ //
+ // await subscription.cancel().then(() => {
+ // setIsSubmitting(false);
+ // handleClose();
+ // }).catch(() => { setHasError(true); setIsSubmitting(false); });
+ };
+
+ // TODO: remove when we can hook up cancel button
+ // return null;
+
+ return (
+
+ {showConfirmation ? (
+
+ Cancel {plan.name} Subscription?
+
+ You can keep using “{plan.name}” features until [DATE], after which you will no longer have
+ access.
+
+ {hasError && (
+ There was a problem canceling your subscription, please try again.
+ )}
+
+ {!isSubmitting && (
+
+ )}
+
+
+
+ ) : (
+
+ )}
+
+ );
+};
diff --git a/packages/clerk-js/src/ui/components/PricingTable/PricingTable.tsx b/packages/clerk-js/src/ui/components/PricingTable/PricingTable.tsx
index 6b788ae4472..8b29e3a5227 100644
--- a/packages/clerk-js/src/ui/components/PricingTable/PricingTable.tsx
+++ b/packages/clerk-js/src/ui/components/PricingTable/PricingTable.tsx
@@ -2,18 +2,20 @@ import { useClerk } from '@clerk/shared/react';
import type { __experimental_CommercePlanResource, __experimental_PricingTableProps } from '@clerk/types';
import { useState } from 'react';
+import { PROFILE_CARD_SCROLLBOX_ID } from '../../constants';
import { __experimental_CheckoutContext, usePricingTableContext } from '../../contexts';
import { Box, descriptors } from '../../customizables';
import { useFetch } from '../../hooks';
import { InternalThemeProvider } from '../../styledSystem';
import { __experimental_Checkout } from '../Checkout';
+import type { PlanPeriod } from './PlanCard';
import { PlanCard } from './PlanCard';
-import { PlanDetailBlade } from './PlanDetailBlade';
+import { PlanDetailDrawer } from './PlanDetailDrawer';
export const __experimental_PricingTable = (props: __experimental_PricingTableProps) => {
const { __experimental_commerce } = useClerk();
const { mode = 'mounted' } = usePricingTableContext();
- const [planPeriod, setPlanPeriod] = useState('month');
+ const [planPeriod, setPlanPeriod] = useState('month');
const [selectedPlan, setSelectedPlan] = useState<__experimental_CommercePlanResource>();
const [showCheckout, setShowCheckout] = useState(false);
const [showPlanDetail, setShowPlanDetail] = useState(false);
@@ -57,8 +59,8 @@ export const __experimental_PricingTable = (props: __experimental_PricingTablePr
setShowCheckout(false),
+ isOpen: showCheckout,
+ setIsOpen: setShowCheckout,
}}
>
{/*TODO: Used by InvisibleRootBox, can we simplify? */}
@@ -81,10 +83,16 @@ export const __experimental_PricingTable = (props: __experimental_PricingTablePr
/>
- setShowPlanDetail(false)}
+ setIsOpen={setShowPlanDetail}
plan={selectedPlan}
+ planPeriod={planPeriod}
+ setPlanPeriod={setPlanPeriod}
+ strategy={mode === 'mounted' ? 'fixed' : 'absolute'}
+ portalProps={{
+ id: mode === 'modal' ? PROFILE_CARD_SCROLLBOX_ID : undefined,
+ }}
/>
);
diff --git a/packages/clerk-js/src/ui/constants.ts b/packages/clerk-js/src/ui/constants.ts
index 1752d530dfc..c7dd82fe64e 100644
--- a/packages/clerk-js/src/ui/constants.ts
+++ b/packages/clerk-js/src/ui/constants.ts
@@ -13,3 +13,5 @@ export const USER_BUTTON_ITEM_ID = {
MANAGE_ACCOUNT: 'manageAccount',
SIGN_OUT: 'signOut',
};
+
+export const PROFILE_CARD_SCROLLBOX_ID = 'clerk-profileCardScrollBox';
diff --git a/packages/clerk-js/src/ui/customizables/elementDescriptors.ts b/packages/clerk-js/src/ui/customizables/elementDescriptors.ts
index 38890433cff..4a4883ab9eb 100644
--- a/packages/clerk-js/src/ui/customizables/elementDescriptors.ts
+++ b/packages/clerk-js/src/ui/customizables/elementDescriptors.ts
@@ -92,6 +92,14 @@ export const APPEARANCE_KEYS = containsAllElementsConfigKeys([
'dividerText',
'dividerLine',
+ 'drawerBackdrop',
+ 'drawerContent',
+ 'drawerHeader',
+ 'drawerTitle',
+ 'drawerBody',
+ 'drawerFooter',
+ 'drawerClose',
+
'formHeader',
'formHeaderTitle',
'formHeaderSubtitle',
@@ -221,6 +229,7 @@ export const APPEARANCE_KEYS = containsAllElementsConfigKeys([
'planCardDescription',
'planCardAvatarBadgeContainer',
'planCardAvatar',
+ 'planCardBadge',
'planCardFeatures',
'planCardFeaturesList',
'planCardFeaturesListItem',
diff --git a/packages/clerk-js/src/ui/elements/Avatar.tsx b/packages/clerk-js/src/ui/elements/Avatar.tsx
index 0c960467858..d87c691fa2e 100644
--- a/packages/clerk-js/src/ui/elements/Avatar.tsx
+++ b/packages/clerk-js/src/ui/elements/Avatar.tsx
@@ -107,7 +107,7 @@ const InitialsAvatarFallback = (props: { initials: string }) => {
return (
({ ...common.centeredFlex('inline-flex'), width: '100%', color: t.colors.$colorText })}
>
{initials}
diff --git a/packages/clerk-js/src/ui/elements/Disclosure.tsx b/packages/clerk-js/src/ui/elements/Disclosure.tsx
index b667ce39f8b..6731e394716 100644
--- a/packages/clerk-js/src/ui/elements/Disclosure.tsx
+++ b/packages/clerk-js/src/ui/elements/Disclosure.tsx
@@ -123,17 +123,17 @@ interface ContentProps {
const Content = React.forwardRef(({ children }, ref) => {
const context = React.useContext(DisclosureContext);
+ const prefersReducedMotion = usePrefersReducedMotion();
+ const { animations: layoutAnimations } = useAppearance().parsedLayout;
if (!context) {
throw new Error('Disclosure.Content must be used within Disclosure.Root');
}
const { isOpen, id } = context;
- const prefersReducedMotion = usePrefersReducedMotion();
- const { animations: appearanceAnimations } = useAppearance().parsedLayout;
+ const isMotionSafe = !prefersReducedMotion && layoutAnimations === true;
const animation: ThemableCssProp = t => ({
- transition:
- appearanceAnimations && !prefersReducedMotion
- ? `grid-template-rows ${t.transitionDuration.$slower} ${t.transitionTiming.$slowBezier}`
- : 'none',
+ transition: isMotionSafe
+ ? `grid-template-rows ${t.transitionDuration.$slower} ${t.transitionTiming.$slowBezier}`
+ : 'none',
});
return (
diff --git a/packages/clerk-js/src/ui/elements/Drawer.tsx b/packages/clerk-js/src/ui/elements/Drawer.tsx
new file mode 100644
index 00000000000..d65ebc68991
--- /dev/null
+++ b/packages/clerk-js/src/ui/elements/Drawer.tsx
@@ -0,0 +1,416 @@
+import { useSafeLayoutEffect } from '@clerk/shared/react/index';
+import type { UseDismissProps, UseFloatingOptions } from '@floating-ui/react';
+import {
+ FloatingFocusManager,
+ FloatingPortal,
+ useClick,
+ useDismiss,
+ useFloating,
+ useInteractions,
+ useMergeRefs,
+ useRole,
+ useTransitionStyles,
+} from '@floating-ui/react';
+import * as React from 'react';
+
+import { transitionDurationValues, transitionTiming } from '../../ui/foundations/transitions';
+import { Box, descriptors, Flex, Heading, Icon, useAppearance } from '../customizables';
+import { usePrefersReducedMotion } from '../hooks';
+import { useScrollLock } from '../hooks/useScrollLock';
+import { Close as CloseIcon } from '../icons';
+import type { ThemableCssProp } from '../styledSystem';
+import { common, InternalThemeProvider } from '../styledSystem';
+import { colors } from '../utils';
+import { IconButton } from './IconButton';
+
+type FloatingPortalProps = React.ComponentProps;
+
+/* -------------------------------------------------------------------------------------------------
+ * Drawer Context
+ * -----------------------------------------------------------------------------------------------*/
+
+interface DrawerContext {
+ isOpen: boolean;
+ setIsOpen: (open: boolean) => void;
+ strategy: UseFloatingOptions['strategy'];
+ refs: ReturnType['refs'];
+ context: ReturnType['context'];
+ getFloatingProps: ReturnType['getFloatingProps'];
+ portalProps: FloatingPortalProps;
+}
+
+const DrawerContext = React.createContext(null);
+
+export const useDrawerContext = () => {
+ const context = React.useContext(DrawerContext);
+ if (!context) {
+ throw new Error('Drawer components must be wrapped in ');
+ }
+ return context;
+};
+
+/* -------------------------------------------------------------------------------------------------
+ * Drawer.Root
+ * -----------------------------------------------------------------------------------------------*/
+
+interface RootProps {
+ children: React.ReactNode;
+ open: boolean;
+ onOpenChange: (open: boolean) => void;
+ /**
+ * The strategy to use when positioning the floating element.
+ * @default 'fixed'
+ * @see https://floating-ui.com/docs/useFloating#strategy
+ */
+ strategy?: UseFloatingOptions['strategy'];
+ /**
+ * @see https://floating-ui.com/docs/useFloating
+ */
+ floatingProps?: Omit;
+ /**
+ * @see https://floating-ui.com/docs/FloatingPortal
+ */
+ portalProps?: FloatingPortalProps;
+ /**
+ * @see https://floating-ui.com/docs/useDismiss
+ */
+ dismissProps?: UseDismissProps;
+}
+
+function Root({
+ children,
+ open,
+ onOpenChange,
+ strategy = 'fixed',
+ floatingProps,
+ portalProps,
+ dismissProps,
+}: RootProps) {
+ const { refs, context } = useFloating({
+ open,
+ onOpenChange,
+ transform: false,
+ strategy,
+ ...floatingProps,
+ });
+
+ const { getFloatingProps } = useInteractions([
+ useClick(context),
+ useDismiss(context, dismissProps),
+ useRole(context),
+ ]);
+
+ return (
+
+
+ {children}
+
+
+ );
+}
+
+/* -------------------------------------------------------------------------------------------------
+ * Drawer.Overlay
+ * -----------------------------------------------------------------------------------------------*/
+
+export const FloatingOverlay = React.forwardRef(function FloatingOverlay(
+ props: React.ComponentPropsWithoutRef,
+ ref: React.ForwardedRef,
+) {
+ const { disableScrollLock, enableScrollLock } = useScrollLock();
+
+ useSafeLayoutEffect(() => {
+ enableScrollLock();
+
+ return () => {
+ disableScrollLock();
+ };
+ }, []);
+
+ return (
+ ({
+ inset: 0,
+ backgroundColor: colors.setAlpha(t.colors.$colorBackground, 0.28),
+ }),
+ props.sx,
+ ]}
+ />
+ );
+});
+
+const Overlay = React.forwardRef((_, ref) => {
+ const { strategy, context } = useDrawerContext();
+
+ const { isMounted, styles: transitionStyles } = useTransitionStyles(context, {
+ initial: { opacity: 0 },
+ open: { opacity: 1 },
+ close: { opacity: 0 },
+ common: {
+ position: strategy,
+ inset: 0,
+ transitionProperty: 'opacity',
+ transitionTimingFunction: transitionTiming.slowBezier,
+ },
+ duration: {
+ open: transitionDurationValues.slower,
+ close: transitionDurationValues.slow,
+ },
+ });
+
+ if (!isMounted) return null;
+
+ return (
+
+ );
+});
+
+Overlay.displayName = 'Drawer.Overlay';
+
+/* -------------------------------------------------------------------------------------------------
+ * Drawer.Content
+ * -----------------------------------------------------------------------------------------------*/
+
+interface ContentProps {
+ children: React.ReactNode;
+}
+
+const Content = React.forwardRef(({ children }, ref) => {
+ const prefersReducedMotion = usePrefersReducedMotion();
+ const { animations: layoutAnimations } = useAppearance().parsedLayout;
+ const isMotionSafe = !prefersReducedMotion && layoutAnimations === true;
+ const { strategy, portalProps, refs, context, getFloatingProps } = useDrawerContext();
+ const mergedRefs = useMergeRefs([ref, refs.setFloating]);
+
+ const { isMounted, styles: transitionStyles } = useTransitionStyles(context, {
+ initial: { transform: 'translateX(100%)' },
+ open: { transform: 'translateX(0)' },
+ close: { transform: 'translateX(100%)' },
+ common: {
+ transitionProperty: 'transform',
+ transitionTimingFunction: transitionTiming.slowBezier,
+ },
+ duration: isMotionSafe
+ ? {
+ open: transitionDurationValues.slower,
+ close: transitionDurationValues.slow,
+ }
+ : 0,
+ });
+
+ if (!isMounted) return null;
+
+ return (
+
+
+ ({
+ position: strategy,
+ insetBlock: strategy === 'fixed' ? t.space.$3 : 0,
+ insetInlineEnd: strategy === 'fixed' ? t.space.$3 : 0,
+ outline: 0,
+ width: t.sizes.$100,
+ backgroundColor: t.colors.$colorBackground,
+ borderStartStartRadius: t.radii.$xl,
+ borderEndStartRadius: t.radii.$xl,
+ borderEndEndRadius: strategy === 'fixed' ? t.radii.$xl : 0,
+ borderStartEndRadius: strategy === 'fixed' ? t.radii.$xl : 0,
+ borderWidth: t.borderWidths.$normal,
+ borderStyle: t.borderStyles.$solid,
+ borderColor: t.colors.$neutralAlpha100,
+ boxShadow: t.shadows.$cardBoxShadow,
+ overflow: 'hidden',
+ zIndex: t.zIndices.$modal,
+ })}
+ >
+ {children}
+
+
+
+ );
+});
+
+Overlay.displayName = 'Drawer.Content';
+
+/* -------------------------------------------------------------------------------------------------
+ * Drawer.Header
+ * -----------------------------------------------------------------------------------------------*/
+
+interface HeaderProps {
+ title?: string;
+ children?: React.ReactNode;
+ sx?: ThemableCssProp;
+}
+
+const Header = React.forwardRef(({ title, children, sx }, ref) => {
+ return (
+ ({
+ display: 'flex',
+ background: common.mergedColorsBackground(
+ colors.setAlpha(t.colors.$colorBackground, 1),
+ t.colors.$neutralAlpha50,
+ ),
+ borderBlockEndWidth: t.borderWidths.$normal,
+ borderBlockEndStyle: t.borderStyles.$solid,
+ borderBlockEndColor: t.colors.$neutralAlpha100,
+ borderStartStartRadius: t.radii.$xl,
+ borderStartEndRadius: t.radii.$xl,
+ paddingBlock: title ? t.space.$3 : undefined,
+ paddingInline: title ? t.space.$4 : undefined,
+ }),
+ sx,
+ ]}
+ >
+ {title ? (
+ <>
+
+ {title}
+
+
+ >
+ ) : (
+ children
+ )}
+
+ );
+});
+
+/* -------------------------------------------------------------------------------------------------
+ * Drawer.Body
+ * -----------------------------------------------------------------------------------------------*/
+
+interface BodyProps {
+ children: React.ReactNode;
+}
+
+const Body = React.forwardRef(({ children }, ref) => {
+ return (
+
+ {children}
+
+ );
+});
+
+/* -------------------------------------------------------------------------------------------------
+ * Drawer.Footer
+ * -----------------------------------------------------------------------------------------------*/
+
+interface FooterProps {
+ children?: React.ReactNode;
+}
+
+const Footer = React.forwardRef(({ children }, ref) => {
+ return (
+ ({
+ display: 'flex',
+ background: common.mergedColorsBackground(
+ colors.setAlpha(t.colors.$colorBackground, 1),
+ t.colors.$neutralAlpha50,
+ ),
+ borderBlockStartWidth: t.borderWidths.$normal,
+ borderBlockStartStyle: t.borderStyles.$solid,
+ borderBlockStartColor: t.colors.$neutralAlpha100,
+ borderEndStartRadius: t.radii.$xl,
+ borderEndEndRadius: t.radii.$xl,
+ paddingBlock: t.space.$3,
+ paddingInline: t.space.$4,
+ })}
+ >
+ {children}
+
+ );
+});
+
+/* -------------------------------------------------------------------------------------------------
+ * Drawer.Close
+ * -----------------------------------------------------------------------------------------------*/
+
+const Close = React.forwardRef((_, ref) => {
+ const { setIsOpen } = useDrawerContext();
+ return (
+ setIsOpen(false)}
+ icon={
+
+ }
+ sx={t => ({
+ color: t.colors.$colorTextSecondary,
+ padding: t.space.$3,
+ marginInlineStart: 'auto',
+ })}
+ />
+ );
+});
+
+Close.displayName = 'Drawer.Close';
+
+export const Drawer = {
+ Root,
+ Overlay,
+ Content,
+ Header,
+ Body,
+ Footer,
+ Close,
+};
diff --git a/packages/clerk-js/src/ui/elements/Modal.tsx b/packages/clerk-js/src/ui/elements/Modal.tsx
index ee4e035aae4..01308703099 100644
--- a/packages/clerk-js/src/ui/elements/Modal.tsx
+++ b/packages/clerk-js/src/ui/elements/Modal.tsx
@@ -54,6 +54,7 @@ export const Modal = withFloatingTree((props: ModalProps) => {
nodeId={nodeId}
context={context}
isOpen={isOpen}
+ outsideElementsInert
>
;
+ /**
+ * Determines whether outside elements are inert when modal is enabled. This enables pointer modality without a backdrop.
+ * @default false
+ */
+ outsideElementsInert?: boolean;
order?: Array<'reference' | 'floating' | 'content'>;
portal?: boolean;
}>;
export const Popover = (props: PopoverProps) => {
- const { context, initialFocus, order = ['reference', 'content'], nodeId, isOpen, portal = true, children } = props;
+ const {
+ context,
+ initialFocus,
+ outsideElementsInert = false,
+ order = ['reference', 'content'],
+ nodeId,
+ isOpen,
+ portal = true,
+ children,
+ } = props;
if (portal) {
return (
@@ -23,6 +37,7 @@ export const Popover = (props: PopoverProps) => {
<>{children}>
diff --git a/packages/clerk-js/src/ui/elements/ProfileCard/ProfileCardContent.tsx b/packages/clerk-js/src/ui/elements/ProfileCard/ProfileCardContent.tsx
index 00eace10d8f..f7c735c9c1e 100644
--- a/packages/clerk-js/src/ui/elements/ProfileCard/ProfileCardContent.tsx
+++ b/packages/clerk-js/src/ui/elements/ProfileCard/ProfileCardContent.tsx
@@ -1,5 +1,6 @@
import React from 'react';
+import { PROFILE_CARD_SCROLLBOX_ID } from '../../constants';
import { Col, descriptors } from '../../customizables';
import { useRouter } from '../../router';
import { common, mqu } from '../../styledSystem';
@@ -42,6 +43,7 @@ export const ProfileCardContent = (props: ProfileCardContentProps) => {
borderColor: t.colors.$neutralAlpha50,
boxShadow: t.shadows.$cardContentShadow,
})}
+ id={PROFILE_CARD_SCROLLBOX_ID}
>
`${value}ms`;
+
+const transitionDuration = Object.freeze(
+ Object.fromEntries(Object.entries(transitionDurationValues).map(([key, value]) => [key, toMs(value)])) as Record<
+ keyof typeof transitionDurationValues,
+ string
+ >,
+);
+
const transitionProperty = Object.freeze({
common: 'background-color,background,border-color,color,fill,stroke,opacity,box-shadow,transform',
} as const);
@@ -18,4 +27,4 @@ const transitionTiming = Object.freeze({
slowBezier: 'cubic-bezier(0.16, 1, 0.3, 1)',
} as const);
-export { transitionDuration, transitionTiming, transitionProperty };
+export { transitionDuration, transitionTiming, transitionProperty, transitionDurationValues };
diff --git a/packages/clerk-js/src/ui/hooks/index.ts b/packages/clerk-js/src/ui/hooks/index.ts
index 1e0b862ae4e..92dc877a1de 100644
--- a/packages/clerk-js/src/ui/hooks/index.ts
+++ b/packages/clerk-js/src/ui/hooks/index.ts
@@ -1,21 +1,22 @@
+export * from './useCheckout';
+export * from './useClerkModalStateParams';
+export * from './useClipboard';
+export * from './useDebounce';
export * from './useDelayedVisibility';
-export * from './useWindowEventListener';
export * from './useEmailLink';
-export * from './useClipboard';
export * from './useEnabledThirdPartyProviders';
+export * from './useEnterpriseSSOLink';
export * from './useFetch';
export * from './useInView';
export * from './useLoadingStatus';
+export * from './useLocalStorage';
+export * from './useNavigateToFlowStart';
export * from './usePassword';
export * from './usePasswordComplexity';
export * from './usePopover';
export * from './usePrefersReducedMotion';
-export * from './useLocalStorage';
export * from './useResizeObserver';
export * from './useSafeState';
+export * from './useScrollLock';
export * from './useSearchInput';
-export * from './useDebounce';
-export * from './useClerkModalStateParams';
-export * from './useNavigateToFlowStart';
-export * from './useEnterpriseSSOLink';
-export * from './useCheckout';
+export * from './useWindowEventListener';
diff --git a/packages/clerk-js/src/ui/polishedAppearance.ts b/packages/clerk-js/src/ui/polishedAppearance.ts
index 5de90e4f628..4ffda9ecf28 100644
--- a/packages/clerk-js/src/ui/polishedAppearance.ts
+++ b/packages/clerk-js/src/ui/polishedAppearance.ts
@@ -221,6 +221,10 @@ export const polishedAppearance: Appearance = {
borderWidth: 0,
boxShadow: `${theme.shadows.$cardBoxShadow}, ${BORDER_SHADOW_LENGTH} ${theme.colors.$neutralAlpha100}`,
},
+ drawerContent: {
+ borderWidth: 0,
+ boxShadow: `${theme.shadows.$cardBoxShadow}, ${BORDER_SHADOW_LENGTH} ${theme.colors.$neutralAlpha100}`,
+ },
popoverBox: {
borderWidth: 0,
boxShadow: `${theme.shadows.$cardBoxShadow}, ${BORDER_SHADOW_LENGTH} ${theme.colors.$neutralAlpha100}`,
diff --git a/packages/clerk-js/src/ui/primitives/Dd.tsx b/packages/clerk-js/src/ui/primitives/Dd.tsx
index fa6d06e8f4a..15d28c26a05 100644
--- a/packages/clerk-js/src/ui/primitives/Dd.tsx
+++ b/packages/clerk-js/src/ui/primitives/Dd.tsx
@@ -1,11 +1,15 @@
import React from 'react';
-export const Dd = React.forwardRef<
- HTMLDListElement,
- React.DetailedHTMLProps, HTMLDListElement>
->((props, ref) => {
+import type { PrimitiveProps } from '../styledSystem';
+import type { BoxProps } from './Box';
+import { Box } from './Box';
+
+export type DdProps = PrimitiveProps<'dd'> & Omit;
+
+export const Dd = React.forwardRef((props, ref) => {
return (
-
diff --git a/packages/clerk-js/src/ui/primitives/Dl.tsx b/packages/clerk-js/src/ui/primitives/Dl.tsx
index be90dbf1b79..d1921fba210 100644
--- a/packages/clerk-js/src/ui/primitives/Dl.tsx
+++ b/packages/clerk-js/src/ui/primitives/Dl.tsx
@@ -1,11 +1,15 @@
import React from 'react';
-export const Dl = React.forwardRef<
- HTMLDListElement,
- React.DetailedHTMLProps, HTMLDListElement>
->((props, ref) => {
+import type { PrimitiveProps } from '../styledSystem';
+import type { BoxProps } from './Box';
+import { Box } from './Box';
+
+export type DlProps = PrimitiveProps<'dl'> & Omit;
+
+export const Dl = React.forwardRef((props, ref) => {
return (
-
diff --git a/packages/clerk-js/src/ui/primitives/Dt.tsx b/packages/clerk-js/src/ui/primitives/Dt.tsx
index ebe0ec18fde..2d55e67ec6b 100644
--- a/packages/clerk-js/src/ui/primitives/Dt.tsx
+++ b/packages/clerk-js/src/ui/primitives/Dt.tsx
@@ -1,11 +1,15 @@
import React from 'react';
-export const Dt = React.forwardRef<
- HTMLDListElement,
- React.DetailedHTMLProps, HTMLDListElement>
->((props, ref) => {
+import type { PrimitiveProps } from '../styledSystem';
+import type { BoxProps } from './Box';
+import { Box } from './Box';
+
+export type DtProps = PrimitiveProps<'dt'> & Omit;
+
+export const Dt = React.forwardRef((props, ref) => {
return (
-
diff --git a/packages/clerk-js/src/ui/styledSystem/animations.ts b/packages/clerk-js/src/ui/styledSystem/animations.ts
index 1975ca4f3f8..6320314e49f 100644
--- a/packages/clerk-js/src/ui/styledSystem/animations.ts
+++ b/packages/clerk-js/src/ui/styledSystem/animations.ts
@@ -92,7 +92,7 @@ const outAnimation = keyframes`
transform: translateY(0px);
max-height: 6rem;
visibility: visible;
- }
+ }
100% {
opacity: 0;
transform: translateY(5px);
@@ -128,18 +128,6 @@ const navbarSlideIn = keyframes`
100% {opacity: 1; transform: translateX(0);}
`;
-const drawerSlideIn = keyframes`
- 0% { opacity: 0; translate: 100% 0; }
- 10% { opacity: 1; }
- 100% { opacity: 1; translate: 0; }
-`;
-
-const drawerSlideOut = keyframes`
- 0% { opacity: 1; translate: 0; }
- 90% { opacity: 1; }
- 100% { opacity: 0; translate: 100% 0; }
-`;
-
export const animations = {
spinning,
dropdownSlideInScaleAndFade,
@@ -155,6 +143,4 @@ export const animations = {
inDelayAnimation,
outAnimation,
notificationAnimation,
- drawerSlideIn,
- drawerSlideOut,
};
diff --git a/packages/clerk-js/src/ui/types.ts b/packages/clerk-js/src/ui/types.ts
index 743b154c78b..70ca173b6e5 100644
--- a/packages/clerk-js/src/ui/types.ts
+++ b/packages/clerk-js/src/ui/types.ts
@@ -108,8 +108,8 @@ export type __experimental_PricingTableCtx = __experimental_PricingTableProps &
export type __experimental_CheckoutCtx = __experimental_CheckoutProps & {
componentName: 'Checkout';
mode?: ComponentMode;
- isShowingBlade?: boolean;
- handleCloseBlade?: () => void;
+ isOpen?: boolean;
+ setIsOpen?: (open: boolean) => void;
};
export type AvailableComponentCtx =
diff --git a/packages/types/src/appearance.ts b/packages/types/src/appearance.ts
index 7e36c95718c..76315ac4b32 100644
--- a/packages/types/src/appearance.ts
+++ b/packages/types/src/appearance.ts
@@ -210,6 +210,14 @@ export type ElementsConfig = {
dividerText: WithOptions;
dividerLine: WithOptions;
+ drawerBackdrop: WithOptions;
+ drawerContent: WithOptions;
+ drawerHeader: WithOptions;
+ drawerTitle: WithOptions;
+ drawerBody: WithOptions;
+ drawerFooter: WithOptions;
+ drawerClose: WithOptions;
+
formHeader: WithOptions;
formHeaderTitle: WithOptions;
formHeaderSubtitle: WithOptions;
@@ -347,10 +355,11 @@ export type ElementsConfig = {
planCardHeader: WithOptions;
planCardAvatarBadgeContainer: WithOptions;
planCardAvatar: WithOptions;
+ planCardBadge: WithOptions;
planCardTitle: WithOptions;
planCardDescription: WithOptions;
planCardFeatures: WithOptions;
- planCardFeaturesList: WithOptions;
+ planCardFeaturesList: WithOptions;
planCardFeaturesListItem: WithOptions;
planCardAction: WithOptions;
planCardPeriodToggle: WithOptions;