feat(ui): Expose profile sub components#8654
Conversation
|
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
!snapshot |
This comment has been minimized.
This comment has been minimized.
|
!snapshot |
This comment has been minimized.
This comment has been minimized.
|
!snapshot |
This comment has been minimized.
This comment has been minimized.
|
!snapshot |
This comment has been minimized.
This comment has been minimized.
|
!snapshot |
|
Hey @alexcarpenter - the snapshot version command generated the following package versions:
Tip: Use the snippet copy button below to quickly install the required packages. npm i @clerk/astro@3.3.0-snapshot.v20260527161602 --save-exact
npm i @clerk/backend@3.4.12-snapshot.v20260527161602 --save-exact
npm i @clerk/chrome-extension@3.1.29-snapshot.v20260527161602 --save-exact
npm i @clerk/clerk-js@6.12.0-snapshot.v20260527161602 --save-exact
npm i @clerk/dev-cli@0.1.1-snapshot.v20260527161602 --save-exact
npm i @clerk/expo@3.2.15-snapshot.v20260527161602 --save-exact
npm i @clerk/expo-passkeys@1.0.28-snapshot.v20260527161602 --save-exact
npm i @clerk/express@2.1.20-snapshot.v20260527161602 --save-exact
npm i @clerk/fastify@3.1.30-snapshot.v20260527161602 --save-exact
npm i @clerk/hono@0.1.30-snapshot.v20260527161602 --save-exact
npm i @clerk/localizations@4.6.7-snapshot.v20260527161602 --save-exact
npm i @clerk/msw@0.0.28-snapshot.v20260527161602 --save-exact
npm i @clerk/nextjs@7.4.0-snapshot.v20260527161602 --save-exact
npm i @clerk/nuxt@2.5.0-snapshot.v20260527161602 --save-exact
npm i @clerk/react@6.7.0-snapshot.v20260527161602 --save-exact
npm i @clerk/react-router@3.3.0-snapshot.v20260527161602 --save-exact
npm i @clerk/shared@4.13.0-snapshot.v20260527161602 --save-exact
npm i @clerk/tanstack-react-start@1.3.0-snapshot.v20260527161602 --save-exact
npm i @clerk/testing@2.0.32-snapshot.v20260527161602 --save-exact
npm i @clerk/ui@1.13.0-snapshot.v20260527161602 --save-exact
npm i @clerk/upgrade@2.0.3-snapshot.v20260527161602 --save-exact
npm i @clerk/vue@2.3.0-snapshot.v20260527161602 --save-exact |
e47d56e to
4c41b6b
Compare
Annotate every exported composed component with a ReactNode return type so the generated .d.ts files are compatible with both React 18 and React 19 consumers. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Export UserProfile and OrganizationProfile as function components with static properties (via Object.assign) instead of plain objects. This matches the existing UserButton pattern and is compatible with React Server Components client references, which support property access on function exports but not on plain object exports. The provider is now the root component itself — consumers write <UserProfile> instead of <UserProfile.Provider>. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
c6528a9 to
3a89eb3
Compare
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The spy returned undefined in jsdom, causing autoAnimate's MutationObserver callback to crash on .addEventListener. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
I like this from a directional standpoint and I agree that exposing subcomponents makes sense for flexibility and customization. At a quick glance, though, this seems to duplicate/add another Profile component stack that then needs extra context plumbed into it to work. That expands our public/internal contract and likely increases the chance of drift and defects over time. Could we instead look at extracting the shared parts from the current Profile components so the existing implementation and the new subcomponent API compose the same internals differently? What do you think? Happy to collab on this if you want or drop a high level proposal for internals |
|
@jacekradko here is an update with shared parts targeting this branch #8714 |
Ephem
left a comment
There was a problem hiding this comment.
I need to think more on the clerk-js/ui boundary, but I thought I'd leave some random thoughts.
| } from './sectionWrappers'; | ||
| import { ConfigureSSO } from './ConfigureSSO'; | ||
|
|
||
| export const OrganizationProfile = Object.assign(OrganizationProfileProvider, { |
There was a problem hiding this comment.
Same comment as here: #8474 (comment)
These are meant to be publicly exported, so do we optimize for treeshaking early even though that's annoying for compound components?
|
|
||
| export const APIKeys = (): ReactNode => ( | ||
| <CardStateProvider> | ||
| <Suspense fallback={''}> |
There was a problem hiding this comment.
The <Suspense> fallbacks are interesting. 🤔 I think designing the loading and error experience should be front and center here.
At what levels do we handle it? (Page, section, component?) How do we make it good out of the box, while also allowing devs to tweak fallbacks?
This is tricky since we really only use Suspense for the initialState now, so maybe the thinking and right move is to just do '' for now? Maybe we don't allow for tweaking loading states besides CSS in the first iteration?
It would be interesting to figure out the best possible long term DX here even if we don't implement it right away. I think that's Suspense+Error boundaries for everything, so the user can control it.
Just thinking out loud, but maybe that's also composable? Also maybe they want a top level loading state for their entire page/section of the page where Clerk is a small part, and that loading state lives far away from the Clerk code? They don't want the rest of the page to reveal first, show Clerk loading state, reveal Clerk, they want to wait for everything to load before revealing, including Clerk? Especially now that they can compose together their UI.
This of course means we can't render the components in their own React application, it needs to compose together with the host app, but I don't think this PR is doing the standalone render right?
|
|
||
| type UserProfileProviderProps = React.PropsWithChildren<{ | ||
| appearance?: Appearance; | ||
| additionalOAuthScopes?: Partial<Record<OAuthProvider, OAuthScope[]>>; |
There was a problem hiding this comment.
If these components keep rendering inside the host app, as opposed to as a separate React app, maybe SSR becomes as "easy" as passing in an initialState prop here, which we put on a context the hooks read from further down? 🤔
There was a problem hiding this comment.
Possibility! I think @jacekradko is looking at bringing these back under the same runtime here #8718
There was a problem hiding this comment.
I was looking at that, and if I understood it correctly, the components were still rendering as part of the host app, and not as a separate app?
Summary
Exposes
UserProfileandOrganizationProfileas composable sub-components from@clerk/ui/experimental. Lets consumers render individual profile sections (Account, Security, Members, Billing, etc.) outside the full modal/page flow.Approach
composed/directory — each profile gets a Provider that wires up the required context tree (appearance, environment, module manager, routing, flow metadata) so individual section components render standalone.moduleManagerStore— module-level get/set because composed components mount outside the normal component tree and can't inheritModuleManagervia context fromClerkUI.init().stubRouter— minimalRouteContextimplementation delegating toclerk.navigate. Child components calluseRouter()internally but composed pages don't do real routing.StyleCacheProvider— moveddocument.querySelectorfrom module scope into the component body; import-time access breaks SSR.useSafeState— resetisMountedReftotruein effect body; StrictMode cleanup between double-invocation left it permanentlyfalse.Animated— guard against StrictMode double-mount leaving animation refs stale.@clerk/ui/experimentalwith'use client'directive.API
Sub-components available:
Account,Security,Billing,APIKeys(user);General,Members,Billing,APIKeys,ConfigureSSO(org). Fine-grained wrappers also exported (e.g.AccountProfile,SecurityPasskeys).Test plan
🤖 Generated with Claude Code