diff --git a/.changeset/twenty-ducks-pump.md b/.changeset/twenty-ducks-pump.md new file mode 100644 index 00000000000..1c3c8cc06fe --- /dev/null +++ b/.changeset/twenty-ducks-pump.md @@ -0,0 +1,6 @@ +--- +'@clerk/clerk-js': patch +'@clerk/types': patch +--- + +Refactor `` components to apply descriptors and ensure styling is properly connected to theming layer. diff --git a/packages/clerk-js/src/ui/components/Checkout/Checkout.tsx b/packages/clerk-js/src/ui/components/Checkout/Checkout.tsx index 848a2edc0da..7d4e4bcb933 100644 --- a/packages/clerk-js/src/ui/components/Checkout/Checkout.tsx +++ b/packages/clerk-js/src/ui/components/Checkout/Checkout.tsx @@ -36,9 +36,7 @@ const AuthenticatedRoutes = withCoreUserGuard((props: __experimental_CheckoutPro - - - + ); diff --git a/packages/clerk-js/src/ui/components/Checkout/CheckoutComplete.tsx b/packages/clerk-js/src/ui/components/Checkout/CheckoutComplete.tsx index 00b9d5cb7ab..c44ff51e7ba 100644 --- a/packages/clerk-js/src/ui/components/Checkout/CheckoutComplete.tsx +++ b/packages/clerk-js/src/ui/components/Checkout/CheckoutComplete.tsx @@ -1,8 +1,8 @@ import type { __experimental_CommerceCheckoutResource } from '@clerk/types'; import { useCheckoutContext } from '../../contexts'; -import { Box, Button, Col, Flex, Icon, Text } from '../../customizables'; -import { LineItems } from '../../elements'; +import { Box, Button, descriptors, Heading, Icon, Span, Text } from '../../customizables'; +import { Drawer, LineItems } from '../../elements'; import { Check } from '../../icons'; export const CheckoutComplete = ({ checkout }: { checkout: __experimental_CommerceCheckoutResource }) => { @@ -15,44 +15,85 @@ export const CheckoutComplete = ({ checkout }: { checkout: __experimental_Commer }; return ( - - ({ - flex: 1, - paddingBlock: t.space.$4, - })} - > - - - + + ({ + margin: 'auto', + position: 'relative', + aspectRatio: '1/1', + display: 'grid', + width: '100%', + padding: t.space.$4, + flexShrink: 0, + })} > - {/* TODO(@COMMERCE): needs localization */} - Payment was successful! - ({ textAlign: 'center', paddingInline: t.space.$8 })}> - {/* TODO(@COMMERCE): needs localization */} - Minim adipisicing enim fugiat enim est ad nisi exercitation nisi exercitation quis culpa. - - - + + + + ({ + margin: 'auto', + gridArea: '1/1', + display: 'flex', + position: 'relative', + width: t.sizes.$16, + height: t.sizes.$16, + borderRadius: t.radii.$circle, + backgroundImage: `linear-gradient(180deg, rgba(255, 255, 255, 0.30) 0%, rgba(0, 0, 0, 0.12) 50%, rgba(0, 0, 0, 0.30) 95.31%)`, + boxShadow: '0px 4px 12px 0px rgba(0, 0, 0, 0.35), 0px 1px 0px 0px rgba(255, 255, 255, 0.05) inset', + ':before': { + content: '""', + position: 'absolute', + inset: t.space.$1, + borderRadius: t.radii.$circle, + backgroundColor: t.colors.$colorBackground, + }, + })} + > + + + ({ + margin: 'auto', + gridArea: '1/1', + position: 'relative', + textAlign: 'center', + transform: `translateY(${t.space.$20})`, + })} + > + + Payment was successful! + + ({ textAlign: 'center', paddingInline: t.space.$8, marginBlockStart: t.space.$2 })} + > + {/* TODO(@COMMERCE): needs localization */} + Minim adipisicing enim fugiat enim est ad nisi exercitation nisi exercitation quis culpa. + + + + - ({ - padding: t.space.$4, - borderTopWidth: t.borderWidths.$normal, - borderTopStyle: t.borderStyles.$solid, - borderTopColor: t.colors.$neutralAlpha100, - position: 'relative', + rowGap: t.space.$4, })} > @@ -77,131 +118,37 @@ export const CheckoutComplete = ({ checkout }: { checkout: __experimental_Commer {checkout.invoice ? checkout.invoice.id : '–'} - - - + + ); }; -const SuccessCircle = () => { +function Ring({ + scale, +}: { + /** + * Number between 0-1 + */ + scale: number; +}) { return ( - ({ - position: 'relative', - width: '100%', - height: t.sizes.$16, + margin: 'auto', + gridArea: '1/1', + width: `${scale * 100}%`, + height: `${scale * 100}%`, + borderWidth: 1, + borderStyle: 'solid', + borderColor: t.colors.$neutralAlpha200, + borderRadius: t.radii.$circle, + maskImage: `linear-gradient(to bottom, transparent 15%, black, transparent 85%)`, })} - > - {/* rings */} - - ({ - position: 'absolute', - top: `-${t.sizes.$8}`, - bottom: `-${t.sizes.$8}`, - left: '50%', - translate: '-50% 0', - aspectRatio: '1/1', - borderWidth: 1, - borderStyle: 'solid', - borderColor: t.colors.$neutralAlpha150, - borderRadius: t.radii.$circle, - })} - /> - ({ - position: 'absolute', - top: `-${t.sizes.$24}`, - bottom: `-${t.sizes.$24}`, - left: '50%', - translate: '-50% 0', - aspectRatio: '1/1', - borderWidth: 1, - borderStyle: 'solid', - borderColor: t.colors.$neutralAlpha200, - borderRadius: t.radii.$circle, - })} - /> - ({ - position: 'absolute', - top: `-${t.sizes.$40}`, - bottom: `-${t.sizes.$40}`, - left: '50%', - translate: '-50% 0', - aspectRatio: '1/1', - borderWidth: 1, - borderStyle: 'solid', - borderColor: t.colors.$neutralAlpha200, - borderRadius: t.radii.$circle, - })} - /> - - - {/* fade overlays */} - ({ - position: 'absolute', - width: '120%', - aspectRatio: '1/1', - top: '50%', - translate: '0 -50%', - backgroundImage: `linear-gradient(to bottom, ${t.colors.$colorBackground} 35%, transparent 48%, transparent 52%, ${t.colors.$colorBackground} 65%)`, - })} - /> - - {/* coin */} - ({ - position: 'relative', - width: t.sizes.$16, - height: t.sizes.$16, - borderRadius: t.radii.$circle, - backgroundImage: - 'linear-gradient(180deg, rgba(255, 255, 255, 0.30) 0%, rgba(0, 0, 0, 0.12) 50%, rgba(0, 0, 0, 0.30) 95.31%)', - })} - > - ({ - position: 'relative', - width: t.sizes.$16, - height: t.sizes.$16, - borderRadius: t.radii.$circle, - backgroundImage: 'linear-gradient(180deg, rgba(255, 255, 255, 0.06) 0%, rgba(255, 255, 255, 0.00) 60.94%)', - backgroundBlendMode: 'plus-lighter, normal', - boxShadow: '0px 4px 12px 0px rgba(0, 0, 0, 0.35), 0px 1px 0px 0px rgba(255, 255, 255, 0.05) inset', - })} - > - ({ - position: 'absolute', - inset: t.space.$1, - borderRadius: t.radii.$circle, - backgroundColor: t.colors.$colorBackground, - })} - > - - - - - + /> ); -}; +} diff --git a/packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx b/packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx index 2e8e1626242..eeee8a29933 100644 --- a/packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx +++ b/packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx @@ -6,17 +6,94 @@ import type { ClerkAPIError, ClerkRuntimeError, } from '@clerk/types'; -import { PaymentElement, useElements, useStripe } from '@stripe/react-stripe-js'; +import { Elements, PaymentElement, useElements, useStripe } from '@stripe/react-stripe-js'; +import type { Stripe } from '@stripe/stripe-js'; import { useCallback, useEffect, useMemo, useState } from 'react'; -import { Button, Col, Flex, Form, Icon, Text } from '../../customizables'; -import { Alert, Disclosure, Divider, Select, SelectButton, SelectOptionList } from '../../elements'; +import { Box, Button, Col, descriptors, Flex, Form, Icon, Text } from '../../customizables'; +import { Alert, Disclosure, Divider, Drawer, LineItems, Select, SelectButton, SelectOptionList } from '../../elements'; import { useFetch } from '../../hooks'; import { ArrowUpDown, CreditCard } from '../../icons'; import { animations } from '../../styledSystem'; import { handleError } from '../../utils'; export const CheckoutForm = ({ + stripe, + checkout, + onCheckoutComplete, +}: { + stripe: Stripe | null; + checkout: __experimental_CommerceCheckoutResource; + onCheckoutComplete: (checkout: __experimental_CommerceCheckoutResource) => void; +}) => { + const { plan, planPeriod, totals } = checkout; + return ( + + ({ + padding: t.space.$4, + borderBottomWidth: t.borderWidths.$normal, + borderBottomStyle: t.borderStyles.$solid, + borderBottomColor: t.colors.$neutralAlpha100, + })} + > + + + {plan.name} + {/* TODO(@Commerce): needs localization */} + + {plan.currencySymbol} + {planPeriod === 'month' ? plan.amountFormatted : plan.annualMonthlyAmountFormatted} + + + + {/* TODO(@Commerce): needs localization */} + Subtotal + + {totals.subtotal.currencySymbol} + {totals.subtotal.amountFormatted} + + + + {/* TODO(@Commerce): needs localization */} + Tax + + {totals.taxTotal.currencySymbol} + {totals.taxTotal.amountFormatted} + + + + {/* TODO(@Commerce): needs localization */} + Total{totals.totalDueNow ? ' Due Today' : ''} + + {totals.totalDueNow + ? `${totals.totalDueNow.currencySymbol}${totals.totalDueNow.amountFormatted}` + : `${totals.grandTotal.currencySymbol}${totals.grandTotal.amountFormatted}`} + + + + + + {stripe && ( + + + + )} + + ); +}; + +const CheckoutFormElements = ({ checkout, onCheckoutComplete, }: { @@ -103,6 +180,7 @@ export const CheckoutForm = ({ return ( ({ padding: t.space.$4 })} > @@ -122,6 +200,7 @@ export const CheckoutForm = ({ open={openAccountFundsDropDown} onOpenChange={setOpenAccountFundsDropDown} > + {/* TODO(@Commerce): needs localization */} Account Funds @@ -142,6 +221,7 @@ export const CheckoutForm = ({ open={openAddNewSourceDropDown} onOpenChange={setOpenAddNewSourceDropDown} > + {/* TODO(@Commerce): needs localization */} Add a New Payment Source - - { + const paymentSource = paymentSources.find(source => source.id === option.value); + setSelectedPaymentSource(paymentSource); + }} + portal + > + {/*Store value inside an input in order to be accessible as form data*/} + + ({ + justifyContent: 'space-between', + backgroundColor: t.colors.$colorBackground, + })} > - {/*Store value inside an input in order to be accessible as form data*/} - - ({ - justifyContent: 'space-between', - backgroundColor: t.colors.$colorBackground, - })} - > - {selectedPaymentSource && ( - + + - - - {selectedPaymentSource.cardType} ⋯ {selectedPaymentSource.last4} - - - )} - - ({ - paddingBlock: t.space.$1, - color: t.colors.$colorText, - })} - /> - - - + {selectedPaymentSource.cardType} ⋯ {selectedPaymentSource.last4} + + + )} + + ({ + paddingBlock: t.space.$1, + color: t.colors.$colorText, + })} + /> + + ); }; @@ -264,83 +352,87 @@ const StripePaymentMethods = ({ }, [collapsed, onExpand]); return ( -
- - - {collapsed ? ( - <> - - - - - ) : ( - <> - - - - )} - + ({ + display: 'flex', + flexDirection: 'column', + rowGap: t.space.$3, + })} + > + + {collapsed ? ( + <> + + + + + ) : ( + <> + + + + )} ); }; diff --git a/packages/clerk-js/src/ui/components/Checkout/CheckoutPage.tsx b/packages/clerk-js/src/ui/components/Checkout/CheckoutPage.tsx index f44fb7666f6..cc6f48f174d 100644 --- a/packages/clerk-js/src/ui/components/Checkout/CheckoutPage.tsx +++ b/packages/clerk-js/src/ui/components/Checkout/CheckoutPage.tsx @@ -1,16 +1,10 @@ -import type { - __experimental_CheckoutProps, - __experimental_CommercePlanResource, - __experimental_CommerceTotals, -} from '@clerk/types'; -import { Elements } from '@stripe/react-stripe-js'; +import type { __experimental_CheckoutProps } from '@clerk/types'; import type { Stripe } from '@stripe/stripe-js'; import { loadStripe } from '@stripe/stripe-js'; import { useEffect, useRef, useState } from 'react'; import { useEnvironment } from '../../contexts'; -import { Alert, Col, Flex, Spinner } from '../../customizables'; -import { LineItems } from '../../elements'; +import { Alert, Spinner } from '../../customizables'; import { useCheckout } from '../../hooks'; import { CheckoutComplete } from './CheckoutComplete'; import { CheckoutForm } from './CheckoutForm'; @@ -41,106 +35,41 @@ export const CheckoutPage = (props: __experimental_CheckoutProps) => { } }, [checkout?.externalGatewayId, __experimental_commerceSettings]); - return ( - <> - {isLoading ? ( - - - - ) : !checkout ? ( - + ); + } + + if (!checkout) { + return ( + <> + {/* TODO(@COMMERCE): needs localization */} + - {/* TODO(@COMMERCE): needs localization */} - There was a problem, please try again later. - - ) : checkout.status === 'completed' ? ( - - ) : ( - <> - ({ - padding: t.space.$4, - borderBottomWidth: t.borderWidths.$normal, - borderBottomStyle: t.borderStyles.$solid, - borderBottomColor: t.colors.$neutralAlpha100, - })} - > - - + There was a problem, please try again later. + + + ); + } - {stripe && ( - - - - )} - - )} - - ); -}; + if (checkout?.status === 'completed') { + return ; + } -// TODO(@COMMERCE): needs localization -const CheckoutPlanRows = ({ - plan, - planPeriod, - totals, -}: { - plan: __experimental_CommercePlanResource; - planPeriod: string; - totals: __experimental_CommerceTotals; -}) => { return ( - - - {plan.name} - - {plan.currencySymbol} - {planPeriod === 'month' ? plan.amountFormatted : plan.annualMonthlyAmountFormatted} - - - - Subtotal - - {totals.subtotal.currencySymbol} - {totals.subtotal.amountFormatted} - - - - Tax - - {totals.taxTotal.currencySymbol} - {totals.taxTotal.amountFormatted} - - - - Total{totals.totalDueNow ? ' Due Today' : ''} - - {totals.totalDueNow - ? `${totals.totalDueNow.currencySymbol}${totals.totalDueNow.amountFormatted}` - : `${totals.grandTotal.currencySymbol}${totals.grandTotal.amountFormatted}`} - - - + ); }; diff --git a/packages/clerk-js/src/ui/customizables/elementDescriptors.ts b/packages/clerk-js/src/ui/customizables/elementDescriptors.ts index aaf612f7781..7f5d2e37457 100644 --- a/packages/clerk-js/src/ui/customizables/elementDescriptors.ts +++ b/packages/clerk-js/src/ui/customizables/elementDescriptors.ts @@ -82,6 +82,15 @@ export const APPEARANCE_KEYS = containsAllElementsConfigKeys([ 'alternativeMethodsBlockButtonText', 'alternativeMethodsBlockButtonArrow', + 'checkoutFormLineItemsRoot', + 'checkoutFormElementsRoot', + + 'checkoutSuccessRoot', + 'checkoutSuccessRing', + 'checkoutSuccessBadge', + 'checkoutSuccessTitle', + 'checkoutSuccessDescription', + 'otpCodeField', 'otpCodeFieldInputs', 'otpCodeFieldInput', diff --git a/packages/clerk-js/src/ui/elements/Drawer.tsx b/packages/clerk-js/src/ui/elements/Drawer.tsx index 17629525c66..05b3ab2c9de 100644 --- a/packages/clerk-js/src/ui/elements/Drawer.tsx +++ b/packages/clerk-js/src/ui/elements/Drawer.tsx @@ -345,27 +345,32 @@ const Body = React.forwardRef(({ children }, ref) => interface FooterProps { children?: React.ReactNode; + sx?: ThemableCssProp; } -const Footer = React.forwardRef(({ children }, ref) => { +const Footer = React.forwardRef(({ children, sx }, 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, - })} + sx={[ + t => ({ + display: 'flex', + flexDirection: 'column', + 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, + }), + sx, + ]} > {children} diff --git a/packages/types/src/appearance.ts b/packages/types/src/appearance.ts index ab917078729..c7a172f7c03 100644 --- a/packages/types/src/appearance.ts +++ b/packages/types/src/appearance.ts @@ -134,7 +134,7 @@ export type ElementObjectKey = K extends `${infer Parent}-${in * Kebab-case is used to differentiate between the container and child elements */ export type ElementsConfig = { - button: WithOptions; + button: WithOptions; input: WithOptions; checkbox: WithOptions; radio: WithOptions; @@ -201,6 +201,15 @@ export type ElementsConfig = { alternativeMethodsBlockButtonText: WithOptions; alternativeMethodsBlockButtonArrow: WithOptions; + checkoutFormLineItemsRoot: WithOptions; + checkoutFormElementsRoot: WithOptions; + + checkoutSuccessRoot: WithOptions; + checkoutSuccessRing: WithOptions; + checkoutSuccessBadge: WithOptions; + checkoutSuccessTitle: WithOptions; + checkoutSuccessDescription: WithOptions; + otpCodeField: WithOptions; otpCodeFieldInputs: WithOptions; otpCodeFieldInput: WithOptions;