Skip to content
Merged
5 changes: 5 additions & 0 deletions .changeset/famous-singers-care.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@clerk/elements": patch
---

Adds support for `asChild` prop within `choose-strategy` and `choose-session` sign-in steps.
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ import {

// ----------------------------------- TYPES ------------------------------------

export type SignInChooseSessionProps = React.HTMLAttributes<HTMLDivElement>;
export type SignInChooseSessionProps = React.HTMLAttributes<HTMLDivElement> & {
asChild?: boolean;
};
export type SignInSessionListProps = React.HTMLAttributes<HTMLUListElement> & {
asChild?: boolean;
includeCurrentSession?: true;
Expand All @@ -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 ? (
<SignInChooseSessionCtx.Provider>
<div {...props}>{children}</div>
<Comp {...props}>{children}</Comp>
</SignInChooseSessionCtx.Provider>
) : null;
}
Expand Down
18 changes: 12 additions & 6 deletions packages/elements/src/react/sign-in/choose-strategy.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,16 @@ export function factorHasLocalStrategy(factor: SignInFactor | undefined | null):

// --------------------------------- COMPONENTS ---------------------------------

export type SignInChooseStrategyProps = React.HTMLAttributes<HTMLDivElement>;
export type SignInForgotPasswordProps = React.HTMLAttributes<HTMLDivElement>;
export type SignInChooseStrategyProps = React.HTMLAttributes<HTMLDivElement> & {
asChild?: boolean;
};
export type SignInForgotPasswordProps = React.HTMLAttributes<HTMLDivElement> & {
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,
Expand All @@ -56,25 +60,27 @@ export function SignInChooseStrategy({ children, ...props }: SignInChooseStrateg
);

const activeState = activeStateFirstFactor || activeStateSecondFactor;
const Comp = asChild ? Slot : 'div';

return activeState ? (
<SignInChooseStrategyCtx.Provider>
<div {...props}>{children}</div>
<Comp {...props}>{children}</Comp>
</SignInChooseStrategyCtx.Provider>
) : 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 ? (
<SignInChooseStrategyCtx.Provider>
<div {...props}>{children}</div>
<Comp {...props}>{children}</Comp>
</SignInChooseStrategyCtx.Provider>
) : null;
}
Expand Down
5 changes: 4 additions & 1 deletion packages/ui/src/components/sign-in/steps/choose-session.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,10 @@ export function SignInChooseSession() {
const { footerProps } = useCard();

return (
<SignIn.Step name='choose-session'>
<SignIn.Step
asChild
name='choose-session'
>
<Card.Root banner={isDev ? LOCALIZATION_NEEDED.developmentMode : null}>
<Card.Content>
<Card.Header>
Expand Down
5 changes: 4 additions & 1 deletion packages/ui/src/components/sign-in/steps/choose-strategy.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,10 @@ export function SignInChooseStrategy() {
<Common.Loading scope='global'>
{isGlobalLoading => {
return (
<SignIn.Step name='choose-strategy'>
<SignIn.Step
asChild
name='choose-strategy'
>
<Card.Root banner={isDev ? LOCALIZATION_NEEDED.developmentMode : null}>
<Card.Content>
<Card.Header>
Expand Down
10 changes: 8 additions & 2 deletions packages/ui/src/components/sign-in/steps/forgot-password.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,14 @@ export function SignInForgotPassword() {
<Common.Loading scope='global'>
{isGlobalLoading => {
return (
<SignIn.Step name='forgot-password'>
<Card.Root banner={isDev ? LOCALIZATION_NEEDED.developmentMode : null}>
<SignIn.Step
asChild
name='forgot-password'
>
<Card.Root
as='form'
banner={isDev ? LOCALIZATION_NEEDED.developmentMode : null}
>
<Card.Content>
<Card.Header>
<Card.Logo {...logoProps} />
Expand Down
10 changes: 8 additions & 2 deletions packages/ui/src/components/sign-in/steps/reset-password.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,14 @@ export function SignInResetPassword() {
<Common.Loading scope='global'>
{isGlobalLoading => {
return (
<SignIn.Step name='reset-password'>
<Card.Root banner={isDev ? LOCALIZATION_NEEDED.developmentMode : null}>
<SignIn.Step
asChild
name='reset-password'
>
<Card.Root
as='form'
banner={isDev ? LOCALIZATION_NEEDED.developmentMode : null}
>
<Card.Content>
<Card.Header>
<Card.Logo {...logoProps} />
Expand Down
12 changes: 9 additions & 3 deletions packages/ui/src/components/sign-in/steps/start.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,14 @@ export function SignInStart() {
hasConnection && hasIdentifier ? <Separator>{t('dividerText')}</Separator> : null,
];
return (
<SignIn.Step name='start'>
<Card.Root banner={isDev ? LOCALIZATION_NEEDED.developmentMode : null}>
<SignIn.Step
asChild
name='start'
>
<Card.Root
as='form'
banner={isDev ? LOCALIZATION_NEEDED.developmentMode : null}
>
<Card.Content>
<Card.Header>
<Card.Logo {...logoProps} />
Expand Down Expand Up @@ -161,7 +167,7 @@ export function SignInStart() {
<SignIn.Passkey asChild>
<LinkButton
type='button'
disabled={isGlobalLoading}
disabled={isGlobalLoading || isSubmitting}
>
{t('signIn.start.actionLink__use_passkey')}
</LinkButton>
Expand Down
10 changes: 8 additions & 2 deletions packages/ui/src/components/sign-in/steps/verifications.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,14 @@ export function SignInVerifications() {
<Common.Loading scope='global'>
{isGlobalLoading => {
return (
<SignIn.Step name='verifications'>
<Card.Root banner={isDev ? LOCALIZATION_NEEDED.developmentMode : null}>
<SignIn.Step
asChild
name='verifications'
>
<Card.Root
as='form'
banner={isDev ? LOCALIZATION_NEEDED.developmentMode : null}
>
<Card.Content>
<SignIn.Strategy name='password'>
<Card.Header>
Expand Down
10 changes: 8 additions & 2 deletions packages/ui/src/components/sign-up/steps/continue.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,14 @@ export function SignUpContinue() {
<Common.Loading scope='global'>
{isGlobalLoading => {
return (
<SignUp.Step name='continue'>
<Card.Root banner={isDev ? LOCALIZATION_NEEDED.developmentMode : null}>
<SignUp.Step
asChild
name='continue'
>
<Card.Root
as='form'
banner={isDev ? LOCALIZATION_NEEDED.developmentMode : null}
>
<Card.Content>
<Card.Header>
<Card.Logo {...logoProps} />
Expand Down
10 changes: 8 additions & 2 deletions packages/ui/src/components/sign-up/steps/start.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,14 @@ export function SignUpStart() {
hasConnection && hasIdentifier ? <Separator>{t('dividerText')}</Separator> : null,
];
return (
<SignUp.Step name='start'>
<Card.Root banner={isDev ? LOCALIZATION_NEEDED.developmentMode : null}>
<SignUp.Step
asChild
name='start'
>
<Card.Root
as='form'
banner={isDev ? LOCALIZATION_NEEDED.developmentMode : null}
>
<Card.Content>
<Card.Header>
<Card.Logo {...logoProps} />
Expand Down
10 changes: 8 additions & 2 deletions packages/ui/src/components/sign-up/steps/verifications.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,14 @@ export function SignUpVerifications() {
<Common.Loading scope='global'>
{isGlobalLoading => {
return (
<SignUp.Step name='verifications'>
<Card.Root banner={isDev ? LOCALIZATION_NEEDED.developmentMode : null}>
<SignUp.Step
asChild
name='verifications'
>
<Card.Root
as='form'
banner={isDev ? LOCALIZATION_NEEDED.developmentMode : null}
>
<Card.Content>
<SignUp.Strategy name='phone_code'>
<Card.Header>
Expand Down
111 changes: 61 additions & 50 deletions packages/ui/src/primitives/card.tsx
Original file line number Diff line number Diff line change
@@ -1,63 +1,74 @@
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 Root = React.forwardRef(function CardRoot(
{ banner, children, className, ...props }: React.HTMLAttributes<HTMLDivElement> & { banner?: React.ReactNode },
forwardedRef: React.ForwardedRef<HTMLDivElement>,
) {
return (
<div
ref={forwardedRef}
data-card-root=''
{...props}
className={cx(
'[--card-banner-height:theme(size.4)]',
'[--card-body-px:theme(spacing.10)]',
'[--card-body-py:theme(spacing.8)]',
'[--card-content-rounded-b:theme(borderRadius.lg)]',
'bg-gray-2 ring-gray-a3 relative mx-auto block w-full max-w-[25rem] rounded-xl ring-1',
banner
? [
'mt-[calc(var(--card-banner-height)/2)]',
'shadow-[0px_-1.5px_0px_0px_theme(colors.warning.DEFAULT),0px_5px_15px_0px_theme(colors.gray.a4),0px_15px_35px_-5px_theme(colors.gray.a4)]',
]
: 'shadow-[0px_5px_15px_0px_theme(colors.gray.a4),0px_15px_35px_-5px_theme(colors.gray.a4)]',
className,
)}
>
{banner && (
<div
data-card-root-banner=''
className={cx(
'pointer-events-none absolute inset-x-0 -top-[calc(var(--card-banner-height)/2)] isolate z-[500] flex justify-center',
className,
)}
>
<p
const RootDefaultElement = 'div';
type RootOwnProps = {
children?: React.ReactNode;
banner?: React.ReactNode;
};

export const Root: PolymorphicForwardRefExoticComponent<RootOwnProps, typeof RootDefaultElement> = React.forwardRef(
function CardRoot<T extends React.ElementType = typeof RootDefaultElement>(
{ as, banner, children, className, ...props }: PolymorphicPropsWithoutRef<RootOwnProps, T>,
forwardedRef: React.ForwardedRef<Element>,
) {
const Element: React.ElementType = as || RootDefaultElement;
return (
<Element
ref={forwardedRef}
data-card-root=''
{...props}
className={cx(
'[--card-banner-height:theme(size.4)]',
'[--card-body-px:theme(spacing.10)]',
'[--card-body-py:theme(spacing.8)]',
'[--card-content-rounded-b:theme(borderRadius.lg)]',
'bg-gray-2 ring-gray-a3 relative mx-auto block w-full max-w-[25rem] rounded-xl ring-1',
banner
? [
'mt-[calc(var(--card-banner-height)/2)]',
'shadow-[0px_-1.5px_0px_0px_theme(colors.warning.DEFAULT),0px_5px_15px_0px_theme(colors.gray.a4),0px_15px_35px_-5px_theme(colors.gray.a4)]',
]
: 'shadow-[0px_5px_15px_0px_theme(colors.gray.a4),0px_15px_35px_-5px_theme(colors.gray.a4)]',
className,
)}
>
{banner && (
<div
data-card-root-banner=''
className={cx(
'bg-warning pointer-events-auto inline-flex h-[--card-banner-height] items-center rounded-full px-2 text-[0.6875rem] font-medium tracking-[2%] text-white',
'pointer-events-none absolute inset-x-0 -top-[calc(var(--card-banner-height)/2)] isolate z-[500] flex justify-center',
className,
)}
{...props}
>
{banner}
</p>
</div>
)}
{children && (
<div
data-card-root-inner=''
className={cx('overflow-hidden rounded-[inherit]', className)}
>
{children}
</div>
)}
</div>
);
});
<p
className={cx(
'bg-warning pointer-events-auto inline-flex h-[--card-banner-height] items-center rounded-full px-2 text-[0.6875rem] font-medium tracking-[2%] text-white',
className,
)}
{...props}
>
{banner}
</p>
</div>
)}
{children && (
<div
data-card-root-inner=''
className={cx('overflow-hidden rounded-[inherit]', className)}
>
{children}
</div>
)}
</Element>
);
},
);

export const Content = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(function CardContent(
{ children, className, ...props },
Expand Down
Loading
Loading