diff --git a/.changeset/fast-bananas-visit.md b/.changeset/fast-bananas-visit.md new file mode 100644 index 00000000000..57d06b1a827 --- /dev/null +++ b/.changeset/fast-bananas-visit.md @@ -0,0 +1,35 @@ +--- +"@clerk/nuxt": minor +"@clerk/vue": minor +--- + +Introduce `updateClerkOptions()` utility function to update Clerk options on the fly. + +Usage: + +```vue + + + + Enable Dark Theme + Change to French + +``` diff --git a/integration/presets/vue.ts b/integration/presets/vue.ts index 1003af19f58..2d6c75a3911 100644 --- a/integration/presets/vue.ts +++ b/integration/presets/vue.ts @@ -10,7 +10,8 @@ const vite = applicationConfig() .addScript('dev', 'pnpm dev') .addScript('build', 'pnpm build') .addScript('serve', 'pnpm preview') - .addDependency('@clerk/vue', linkPackage('vue')); + .addDependency('@clerk/vue', linkPackage('vue')) + .addDependency('@clerk/localizations', linkPackage('localizations')); export const vue = { vite, diff --git a/integration/templates/vue-vite/src/App.vue b/integration/templates/vue-vite/src/App.vue index f913f45b9e4..6477a90213f 100644 --- a/integration/templates/vue-vite/src/App.vue +++ b/integration/templates/vue-vite/src/App.vue @@ -1,6 +1,7 @@ @@ -9,6 +10,7 @@ import CustomUserButton from './components/CustomUserButton.vue'; Vue Clerk Integration test + diff --git a/integration/templates/vue-vite/src/components/LanguagePicker.vue b/integration/templates/vue-vite/src/components/LanguagePicker.vue new file mode 100644 index 00000000000..c1ba96a01fa --- /dev/null +++ b/integration/templates/vue-vite/src/components/LanguagePicker.vue @@ -0,0 +1,25 @@ + + + + + English + French + + diff --git a/integration/tests/vue/components.test.ts b/integration/tests/vue/components.test.ts index 603aba71c58..23aa03344d4 100644 --- a/integration/tests/vue/components.test.ts +++ b/integration/tests/vue/components.test.ts @@ -297,4 +297,23 @@ testAgainstRunningApps({ withEnv: [appConfigs.envs.withCustomRoles] })('basic te await fakeAdmin.deleteIfExists(); }); + + test('Update Clerk options on the fly with updateClerkOptions()', async ({ page, context }) => { + const u = createTestUtils({ app, page, context }); + + // Navigate and wait for sign-in component to load + await u.page.goToRelative('/sign-in'); + await u.po.signIn.waitForMounted(); + + // Verify initial English state + await expect(u.page.getByText('Welcome back! Please sign in to continue')).toBeVisible(); + + // Change to French and verify + await u.page.locator('select').selectOption({ label: 'French' }); + await expect(u.page.getByText('pour continuer vers')).toBeVisible(); + + // Revert to English and verify + await u.page.locator('select').selectOption({ label: 'English' }); + await expect(u.page.getByText('Welcome back! Please sign in to continue')).toBeVisible(); + }); }); diff --git a/packages/nuxt/src/module.ts b/packages/nuxt/src/module.ts index a39fb8c8369..8ac87696075 100644 --- a/packages/nuxt/src/module.ts +++ b/packages/nuxt/src/module.ts @@ -99,10 +99,16 @@ export default defineNuxtModule({ // Add auto-imports for Clerk components, composables and client utils addImportsDir(resolver.resolve('./runtime/composables')); - addImports({ - name: 'createRouteMatcher', - from: resolver.resolve('./runtime/client'), - }); + addImports([ + { + name: 'createRouteMatcher', + from: resolver.resolve('./runtime/client'), + }, + { + name: 'updateClerkOptions', + from: resolver.resolve('./runtime/client'), + }, + ]); // eslint-disable-next-line @typescript-eslint/consistent-type-imports const components: Array = [ diff --git a/packages/nuxt/src/runtime/client/index.ts b/packages/nuxt/src/runtime/client/index.ts index 209ae9608dc..631ad5718bf 100644 --- a/packages/nuxt/src/runtime/client/index.ts +++ b/packages/nuxt/src/runtime/client/index.ts @@ -1 +1,2 @@ export { createRouteMatcher } from './routeMatcher'; +export { updateClerkOptions } from '@clerk/vue'; diff --git a/packages/vue/src/index.ts b/packages/vue/src/index.ts index 3c836dbbe50..dc70787fab9 100644 --- a/packages/vue/src/index.ts +++ b/packages/vue/src/index.ts @@ -6,6 +6,7 @@ export * from './components'; export * from './composables'; export { clerkPlugin } from './plugin'; +export { updateClerkOptions } from './utils'; setErrorThrowerOptions({ packageName: PACKAGE_NAME }); setClerkJsLoadingErrorPackageName(PACKAGE_NAME); diff --git a/packages/vue/src/utils/index.ts b/packages/vue/src/utils/index.ts index 3b0a7909589..c4a11a1afd8 100644 --- a/packages/vue/src/utils/index.ts +++ b/packages/vue/src/utils/index.ts @@ -1,3 +1,4 @@ export * from './childrenUtils'; export * from './toComputedRefs'; export * from './useClerkLoaded'; +export * from './updateClerkOptions'; diff --git a/packages/vue/src/utils/updateClerkOptions.ts b/packages/vue/src/utils/updateClerkOptions.ts new file mode 100644 index 00000000000..c146e2045b0 --- /dev/null +++ b/packages/vue/src/utils/updateClerkOptions.ts @@ -0,0 +1,31 @@ +import type { ClerkOptions } from '@clerk/types'; + +type ClerkUpdateOptions = Pick; + +/** + * Updates Clerk's options at runtime. + * + * @param options - The Clerk options to update + * + * @example + * import { frFR } from '@clerk/localizations'; + * import { dark } from '@clerk/themes'; + * + * updateClerkOptions({ + * appearance: { baseTheme: dark }, + * localization: frFR + * }); + */ +export function updateClerkOptions(options: ClerkUpdateOptions) { + if (!window.Clerk) { + throw new Error('Missing Clerk instance'); + } + + // @ts-expect-error - `__unstable__updateProps` is not exposed as public API from `@clerk/types` + void window.Clerk.__unstable__updateProps({ + options: { + localization: options.localization, + }, + appearance: options.appearance, + }); +}
Vue Clerk Integration test