diff --git a/.changeset/short-mails-brush.md b/.changeset/short-mails-brush.md new file mode 100644 index 00000000000..6ca8d0dfe7b --- /dev/null +++ b/.changeset/short-mails-brush.md @@ -0,0 +1,20 @@ +--- +'@clerk/elements': patch +--- + +Add Elements `` component. + +```tsx +import * as Clerk from '@clerk/elements/common'; +import NextLink from 'next/link'; + +function SignInPage() { + return ( + <> + Sign up + + {url => Sign up} + + ); +} +``` diff --git a/.changeset/weak-hornets-hunt.md b/.changeset/weak-hornets-hunt.md new file mode 100644 index 00000000000..b15f996d895 --- /dev/null +++ b/.changeset/weak-hornets-hunt.md @@ -0,0 +1,8 @@ +--- +'@clerk/clerk-js': patch +'@clerk/shared': patch +'@clerk/clerk-react': patch +'@clerk/types': patch +--- + +Expose internal `__internal_getOption` method from Clerk. diff --git a/packages/clerk-js/src/core/clerk.ts b/packages/clerk-js/src/core/clerk.ts index 9d6d253b9af..7c610a15d50 100644 --- a/packages/clerk-js/src/core/clerk.ts +++ b/packages/clerk-js/src/core/clerk.ts @@ -252,6 +252,10 @@ export class Clerk implements ClerkInterface { return this.#options.standardBrowser || false; } + public __internal_getOption(key: K): ClerkOptions[K] { + return this.#options[key]; + } + public constructor(key: string, options?: DomainOrProxyUrl) { key = (key || '').trim(); diff --git a/packages/elements/src/react/common/index.ts b/packages/elements/src/react/common/index.ts index 72504ef7901..23898e39274 100644 --- a/packages/elements/src/react/common/index.ts +++ b/packages/elements/src/react/common/index.ts @@ -4,6 +4,7 @@ import 'client-only'; export { Field, FieldError, FieldState, GlobalError, Input, Label, Submit } from '~/react/common/form'; export { Connection, Icon } from '~/react/common/connections'; export { Loading } from '~/react/common/loading'; +export { Link } from '~/react/common/link'; export type { FormFieldErrorProps, diff --git a/packages/elements/src/react/common/link.tsx b/packages/elements/src/react/common/link.tsx new file mode 100644 index 00000000000..74d2b6c9ed7 --- /dev/null +++ b/packages/elements/src/react/common/link.tsx @@ -0,0 +1,60 @@ +import { useClerk } from '@clerk/shared/react'; +import { useClerkRouter } from '@clerk/shared/router'; +import type { ClerkOptions } from '@clerk/types'; +import React from 'react'; + +type Destination = 'sign-in' | 'sign-up'; +export interface LinkProps extends Omit, 'children'> { + navigate: Destination; + children: React.ReactNode | ((props: { url: string }) => React.ReactNode); +} + +const paths: Record> = { + 'sign-in': 'signInUrl', + 'sign-up': 'signUpUrl', +}; + +/** + * The `` component is used to navigate between sign-in and sign-up flows. + * + * @param {Destination} navigate - The destination to navigate to. + * + * @example + * ```tsx + * Sign in + * ``` + * @example + * ```tsx + * + * {({ url }) => ( + * Sign in + * )} + * + */ + +export function Link({ navigate, children, ...rest }: LinkProps) { + const router = useClerkRouter(); + const clerk = useClerk(); + const destinationUrl = router.makeDestinationUrlWithPreservedQueryParameters( + clerk.__internal_getOption(paths[navigate])!, + ); + + if (typeof children === 'function') { + return children({ url: destinationUrl }); + } + + return ( + { + if (router) { + e.preventDefault(); + router.push(destinationUrl); + } + }} + href={destinationUrl} + {...rest} + > + {children} + + ); +} diff --git a/packages/react/src/isomorphicClerk.ts b/packages/react/src/isomorphicClerk.ts index 65684e4a91b..40c865118d9 100644 --- a/packages/react/src/isomorphicClerk.ts +++ b/packages/react/src/isomorphicClerk.ts @@ -11,6 +11,7 @@ import type { AuthenticateWithMetamaskParams, Clerk, ClerkAuthenticateWithWeb3Params, + ClerkOptions, ClientResource, CreateOrganizationParams, CreateOrganizationProps, @@ -254,6 +255,10 @@ export class IsomorphicClerk implements IsomorphicLoadedClerk { return this.#proxyUrl || ''; } + public __internal_getOption(key: K): ClerkOptions[K] | undefined { + return this.clerkjs?.__internal_getOption(key); + } + constructor(options: IsomorphicClerkOptions) { const { Clerk = null, publishableKey } = options || {}; this.#publishableKey = publishableKey; diff --git a/packages/shared/src/router/router.ts b/packages/shared/src/router/router.ts index e8c16969228..69cd41eb159 100644 --- a/packages/shared/src/router/router.ts +++ b/packages/shared/src/router/router.ts @@ -8,6 +8,7 @@ export const PRESERVED_QUERYSTRING_PARAMS = ['after_sign_in_url', 'after_sign_up * Internal Clerk router, used by Clerk components to interact with the host's router. */ export type ClerkRouter = { + makeDestinationUrlWithPreservedQueryParameters: (path: string) => string; /** * The basePath the router is currently mounted on. */ @@ -132,6 +133,7 @@ export function createClerkRouter(router: ClerkHostRouter, basePath: string = '/ } return { + makeDestinationUrlWithPreservedQueryParameters, child, match, mode: router.mode, diff --git a/packages/types/src/clerk.ts b/packages/types/src/clerk.ts index b9fea3d28fb..ffc92d5a403 100644 --- a/packages/types/src/clerk.ts +++ b/packages/types/src/clerk.ts @@ -106,6 +106,8 @@ export interface Clerk { */ loaded: boolean; + __internal_getOption(key: K): ClerkOptions[K]; + frontendApi: string; /** Clerk Publishable Key string. */