From c4e3d633344eefebe8163f73c5c9fb7ae6ad9e2e Mon Sep 17 00:00:00 2001 From: Alex Carpenter Date: Tue, 3 Sep 2024 17:01:37 -0400 Subject: [PATCH 1/7] fix(ui): Ensure card take up their max available space --- .../ui/src/components/sign-in/steps/start.tsx | 286 +++++++++--------- packages/ui/src/primitives/card.tsx | 19 ++ 2 files changed, 163 insertions(+), 142 deletions(-) diff --git a/packages/ui/src/components/sign-in/steps/start.tsx b/packages/ui/src/components/sign-in/steps/start.tsx index 93609221b76..3a69297eacf 100644 --- a/packages/ui/src/components/sign-in/steps/start.tsx +++ b/packages/ui/src/components/sign-in/steps/start.tsx @@ -47,150 +47,152 @@ export function SignInStart() { {isGlobalLoading => { return ( - - - - - {logoImageUrl ? ( - - ) : null} - {t('signIn.start.title', { applicationName })} - {t('signIn.start.subtitle', { applicationName })} - - - - - - - - {hasConnection && hasIdentifier ? {t('dividerText')} : null} - - {hasIdentifier ? ( -
- {emailAddressEnabled && !phoneNumberEnabled && !usernameEnabled ? ( - - ) : null} - - {usernameEnabled && !emailAddressEnabled && !phoneNumberEnabled ? ( - - ) : null} - - {phoneNumberEnabled && !emailAddressEnabled && !usernameEnabled ? ( - - ) : null} - - {emailAddressEnabled && usernameEnabled && !phoneNumberEnabled ? ( - - ) : null} - - {emailAddressEnabled && phoneNumberEnabled && !usernameEnabled ? ( - - ) : null} - - {usernameEnabled && phoneNumberEnabled && !emailAddressEnabled ? ( - - ) : null} - - {emailAddressEnabled && usernameEnabled && phoneNumberEnabled ? ( - - ) : null} -
- ) : null} -
- - - {isSubmitting => { - return ( - - - - ); - }} - - - { - // Note: - // Currently this triggers the loading spinner for "Continue" - // which is a little confusing. We could use a manual - // setState on click, but we'll need to find a way to clean - // up the state based on `isSubmitting` - passkeyEnabled ? ( - - {isSubmitting => { - return ( - - - {t('signIn.start.actionLink__use_passkey')} - - - ); - }} - - ) : null - } - -
- - - - - {t('signIn.start.actionText')}{' '} - {t('signIn.start.actionLink')} - - - -
-
+ + + ); + }} +
+ + { + // Note: + // Currently this triggers the loading spinner for "Continue" + // which is a little confusing. We could use a manual + // setState on click, but we'll need to find a way to clean + // up the state based on `isSubmitting` + passkeyEnabled ? ( + + {isSubmitting => { + return ( + + + {t('signIn.start.actionLink__use_passkey')} + + + ); + }} + + ) : null + } + + + + + + + {t('signIn.start.actionText')}{' '} + {t('signIn.start.actionLink')} + + + + + + ); }} diff --git a/packages/ui/src/primitives/card.tsx b/packages/ui/src/primitives/card.tsx index dc1f4b61959..bfc8e020860 100644 --- a/packages/ui/src/primitives/card.tsx +++ b/packages/ui/src/primitives/card.tsx @@ -1,9 +1,28 @@ +import { Slot } from '@radix-ui/react-slot'; import { cva, cx } from 'cva'; import * as React from 'react'; import { ClerkLogo } from './clerk-logo'; import { Image } from './image'; +export const Wrapper = React.forwardRef(function CardRoot( + { asChild, className, children, ...props }: React.HTMLAttributes & { asChild?: boolean }, + forwardedRef: React.ForwardedRef, +) { + const Comp = asChild ? Slot : 'div'; + + return ( + + {children} + + ); +}); + export const Root = React.forwardRef(function CardRoot( { banner, children, className, ...props }: React.HTMLAttributes & { banner?: React.ReactNode }, forwardedRef: React.ForwardedRef, From df05db1018be53565558ca8d8734dfc3fdc478b6 Mon Sep 17 00:00:00 2001 From: Alex Carpenter Date: Wed, 4 Sep 2024 11:16:59 -0400 Subject: [PATCH 2/7] polymorphic root --- .../ui/src/components/sign-in/steps/start.tsx | 292 +++++++++--------- packages/ui/src/primitives/card.tsx | 128 ++++---- packages/ui/src/types/utils.ts | 44 +++ 3 files changed, 252 insertions(+), 212 deletions(-) diff --git a/packages/ui/src/components/sign-in/steps/start.tsx b/packages/ui/src/components/sign-in/steps/start.tsx index 3a69297eacf..53c6194fed4 100644 --- a/packages/ui/src/components/sign-in/steps/start.tsx +++ b/packages/ui/src/components/sign-in/steps/start.tsx @@ -47,152 +47,156 @@ export function SignInStart() { {isGlobalLoading => { return ( - - - - - - {logoImageUrl ? ( - - ) : null} - {t('signIn.start.title', { applicationName })} - {t('signIn.start.subtitle', { applicationName })} - - - - - - - - {hasConnection && hasIdentifier ? {t('dividerText')} : null} - - {hasIdentifier ? ( -
- {emailAddressEnabled && !phoneNumberEnabled && !usernameEnabled ? ( - + + + + {logoImageUrl ? ( + + ) : null} + {t('signIn.start.title', { applicationName })} + {t('signIn.start.subtitle', { applicationName })} + + + + + + + + {hasConnection && hasIdentifier ? {t('dividerText')} : null} + + {hasIdentifier ? ( +
+ {emailAddressEnabled && !phoneNumberEnabled && !usernameEnabled ? ( + + ) : null} + + {usernameEnabled && !emailAddressEnabled && !phoneNumberEnabled ? ( + + ) : null} + + {phoneNumberEnabled && !emailAddressEnabled && !usernameEnabled ? ( + + ) : null} + + {emailAddressEnabled && usernameEnabled && !phoneNumberEnabled ? ( + + ) : null} + + {emailAddressEnabled && phoneNumberEnabled && !usernameEnabled ? ( + + ) : null} + + {usernameEnabled && phoneNumberEnabled && !emailAddressEnabled ? ( + + ) : null} + + {emailAddressEnabled && usernameEnabled && phoneNumberEnabled ? ( + + ) : null} +
+ ) : null} +
+ + + {isSubmitting => { + return ( + +
- ) : null} -
- - - {isSubmitting => { - return ( - } > - - - ); - }} - - - { - // Note: - // Currently this triggers the loading spinner for "Continue" - // which is a little confusing. We could use a manual - // setState on click, but we'll need to find a way to clean - // up the state based on `isSubmitting` - passkeyEnabled ? ( - - {isSubmitting => { - return ( - - - {t('signIn.start.actionLink__use_passkey')} - - - ); - }} - - ) : null - } - -
- - - - - {t('signIn.start.actionText')}{' '} - {t('signIn.start.actionLink')} - - - -
-
-
+ {t('formButtonPrimary')} + + + ); + }} +
+ + { + // Note: + // Currently this triggers the loading spinner for "Continue" + // which is a little confusing. We could use a manual + // setState on click, but we'll need to find a way to clean + // up the state based on `isSubmitting` + passkeyEnabled ? ( + + {isSubmitting => { + return ( + + + {t('signIn.start.actionLink__use_passkey')} + + + ); + }} + + ) : null + } + + + + + + + {t('signIn.start.actionText')}{' '} + {t('signIn.start.actionLink')} + + + + + ); }} diff --git a/packages/ui/src/primitives/card.tsx b/packages/ui/src/primitives/card.tsx index bfc8e020860..7f390b1d615 100644 --- a/packages/ui/src/primitives/card.tsx +++ b/packages/ui/src/primitives/card.tsx @@ -1,82 +1,74 @@ -import { Slot } from '@radix-ui/react-slot'; import { cva, cx } from 'cva'; import * as React from 'react'; +import type { PolymorphicForwardRefExoticComponent, PolymorphicPropsWithoutRef } from '~/types/utils'; + import { ClerkLogo } from './clerk-logo'; import { Image } from './image'; -export const Wrapper = React.forwardRef(function CardRoot( - { asChild, className, children, ...props }: React.HTMLAttributes & { asChild?: boolean }, - forwardedRef: React.ForwardedRef, -) { - const Comp = asChild ? Slot : 'div'; - - return ( - - {children} - - ); -}); +const RootDefaultElement = 'div'; +type RootOwnProps = { + children?: React.ReactNode; + banner?: React.ReactNode; +}; -export const Root = React.forwardRef(function CardRoot( - { banner, children, className, ...props }: React.HTMLAttributes & { banner?: React.ReactNode }, - forwardedRef: React.ForwardedRef, -) { - return ( -
- {banner && ( -
-

= React.forwardRef( + function CardRoot( + { as, banner, children, className, ...props }: PolymorphicPropsWithoutRef, + forwardedRef: React.ForwardedRef, + ) { + const Element: React.ElementType = as || RootDefaultElement; + return ( + + {banner && ( +

- {banner} -

-
- )} - {children && ( -
- {children} -
- )} -
- ); -}); +

+ {banner} +

+
+ )} + {children && ( +
+ {children} +
+ )} + + ); + }, +); export const Content = React.forwardRef>(function CardContent( { children, className, ...props }, diff --git a/packages/ui/src/types/utils.ts b/packages/ui/src/types/utils.ts index c039d03031e..12e716d7a3e 100644 --- a/packages/ui/src/types/utils.ts +++ b/packages/ui/src/types/utils.ts @@ -11,3 +11,47 @@ export type VerificationStatus = | 'verified' | 'verified_switch_tab' | 'client_mismatch'; + +/** + * @link https://github.com/kripod/react-polymorphic-types + */ +type Merge = Omit & U; + +type PropsWithAs = P & { as?: T }; + +export type PolymorphicPropsWithoutRef = Merge< + T extends keyof JSX.IntrinsicElements + ? React.PropsWithoutRef + : React.ComponentPropsWithoutRef, + PropsWithAs +>; + +export type PolymorphicPropsWithRef = Merge< + T extends keyof JSX.IntrinsicElements ? React.PropsWithRef : React.ComponentPropsWithRef, + PropsWithAs +>; + +type PolymorphicExoticComponent

= Merge< + React.ExoticComponent

, + { + /** + * **NOTE**: Exotic components are not callable. + */ + (props: PolymorphicPropsWithRef): React.ReactElement | null; + } +>; + +export type PolymorphicForwardRefExoticComponent = Merge< + React.ForwardRefExoticComponent

, + PolymorphicExoticComponent +>; + +export type PolymorphicMemoExoticComponent = Merge< + React.MemoExoticComponent>, + PolymorphicExoticComponent +>; + +export type PolymorphicLazyExoticComponent = Merge< + React.LazyExoticComponent>, + PolymorphicExoticComponent +>; From 5f737e3a8f0187ed8727d543c3e250f47913fec0 Mon Sep 17 00:00:00 2001 From: Alex Carpenter Date: Wed, 4 Sep 2024 11:47:26 -0400 Subject: [PATCH 3/7] update sign-in components --- .../sign-in/choose-session/choose-session.tsx | 9 ++++++--- .../src/react/sign-in/choose-strategy.tsx | 18 ++++++++++++------ .../sign-in/steps/choose-session.tsx | 5 ++++- .../sign-in/steps/choose-strategy.tsx | 5 ++++- .../sign-in/steps/forgot-password.tsx | 10 ++++++++-- .../sign-in/steps/reset-password.tsx | 10 ++++++++-- .../components/sign-in/steps/verifications.tsx | 10 ++++++++-- 7 files changed, 50 insertions(+), 17 deletions(-) diff --git a/packages/elements/src/react/sign-in/choose-session/choose-session.tsx b/packages/elements/src/react/sign-in/choose-session/choose-session.tsx index f65dde9bb5d..020464b9bd8 100644 --- a/packages/elements/src/react/sign-in/choose-session/choose-session.tsx +++ b/packages/elements/src/react/sign-in/choose-session/choose-session.tsx @@ -14,7 +14,9 @@ import { // ----------------------------------- TYPES ------------------------------------ -export type SignInChooseSessionProps = React.HTMLAttributes; +export type SignInChooseSessionProps = React.HTMLAttributes & { + asChild?: boolean; +}; export type SignInSessionListProps = React.HTMLAttributes & { asChild?: boolean; includeCurrentSession?: true; @@ -30,12 +32,13 @@ export const SignInChooseSessionCtx = createContextForDomValidation('SignInChoos // --------------------------------- COMPONENTS --------------------------------- -export function SignInChooseSession({ children, ...props }: SignInChooseSessionProps) { +export function SignInChooseSession({ asChild, children, ...props }: SignInChooseSessionProps) { const activeState = useSignInChooseSessionIsActive(); + const Comp = asChild ? Slot : 'div'; return activeState ? ( -

{children}
+ {children} ) : null; } diff --git a/packages/elements/src/react/sign-in/choose-strategy.tsx b/packages/elements/src/react/sign-in/choose-strategy.tsx index 384c91890ab..84fbf15ebee 100644 --- a/packages/elements/src/react/sign-in/choose-strategy.tsx +++ b/packages/elements/src/react/sign-in/choose-strategy.tsx @@ -36,12 +36,16 @@ export function factorHasLocalStrategy(factor: SignInFactor | undefined | null): // --------------------------------- COMPONENTS --------------------------------- -export type SignInChooseStrategyProps = React.HTMLAttributes; -export type SignInForgotPasswordProps = React.HTMLAttributes; +export type SignInChooseStrategyProps = React.HTMLAttributes & { + asChild?: boolean; +}; +export type SignInForgotPasswordProps = React.HTMLAttributes & { + asChild?: boolean; +}; export const SignInChooseStrategyCtx = createContextForDomValidation('SignInChooseStrategyCtx'); -export function SignInChooseStrategy({ children, ...props }: SignInChooseStrategyProps) { +export function SignInChooseStrategy({ asChild, children, ...props }: SignInChooseStrategyProps) { const routerRef = SignInRouterCtx.useActorRef(); const activeStateFirstFactor = useActiveTags( routerRef, @@ -56,25 +60,27 @@ export function SignInChooseStrategy({ children, ...props }: SignInChooseStrateg ); const activeState = activeStateFirstFactor || activeStateSecondFactor; + const Comp = asChild ? Slot : 'div'; return activeState ? ( -
{children}
+ {children}
) : null; } -export function SignInForgotPassword({ children, ...props }: SignInForgotPasswordProps) { +export function SignInForgotPassword({ asChild, children, ...props }: SignInForgotPasswordProps) { const routerRef = SignInRouterCtx.useActorRef(); const activeState = useActiveTags( routerRef, ['step:verifications', 'step:first-factor', 'step:forgot-password'], ActiveTagsMode.all, ); + const Comp = asChild ? Slot : 'div'; return activeState ? ( -
{children}
+ {children}
) : null; } diff --git a/packages/ui/src/components/sign-in/steps/choose-session.tsx b/packages/ui/src/components/sign-in/steps/choose-session.tsx index 81e9a4c535e..a2a8d442fea 100644 --- a/packages/ui/src/components/sign-in/steps/choose-session.tsx +++ b/packages/ui/src/components/sign-in/steps/choose-session.tsx @@ -72,7 +72,10 @@ export function SignInChooseSession() { }; return ( - + diff --git a/packages/ui/src/components/sign-in/steps/choose-strategy.tsx b/packages/ui/src/components/sign-in/steps/choose-strategy.tsx index 602119d01e4..479f9347531 100644 --- a/packages/ui/src/components/sign-in/steps/choose-strategy.tsx +++ b/packages/ui/src/components/sign-in/steps/choose-strategy.tsx @@ -65,7 +65,10 @@ export function SignInChooseStrategy() { {isGlobalLoading => { return ( - + diff --git a/packages/ui/src/components/sign-in/steps/forgot-password.tsx b/packages/ui/src/components/sign-in/steps/forgot-password.tsx index 8f8957b2ff3..f5f7e965cca 100644 --- a/packages/ui/src/components/sign-in/steps/forgot-password.tsx +++ b/packages/ui/src/components/sign-in/steps/forgot-password.tsx @@ -33,8 +33,14 @@ export function SignInForgotPassword() { {isGlobalLoading => { return ( - - + + {logoImageUrl ? ( diff --git a/packages/ui/src/components/sign-in/steps/reset-password.tsx b/packages/ui/src/components/sign-in/steps/reset-password.tsx index 46620eb9e83..4659bfd3d93 100644 --- a/packages/ui/src/components/sign-in/steps/reset-password.tsx +++ b/packages/ui/src/components/sign-in/steps/reset-password.tsx @@ -30,8 +30,14 @@ export function SignInResetPassword() { {isGlobalLoading => { return ( - - + + {logoImageUrl ? ( diff --git a/packages/ui/src/components/sign-in/steps/verifications.tsx b/packages/ui/src/components/sign-in/steps/verifications.tsx index 399ebdf9312..fab9a91921f 100644 --- a/packages/ui/src/components/sign-in/steps/verifications.tsx +++ b/packages/ui/src/components/sign-in/steps/verifications.tsx @@ -35,8 +35,14 @@ export function SignInVerifications() { {isGlobalLoading => { return ( - - + + From 3bb624753d31ff24075b9dc7588eb7b3acebeebc Mon Sep 17 00:00:00 2001 From: Alex Carpenter Date: Wed, 4 Sep 2024 11:48:33 -0400 Subject: [PATCH 4/7] update sign up components --- packages/ui/src/components/sign-up/steps/continue.tsx | 10 ++++++++-- packages/ui/src/components/sign-up/steps/start.tsx | 10 ++++++++-- .../ui/src/components/sign-up/steps/verifications.tsx | 10 ++++++++-- 3 files changed, 24 insertions(+), 6 deletions(-) diff --git a/packages/ui/src/components/sign-up/steps/continue.tsx b/packages/ui/src/components/sign-up/steps/continue.tsx index 37c02d3848a..16769805237 100644 --- a/packages/ui/src/components/sign-up/steps/continue.tsx +++ b/packages/ui/src/components/sign-up/steps/continue.tsx @@ -39,8 +39,14 @@ export function SignUpContinue() { {isGlobalLoading => { return ( - - + + {logoImageUrl ? ( diff --git a/packages/ui/src/components/sign-up/steps/start.tsx b/packages/ui/src/components/sign-up/steps/start.tsx index 2a37c627bb3..a19465eea6a 100644 --- a/packages/ui/src/components/sign-up/steps/start.tsx +++ b/packages/ui/src/components/sign-up/steps/start.tsx @@ -52,8 +52,14 @@ export function SignUpStart() { {isGlobalLoading => { return ( - - + + {logoImageUrl ? ( diff --git a/packages/ui/src/components/sign-up/steps/verifications.tsx b/packages/ui/src/components/sign-up/steps/verifications.tsx index f5eeb600091..253bb45c479 100644 --- a/packages/ui/src/components/sign-up/steps/verifications.tsx +++ b/packages/ui/src/components/sign-up/steps/verifications.tsx @@ -61,8 +61,14 @@ export function SignUpVerifications() { {isGlobalLoading => { return ( - - + + From 9115cb41a0c0adf6523123e10759e9075e14eeac Mon Sep 17 00:00:00 2001 From: Alex Carpenter Date: Wed, 4 Sep 2024 11:50:51 -0400 Subject: [PATCH 5/7] add changeset --- .changeset/famous-singers-care.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/famous-singers-care.md diff --git a/.changeset/famous-singers-care.md b/.changeset/famous-singers-care.md new file mode 100644 index 00000000000..d3a2bd27f0b --- /dev/null +++ b/.changeset/famous-singers-care.md @@ -0,0 +1,5 @@ +--- +"@clerk/elements": patch +--- + +Adds support for `asChild` prop within `choose-strategy` and `choose-session` sign-in steps. From 6c9c2c1ccb510d74cab67bc8f8f8baa135b4125c Mon Sep 17 00:00:00 2001 From: Alex Carpenter Date: Wed, 4 Sep 2024 13:07:26 -0400 Subject: [PATCH 6/7] doc title --- packages/ui/src/types/utils.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/ui/src/types/utils.ts b/packages/ui/src/types/utils.ts index 12e716d7a3e..80f19e8a482 100644 --- a/packages/ui/src/types/utils.ts +++ b/packages/ui/src/types/utils.ts @@ -13,6 +13,8 @@ export type VerificationStatus = | 'client_mismatch'; /** + * Zero-runtime polymorphic component definitions for React + * * @link https://github.com/kripod/react-polymorphic-types */ type Merge = Omit & U; From 12e99d9bc10d654207e0f765d8f8bced586010b6 Mon Sep 17 00:00:00 2001 From: Alex Carpenter Date: Wed, 4 Sep 2024 14:01:19 -0400 Subject: [PATCH 7/7] fix return type --- packages/ui/src/types/utils.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/ui/src/types/utils.ts b/packages/ui/src/types/utils.ts index 80f19e8a482..2863e835852 100644 --- a/packages/ui/src/types/utils.ts +++ b/packages/ui/src/types/utils.ts @@ -39,7 +39,9 @@ type PolymorphicExoticComponent

(props: PolymorphicPropsWithRef): React.ReactElement | null; + ( + props: PolymorphicPropsWithRef, + ): ReturnType; } >;