diff --git a/.changeset/dirty-panthers-build.md b/.changeset/dirty-panthers-build.md new file mode 100644 index 0000000000..4cdea45623 --- /dev/null +++ b/.changeset/dirty-panthers-build.md @@ -0,0 +1,13 @@ +--- +'@clerk/chrome-extension': minor +'@clerk/localizations': minor +'@clerk/clerk-js': minor +'@clerk/nextjs': minor +'@clerk/clerk-react': minor +'@clerk/types': minor +--- + +Introduce the new brand new component + +- Lists all the memberships, invitations or suggestions an active user may have +- Powered by our `useOrganizationList` react hook diff --git a/packages/chrome-extension/src/__snapshots__/exports.test.ts.snap b/packages/chrome-extension/src/__snapshots__/exports.test.ts.snap index 8f82a739b7..448480e58f 100644 --- a/packages/chrome-extension/src/__snapshots__/exports.test.ts.snap +++ b/packages/chrome-extension/src/__snapshots__/exports.test.ts.snap @@ -9,6 +9,7 @@ exports[`public exports should not include a breaking change 1`] = ` "CreateOrganization", "MagicLinkErrorCode", "MultisessionAppSupport", + "OrganizationList", "OrganizationProfile", "OrganizationSwitcher", "RedirectToCreateOrganization", diff --git a/packages/clerk-js/bundlewatch.config.json b/packages/clerk-js/bundlewatch.config.json index 36d5708d06..4703390f12 100644 --- a/packages/clerk-js/bundlewatch.config.json +++ b/packages/clerk-js/bundlewatch.config.json @@ -8,6 +8,7 @@ { "path": "./dist/impersonationfab*.js", "maxSize": "5KB" }, { "path": "./dist/organizationprofile*.js", "maxSize": "10KB" }, { "path": "./dist/organizationswitcher*.js", "maxSize": "5KB" }, + { "path": "./dist/organizationlist*.js", "maxSize": "5.5KB" }, { "path": "./dist/signin*.js", "maxSize": "10KB" }, { "path": "./dist/signup*.js", "maxSize": "10KB" }, { "path": "./dist/userbutton*.js", "maxSize": "5KB" }, diff --git a/packages/clerk-js/src/core/clerk.ts b/packages/clerk-js/src/core/clerk.ts index b1462c15ad..36a2a5cc3d 100644 --- a/packages/clerk-js/src/core/clerk.ts +++ b/packages/clerk-js/src/core/clerk.ts @@ -32,6 +32,7 @@ import type { InstanceType, ListenerCallback, OrganizationInvitationResource, + OrganizationListProps, OrganizationMembershipResource, OrganizationProfileProps, OrganizationResource, @@ -506,6 +507,23 @@ export default class Clerk implements ClerkInterface { void this.#componentControls?.ensureMounted().then(controls => controls.unmountComponent({ node })); }; + public mountOrganizationList = (node: HTMLDivElement, props?: OrganizationListProps) => { + this.assertComponentsReady(this.#componentControls); + void this.#componentControls?.ensureMounted({ preloadHint: 'OrganizationList' }).then(controls => + controls.mountComponent({ + name: 'OrganizationList', + appearanceKey: 'organizationList', + node, + props, + }), + ); + }; + + public unmountOrganizationList = (node: HTMLDivElement): void => { + this.assertComponentsReady(this.#componentControls); + void this.#componentControls?.ensureMounted().then(controls => controls.unmountComponent({ node })); + }; + public mountUserButton = (node: HTMLDivElement, props?: UserButtonProps) => { this.assertComponentsReady(this.#componentControls); void this.#componentControls?.ensureMounted({ preloadHint: 'UserButton' }).then(controls => diff --git a/packages/clerk-js/src/ui/lazyModules/components.ts b/packages/clerk-js/src/ui/lazyModules/components.ts index 3f1d60a9ff..f4fe93f9a4 100644 --- a/packages/clerk-js/src/ui/lazyModules/components.ts +++ b/packages/clerk-js/src/ui/lazyModules/components.ts @@ -10,6 +10,7 @@ const componentImportPaths = { import(/* webpackChunkName: "organizationprofile" */ './../components/OrganizationProfile'), OrganizationSwitcher: () => import(/* webpackChunkName: "organizationswitcher" */ './../components/OrganizationSwitcher'), + OrganizationList: () => import(/* webpackChunkName: "organizationlist" */ './../components/OrganizationList'), ImpersonationFab: () => import(/* webpackChunkName: "impersonationfab" */ './../components/ImpersonationFab'), } as const; @@ -50,6 +51,10 @@ export const OrganizationSwitcher = lazy(() => componentImportPaths.OrganizationSwitcher().then(module => ({ default: module.OrganizationSwitcher })), ); +export const OrganizationList = lazy(() => + componentImportPaths.OrganizationList().then(module => ({ default: module.OrganizationList })), +); + export const ImpersonationFab = lazy(() => componentImportPaths.ImpersonationFab().then(module => ({ default: module.ImpersonationFab })), ); @@ -64,6 +69,7 @@ export const ClerkComponents = { UserButton, UserProfile, OrganizationSwitcher, + OrganizationList, OrganizationProfile, CreateOrganization, SignInModal, diff --git a/packages/nextjs/src/client-boundary/uiComponents.tsx b/packages/nextjs/src/client-boundary/uiComponents.tsx index 52b84390d5..ef57bd5d6d 100644 --- a/packages/nextjs/src/client-boundary/uiComponents.tsx +++ b/packages/nextjs/src/client-boundary/uiComponents.tsx @@ -15,6 +15,7 @@ export { SignInButton, SignUpButton, SignOutButton, + OrganizationList, } from '@clerk/clerk-react'; export const SignIn = (props: SignInProps) => { diff --git a/packages/nextjs/src/index.ts b/packages/nextjs/src/index.ts index 55c663215a..eb4c122a96 100644 --- a/packages/nextjs/src/index.ts +++ b/packages/nextjs/src/index.ts @@ -29,6 +29,7 @@ export { CreateOrganization, SignInButton, SignOutButton, + OrganizationList, } from './client-boundary/uiComponents'; /** diff --git a/packages/react/src/components/index.ts b/packages/react/src/components/index.ts index 55050610cb..1d00b724a3 100644 --- a/packages/react/src/components/index.ts +++ b/packages/react/src/components/index.ts @@ -6,6 +6,7 @@ export { OrganizationSwitcher, OrganizationProfile, CreateOrganization, + OrganizationList, } from './uiComponents'; export { diff --git a/packages/react/src/components/uiComponents.tsx b/packages/react/src/components/uiComponents.tsx index f5621dff59..63bd8fe398 100644 --- a/packages/react/src/components/uiComponents.tsx +++ b/packages/react/src/components/uiComponents.tsx @@ -1,5 +1,6 @@ import type { CreateOrganizationProps, + OrganizationListProps, OrganizationProfileProps, OrganizationSwitcherProps, SignInProps, @@ -142,3 +143,14 @@ export const OrganizationSwitcher = withClerk(({ clerk, ...props }: WithClerkPro /> ); }, 'OrganizationSwitcher'); + +export const OrganizationList = withClerk(({ clerk, ...props }: WithClerkProp) => { + return ( + + ); +}, 'OrganizationList'); diff --git a/packages/react/src/isomorphicClerk.ts b/packages/react/src/isomorphicClerk.ts index 8927f0d8d6..2a6b3a930d 100644 --- a/packages/react/src/isomorphicClerk.ts +++ b/packages/react/src/isomorphicClerk.ts @@ -11,6 +11,7 @@ import type { HandleMagicLinkVerificationParams, HandleOAuthCallbackParams, ListenerCallback, + OrganizationListProps, OrganizationMembershipResource, OrganizationResource, RedirectOptions, @@ -71,6 +72,7 @@ export default class IsomorphicClerk { private premountOrganizationProfileNodes = new Map(); private premountCreateOrganizationNodes = new Map(); private premountOrganizationSwitcherNodes = new Map(); + private premountOrganizationListNodes = new Map(); private premountMethodCalls = new Map, MethodCallback>(); private loadedListeners: Array<() => void> = []; @@ -266,6 +268,10 @@ export default class IsomorphicClerk { clerkjs.mountUserButton(node, props); }); + this.premountOrganizationListNodes.forEach((props: OrganizationListProps, node: HTMLDivElement) => { + clerkjs.mountOrganizationList(node, props); + }); + this.#loaded = true; this.emitLoaded(); return this.clerkjs; @@ -528,6 +534,22 @@ export default class IsomorphicClerk { } }; + mountOrganizationList = (node: HTMLDivElement, props: OrganizationListProps): void => { + if (this.clerkjs && this.#loaded) { + this.clerkjs.mountOrganizationList(node, props); + } else { + this.premountOrganizationListNodes.set(node, props); + } + }; + + unmountOrganizationList = (node: HTMLDivElement): void => { + if (this.clerkjs && this.#loaded) { + this.clerkjs.unmountOrganizationList(node); + } else { + this.premountOrganizationListNodes.delete(node); + } + }; + mountUserButton = (node: HTMLDivElement, userButtonProps: UserButtonProps): void => { if (this.clerkjs && this.#loaded) { this.clerkjs.mountUserButton(node, userButtonProps); diff --git a/packages/types/src/clerk.ts b/packages/types/src/clerk.ts index bc334cffd5..90cbf6f837 100644 --- a/packages/types/src/clerk.ts +++ b/packages/types/src/clerk.ts @@ -218,14 +218,14 @@ export interface Clerk { /** * Mount an organization profile component at the target element. - * @param targetNode Target to mount the UserProfile component. + * @param targetNode Target to mount the OrganizationProfile component. * @param props Configuration parameters. */ mountOrganizationProfile: (targetNode: HTMLDivElement, props?: OrganizationProfileProps) => void; /** * Unmount the organization profile component from the target node. - * @param targetNode Target node to unmount the UserProfile component from. + * @param targetNode Target node to unmount the OrganizationProfile component from. */ unmountOrganizationProfile: (targetNode: HTMLDivElement) => void; @@ -244,17 +244,30 @@ export interface Clerk { /** * Mount an organization switcher component at the target element. - * @param targetNode Target to mount the UserProfile component. + * @param targetNode Target to mount the OrganizationSwitcher component. * @param props Configuration parameters. */ mountOrganizationSwitcher: (targetNode: HTMLDivElement, props?: OrganizationSwitcherProps) => void; /** * Unmount the organization profile component from the target node.* - * @param targetNode Target node to unmount the UserProfile component from. + * @param targetNode Target node to unmount the OrganizationSwitcher component from. */ unmountOrganizationSwitcher: (targetNode: HTMLDivElement) => void; + /** + * Mount an organization list component at the target element. + * @param targetNode Target to mount the OrganizationList component. + * @param props Configuration parameters. + */ + mountOrganizationList: (targetNode: HTMLDivElement, props?: OrganizationListProps) => void; + + /** + * Unmount the organization list component from the target node.* + * @param targetNode Target node to unmount the OrganizationList component from. + */ + unmountOrganizationList: (targetNode: HTMLDivElement) => void; + /** * Register a listener that triggers a callback each time important Clerk resources are changed. * Allows to hook up at different steps in the sign up, sign in processes.