(function CustomField(
{ name, label },
forwardedRef,
) {
@@ -43,6 +43,7 @@ export const CustomField = forwardRef(fu
}
: {
className: 'bg-tertiary rounded-sm px-2 py-1 border border-foreground data-[invalid]:border-red-500',
+ ref: forwardedRef,
};
return (
@@ -53,7 +54,6 @@ export const CustomField = forwardRef(fu
diff --git a/packages/elements/examples/nextjs/components/sign-in-debug.tsx b/packages/elements/examples/nextjs/components/sign-in-debug.tsx
index ca71f1c052a..51e872fcdd7 100644
--- a/packages/elements/examples/nextjs/components/sign-in-debug.tsx
+++ b/packages/elements/examples/nextjs/components/sign-in-debug.tsx
@@ -1,7 +1,7 @@
'use client';
import { SignedIn } from '@clerk/clerk-react';
-import { useSignInFlow, useSignInFlowSelector } from '@clerk/elements';
+import { useSignInFlow, useSignInFlowSelector } from '@clerk/elements/sign-in';
import { SignOutButton } from '@clerk/nextjs';
import { Button } from './design';
@@ -27,6 +27,7 @@ export function SignInLogButtons() {
+ {/* @ts-expect-error - Intentionally not in exported clerk-js types */}
diff --git a/packages/elements/examples/nextjs/components/sign-up-debug.tsx b/packages/elements/examples/nextjs/components/sign-up-debug.tsx
index dfd42a11dd4..6bb47f1d433 100644
--- a/packages/elements/examples/nextjs/components/sign-up-debug.tsx
+++ b/packages/elements/examples/nextjs/components/sign-up-debug.tsx
@@ -1,7 +1,7 @@
'use client';
import { SignedIn } from '@clerk/clerk-react';
-import { useSignUpFlow, useSignUpFlowSelector } from '@clerk/elements';
+import { useSignUpFlow, useSignUpFlowSelector } from '@clerk/elements/sign-up';
import { SignOutButton } from '@clerk/nextjs';
import { Button } from './design';
@@ -27,6 +27,7 @@ export function SignUpLogButtons() {
+ {/* @ts-expect-error - Intentionally not in exported clerk-js types */}
diff --git a/packages/elements/examples/nextjs/components/social-providers.tsx b/packages/elements/examples/nextjs/components/social-providers.tsx
index 63cd4e76f2b..2dc280e0220 100644
--- a/packages/elements/examples/nextjs/components/social-providers.tsx
+++ b/packages/elements/examples/nextjs/components/social-providers.tsx
@@ -1,6 +1,6 @@
'use client';
-import { SocialProviderIcon as ClerkElementsSocialProviderIcon } from '@clerk/elements';
+import { SocialProviderIcon as ClerkElementsSocialProviderIcon } from '@clerk/elements/common';
import Image from 'next/image';
import type { ComponentProps } from 'react';
diff --git a/packages/elements/examples/nextjs/package.json b/packages/elements/examples/nextjs/package.json
index 3bd337c37a0..2b6f6eb5f71 100644
--- a/packages/elements/examples/nextjs/package.json
+++ b/packages/elements/examples/nextjs/package.json
@@ -15,7 +15,7 @@
"@radix-ui/react-form": "^0.0.3",
"clsx": "^2.0.0",
"framer-motion": "^11.0.2",
- "next": "14.0.4",
+ "next": "^14.1.0",
"react": "^18",
"react-dom": "^18"
},
diff --git a/packages/elements/package.json b/packages/elements/package.json
index 42a3cf005b7..31ff57bb9fc 100644
--- a/packages/elements/package.json
+++ b/packages/elements/package.json
@@ -1,17 +1,17 @@
{
"name": "@clerk/elements",
- "version": "0.0.2-beta-v5.8",
- "private": true,
+ "version": "0.0.0",
"description": "Clerk Elements",
"keywords": [
"clerk",
"typescript",
- "nextjs",
"auth",
"authentication",
"passwordless",
"session",
- "jwt"
+ "jwt",
+ "elements",
+ "radix"
],
"homepage": "https://clerk.com/",
"bugs": {
@@ -26,6 +26,16 @@
"author": "Clerk",
"sideEffects": false,
"exports": {
+ "./*": {
+ "import": {
+ "types": "./dist/react/*/index.d.mts",
+ "default": "./dist/react/*/index.mjs"
+ },
+ "require": {
+ "types": "./dist/react/*/index.d.ts",
+ "default": "./dist/react/*/index.js"
+ }
+ },
".": {
"import": {
"types": "./dist/index.d.mts",
@@ -37,8 +47,6 @@
}
}
},
- "main": "./dist/index.js",
- "types": "./dist/index.d.ts",
"files": [
"dist"
],
@@ -49,9 +57,9 @@
"app:e2e": "(cd examples/nextjs && npm run e2e)",
"app:lint": "(cd examples/nextjs && npm run lint)",
"build": "tsup",
- "clean": "rimraf ./dist",
+ "build:analyze": "tsup --metafile; open https://esbuild.github.io/analyze/",
+ "build:declarations": "tsc -p tsconfig.json",
"dev": "tsup --watch",
- "dev:publish": "npm run dev -- --env.publish",
"lint": "eslint src/",
"lint:attw": "attw --pack .",
"lint:publint": "publint",
@@ -60,13 +68,11 @@
},
"dependencies": {
"@clerk/clerk-react": "5.0.0-beta-v5.17",
- "@clerk/nextjs": "5.0.0-beta-v5.20",
"@clerk/shared": "2.0.0-beta-v5.11",
"@radix-ui/react-form": "^0.0.3",
"@radix-ui/react-slot": "^1.0.2",
"@statelyai/inspect": "^0.1.0",
"@xstate/react": "^4.0.2",
- "clsx": "^2.1.0",
"xstate": "^5.4.1"
},
"devDependencies": {
@@ -77,6 +83,7 @@
"eslint-config-custom": "*",
"next": "^14.0.4",
"tslib": "2.4.1",
+ "tsup": "^8.0.1",
"type-fest": "^4.9.0",
"typescript": "^5.3.3"
},
diff --git a/packages/elements/src/index.ts b/packages/elements/src/index.ts
index b18a1219d87..7d21ce99b4b 100644
--- a/packages/elements/src/index.ts
+++ b/packages/elements/src/index.ts
@@ -1,37 +1,6 @@
-'use client';
+// TODO: Add link to docs
+throw new Error(`No exports are available from the top-level "@clerk/elements" package.
+Use specific subpath imports instead, e.g. "@clerk/elements/sign-in".
-import { useNextRouter } from '~/react/router/next';
-
-/** Common Components */
-export { Field, FieldError, FieldState, Form, GlobalError, Input, Label, Submit } from '~/react/common/form';
-export { SocialProviderIcon } from '~/react/common/third-party-providers/social-provider';
-
-/** Sign In Components */
-export {
- SignIn,
- SignInStart,
- SignInFactorOne,
- SignInFactorTwo,
- SignInContinue,
- SignInSocialProviders,
- SignInSocialProvider,
- SignInSocialProviderIcon,
- SignInStrategy,
-} from '~/react/sign-in';
-
-/** Sign Up Components */
-export {
- SignUp,
- SignUpStart,
- SignUpContinue,
- SignUpVerify,
- SignUpSocialProviders,
- SignUpSocialProvider,
- SignUpSocialProviderIcon,
- SignUpStrategy,
-} from '~/react/sign-up';
-
-/** Hooks */
-export { useSignUpFlow, useSignUpFlowSelector } from '~/internals/machines/sign-up/sign-up.context';
-export { useSignInFlow, useSignInFlowSelector } from '~/internals/machines/sign-in/sign-in.context';
-export { useNextRouter };
+Find all available exports in the documentation:
+https://clerk.com/docs`);
diff --git a/packages/elements/src/internals/machines/sign-in/sign-in.machine.ts b/packages/elements/src/internals/machines/sign-in/sign-in.machine.ts
index 0cc350378fb..26fd316c709 100644
--- a/packages/elements/src/internals/machines/sign-in/sign-in.machine.ts
+++ b/packages/elements/src/internals/machines/sign-in/sign-in.machine.ts
@@ -476,15 +476,17 @@ export const SignInMachine = setup({
// continueSignUp
},
};
+ } else if (event.type === 'AUTHENTICATE.OAUTH' && event.strategy) {
+ return {
+ clerk: context.clerk,
+ params: {
+ strategy: event.strategy,
+ // continueSignUp
+ },
+ };
}
- return {
- clerk: context.clerk,
- params: {
- strategy: event.strategy,
- // continueSignUp
- },
- };
+ throw new ClerkElementsRuntimeError('Invalid strategy');
},
onError: {
actions: 'setFormErrors',
diff --git a/packages/elements/src/internals/machines/sign-up/sign-up.machine.ts b/packages/elements/src/internals/machines/sign-up/sign-up.machine.ts
index bee35afd182..74a5048d88f 100644
--- a/packages/elements/src/internals/machines/sign-up/sign-up.machine.ts
+++ b/packages/elements/src/internals/machines/sign-up/sign-up.machine.ts
@@ -55,7 +55,7 @@ export type SignUpMachineEvents =
| ErrorActorEvent
| { type: 'AUTHENTICATE.OAUTH'; strategy: OAuthStrategy }
| { type: 'AUTHENTICATE.SAML'; strategy: SamlStrategy }
- | { type: 'AUTHENTICATE.WEB3'; strategy?: Web3Strategy }
+ | { type: 'AUTHENTICATE.WEB3'; strategy: Web3Strategy }
| { type: 'FAILURE'; error: Error }
| { type: 'OAUTH.CALLBACK' }
| { type: 'SUBMIT' }
@@ -280,15 +280,17 @@ export const SignUpMachine = setup({
// continueSignUp
},
};
+ } else if (event.type === 'AUTHENTICATE.OAUTH' && event.strategy) {
+ return {
+ clerk: context.clerk,
+ params: {
+ strategy: event.strategy,
+ // continueSignUp
+ },
+ };
}
- return {
- clerk: context.clerk,
- params: {
- strategy: event.strategy,
- // continueSignUp
- },
- };
+ throw new ClerkElementsRuntimeError('Invalid strategy');
},
onError: {
actions: 'setFormErrors',
diff --git a/packages/elements/src/react/common/form/index.tsx b/packages/elements/src/react/common/form/index.tsx
index 37ae68b5759..82ef1da4f85 100644
--- a/packages/elements/src/react/common/form/index.tsx
+++ b/packages/elements/src/react/common/form/index.tsx
@@ -4,6 +4,7 @@ import type {
FormLabelProps,
FormMessageProps,
FormProps,
+ FormSubmitProps as RadixFormSubmitProps,
} from '@radix-ui/react-form';
import {
Control as RadixControl,
@@ -11,10 +12,11 @@ import {
Form as RadixForm,
FormMessage,
Label as RadixLabel,
- Submit,
+ Submit as RadixSubmit,
} from '@radix-ui/react-form';
import type { ComponentProps, ReactNode } from 'react';
import React, { createContext, useCallback, useContext, useEffect } from 'react';
+import type { SetRequired } from 'type-fest';
import type { BaseActorRef } from 'xstate';
import type { ClerkElementsError } from '~/internals/errors/error';
@@ -109,7 +111,7 @@ const determineInputTypeFromName = (name: string) => {
return 'text' as const;
};
-const useInput = ({ name: inputName, value: initialValue, type: inputType, ...passthroughProps }: ClerkInputProps) => {
+const useInput = ({ name: inputName, value: initialValue, type: inputType, ...passthroughProps }: FormInputProps) => {
// Inputs can be used outside of a wrapper if desired, so safely destructure here
const fieldContext = useFieldContext();
const name = inputName || fieldContext?.name;
@@ -223,9 +225,9 @@ function FieldState({ children }: { children: (state: { state: FieldStates }) =>
return children(fieldState);
}
-type ClerkInputProps = FormControlProps | ({ type: 'otp' } & OTPInputProps);
+type FormInputProps = FormControlProps | ({ type: 'otp' } & OTPInputProps);
-function Input(props: ClerkInputProps) {
+function Input(props: FormInputProps) {
const field = useInput(props);
return ;
@@ -235,6 +237,12 @@ function Label(props: FormLabelProps) {
return ;
}
+type FormSubmitProps = SetRequired;
+
+function Submit(props: FormSubmitProps) {
+ return ;
+}
+
// ================= ERRORS ================= //
type FormErrorRenderProps = Pick;
@@ -302,10 +310,12 @@ function FieldError({ children, code, name, ...rest }: FormFieldErrorProps) {
export { Field, FieldError, FieldState, Form, GlobalError, Input, Label, Submit };
export type {
FormControlProps,
+ FormFieldErrorProps,
FormErrorProps,
- FormGlobalErrorProps,
FormErrorRenderProps,
- FormFieldErrorProps,
FormFieldProps,
+ FormGlobalErrorProps,
+ FormInputProps,
FormProps,
+ FormSubmitProps,
};
diff --git a/packages/elements/src/react/common/index.ts b/packages/elements/src/react/common/index.ts
new file mode 100644
index 00000000000..50e57f4c59c
--- /dev/null
+++ b/packages/elements/src/react/common/index.ts
@@ -0,0 +1,2 @@
+export { Field, FieldError, FieldState, Form, GlobalError, Input, Label, Submit } from '~/react/common/form';
+export { SocialProviderIcon } from '~/react/common/third-party-providers/social-provider';
diff --git a/packages/elements/src/react/common/third-party-providers/social-provider.tsx b/packages/elements/src/react/common/third-party-providers/social-provider.tsx
index 21b3cbd3f96..5a3b93de7dd 100644
--- a/packages/elements/src/react/common/third-party-providers/social-provider.tsx
+++ b/packages/elements/src/react/common/third-party-providers/social-provider.tsx
@@ -33,11 +33,13 @@ export function SocialProvider({ asChild, provider, ...rest }: SocialProviderPro
}
const Comp = asChild ? Slot : 'button';
+ const defaultProps = asChild ? {} : { type: 'button' as const };
return (
diff --git a/packages/elements/src/react/sign-in/continue.tsx b/packages/elements/src/react/sign-in/continue.tsx
new file mode 100644
index 00000000000..c9af9dc3994
--- /dev/null
+++ b/packages/elements/src/react/sign-in/continue.tsx
@@ -0,0 +1,20 @@
+'use client';
+
+import type { SignInStrategy as ClerkSignInStrategy } from '@clerk/types';
+import type { PropsWithChildren } from 'react';
+
+import { StrategiesContext, useSignInFlow, useSignInStrategies } from '~/internals/machines/sign-in/sign-in.context';
+import { Form } from '~/react/common/form';
+
+export type SignInContinueProps = PropsWithChildren<{ preferred?: ClerkSignInStrategy }>;
+
+export function SignInContinue({ children, preferred }: SignInContinueProps) {
+ const { current, isActive, shouldRender } = useSignInStrategies(preferred);
+ const actorRef = useSignInFlow();
+
+ return shouldRender ? (
+
+
+
+ ) : null;
+}
diff --git a/packages/elements/src/react/sign-in/index.ts b/packages/elements/src/react/sign-in/index.ts
new file mode 100644
index 00000000000..a8ad8ad6e5b
--- /dev/null
+++ b/packages/elements/src/react/sign-in/index.ts
@@ -0,0 +1,13 @@
+'use client';
+
+export { SignInContinue as Continue } from './continue';
+export { SignInRoot as SignIn, SignInRoot as Root } from './root';
+export {
+ SignInSocialProvider as SocialProvider,
+ SignInSocialProviderIcon as SocialProviderIcon,
+} from './social-providers';
+export { SignInStart as Start } from './start';
+export { SignInFactor as Factor, SignInVerification as Verification } from './verifications';
+
+// TODO: Move contexts from /internals to /react
+export { useSignInFlow, useSignInFlowSelector } from '~/internals/machines/sign-in/sign-in.context';
diff --git a/packages/elements/src/react/sign-in/index.tsx b/packages/elements/src/react/sign-in/index.tsx
deleted file mode 100644
index 776b28a86a1..00000000000
--- a/packages/elements/src/react/sign-in/index.tsx
+++ /dev/null
@@ -1,176 +0,0 @@
-'use client';
-
-import { ClerkLoaded, useClerk } from '@clerk/clerk-react';
-import type { OAuthProvider, SignInStrategy as ClerkSignInStrategy, Web3Provider } from '@clerk/types';
-import { Slot } from '@radix-ui/react-slot';
-import type { PropsWithChildren } from 'react';
-
-import { FormStoreProvider, useFormStore } from '~/internals/machines/form/form.context';
-import {
- SignInFlowProvider as SignInFlowContextProvider,
- StrategiesContext,
- useSignInFlow,
- useSignInStateMatcher,
- useSignInStrategies,
- useSignInThirdPartyProvider,
- useSignInThirdPartyProviders,
- useStrategy,
-} from '~/internals/machines/sign-in/sign-in.context';
-import type { SignInStrategyName } from '~/internals/machines/sign-in/sign-in.types';
-import { Form } from '~/react/common/form';
-import { Router, useClerkRouter, useNextRouter } from '~/react/router';
-import { createBrowserInspectorReactHook } from '~/react/utils/xstate';
-import type { ThirdPartyStrategy } from '~/utils/third-party-strategies';
-
-import type { SocialProviderProps } from '../common/third-party-providers/social-provider';
-import { SocialProvider, SocialProviderIcon } from '../common/third-party-providers/social-provider';
-
-const { useBrowserInspector } = createBrowserInspectorReactHook();
-
-// ================= SignInFlowProvider ================= //
-
-function SignInFlowProvider({ children }: PropsWithChildren) {
- const clerk = useClerk();
- const router = useClerkRouter();
- const form = useFormStore();
- const { loading: inspectorLoading, inspector } = useBrowserInspector();
-
- if (!router) {
- throw new Error('clerk: Unable to locate ClerkRouter, make sure this is rendered within ``.');
- }
-
- if (inspectorLoading) {
- return null;
- }
-
- return (
-
- {children}
-
- );
-}
-
-// ================= SignIn ================= //
-
-export function SignIn({ children }: PropsWithChildren): JSX.Element | null {
- // TODO: eventually we'll rely on the framework SDK to specify its host router, but for now we'll default to Next.js
- const router = useNextRouter();
-
- return (
-
- {/* TODO: Temporary hydration fix */}
-
-
- {children}
-
-
-
- );
-}
-
-// ================= SignInStart ================= //
-
-export function SignInStart({ children }: PropsWithChildren) {
- const state = useSignInStateMatcher();
- const actorRef = useSignInFlow();
-
- return state.matches('Start') ? : null;
-}
-
-// ================= SignInFactorOne ================= //
-
-export function SignInFactorOne({ children }: PropsWithChildren) {
- const state = useSignInStateMatcher();
- const actorRef = useSignInFlow();
-
- return state.matches('FirstFactor') ? : null;
-}
-
-// ================= SignInFactorTwo ================= //
-
-export function SignInFactorTwo({ children }: PropsWithChildren) {
- const state = useSignInStateMatcher();
- const actorRef = useSignInFlow();
-
- return state.matches('SecondFactor') ? : null;
-}
-
-// ================= SignInContinue ================= //
-
-export type SignInContinueProps = PropsWithChildren<{ preferred?: ClerkSignInStrategy }>;
-
-export function SignInContinue({ children, preferred }: SignInContinueProps) {
- const { current, isActive, shouldRender } = useSignInStrategies(preferred);
- const actorRef = useSignInFlow();
-
- return shouldRender ? (
-
-
-
- ) : null;
-}
-
-// ================= SignInStrategy ================= //
-
-export type SignInStrategyProps = PropsWithChildren<{ name: SignInStrategyName }>;
-
-export function SignInStrategy({ children, name }: SignInStrategyProps) {
- const { shouldRender } = useStrategy(name);
- return shouldRender ? children : null;
-}
-
-// ================= SignInSocialProviders ================= //
-
-export type SignInSocialProvidersProps = { render(provider: ThirdPartyStrategy): React.ReactNode };
-
-export function SignInSocialProviders({ render: provider }: SignInSocialProvidersProps) {
- const thirdPartyProviders = useSignInThirdPartyProviders();
-
- if (!thirdPartyProviders) {
- return null;
- }
-
- return (
- <>
- {thirdPartyProviders.strategies.map(strategy => (
-
- {provider(thirdPartyProviders.strategyToDisplayData[strategy])}
-
- ))}
- >
- );
-}
-
-// ================= SignInSocialProvider ================= //
-
-export interface SignInSocialProviderProps extends Omit {
- name: OAuthProvider | Web3Provider;
-}
-
-export function SignInSocialProvider({ name, ...rest }: SignInSocialProviderProps) {
- const thirdPartyProvider = useSignInThirdPartyProvider(name);
-
- return (
-
- );
-}
-
-export const SignInSocialProviderIcon = SocialProviderIcon;
diff --git a/packages/elements/src/react/sign-in/root.tsx b/packages/elements/src/react/sign-in/root.tsx
new file mode 100644
index 00000000000..f4aee729e12
--- /dev/null
+++ b/packages/elements/src/react/sign-in/root.tsx
@@ -0,0 +1,60 @@
+'use client';
+
+import { ClerkLoaded, useClerk } from '@clerk/clerk-react';
+import type { PropsWithChildren } from 'react';
+
+import { FormStoreProvider, useFormStore } from '~/internals/machines/form/form.context';
+import { SignInFlowProvider as SignInFlowContextProvider } from '~/internals/machines/sign-in/sign-in.context';
+import { Router, useClerkRouter, useNextRouter } from '~/react/router';
+import { createBrowserInspectorReactHook } from '~/react/utils/xstate';
+
+const { useBrowserInspector } = createBrowserInspectorReactHook();
+
+function SignInFlowProvider({ children }: PropsWithChildren) {
+ const clerk = useClerk();
+ const router = useClerkRouter();
+ const form = useFormStore();
+ const { loading: inspectorLoading, inspector } = useBrowserInspector();
+
+ if (!router) {
+ throw new Error('clerk: Unable to locate ClerkRouter, make sure this is rendered within ``.');
+ }
+
+ if (inspectorLoading) {
+ return null;
+ }
+
+ return (
+
+ {children}
+
+ );
+}
+
+export function SignInRoot({ children }: PropsWithChildren): JSX.Element | null {
+ // TODO: eventually we'll rely on the framework SDK to specify its host router, but for now we'll default to Next.js
+ const router = useNextRouter();
+
+ return (
+
+ {/* TODO: Temporary hydration fix */}
+
+
+ {children}
+
+
+
+ );
+}
diff --git a/packages/elements/src/react/sign-in/social-providers.tsx b/packages/elements/src/react/sign-in/social-providers.tsx
new file mode 100644
index 00000000000..9c8f1dddb53
--- /dev/null
+++ b/packages/elements/src/react/sign-in/social-providers.tsx
@@ -0,0 +1,24 @@
+'use client';
+
+import type { OAuthProvider, Web3Provider } from '@clerk/types';
+
+import { useSignInThirdPartyProvider } from '~/internals/machines/sign-in/sign-in.context';
+import type { SocialProviderProps } from '~/react/common/third-party-providers/social-provider';
+import { SocialProvider, SocialProviderIcon } from '~/react/common/third-party-providers/social-provider';
+
+export interface SignInSocialProviderProps extends Omit {
+ name: OAuthProvider | Web3Provider;
+}
+
+export function SignInSocialProvider({ name, ...rest }: SignInSocialProviderProps) {
+ const thirdPartyProvider = useSignInThirdPartyProvider(name);
+
+ return (
+
+ );
+}
+
+export const SignInSocialProviderIcon = SocialProviderIcon;
diff --git a/packages/elements/src/react/sign-in/start.tsx b/packages/elements/src/react/sign-in/start.tsx
new file mode 100644
index 00000000000..23b2cf23a7d
--- /dev/null
+++ b/packages/elements/src/react/sign-in/start.tsx
@@ -0,0 +1,15 @@
+'use client';
+
+import type { PropsWithChildren } from 'react';
+
+import { useSignInFlow, useSignInStateMatcher } from '~/internals/machines/sign-in/sign-in.context';
+import { Form } from '~/react/common/form';
+
+export type SignInStartProps = PropsWithChildren;
+
+export function SignInStart({ children }: SignInStartProps) {
+ const state = useSignInStateMatcher();
+ const actorRef = useSignInFlow();
+
+ return state.matches('Start') ? : null;
+}
diff --git a/packages/elements/src/react/sign-in/verifications.tsx b/packages/elements/src/react/sign-in/verifications.tsx
new file mode 100644
index 00000000000..289a46eac56
--- /dev/null
+++ b/packages/elements/src/react/sign-in/verifications.tsx
@@ -0,0 +1,21 @@
+'use client';
+
+import { useSignInStateMatcher, useStrategy } from '~/internals/machines/sign-in/sign-in.context';
+import type { SignInStrategyName } from '~/internals/machines/sign-in/sign-in.types';
+
+export type SignInFactorProps = React.PropsWithChildren<
+ { first: true; second?: never } | { first?: never; second: true }
+>;
+
+export function SignInFactor({ children, first, second }: SignInFactorProps) {
+ const state = useSignInStateMatcher();
+ const render = (first && state.matches('FirstFactor')) || (second && state.matches('SecondFactor'));
+ return render ? children : null;
+}
+
+export type SignInVerificationProps = React.PropsWithChildren<{ name: SignInStrategyName }>;
+
+export function SignInVerification({ children, name }: SignInVerificationProps) {
+ const { shouldRender } = useStrategy(name);
+ return shouldRender ? children : null;
+}
diff --git a/packages/elements/src/react/sign-up/continue.tsx b/packages/elements/src/react/sign-up/continue.tsx
new file mode 100644
index 00000000000..dae513e420f
--- /dev/null
+++ b/packages/elements/src/react/sign-up/continue.tsx
@@ -0,0 +1,15 @@
+'use client';
+
+import type { PropsWithChildren } from 'react';
+
+import { useSignUpFlow, useSignUpStateMatcher } from '~/internals/machines/sign-up/sign-up.context';
+import { Form } from '~/react/common/form';
+
+export type SignUpContinueProps = PropsWithChildren;
+
+export function SignUpContinue({ children }: SignUpContinueProps) {
+ const state = useSignUpStateMatcher();
+ const actorRef = useSignUpFlow();
+
+ return state.matches('Continue') ? : null;
+}
diff --git a/packages/elements/src/react/sign-up/index.ts b/packages/elements/src/react/sign-up/index.ts
new file mode 100644
index 00000000000..93e63845934
--- /dev/null
+++ b/packages/elements/src/react/sign-up/index.ts
@@ -0,0 +1,13 @@
+'use client';
+
+export { SignUpContinue as Continue } from './continue';
+export { SignUpRoot as SignUp, SignUpRoot as Root } from './root';
+export {
+ SignUpSocialProvider as SocialProvider,
+ SignUpSocialProviderIcon as SocialProviderIcon,
+} from './social-providers';
+export { SignUpStart as Start } from './start';
+export { SignUpVerification as Verification, SignUpVerify as Verify } from './verifications';
+
+// TODO: Move contexts from /internals to /react
+export { useSignUpFlow, useSignUpFlowSelector } from '~/internals/machines/sign-up/sign-up.context';
diff --git a/packages/elements/src/react/sign-up/index.tsx b/packages/elements/src/react/sign-up/index.tsx
deleted file mode 100644
index 8886900783c..00000000000
--- a/packages/elements/src/react/sign-up/index.tsx
+++ /dev/null
@@ -1,159 +0,0 @@
-'use client';
-
-import { ClerkLoaded, useClerk } from '@clerk/clerk-react';
-import type { OAuthProvider, Web3Provider } from '@clerk/types';
-import { Slot } from '@radix-ui/react-slot';
-import type { PropsWithChildren } from 'react';
-
-import { FormStoreProvider, useFormStore } from '~/internals/machines/form/form.context';
-import {
- SignUpFlowProvider as SignUpFlowContextProvider,
- useSignUpFlow,
- useSignUpStateMatcher,
- useSignUpThirdPartyProvider,
- useSignUpThirdPartyProviders,
-} from '~/internals/machines/sign-up/sign-up.context';
-import type { SignUpVerificationTags } from '~/internals/machines/sign-up/sign-up.machine';
-import { Form } from '~/react/common/form';
-import { Router, useClerkRouter, useNextRouter } from '~/react/router';
-import { createBrowserInspectorReactHook } from '~/react/utils/xstate';
-import type { ThirdPartyStrategy } from '~/utils/third-party-strategies';
-
-import type { SocialProviderProps } from '../common/third-party-providers/social-provider';
-import { SocialProvider, SocialProviderIcon } from '../common/third-party-providers/social-provider';
-
-const { useBrowserInspector } = createBrowserInspectorReactHook();
-
-// ================= SignUpFlowProvider ================= //
-
-function SignUpFlowProvider({ children }: PropsWithChildren) {
- const clerk = useClerk();
- const router = useClerkRouter();
- const form = useFormStore();
- const { loading: inspectorLoading, inspector } = useBrowserInspector();
-
- if (!router) {
- throw new Error('clerk: Unable to locate ClerkRouter, make sure this is rendered within ``.');
- }
-
- if (inspectorLoading) {
- return null;
- }
-
- return (
-
- {children}
-
- );
-}
-
-// ================= SignUp ================= //
-
-export function SignUp({ children }: PropsWithChildren): JSX.Element | null {
- // TODO: eventually we'll rely on the framework SDK to specify its host router, but for now we'll default to Next.js
- const router = useNextRouter();
-
- return (
-
-
-
- {children}
-
-
-
- );
-}
-
-// ================= SignUpStart ================= //
-
-export function SignUpStart({ children }: PropsWithChildren) {
- const state = useSignUpStateMatcher();
- const actorRef = useSignUpFlow();
-
- return state.matches('Start') ? : null;
-}
-
-// ================= SignUpContinue ================= //
-
-export function SignUpContinue({ children }: PropsWithChildren) {
- const state = useSignUpStateMatcher();
- const actorRef = useSignUpFlow();
-
- return state.matches('Continue') ? : null;
-}
-
-// ================= SignUpVerify ================= //
-
-export type SignUpVerifyProps = PropsWithChildren;
-
-export function SignUpVerify({ children }: SignUpVerifyProps) {
- const actorRef = useSignUpFlow();
- const state = useSignUpStateMatcher();
-
- return state.matches('Verification') ? : null;
-}
-
-// ================= SignUpStrategy ================= //
-
-export type SignUpStrategyProps = PropsWithChildren<{ name: SignUpVerificationTags }>;
-
-export function SignUpStrategy({ children, name }: SignUpStrategyProps) {
- const state = useSignUpStateMatcher();
- return state.hasTag(name) ? children : null;
-}
-
-// ================= SignUpSocialProviders ================= //
-
-export type SignUpSocialProvidersProps = { render(provider: ThirdPartyStrategy): React.ReactNode };
-
-export function SignUpSocialProviders({ render: provider }: SignUpSocialProvidersProps) {
- const thirdPartyProviders = useSignUpThirdPartyProviders();
-
- if (!thirdPartyProviders) {
- return null;
- }
-
- return (
- <>
- {thirdPartyProviders.strategies.map(strategy => (
-
- {provider(thirdPartyProviders.strategyToDisplayData[strategy])}
-
- ))}
- >
- );
-}
-
-// ================= SignUpSocialProvider ================= //
-
-export interface SignUpSocialProviderProps extends Omit {
- name: OAuthProvider | Web3Provider;
-}
-
-export function SignUpSocialProvider({ name, ...rest }: SignUpSocialProviderProps) {
- const thirdPartyProvider = useSignUpThirdPartyProvider(name);
-
- return (
-
- );
-}
-
-export const SignUpSocialProviderIcon = SocialProviderIcon;
diff --git a/packages/elements/src/react/sign-up/root.tsx b/packages/elements/src/react/sign-up/root.tsx
new file mode 100644
index 00000000000..5c42e8ceb49
--- /dev/null
+++ b/packages/elements/src/react/sign-up/root.tsx
@@ -0,0 +1,59 @@
+'use client';
+
+import { ClerkLoaded, useClerk } from '@clerk/clerk-react';
+import type { PropsWithChildren } from 'react';
+
+import { FormStoreProvider, useFormStore } from '~/internals/machines/form/form.context';
+import { SignUpFlowProvider as SignUpFlowContextProvider } from '~/internals/machines/sign-up/sign-up.context';
+import { Router, useClerkRouter, useNextRouter } from '~/react/router';
+import { createBrowserInspectorReactHook } from '~/react/utils/xstate';
+
+const { useBrowserInspector } = createBrowserInspectorReactHook();
+
+function SignUpFlowProvider({ children }: PropsWithChildren) {
+ const clerk = useClerk();
+ const router = useClerkRouter();
+ const form = useFormStore();
+ const { loading: inspectorLoading, inspector } = useBrowserInspector();
+
+ if (!router) {
+ throw new Error('clerk: Unable to locate ClerkRouter, make sure this is rendered within ``.');
+ }
+
+ if (inspectorLoading) {
+ return null;
+ }
+
+ return (
+
+ {children}
+
+ );
+}
+
+export function SignUpRoot({ children }: PropsWithChildren): JSX.Element | null {
+ // TODO: eventually we'll rely on the framework SDK to specify its host router, but for now we'll default to Next.js
+ const router = useNextRouter();
+
+ return (
+
+
+
+ {children}
+
+
+
+ );
+}
diff --git a/packages/elements/src/react/sign-up/social-providers.tsx b/packages/elements/src/react/sign-up/social-providers.tsx
new file mode 100644
index 00000000000..4aa17717071
--- /dev/null
+++ b/packages/elements/src/react/sign-up/social-providers.tsx
@@ -0,0 +1,25 @@
+'use client';
+
+import type { OAuthProvider, Web3Provider } from '@clerk/types';
+
+import { useSignUpThirdPartyProvider } from '~/internals/machines/sign-up/sign-up.context';
+
+import type { SocialProviderProps } from '../common/third-party-providers/social-provider';
+import { SocialProvider, SocialProviderIcon } from '../common/third-party-providers/social-provider';
+
+export interface SignUpSocialProviderProps extends Omit {
+ name: OAuthProvider | Web3Provider;
+}
+
+export function SignUpSocialProvider({ name, ...rest }: SignUpSocialProviderProps) {
+ const thirdPartyProvider = useSignUpThirdPartyProvider(name);
+
+ return (
+
+ );
+}
+
+export const SignUpSocialProviderIcon = SocialProviderIcon;
diff --git a/packages/elements/src/react/sign-up/start.tsx b/packages/elements/src/react/sign-up/start.tsx
new file mode 100644
index 00000000000..22c75afe40e
--- /dev/null
+++ b/packages/elements/src/react/sign-up/start.tsx
@@ -0,0 +1,15 @@
+'use client';
+
+import type { PropsWithChildren } from 'react';
+
+import { useSignUpFlow, useSignUpStateMatcher } from '~/internals/machines/sign-up/sign-up.context';
+import { Form } from '~/react/common/form';
+
+export type SignUpStartProps = PropsWithChildren;
+
+export function SignUpStart({ children }: SignUpStartProps) {
+ const state = useSignUpStateMatcher();
+ const actorRef = useSignUpFlow();
+
+ return state.matches('Start') ? : null;
+}
diff --git a/packages/elements/src/react/sign-up/verifications.tsx b/packages/elements/src/react/sign-up/verifications.tsx
new file mode 100644
index 00000000000..e819481a2c6
--- /dev/null
+++ b/packages/elements/src/react/sign-up/verifications.tsx
@@ -0,0 +1,23 @@
+'use client';
+
+import type { PropsWithChildren } from 'react';
+
+import { useSignUpFlow, useSignUpStateMatcher } from '~/internals/machines/sign-up/sign-up.context';
+import type { SignUpVerificationTags } from '~/internals/machines/sign-up/sign-up.machine';
+import { Form } from '~/react/common/form';
+
+export type SignUpVerifyProps = PropsWithChildren;
+
+export function SignUpVerify({ children }: SignUpVerifyProps) {
+ const actorRef = useSignUpFlow();
+ const state = useSignUpStateMatcher();
+
+ return state.matches('Verification') ? : null;
+}
+
+export type SignUpVerificationProps = PropsWithChildren<{ name: SignUpVerificationTags }>;
+
+export function SignUpVerification({ children, name }: SignUpVerificationProps) {
+ const state = useSignUpStateMatcher();
+ return state.hasTag(name) ? children : null;
+}
diff --git a/packages/elements/tsup.config.ts b/packages/elements/tsup.config.ts
index 6ab5d5f4bf5..86b57be791d 100644
--- a/packages/elements/tsup.config.ts
+++ b/packages/elements/tsup.config.ts
@@ -17,6 +17,9 @@ export default defineConfig(overrideOptions => {
dts: true,
entry: {
index: 'src/index.ts',
+ 'react/common/index': 'src/react/common/index.ts',
+ 'react/sign-in/index': 'src/react/sign-in/index.ts',
+ 'react/sign-up/index': 'src/react/sign-up/index.ts',
},
external: ['react', 'react-dom'],
format: ['cjs', 'esm'],