Skip to content

Commit 6bbf272

Browse files
committed
feat(react,vue,astro): Replace clerkUiCtor prop with ui prop
Refactors the public API so users use `ui={ui}` instead of exposing `clerkUiCtor` directly. The bundled UI can now be imported from `@clerk/ui` and passed via the `ui` prop. - Omit `clerkUiCtor` from public `ClerkProviderProps` - Add `ctor` property to `Ui` type for bundled constructor - Convert `ui.ctor` to `clerkUiCtor` internally in providers - Update Chrome Extension, Vue, and Astro to use new pattern
1 parent fd5cae5 commit 6bbf272

File tree

9 files changed

+58
-12
lines changed

9 files changed

+58
-12
lines changed

packages/astro/src/internal/create-clerk-instance.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -115,8 +115,10 @@ async function getClerkJsEntryChunk<TUi extends Ui = Ui>(options?: AstroClerkCre
115115
async function getClerkUiEntryChunk<TUi extends Ui = Ui>(
116116
options?: AstroClerkCreateInstanceParams<TUi>,
117117
): Promise<ClerkUiConstructor> {
118-
if (options?.clerkUiCtor) {
119-
return options.clerkUiCtor;
118+
// Check for ui.ctor first (new API), then fall back to clerkUiCtor (deprecated)
119+
const ctorFromUi = options?.ui?.ctor;
120+
if (ctorFromUi) {
121+
return ctorFromUi;
120122
}
121123

122124
await loadClerkUiScript(options);

packages/astro/src/types.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,11 @@ type AstroClerkIntegrationParams<TUi extends Ui = Ui> = Without<
3636
* The URL that `@clerk/ui` should be hot-loaded from.
3737
*/
3838
clerkUiUrl?: string;
39+
/**
40+
* The Clerk UI bundle to use. When provided with a bundled UI via
41+
* `ui.ctor`, it will be used instead of loading from CDN.
42+
*/
43+
ui?: TUi;
3944
};
4045

4146
type AstroClerkCreateInstanceParams<TUi extends Ui = Ui> = AstroClerkIntegrationParams<TUi> & {

packages/chrome-extension/src/react/ClerkProvider.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import type { Clerk } from '@clerk/clerk-js/no-rhc';
22
import type { ClerkProviderProps as ClerkReactProviderProps } from '@clerk/react';
33
import { ClerkProvider as ClerkReactProvider } from '@clerk/react';
44
import type { Ui } from '@clerk/react/internal';
5-
import { ClerkUi } from '@clerk/ui/entry';
5+
import { ui as clerkUi } from '@clerk/ui';
66
import React from 'react';
77

88
import { createClerkClient } from '../internal/clerk';
@@ -36,7 +36,7 @@ export function ClerkProvider<TUi extends Ui = Ui>(props: ChromeExtensionClerkPr
3636
<ClerkReactProvider
3737
{...rest}
3838
Clerk={clerkInstance}
39-
clerkUiCtor={ClerkUi}
39+
ui={clerkUi as TUi}
4040
standardBrowser={!syncHost}
4141
>
4242
{children}

packages/react/src/contexts/ClerkProvider.tsx

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,16 @@ import { withMaxAllowedInstancesGuard } from '../utils';
88
import { ClerkContextProvider } from './ClerkContextProvider';
99

1010
function ClerkProviderBase<TUi extends Ui>(props: ClerkProviderProps<TUi>) {
11-
const { initialState, children, ...restIsomorphicClerkOptions } = props;
12-
const isomorphicClerkOptions = restIsomorphicClerkOptions as unknown as IsomorphicClerkOptions;
11+
const { initialState, children, ui, ...restIsomorphicClerkOptions } = props;
12+
13+
// Convert ui prop to internal options
14+
const isomorphicClerkOptions = {
15+
...restIsomorphicClerkOptions,
16+
// Pass ui metadata for version/url handling
17+
ui,
18+
// Convert ui.ctor to clerkUiCtor if provided
19+
...(ui?.ctor && { clerkUiCtor: ui.ctor }),
20+
} as unknown as IsomorphicClerkOptions;
1321

1422
return (
1523
<ClerkContextProvider

packages/react/src/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ declare global {
3636
/**
3737
* @interface
3838
*/
39-
export type ClerkProviderProps<TUi extends Ui = Ui> = Omit<IsomorphicClerkOptions, 'appearance'> & {
39+
export type ClerkProviderProps<TUi extends Ui = Ui> = Omit<IsomorphicClerkOptions, 'appearance' | 'clerkUiCtor'> & {
4040
children: React.ReactNode;
4141
/**
4242
* Provide an initial state of the Clerk client during server-side rendering. You don't need to set this value yourself unless you're [developing an SDK](https://clerk.com/docs/guides/development/sdk-development/overview).

packages/shared/src/types/clerk.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2436,7 +2436,15 @@ export type IsomorphicClerkOptions = Without<ClerkOptions, 'isSatellite'> & {
24362436
* This is a structural-only type for the `ui` object that can be passed
24372437
* to Clerk.load() and ClerkProvider
24382438
*/
2439-
ui?: { version: string; url?: string };
2439+
ui?: {
2440+
version: string;
2441+
url?: string;
2442+
/**
2443+
* The Clerk UI constructor. When provided, this will be used instead of
2444+
* loading the UI from CDN. This is useful for bundling the UI with your app.
2445+
*/
2446+
ctor?: ClerkUiConstructor | Promise<ClerkUiConstructor>;
2447+
};
24402448
} & MultiDomainAndOrProxy;
24412449

24422450
export interface LoadedClerk extends Clerk {

packages/ui/src/index.ts

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,26 @@
11
import type { Ui } from './internal';
22
import type { Appearance } from './internal/appearance';
33

4+
import { ClerkUi } from './ClerkUi';
5+
46
declare const PACKAGE_VERSION: string;
57

8+
export { ClerkUi };
9+
610
/**
7-
* Default ui object for Clerk UI components
8-
* Tagged with the internal Appearance type for type-safe appearance prop inference
11+
* Bundled ui object that includes the Clerk UI constructor.
12+
* Use this when you want to bundle the UI with your app instead of loading from CDN.
13+
*
14+
* @example
15+
* ```tsx
16+
* import { ui } from '@clerk/ui';
17+
*
18+
* <ClerkProvider ui={ui}>
19+
* ...
20+
* </ClerkProvider>
21+
* ```
922
*/
1023
export const ui = {
1124
version: PACKAGE_VERSION,
25+
ctor: ClerkUi,
1226
} as Ui<Appearance>;

packages/ui/src/internal/index.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import type { ClerkUiConstructor } from '@clerk/shared/ui';
2+
13
import type { Appearance } from './appearance';
24

35
export type { ComponentControls, MountComponentRenderer } from '../Components';
@@ -26,6 +28,11 @@ export type Ui<A = any> = Tagged<
2628
{
2729
version: string;
2830
url?: string;
31+
/**
32+
* The Clerk UI constructor. When provided, this will be used instead of
33+
* loading the UI from CDN. This is useful for bundling the UI with your app.
34+
*/
35+
ctor?: ClerkUiConstructor | Promise<ClerkUiConstructor>;
2936
/**
3037
* Phantom property for type-level appearance inference
3138
* This property never exists at runtime

packages/vue/src/plugin.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,8 +78,10 @@ export const clerkPlugin: Plugin<[PluginOptions]> = {
7878
void (async () => {
7979
try {
8080
const clerkPromise = loadClerkJsScript(options);
81-
const clerkUiCtorPromise = pluginOptions.clerkUiCtor
82-
? Promise.resolve(pluginOptions.clerkUiCtor)
81+
// Check for ui.ctor first (new API), then fall back to clerkUiCtor (deprecated)
82+
const ctorFromUi = pluginOptions.ui?.ctor;
83+
const clerkUiCtorPromise = ctorFromUi
84+
? Promise.resolve(ctorFromUi)
8385
: (async () => {
8486
await loadClerkUiScript(options);
8587
if (!window.__internal_ClerkUiCtor) {

0 commit comments

Comments
 (0)