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;