Skip to content

Commit 1a0c8fe

Browse files
alexcarpenterLauraBeatris
andauthoredNov 4, 2024··
feat(elements): Add <Link /> component (#4456)
Co-authored-by: Laura Beatris <48022589+LauraBeatris@users.noreply.github.com>
1 parent 0800fc3 commit 1a0c8fe

File tree

8 files changed

+102
-0
lines changed

8 files changed

+102
-0
lines changed
 

‎.changeset/short-mails-brush.md

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
---
2+
'@clerk/elements': patch
3+
---
4+
5+
Add Elements `<Link />` component.
6+
7+
```tsx
8+
import * as Clerk from '@clerk/elements/common';
9+
import NextLink from 'next/link';
10+
11+
function SignInPage() {
12+
return (
13+
<>
14+
<Clerk.Link navigate='sign-up'>Sign up</Clerk.Link>
15+
16+
<Clerk.Link navigate='sign-up'>{url => <NextLink href={url}>Sign up</NextLink>}</Clerk.Link>
17+
</>
18+
);
19+
}
20+
```

‎.changeset/weak-hornets-hunt.md

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
'@clerk/clerk-js': patch
3+
'@clerk/shared': patch
4+
'@clerk/clerk-react': patch
5+
'@clerk/types': patch
6+
---
7+
8+
Expose internal `__internal_getOption` method from Clerk.

‎packages/clerk-js/src/core/clerk.ts

+4
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,10 @@ export class Clerk implements ClerkInterface {
252252
return this.#options.standardBrowser || false;
253253
}
254254

255+
public __internal_getOption<K extends keyof ClerkOptions>(key: K): ClerkOptions[K] {
256+
return this.#options[key];
257+
}
258+
255259
public constructor(key: string, options?: DomainOrProxyUrl) {
256260
key = (key || '').trim();
257261

‎packages/elements/src/react/common/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import 'client-only';
44
export { Field, FieldError, FieldState, GlobalError, Input, Label, Submit } from '~/react/common/form';
55
export { Connection, Icon } from '~/react/common/connections';
66
export { Loading } from '~/react/common/loading';
7+
export { Link } from '~/react/common/link';
78

89
export type {
910
FormFieldErrorProps,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import { useClerk } from '@clerk/shared/react';
2+
import { useClerkRouter } from '@clerk/shared/router';
3+
import type { ClerkOptions } from '@clerk/types';
4+
import React from 'react';
5+
6+
type Destination = 'sign-in' | 'sign-up';
7+
export interface LinkProps extends Omit<React.HTMLAttributes<HTMLAnchorElement>, 'children'> {
8+
navigate: Destination;
9+
children: React.ReactNode | ((props: { url: string }) => React.ReactNode);
10+
}
11+
12+
const paths: Record<Destination, keyof Pick<ClerkOptions, 'signInUrl' | 'signUpUrl'>> = {
13+
'sign-in': 'signInUrl',
14+
'sign-up': 'signUpUrl',
15+
};
16+
17+
/**
18+
* The `<Link>` component is used to navigate between sign-in and sign-up flows.
19+
*
20+
* @param {Destination} navigate - The destination to navigate to.
21+
*
22+
* @example
23+
* ```tsx
24+
* <Link navigate="sign-in">Sign in</Link>
25+
* ```
26+
* @example
27+
* ```tsx
28+
* <Link navigate="sign-in">
29+
* {({ url }) => (
30+
* <NextLink href={url}>Sign in</NextLink>
31+
* )}
32+
* </Link>
33+
*/
34+
35+
export function Link({ navigate, children, ...rest }: LinkProps) {
36+
const router = useClerkRouter();
37+
const clerk = useClerk();
38+
const destinationUrl = router.makeDestinationUrlWithPreservedQueryParameters(
39+
clerk.__internal_getOption(paths[navigate])!,
40+
);
41+
42+
if (typeof children === 'function') {
43+
return children({ url: destinationUrl });
44+
}
45+
46+
return (
47+
<a
48+
onClick={e => {
49+
if (router) {
50+
e.preventDefault();
51+
router.push(destinationUrl);
52+
}
53+
}}
54+
href={destinationUrl}
55+
{...rest}
56+
>
57+
{children}
58+
</a>
59+
);
60+
}

‎packages/react/src/isomorphicClerk.ts

+5
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import type {
1111
AuthenticateWithMetamaskParams,
1212
Clerk,
1313
ClerkAuthenticateWithWeb3Params,
14+
ClerkOptions,
1415
ClientResource,
1516
CreateOrganizationParams,
1617
CreateOrganizationProps,
@@ -254,6 +255,10 @@ export class IsomorphicClerk implements IsomorphicLoadedClerk {
254255
return this.#proxyUrl || '';
255256
}
256257

258+
public __internal_getOption<K extends keyof ClerkOptions>(key: K): ClerkOptions[K] | undefined {
259+
return this.clerkjs?.__internal_getOption(key);
260+
}
261+
257262
constructor(options: IsomorphicClerkOptions) {
258263
const { Clerk = null, publishableKey } = options || {};
259264
this.#publishableKey = publishableKey;

‎packages/shared/src/router/router.ts

+2
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ export const PRESERVED_QUERYSTRING_PARAMS = ['after_sign_in_url', 'after_sign_up
88
* Internal Clerk router, used by Clerk components to interact with the host's router.
99
*/
1010
export type ClerkRouter = {
11+
makeDestinationUrlWithPreservedQueryParameters: (path: string) => string;
1112
/**
1213
* The basePath the router is currently mounted on.
1314
*/
@@ -132,6 +133,7 @@ export function createClerkRouter(router: ClerkHostRouter, basePath: string = '/
132133
}
133134

134135
return {
136+
makeDestinationUrlWithPreservedQueryParameters,
135137
child,
136138
match,
137139
mode: router.mode,

‎packages/types/src/clerk.ts

+2
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,8 @@ export interface Clerk {
106106
*/
107107
loaded: boolean;
108108

109+
__internal_getOption<K extends keyof ClerkOptions>(key: K): ClerkOptions[K];
110+
109111
frontendApi: string;
110112

111113
/** Clerk Publishable Key string. */

0 commit comments

Comments
 (0)
Please sign in to comment.