-
Notifications
You must be signed in to change notification settings - Fork 421
feat(clerk-js): Introduce UNSAFE_PortalProvider
#7310
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
🦋 Changeset detectedLatest commit: fa6ef65 The changes in this PR will be included in the next version bump. This PR includes changesets to release 19 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
📝 WalkthroughWalkthroughIntroduces portal container customization via UNSAFE_PortalProvider (React/Vue) and usePortalRoot hook across multiple packages, enabling consumers to specify custom containers for floating UI elements. Updates modal type definitions with optional getContainer prop, integrates container resolution in UI components, and re-exports new APIs through package entry points. Changes
Sequence Diagram(s)sequenceDiagram
participant Consumer
participant PortalProvider
participant usePortalRoot
participant Modal/Component
participant FloatingPortal
Consumer->>PortalProvider: Wrap component with UNSAFE_PortalProvider(getContainer)
PortalProvider->>PortalProvider: Create context with getContainer function
Consumer->>Modal/Component: Render child component
Modal/Component->>usePortalRoot: Call hook to retrieve portal container
usePortalRoot->>PortalProvider: Inject/consume PortalContext
usePortalRoot-->>Modal/Component: Return getContainer function
Modal/Component->>FloatingPortal: Pass getContainer result as root prop
FloatingPortal->>FloatingPortal: Render floating UI in custom container
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
Comment |
|
!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. |
|
!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. |
|
!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/agent-toolkit@0.2.6-snapshot.v20251125180024 --save-exact
npm i @clerk/astro@2.16.4-snapshot.v20251125180024 --save-exact
npm i @clerk/backend@2.24.1-snapshot.v20251125180024 --save-exact
npm i @clerk/chrome-extension@2.8.7-snapshot.v20251125180024 --save-exact
npm i @clerk/clerk-js@5.111.1-snapshot.v20251125180024 --save-exact
npm i @clerk/elements@0.23.87-snapshot.v20251125180024 --save-exact
npm i @clerk/clerk-expo@2.19.7-snapshot.v20251125180024 --save-exact
npm i @clerk/expo-passkeys@0.4.23-snapshot.v20251125180024 --save-exact
npm i @clerk/express@1.7.54-snapshot.v20251125180024 --save-exact
npm i @clerk/fastify@2.6.6-snapshot.v20251125180024 --save-exact
npm i @clerk/localizations@3.28.6-snapshot.v20251125180024 --save-exact
npm i @clerk/nextjs@6.35.6-snapshot.v20251125180024 --save-exact
npm i @clerk/nuxt@1.13.4-snapshot.v20251125180024 --save-exact
npm i @clerk/clerk-react@5.57.1-snapshot.v20251125180024 --save-exact
npm i @clerk/react-router@2.3.1-snapshot.v20251125180024 --save-exact
npm i @clerk/remix@4.13.21-snapshot.v20251125180024 --save-exact
npm i @clerk/shared@3.36.1-snapshot.v20251125180024 --save-exact
npm i @clerk/tanstack-react-start@0.27.6-snapshot.v20251125180024 --save-exact
npm i @clerk/testing@1.13.20-snapshot.v20251125180024 --save-exact
npm i @clerk/themes@2.4.41-snapshot.v20251125180024 --save-exact
npm i @clerk/types@4.101.4-snapshot.v20251125180024 --save-exact
npm i @clerk/vue@1.17.1-snapshot.v20251125180024 --save-exact |
…vascript into alexcarpenter/portal-provider-3
|
!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/agent-toolkit@0.2.9-snapshot.v20251215203425 --save-exact
npm i @clerk/astro@3.0.0-snapshot.v20251215203425 --save-exact
npm i @clerk/backend@3.0.0-snapshot.v20251215203425 --save-exact
npm i @clerk/chrome-extension@3.0.0-snapshot.v20251215203425 --save-exact
npm i @clerk/clerk-js@6.0.0-snapshot.v20251215203425 --save-exact
npm i @clerk/dev-cli@1.0.0-snapshot.v20251215203425 --save-exact
npm i @clerk/expo@3.0.0-snapshot.v20251215203425 --save-exact
npm i @clerk/expo-passkeys@1.0.0-snapshot.v20251215203425 --save-exact
npm i @clerk/express@2.0.0-snapshot.v20251215203425 --save-exact
npm i @clerk/fastify@2.6.9-snapshot.v20251215203425 --save-exact
npm i @clerk/localizations@4.0.0-snapshot.v20251215203425 --save-exact
npm i @clerk/nextjs@7.0.0-snapshot.v20251215203425 --save-exact
npm i @clerk/nuxt@2.0.0-snapshot.v20251215203425 --save-exact
npm i @clerk/react@6.0.0-snapshot.v20251215203425 --save-exact
npm i @clerk/react-router@3.0.0-snapshot.v20251215203425 --save-exact
npm i @clerk/shared@4.0.0-snapshot.v20251215203425 --save-exact
npm i @clerk/tanstack-react-start@1.0.0-snapshot.v20251215203425 --save-exact
npm i @clerk/testing@2.0.0-snapshot.v20251215203425 --save-exact
npm i @clerk/ui@1.0.0-snapshot.v20251215203425 --save-exact
npm i @clerk/upgrade@2.0.0-snapshot.v20251215203425 --save-exact
npm i @clerk/vue@2.0.0-snapshot.v20251215203425 --save-exact |
|
!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/agent-toolkit@0.2.9-snapshot.v20251215210631 --save-exact
npm i @clerk/astro@3.0.0-snapshot.v20251215210631 --save-exact
npm i @clerk/backend@3.0.0-snapshot.v20251215210631 --save-exact
npm i @clerk/chrome-extension@3.0.0-snapshot.v20251215210631 --save-exact
npm i @clerk/clerk-js@6.0.0-snapshot.v20251215210631 --save-exact
npm i @clerk/dev-cli@1.0.0-snapshot.v20251215210631 --save-exact
npm i @clerk/expo@3.0.0-snapshot.v20251215210631 --save-exact
npm i @clerk/expo-passkeys@1.0.0-snapshot.v20251215210631 --save-exact
npm i @clerk/express@2.0.0-snapshot.v20251215210631 --save-exact
npm i @clerk/fastify@2.6.9-snapshot.v20251215210631 --save-exact
npm i @clerk/localizations@4.0.0-snapshot.v20251215210631 --save-exact
npm i @clerk/nextjs@7.0.0-snapshot.v20251215210631 --save-exact
npm i @clerk/nuxt@2.0.0-snapshot.v20251215210631 --save-exact
npm i @clerk/react@6.0.0-snapshot.v20251215210631 --save-exact
npm i @clerk/react-router@3.0.0-snapshot.v20251215210631 --save-exact
npm i @clerk/shared@4.0.0-snapshot.v20251215210631 --save-exact
npm i @clerk/tanstack-react-start@1.0.0-snapshot.v20251215210631 --save-exact
npm i @clerk/testing@2.0.0-snapshot.v20251215210631 --save-exact
npm i @clerk/ui@1.0.0-snapshot.v20251215210631 --save-exact
npm i @clerk/upgrade@2.0.0-snapshot.v20251215210631 --save-exact
npm i @clerk/vue@2.0.0-snapshot.v20251215210631 --save-exact |
|
!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/agent-toolkit@0.2.9-snapshot.v20251215212157 --save-exact
npm i @clerk/astro@3.0.0-snapshot.v20251215212157 --save-exact
npm i @clerk/backend@3.0.0-snapshot.v20251215212157 --save-exact
npm i @clerk/chrome-extension@3.0.0-snapshot.v20251215212157 --save-exact
npm i @clerk/clerk-js@6.0.0-snapshot.v20251215212157 --save-exact
npm i @clerk/dev-cli@1.0.0-snapshot.v20251215212157 --save-exact
npm i @clerk/expo@3.0.0-snapshot.v20251215212157 --save-exact
npm i @clerk/expo-passkeys@1.0.0-snapshot.v20251215212157 --save-exact
npm i @clerk/express@2.0.0-snapshot.v20251215212157 --save-exact
npm i @clerk/fastify@2.6.9-snapshot.v20251215212157 --save-exact
npm i @clerk/localizations@4.0.0-snapshot.v20251215212157 --save-exact
npm i @clerk/nextjs@7.0.0-snapshot.v20251215212157 --save-exact
npm i @clerk/nuxt@2.0.0-snapshot.v20251215212157 --save-exact
npm i @clerk/react@6.0.0-snapshot.v20251215212157 --save-exact
npm i @clerk/react-router@3.0.0-snapshot.v20251215212157 --save-exact
npm i @clerk/shared@4.0.0-snapshot.v20251215212157 --save-exact
npm i @clerk/tanstack-react-start@1.0.0-snapshot.v20251215212157 --save-exact
npm i @clerk/testing@2.0.0-snapshot.v20251215212157 --save-exact
npm i @clerk/ui@1.0.0-snapshot.v20251215212157 --save-exact
npm i @clerk/upgrade@2.0.0-snapshot.v20251215212157 --save-exact
npm i @clerk/vue@2.0.0-snapshot.v20251215212157 --save-exact |
UNSAFE_PortalProvider
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
🤖 Fix all issues with AI agents
In @.changeset/wet-phones-camp.md:
- Around line 1-3: The changeset currently declares a patch release for
'@clerk/shared' but you added a new public export (UNSAFE_PortalProvider);
update the changeset header from 'patch' to 'minor' so the release semver
reflects a new public API export; edit the top YAML block in
.changeset/wet-phones-camp.md and replace 'patch' with 'minor' for the
'@clerk/shared' entry.
In @packages/ui/src/components/APIKeys/__tests__/APIKeyModal.test.tsx:
- Around line 34-56: The test description for APIKeyModal in
APIKeyModal.test.tsx claims scoped styles (position: absolute) are applied when
modalRoot is provided but only asserts the backdrop exists; either update the
assertion to verify the style (get the element selected by
container.querySelector('[data-clerk-element="modalBackdrop"]') and assert its
computed style position is 'absolute') or change the test description/comment to
reflect that it only checks presence of the backdrop; reference APIKeyModal,
modalRoot, and the queried modalElement when making the change.
In @packages/ui/src/lazyModules/providers.tsx:
- Line 119: The type for getContainer is currently required but some usages
(e.g., blankCaptchaModal in Components.tsx and other LazyModalRenderer call
sites) omit it; update the declaration of getContainer in providers (the getter
on the modal/provider interface) to be optional by changing it to getContainer?:
() => HTMLElement | null so callers that don’t supply a custom container compile
cleanly and existing callers that pass getContainer continue to work.
🧹 Nitpick comments (4)
packages/ui/src/elements/Popover.tsx (1)
39-40: Consider memoizing the effectiveRoot computation.While the current computation is inexpensive, wrapping it in
useMemowould align with React best practices for derived values and ensure consistency if the portal root resolution logic becomes more complex in the future.♻️ Proposed refactor
const portalRoot = usePortalRoot(); - const effectiveRoot = root ?? portalRoot?.() ?? undefined; + const effectiveRoot = React.useMemo(() => root ?? portalRoot?.() ?? undefined, [root, portalRoot]);packages/ui/src/components/UserButton/__tests__/UserButton.test.tsx (1)
2-4: Consider removing explicit React import if using modern JSX transform.With the modern JSX transform (React 17+), explicit
import React from 'react'is not required for JSX. Other tests in this file don't appear to need this import for JSX usage. If this import is only for JSX, it can be removed.However, if the test framework or configuration requires it, this is fine to keep.
packages/ui/src/components/OrganizationSwitcher/__tests__/OrganizationSwitcher.test.tsx (1)
4-6: Consider removing explicit React import if using modern JSX transform.Same consideration as in the UserButton test file - the explicit React import may not be necessary with the modern JSX transform.
packages/shared/src/react/PortalProvider.tsx (1)
56-65: Hook implementation is correct with good defensive checks.The
usePortalRoothook properly handles both cases: returning the context-providedgetContainerwhen inside a provider, and falling back to a null-returning function otherwise. The explicit return type and defensive conditional checks ensure type safety and runtime reliability.💡 Optional: Simplify condition check
The condition on lines 59-61 could be slightly simplified while maintaining safety:
- if (contextValue && 'getContainer' in contextValue && contextValue.getContainer) { + if (contextValue?.getContainer) { return contextValue.getContainer; }However, the current explicit approach is more defensive and equally valid.
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Disabled knowledge base sources:
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (34)
.changeset/wet-phones-camp.mdpackages/astro/src/react/index.tspackages/expo/src/index.tspackages/nextjs/src/client-boundary/controlComponents.tspackages/nextjs/src/index.tspackages/nuxt/src/runtime/components/index.tspackages/react-router/src/client/index.tspackages/react/src/components/withClerk.tsxpackages/react/src/contexts/index.tspackages/shared/src/react/PortalProvider.tsxpackages/shared/src/react/__tests__/PortalProvider.test.tsxpackages/shared/src/react/index.tspackages/shared/src/types/clerk.tspackages/tanstack-react-start/src/client/index.tspackages/ui/src/Components.tsxpackages/ui/src/components/APIKeys/__tests__/APIKeyModal.test.tsxpackages/ui/src/components/OrganizationSwitcher/OrganizationSwitcherPopover.tsxpackages/ui/src/components/OrganizationSwitcher/__tests__/OrganizationSwitcher.test.tsxpackages/ui/src/components/PricingTable/PricingTable.tsxpackages/ui/src/components/UserButton/__tests__/UserButton.test.tsxpackages/ui/src/components/UserButton/useMultisessionActions.tsxpackages/ui/src/elements/Drawer.tsxpackages/ui/src/elements/Menu.tsxpackages/ui/src/elements/Modal.tsxpackages/ui/src/elements/Popover.tsxpackages/ui/src/elements/Tooltip.tsxpackages/ui/src/lazyModules/providers.tsxpackages/vue/src/components/ClerkHostRenderer.tspackages/vue/src/components/PortalProvider.tspackages/vue/src/components/index.tspackages/vue/src/composables/__tests__/usePortalRoot.test.tspackages/vue/src/composables/index.tspackages/vue/src/composables/usePortalRoot.tspackages/vue/src/keys.ts
🧰 Additional context used
📓 Path-based instructions (18)
**/*.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/development.mdc)
All code must pass ESLint checks with the project's configuration
Files:
packages/react-router/src/client/index.tspackages/vue/src/components/index.tspackages/react/src/contexts/index.tspackages/ui/src/elements/Popover.tsxpackages/nextjs/src/client-boundary/controlComponents.tspackages/ui/src/components/PricingTable/PricingTable.tsxpackages/ui/src/elements/Menu.tsxpackages/ui/src/elements/Modal.tsxpackages/nuxt/src/runtime/components/index.tspackages/ui/src/elements/Tooltip.tsxpackages/vue/src/composables/__tests__/usePortalRoot.test.tspackages/vue/src/keys.tspackages/nextjs/src/index.tspackages/shared/src/react/__tests__/PortalProvider.test.tsxpackages/vue/src/components/ClerkHostRenderer.tspackages/ui/src/elements/Drawer.tsxpackages/vue/src/composables/usePortalRoot.tspackages/expo/src/index.tspackages/shared/src/types/clerk.tspackages/ui/src/lazyModules/providers.tsxpackages/ui/src/Components.tsxpackages/vue/src/composables/index.tspackages/ui/src/components/OrganizationSwitcher/__tests__/OrganizationSwitcher.test.tsxpackages/shared/src/react/index.tspackages/ui/src/components/OrganizationSwitcher/OrganizationSwitcherPopover.tsxpackages/astro/src/react/index.tspackages/ui/src/components/UserButton/__tests__/UserButton.test.tsxpackages/shared/src/react/PortalProvider.tsxpackages/vue/src/components/PortalProvider.tspackages/ui/src/components/APIKeys/__tests__/APIKeyModal.test.tsxpackages/ui/src/components/UserButton/useMultisessionActions.tsxpackages/tanstack-react-start/src/client/index.tspackages/react/src/components/withClerk.tsx
**/*.{js,jsx,ts,tsx,json,md,yml,yaml}
📄 CodeRabbit inference engine (.cursor/rules/development.mdc)
Use Prettier for consistent code formatting
Files:
packages/react-router/src/client/index.tspackages/vue/src/components/index.tspackages/react/src/contexts/index.tspackages/ui/src/elements/Popover.tsxpackages/nextjs/src/client-boundary/controlComponents.tspackages/ui/src/components/PricingTable/PricingTable.tsxpackages/ui/src/elements/Menu.tsxpackages/ui/src/elements/Modal.tsxpackages/nuxt/src/runtime/components/index.tspackages/ui/src/elements/Tooltip.tsxpackages/vue/src/composables/__tests__/usePortalRoot.test.tspackages/vue/src/keys.tspackages/nextjs/src/index.tspackages/shared/src/react/__tests__/PortalProvider.test.tsxpackages/vue/src/components/ClerkHostRenderer.tspackages/ui/src/elements/Drawer.tsxpackages/vue/src/composables/usePortalRoot.tspackages/expo/src/index.tspackages/shared/src/types/clerk.tspackages/ui/src/lazyModules/providers.tsxpackages/ui/src/Components.tsxpackages/vue/src/composables/index.tspackages/ui/src/components/OrganizationSwitcher/__tests__/OrganizationSwitcher.test.tsxpackages/shared/src/react/index.tspackages/ui/src/components/OrganizationSwitcher/OrganizationSwitcherPopover.tsxpackages/astro/src/react/index.tspackages/ui/src/components/UserButton/__tests__/UserButton.test.tsxpackages/shared/src/react/PortalProvider.tsxpackages/vue/src/components/PortalProvider.tspackages/ui/src/components/APIKeys/__tests__/APIKeyModal.test.tsxpackages/ui/src/components/UserButton/useMultisessionActions.tsxpackages/tanstack-react-start/src/client/index.tspackages/react/src/components/withClerk.tsx
packages/**/src/**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/development.mdc)
TypeScript is required for all packages
Files:
packages/react-router/src/client/index.tspackages/vue/src/components/index.tspackages/react/src/contexts/index.tspackages/ui/src/elements/Popover.tsxpackages/nextjs/src/client-boundary/controlComponents.tspackages/ui/src/components/PricingTable/PricingTable.tsxpackages/ui/src/elements/Menu.tsxpackages/ui/src/elements/Modal.tsxpackages/nuxt/src/runtime/components/index.tspackages/ui/src/elements/Tooltip.tsxpackages/vue/src/composables/__tests__/usePortalRoot.test.tspackages/vue/src/keys.tspackages/nextjs/src/index.tspackages/shared/src/react/__tests__/PortalProvider.test.tsxpackages/vue/src/components/ClerkHostRenderer.tspackages/ui/src/elements/Drawer.tsxpackages/vue/src/composables/usePortalRoot.tspackages/expo/src/index.tspackages/shared/src/types/clerk.tspackages/ui/src/lazyModules/providers.tsxpackages/ui/src/Components.tsxpackages/vue/src/composables/index.tspackages/ui/src/components/OrganizationSwitcher/__tests__/OrganizationSwitcher.test.tsxpackages/shared/src/react/index.tspackages/ui/src/components/OrganizationSwitcher/OrganizationSwitcherPopover.tsxpackages/astro/src/react/index.tspackages/ui/src/components/UserButton/__tests__/UserButton.test.tsxpackages/shared/src/react/PortalProvider.tsxpackages/vue/src/components/PortalProvider.tspackages/ui/src/components/APIKeys/__tests__/APIKeyModal.test.tsxpackages/ui/src/components/UserButton/useMultisessionActions.tsxpackages/tanstack-react-start/src/client/index.tspackages/react/src/components/withClerk.tsx
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (.cursor/rules/development.mdc)
Follow established naming conventions (PascalCase for components, camelCase for variables)
Files:
packages/react-router/src/client/index.tspackages/vue/src/components/index.tspackages/react/src/contexts/index.tspackages/ui/src/elements/Popover.tsxpackages/nextjs/src/client-boundary/controlComponents.tspackages/ui/src/components/PricingTable/PricingTable.tsxpackages/ui/src/elements/Menu.tsxpackages/ui/src/elements/Modal.tsxpackages/nuxt/src/runtime/components/index.tspackages/ui/src/elements/Tooltip.tsxpackages/vue/src/composables/__tests__/usePortalRoot.test.tspackages/vue/src/keys.tspackages/nextjs/src/index.tspackages/shared/src/react/__tests__/PortalProvider.test.tsxpackages/vue/src/components/ClerkHostRenderer.tspackages/ui/src/elements/Drawer.tsxpackages/vue/src/composables/usePortalRoot.tspackages/expo/src/index.tspackages/shared/src/types/clerk.tspackages/ui/src/lazyModules/providers.tsxpackages/ui/src/Components.tsxpackages/vue/src/composables/index.tspackages/ui/src/components/OrganizationSwitcher/__tests__/OrganizationSwitcher.test.tsxpackages/shared/src/react/index.tspackages/ui/src/components/OrganizationSwitcher/OrganizationSwitcherPopover.tsxpackages/astro/src/react/index.tspackages/ui/src/components/UserButton/__tests__/UserButton.test.tsxpackages/shared/src/react/PortalProvider.tsxpackages/vue/src/components/PortalProvider.tspackages/ui/src/components/APIKeys/__tests__/APIKeyModal.test.tsxpackages/ui/src/components/UserButton/useMultisessionActions.tsxpackages/tanstack-react-start/src/client/index.tspackages/react/src/components/withClerk.tsx
packages/**/src/**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (.cursor/rules/development.mdc)
packages/**/src/**/*.{ts,tsx,js,jsx}: Maintain comprehensive JSDoc comments for public APIs
Use tree-shaking friendly exports
Validate all inputs and sanitize outputs
All public APIs must be documented with JSDoc
Use dynamic imports for optional features
Provide meaningful error messages to developers
Include error recovery suggestions where applicable
Log errors appropriately for debugging
Lazy load components and features when possible
Implement proper caching strategies
Use efficient data structures and algorithms
Implement proper logging with different levels
Files:
packages/react-router/src/client/index.tspackages/vue/src/components/index.tspackages/react/src/contexts/index.tspackages/ui/src/elements/Popover.tsxpackages/nextjs/src/client-boundary/controlComponents.tspackages/ui/src/components/PricingTable/PricingTable.tsxpackages/ui/src/elements/Menu.tsxpackages/ui/src/elements/Modal.tsxpackages/nuxt/src/runtime/components/index.tspackages/ui/src/elements/Tooltip.tsxpackages/vue/src/composables/__tests__/usePortalRoot.test.tspackages/vue/src/keys.tspackages/nextjs/src/index.tspackages/shared/src/react/__tests__/PortalProvider.test.tsxpackages/vue/src/components/ClerkHostRenderer.tspackages/ui/src/elements/Drawer.tsxpackages/vue/src/composables/usePortalRoot.tspackages/expo/src/index.tspackages/shared/src/types/clerk.tspackages/ui/src/lazyModules/providers.tsxpackages/ui/src/Components.tsxpackages/vue/src/composables/index.tspackages/ui/src/components/OrganizationSwitcher/__tests__/OrganizationSwitcher.test.tsxpackages/shared/src/react/index.tspackages/ui/src/components/OrganizationSwitcher/OrganizationSwitcherPopover.tsxpackages/astro/src/react/index.tspackages/ui/src/components/UserButton/__tests__/UserButton.test.tsxpackages/shared/src/react/PortalProvider.tsxpackages/vue/src/components/PortalProvider.tspackages/ui/src/components/APIKeys/__tests__/APIKeyModal.test.tsxpackages/ui/src/components/UserButton/useMultisessionActions.tsxpackages/tanstack-react-start/src/client/index.tspackages/react/src/components/withClerk.tsx
**/*.ts?(x)
📄 CodeRabbit inference engine (.cursor/rules/development.mdc)
Use proper TypeScript error types
Files:
packages/react-router/src/client/index.tspackages/vue/src/components/index.tspackages/react/src/contexts/index.tspackages/ui/src/elements/Popover.tsxpackages/nextjs/src/client-boundary/controlComponents.tspackages/ui/src/components/PricingTable/PricingTable.tsxpackages/ui/src/elements/Menu.tsxpackages/ui/src/elements/Modal.tsxpackages/nuxt/src/runtime/components/index.tspackages/ui/src/elements/Tooltip.tsxpackages/vue/src/composables/__tests__/usePortalRoot.test.tspackages/vue/src/keys.tspackages/nextjs/src/index.tspackages/shared/src/react/__tests__/PortalProvider.test.tsxpackages/vue/src/components/ClerkHostRenderer.tspackages/ui/src/elements/Drawer.tsxpackages/vue/src/composables/usePortalRoot.tspackages/expo/src/index.tspackages/shared/src/types/clerk.tspackages/ui/src/lazyModules/providers.tsxpackages/ui/src/Components.tsxpackages/vue/src/composables/index.tspackages/ui/src/components/OrganizationSwitcher/__tests__/OrganizationSwitcher.test.tsxpackages/shared/src/react/index.tspackages/ui/src/components/OrganizationSwitcher/OrganizationSwitcherPopover.tsxpackages/astro/src/react/index.tspackages/ui/src/components/UserButton/__tests__/UserButton.test.tsxpackages/shared/src/react/PortalProvider.tsxpackages/vue/src/components/PortalProvider.tspackages/ui/src/components/APIKeys/__tests__/APIKeyModal.test.tsxpackages/ui/src/components/UserButton/useMultisessionActions.tsxpackages/tanstack-react-start/src/client/index.tspackages/react/src/components/withClerk.tsx
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/typescript.mdc)
**/*.{ts,tsx}: Always define explicit return types for functions, especially public APIs
Use proper type annotations for variables and parameters where inference isn't clear
Avoidanytype - preferunknownwhen type is uncertain, then narrow with type guards
Implement type guards forunknowntypes using the patternfunction isType(value: unknown): value is Type
Useinterfacefor object shapes that might be extended
Usetypefor unions, primitives, and computed types
Preferreadonlyproperties for immutable data structures
Useprivatefor internal implementation details in classes
Useprotectedfor inheritance hierarchies
Usepublicexplicitly for clarity in public APIs
Use mixins for shared behavior across unrelated classes in TypeScript
Use generic constraints with bounded type parameters like<T extends { id: string }>
Use utility types likeOmit,Partial, andPickfor data transformation instead of manual type construction
Use discriminated unions instead of boolean flags for state management and API responses
Use mapped types for transforming object types
Use conditional types for type-level logic
Leverage template literal types for string manipulation at the type level
Use ES6 imports/exports consistently
Use default exports sparingly, prefer named exports
Document functions with JSDoc comments including @param, @returns, @throws, and @example tags
Create custom error classes that extend Error for specific error types
Use the Result pattern for error handling instead of throwing exceptions
Use optional chaining (?.) and nullish coalescing (??) operators for safe property access
Let TypeScript infer obvious types to reduce verbosity
Useconst assertionswithas constfor literal types
Usesatisfiesoperator for type checking without widening types
Declare readonly arrays and objects for immutable data structures
Use spread operator and array spread for immutable updates instead of mutations
Use lazy loading for large types...
Files:
packages/react-router/src/client/index.tspackages/vue/src/components/index.tspackages/react/src/contexts/index.tspackages/ui/src/elements/Popover.tsxpackages/nextjs/src/client-boundary/controlComponents.tspackages/ui/src/components/PricingTable/PricingTable.tsxpackages/ui/src/elements/Menu.tsxpackages/ui/src/elements/Modal.tsxpackages/nuxt/src/runtime/components/index.tspackages/ui/src/elements/Tooltip.tsxpackages/vue/src/composables/__tests__/usePortalRoot.test.tspackages/vue/src/keys.tspackages/nextjs/src/index.tspackages/shared/src/react/__tests__/PortalProvider.test.tsxpackages/vue/src/components/ClerkHostRenderer.tspackages/ui/src/elements/Drawer.tsxpackages/vue/src/composables/usePortalRoot.tspackages/expo/src/index.tspackages/shared/src/types/clerk.tspackages/ui/src/lazyModules/providers.tsxpackages/ui/src/Components.tsxpackages/vue/src/composables/index.tspackages/ui/src/components/OrganizationSwitcher/__tests__/OrganizationSwitcher.test.tsxpackages/shared/src/react/index.tspackages/ui/src/components/OrganizationSwitcher/OrganizationSwitcherPopover.tsxpackages/astro/src/react/index.tspackages/ui/src/components/UserButton/__tests__/UserButton.test.tsxpackages/shared/src/react/PortalProvider.tsxpackages/vue/src/components/PortalProvider.tspackages/ui/src/components/APIKeys/__tests__/APIKeyModal.test.tsxpackages/ui/src/components/UserButton/useMultisessionActions.tsxpackages/tanstack-react-start/src/client/index.tspackages/react/src/components/withClerk.tsx
**/index.ts
📄 CodeRabbit inference engine (.cursor/rules/typescript.mdc)
Avoid barrel files (index.ts re-exports) as they can cause circular dependencies
Files:
packages/react-router/src/client/index.tspackages/vue/src/components/index.tspackages/react/src/contexts/index.tspackages/nuxt/src/runtime/components/index.tspackages/nextjs/src/index.tspackages/expo/src/index.tspackages/vue/src/composables/index.tspackages/shared/src/react/index.tspackages/astro/src/react/index.tspackages/tanstack-react-start/src/client/index.ts
**/*.{js,ts,jsx,tsx}
📄 CodeRabbit inference engine (.cursor/rules/monorepo.mdc)
Use ESLint with custom configurations tailored for different package types
Files:
packages/react-router/src/client/index.tspackages/vue/src/components/index.tspackages/react/src/contexts/index.tspackages/ui/src/elements/Popover.tsxpackages/nextjs/src/client-boundary/controlComponents.tspackages/ui/src/components/PricingTable/PricingTable.tsxpackages/ui/src/elements/Menu.tsxpackages/ui/src/elements/Modal.tsxpackages/nuxt/src/runtime/components/index.tspackages/ui/src/elements/Tooltip.tsxpackages/vue/src/composables/__tests__/usePortalRoot.test.tspackages/vue/src/keys.tspackages/nextjs/src/index.tspackages/shared/src/react/__tests__/PortalProvider.test.tsxpackages/vue/src/components/ClerkHostRenderer.tspackages/ui/src/elements/Drawer.tsxpackages/vue/src/composables/usePortalRoot.tspackages/expo/src/index.tspackages/shared/src/types/clerk.tspackages/ui/src/lazyModules/providers.tsxpackages/ui/src/Components.tsxpackages/vue/src/composables/index.tspackages/ui/src/components/OrganizationSwitcher/__tests__/OrganizationSwitcher.test.tsxpackages/shared/src/react/index.tspackages/ui/src/components/OrganizationSwitcher/OrganizationSwitcherPopover.tsxpackages/astro/src/react/index.tspackages/ui/src/components/UserButton/__tests__/UserButton.test.tsxpackages/shared/src/react/PortalProvider.tsxpackages/vue/src/components/PortalProvider.tspackages/ui/src/components/APIKeys/__tests__/APIKeyModal.test.tsxpackages/ui/src/components/UserButton/useMultisessionActions.tsxpackages/tanstack-react-start/src/client/index.tspackages/react/src/components/withClerk.tsx
**/*.{js,ts,jsx,tsx,json,md,yml,yaml}
📄 CodeRabbit inference engine (.cursor/rules/monorepo.mdc)
Use Prettier for code formatting across all packages
Files:
packages/react-router/src/client/index.tspackages/vue/src/components/index.tspackages/react/src/contexts/index.tspackages/ui/src/elements/Popover.tsxpackages/nextjs/src/client-boundary/controlComponents.tspackages/ui/src/components/PricingTable/PricingTable.tsxpackages/ui/src/elements/Menu.tsxpackages/ui/src/elements/Modal.tsxpackages/nuxt/src/runtime/components/index.tspackages/ui/src/elements/Tooltip.tsxpackages/vue/src/composables/__tests__/usePortalRoot.test.tspackages/vue/src/keys.tspackages/nextjs/src/index.tspackages/shared/src/react/__tests__/PortalProvider.test.tsxpackages/vue/src/components/ClerkHostRenderer.tspackages/ui/src/elements/Drawer.tsxpackages/vue/src/composables/usePortalRoot.tspackages/expo/src/index.tspackages/shared/src/types/clerk.tspackages/ui/src/lazyModules/providers.tsxpackages/ui/src/Components.tsxpackages/vue/src/composables/index.tspackages/ui/src/components/OrganizationSwitcher/__tests__/OrganizationSwitcher.test.tsxpackages/shared/src/react/index.tspackages/ui/src/components/OrganizationSwitcher/OrganizationSwitcherPopover.tsxpackages/astro/src/react/index.tspackages/ui/src/components/UserButton/__tests__/UserButton.test.tsxpackages/shared/src/react/PortalProvider.tsxpackages/vue/src/components/PortalProvider.tspackages/ui/src/components/APIKeys/__tests__/APIKeyModal.test.tsxpackages/ui/src/components/UserButton/useMultisessionActions.tsxpackages/tanstack-react-start/src/client/index.tspackages/react/src/components/withClerk.tsx
**/*.tsx
📄 CodeRabbit inference engine (.cursor/rules/development.mdc)
**/*.tsx: Use error boundaries in React components
Minimize re-renders in React components
**/*.tsx: Use proper type definitions for props and state in React components
Leverage TypeScript's type inference where possible in React components
Use proper event types for handlers in React components
Implement proper generic types for reusable React components
Use proper type guards for conditional rendering in React components
Files:
packages/ui/src/elements/Popover.tsxpackages/ui/src/components/PricingTable/PricingTable.tsxpackages/ui/src/elements/Menu.tsxpackages/ui/src/elements/Modal.tsxpackages/ui/src/elements/Tooltip.tsxpackages/shared/src/react/__tests__/PortalProvider.test.tsxpackages/ui/src/elements/Drawer.tsxpackages/ui/src/lazyModules/providers.tsxpackages/ui/src/Components.tsxpackages/ui/src/components/OrganizationSwitcher/__tests__/OrganizationSwitcher.test.tsxpackages/ui/src/components/OrganizationSwitcher/OrganizationSwitcherPopover.tsxpackages/ui/src/components/UserButton/__tests__/UserButton.test.tsxpackages/shared/src/react/PortalProvider.tsxpackages/ui/src/components/APIKeys/__tests__/APIKeyModal.test.tsxpackages/ui/src/components/UserButton/useMultisessionActions.tsxpackages/react/src/components/withClerk.tsx
**/*.{md,tsx}
📄 CodeRabbit inference engine (.cursor/rules/development.mdc)
Update documentation for API changes
Files:
packages/ui/src/elements/Popover.tsxpackages/ui/src/components/PricingTable/PricingTable.tsxpackages/ui/src/elements/Menu.tsxpackages/ui/src/elements/Modal.tsxpackages/ui/src/elements/Tooltip.tsxpackages/shared/src/react/__tests__/PortalProvider.test.tsxpackages/ui/src/elements/Drawer.tsxpackages/ui/src/lazyModules/providers.tsxpackages/ui/src/Components.tsxpackages/ui/src/components/OrganizationSwitcher/__tests__/OrganizationSwitcher.test.tsxpackages/ui/src/components/OrganizationSwitcher/OrganizationSwitcherPopover.tsxpackages/ui/src/components/UserButton/__tests__/UserButton.test.tsxpackages/shared/src/react/PortalProvider.tsxpackages/ui/src/components/APIKeys/__tests__/APIKeyModal.test.tsxpackages/ui/src/components/UserButton/useMultisessionActions.tsxpackages/react/src/components/withClerk.tsx
**/*.{jsx,tsx}
📄 CodeRabbit inference engine (.cursor/rules/react.mdc)
**/*.{jsx,tsx}: Always use functional components with hooks instead of class components
Follow PascalCase naming for components (e.g.,UserProfile,NavigationMenu)
Keep components focused on a single responsibility - split large components
Limit component size to 150-200 lines; extract logic into custom hooks
Use composition over inheritance - prefer smaller, composable components
Export components as named exports for better tree-shaking
One component per file with matching filename and component name
Separate UI components from business logic components
Use useState for simple state management in React components
Use useReducer for complex state logic in React components
Implement proper state initialization in React components
Use proper state updates with callbacks in React components
Implement proper state cleanup in React components
Use Context API for theme/authentication state management
Implement proper state persistence in React applications
Use React.memo for expensive components
Implement proper useCallback for handlers in React components
Use proper useMemo for expensive computations in React components
Implement proper virtualization for lists in React components
Use proper code splitting with React.lazy in React applications
Implement proper cleanup in useEffect hooks
Use proper refs for DOM access in React components
Implement proper event listener cleanup in React components
Use proper abort controllers for fetch in React components
Implement proper subscription cleanup in React components
Use proper HTML elements for semantic HTML in React components
Implement proper ARIA attributes for accessibility in React components
Use proper heading hierarchy in React components
Implement proper form labels in React components
Use proper button types in React components
Implement proper focus management for keyboard navigation in React components
Use proper keyboard shortcuts in React components
Implement proper tab order in React components
Use proper ...
Files:
packages/ui/src/elements/Popover.tsxpackages/ui/src/components/PricingTable/PricingTable.tsxpackages/ui/src/elements/Menu.tsxpackages/ui/src/elements/Modal.tsxpackages/ui/src/elements/Tooltip.tsxpackages/shared/src/react/__tests__/PortalProvider.test.tsxpackages/ui/src/elements/Drawer.tsxpackages/ui/src/lazyModules/providers.tsxpackages/ui/src/Components.tsxpackages/ui/src/components/OrganizationSwitcher/__tests__/OrganizationSwitcher.test.tsxpackages/ui/src/components/OrganizationSwitcher/OrganizationSwitcherPopover.tsxpackages/ui/src/components/UserButton/__tests__/UserButton.test.tsxpackages/shared/src/react/PortalProvider.tsxpackages/ui/src/components/APIKeys/__tests__/APIKeyModal.test.tsxpackages/ui/src/components/UserButton/useMultisessionActions.tsxpackages/react/src/components/withClerk.tsx
**/*.{test,spec}.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (.cursor/rules/development.mdc)
**/*.{test,spec}.{ts,tsx,js,jsx}: Unit tests are required for all new functionality
Verify proper error handling and edge cases
Include tests for all new features
Files:
packages/vue/src/composables/__tests__/usePortalRoot.test.tspackages/shared/src/react/__tests__/PortalProvider.test.tsxpackages/ui/src/components/OrganizationSwitcher/__tests__/OrganizationSwitcher.test.tsxpackages/ui/src/components/UserButton/__tests__/UserButton.test.tsxpackages/ui/src/components/APIKeys/__tests__/APIKeyModal.test.tsx
**/*.{test,spec,e2e}.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (.cursor/rules/development.mdc)
Use real Clerk instances for integration tests
Files:
packages/vue/src/composables/__tests__/usePortalRoot.test.tspackages/shared/src/react/__tests__/PortalProvider.test.tsxpackages/ui/src/components/OrganizationSwitcher/__tests__/OrganizationSwitcher.test.tsxpackages/ui/src/components/UserButton/__tests__/UserButton.test.tsxpackages/ui/src/components/APIKeys/__tests__/APIKeyModal.test.tsx
**/*.test.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/monorepo.mdc)
Use React Testing Library for component testing
Files:
packages/vue/src/composables/__tests__/usePortalRoot.test.tspackages/shared/src/react/__tests__/PortalProvider.test.tsxpackages/ui/src/components/OrganizationSwitcher/__tests__/OrganizationSwitcher.test.tsxpackages/ui/src/components/UserButton/__tests__/UserButton.test.tsxpackages/ui/src/components/APIKeys/__tests__/APIKeyModal.test.tsx
**/*.test.tsx
📄 CodeRabbit inference engine (.cursor/rules/development.mdc)
Use React Testing Library for component testing
Files:
packages/shared/src/react/__tests__/PortalProvider.test.tsxpackages/ui/src/components/OrganizationSwitcher/__tests__/OrganizationSwitcher.test.tsxpackages/ui/src/components/UserButton/__tests__/UserButton.test.tsxpackages/ui/src/components/APIKeys/__tests__/APIKeyModal.test.tsx
**/*.{test,spec}.{jsx,tsx}
📄 CodeRabbit inference engine (.cursor/rules/react.mdc)
**/*.{test,spec}.{jsx,tsx}: Use React Testing Library for unit testing React components
Test component behavior, not implementation details
Use proper test queries in React Testing Library tests
Implement proper test isolation in React component tests
Use proper test coverage in React component tests
Test component interactions in integration tests
Use proper test data in React component tests
Implement proper test setup in React component tests
Use proper test cleanup in React component tests
Implement proper test assertions in React component tests
Use proper test structure for React component tests
Files:
packages/shared/src/react/__tests__/PortalProvider.test.tsxpackages/ui/src/components/OrganizationSwitcher/__tests__/OrganizationSwitcher.test.tsxpackages/ui/src/components/UserButton/__tests__/UserButton.test.tsxpackages/ui/src/components/APIKeys/__tests__/APIKeyModal.test.tsx
🧬 Code graph analysis (10)
packages/shared/src/react/__tests__/PortalProvider.test.tsx (1)
packages/shared/src/react/PortalProvider.tsx (2)
usePortalRoot(56-65)UNSAFE_PortalProvider(45-49)
packages/vue/src/composables/usePortalRoot.ts (2)
packages/vue/src/composables/index.ts (1)
usePortalRoot(17-17)packages/vue/src/keys.ts (1)
PortalInjectionKey(23-25)
packages/shared/src/types/clerk.ts (1)
packages/ui/src/types.ts (7)
SignInProps(37-37)__internal_UserVerificationProps(31-31)SignUpProps(38-38)UserProfileProps(41-41)OrganizationProfileProps(35-35)CreateOrganizationProps(32-32)WaitlistProps(42-42)
packages/ui/src/lazyModules/providers.tsx (1)
packages/shared/src/react/PortalProvider.tsx (1)
UNSAFE_PortalProvider(45-49)
packages/ui/src/components/OrganizationSwitcher/__tests__/OrganizationSwitcher.test.tsx (1)
packages/shared/src/react/PortalProvider.tsx (1)
UNSAFE_PortalProvider(45-49)
packages/ui/src/components/UserButton/__tests__/UserButton.test.tsx (1)
packages/shared/src/react/PortalProvider.tsx (1)
UNSAFE_PortalProvider(45-49)
packages/shared/src/react/PortalProvider.tsx (2)
packages/shared/src/react/hooks/index.ts (1)
createContextAndHook(1-1)packages/shared/src/react/index.ts (2)
UNSAFE_PortalProvider(24-24)usePortalRoot(24-24)
packages/vue/src/components/PortalProvider.ts (3)
packages/nuxt/src/runtime/components/index.ts (1)
UNSAFE_PortalProvider(28-28)packages/vue/src/components/index.ts (1)
UNSAFE_PortalProvider(33-33)packages/vue/src/keys.ts (1)
PortalInjectionKey(23-25)
packages/ui/src/components/APIKeys/__tests__/APIKeyModal.test.tsx (2)
packages/ui/src/components/APIKeys/APIKeyModal.tsx (1)
APIKeyModal(36-44)packages/shared/src/react/PortalProvider.tsx (1)
UNSAFE_PortalProvider(45-49)
packages/react/src/components/withClerk.tsx (1)
packages/shared/src/react/PortalProvider.tsx (1)
usePortalRoot(56-65)
🔇 Additional comments (59)
packages/tanstack-react-start/src/client/index.ts (1)
3-3: LGTM! Consistent API expansion.The re-export of
UNSAFE_PortalProviderfollows the established pattern across framework integrations. TheUNSAFE_prefix appropriately signals that this is an experimental API.packages/ui/src/elements/Menu.tsx (1)
202-202: LGTM! Good UX improvement.Adding
whiteSpace: 'nowrap'prevents menu item text from wrapping, which maintains consistent and predictable menu item presentation.packages/nextjs/src/client-boundary/controlComponents.ts (1)
18-18: LGTM! Consistent with Next.js client boundary pattern.The addition of
UNSAFE_PortalProviderto the control components re-exports is consistent with the existing pattern and appropriately marked with the'use client'directive for Next.js client boundaries.packages/react-router/src/client/index.ts (1)
4-4: LGTM! Consistent framework integration.The re-export of
UNSAFE_PortalProvideraligns with the coordinated API expansion across framework integrations and follows the established export pattern.packages/react/src/contexts/index.ts (1)
2-2: LGTM! Clean re-export of the new Portal API.The re-export follows established patterns and the
UNSAFE_prefix appropriately signals the experimental nature of this API.packages/shared/src/react/index.ts (1)
24-24: LGTM! Proper public API exposure.Both the provider component and the hook are correctly exported together, enabling consumers to use the Portal customization feature.
packages/ui/src/components/UserButton/useMultisessionActions.tsx (4)
3-3: LGTM! Proper import of the portal hook.
30-30: LGTM! Correct hook usage.The hook is called at the top level of the custom hook, following React's Rules of Hooks.
50-50: LGTM! Consistent container propagation.The
getContaineris properly passed toopenUserProfile, enabling portal customization for the user profile modal.
63-67: LGTM! Consistent pattern across call sites.The second
openUserProfilecall also receivesgetContainer, maintaining consistency with Line 50.packages/ui/src/lazyModules/providers.tsx (3)
3-3: LGTM! Proper import of the portal provider.
89-100: LGTM! Clean portal provider integration.The
UNSAFE_PortalProvidercorrectly wraps the Portal component and properly extractsgetContainerfrom component props using optional chaining.
133-155: LGTM! Consistent portal provider usage in modal renderer.The
UNSAFE_PortalProvidercorrectly wraps the Modal component, and the usage ofprops.getContaineris consistent with the type definition at Line 119.packages/react/src/components/withClerk.tsx (3)
1-1: LGTM! Proper import of the portal hook.
23-23: LGTM! Correct hook usage.The hook is called at the top level, following React's Rules of Hooks.
30-32: Type assertion handles getContainer injection without requiring component type updates.The HOC currently injects
getContainerusing a type assertion (as P), which allows the prop to be passed to the wrapped component even though it's not declared in the component's prop type. This works because no wrapped components actually usegetContainer– it's only injected but never consumed. The current implementation is functionally correct but could be improved for type safety by either:
- Adding
getContainer?: () => HTMLElement | nullto theWithClerkProptype definition so all wrapped components properly declare it, or- Explicitly documenting this pattern if
getContaineris intentionally unused or reserved for future use.packages/shared/src/types/clerk.ts (1)
1425-1432: LGTM! Well-documented and consistent type additions.The
getContainerproperty is correctly defined across all modal prop types with consistent JSDoc documentation and appropriate type signature. The optional nature ensures backward compatibility, and the examples in JSDoc (Radix Dialog, React Aria) provide helpful context for consumers.Also applies to: 1434-1440, 1582-1589, 1631-1638, 1681-1688, 1714-1721, 1944-1951
packages/ui/src/components/OrganizationSwitcher/OrganizationSwitcherPopover.tsx (2)
1-1: LGTM! Correct hook usage.The
usePortalRoothook is properly imported and called at the component's top level, following React hook rules.Also applies to: 28-28
80-80: LGTM! Consistent container propagation.The
getContaineris correctly passed to bothopenCreateOrganizationandopenOrganizationProfilefunctions, enabling modals to render into custom portal containers when provided viaUNSAFE_PortalProvider.Also applies to: 92-92
packages/vue/src/components/index.ts (1)
33-33: LGTM! Consistent API surface expansion.The
UNSAFE_PortalProviderre-export aligns with the cross-framework portal provider pattern. TheUNSAFE_prefix appropriately signals the experimental nature of this API.packages/astro/src/react/index.ts (1)
11-11: LGTM! Consistent framework integration.The
UNSAFE_PortalProviderre-export from@clerk/shared/reactmaintains consistency across framework integrations (React, Vue, Astro, etc.). TheUNSAFE_prefix appropriately signals the experimental nature of this API.packages/ui/src/components/PricingTable/PricingTable.tsx (2)
1-1: LGTM! Correct hook usage.The
usePortalRoothook is properly imported and called at the component's top level withinPricingTableRoot, adhering to React hook rules.Also applies to: 13-13
53-70: LGTM! Appropriate container usage.The
getContaineris correctly passed toclerk.openSignIn()only in the modal flow (line 56), while the redirect flow (line 58) appropriately omits it since redirects don't require portal containers. This demonstrates proper understanding of when portal containers are needed.packages/nuxt/src/runtime/components/index.ts (1)
28-28: LGTM! Export follows established pattern.The re-export of
UNSAFE_PortalProvideris consistent with other exports and correctly signals the experimental nature of the API with theUNSAFE_prefix.packages/vue/src/composables/index.ts (1)
16-17: LGTM! Export follows established pattern.The addition of
usePortalRootexport is consistent with the existing export pattern in this file and properly expands the composables API surface.packages/expo/src/index.ts (1)
20-20: LGTM! Export follows established pattern.The re-export of
UNSAFE_PortalProviderfrom@clerk/reactis appropriately placed and consistent with similar exports across other packages in this PR.packages/nextjs/src/index.ts (1)
11-11: LGTM! Export follows required Next.js pattern.The addition of
UNSAFE_PortalProviderto the explicit named exports correctly follows the pattern required for Next.js App Router compatibility, as indicated by the warning comment above.packages/ui/src/elements/Popover.tsx (1)
1-1: LGTM! Portal root integration implemented correctly.The integration of
usePortalRootenables custom portal container resolution while maintaining backward compatibility through the fallback chain (explicitrootprop → portal provider context →undefined). The optional chaining correctly handles cases where no portal provider is present.Also applies to: 39-40, 45-45
packages/ui/src/elements/Drawer.tsx (3)
1-1: LGTM: Portal root resolution is correctly implemented.The import and fallback chain properly handle the precedence: explicit
portalProps.root→ context portal root → undefined (default). The optional chaining onportalRoot?.()correctly handles cases outside the provider.Also applies to: 91-92
115-115: LGTM: Context propagation is correct.Spreading
portalPropswhile explicitly overridingrootwitheffectivePortalRootensures the computed value propagates correctly to drawer context consumers.
122-127: The current implementation is correct.Drawer.Rootwraps business components (e.g.,Checkout,SubscriptionDetails) that internally useDrawer.Content,Drawer.Header, and other subcomponents. These business components are not trigger elements—they are the drawer's content. Portaling all children together is the intended and appropriate pattern for drawer/modal implementations, unlike tooltip/popover patterns that only portal a single floating element. The design is sound and no changes are needed.Likely an incorrect or invalid review comment.
packages/ui/src/elements/Tooltip.tsx (1)
19-19: LGTM: Tooltip portal integration is correctly implemented.The portal root resolution is properly integrated into the
Contentcomponent. The pattern is simpler than Drawer since Tooltip doesn't accept explicit portal props, makingportalRoot?.() ?? undefinedthe appropriate fallback chain.Also applies to: 197-198, 205-205
packages/shared/src/react/__tests__/PortalProvider.test.tsx (4)
7-24: LGTM: Test correctly verifies context propagation.The test properly validates that
usePortalRootreturns the same function reference provided toUNSAFE_PortalProvider, using identity comparison to ensure the exact function is passed through context.
26-51: LGTM: Test correctly verifies provider scope boundaries.The test properly validates that only components within the
UNSAFE_PortalProviderreceive the custom container, while components outside receivenull, confirming the provider's scope is correctly limited to its descendants.
54-71: LGTM: Test correctly verifies function invocation.The test properly validates that invoking the returned function (
portalRoot()) yields the expected container element, complementing the identity test with behavioral verification.
73-82: LGTM: Tests cover critical edge cases.Both tests properly validate important edge cases:
- Default behavior outside any provider (returns null)
- Nested provider precedence (innermost provider wins)
These tests ensure the context system behaves correctly in complex component trees.
Also applies to: 84-102
packages/ui/src/components/APIKeys/__tests__/APIKeyModal.test.tsx (3)
10-32: LGTM: Test correctly verifies modal renders in custom container.The test properly validates that the modal renders inside the provided
modalRootcontainer. The manual ref assignment (modalRoot.current = container) is acceptable for test purposes and allows direct control over the ref value.Cleanup is properly handled with
document.body.removeChild(container).
58-87: LGTM: Test correctly verifies precedence behavior.The test properly validates that an explicit
modalRootprop takes precedence over theUNSAFE_PortalProvidercontext, which is the correct and expected behavior. Assertions and cleanup are both correct.
5-5: Incorrect import path: use@clerk/shared/reactinstead of@clerk/react.This test file is within
packages/ui, and the production code in this same package (packages/ui/src/lazyModules/providers.tsx) importsUNSAFE_PortalProviderfrom@clerk/shared/react. The test file should follow the same import pattern for consistency within the package.Change line 5 from:
import { UNSAFE_PortalProvider } from '@clerk/react';to:
import { UNSAFE_PortalProvider } from '@clerk/shared/react';Likely an incorrect or invalid review comment.
packages/vue/src/composables/usePortalRoot.ts (1)
1-19: LGTM: Vue composable correctly mirrors React implementation.The composable properly uses Vue's
injectto access the portal context and returns either the providedgetContainerfunction or a null-returning fallback. The implementation is consistent with the React version and follows Vue patterns.The defensive check on line 13 for
context.getContaineris acceptable, though TypeScript should guarantee its presence given theInjectionKeytype definition.packages/ui/src/components/UserButton/__tests__/UserButton.test.tsx (1)
90-115: LGTM! Test correctly verifies PortalProvider integration.The test properly validates that when
UserButtonis wrapped inUNSAFE_PortalProvider, thegetContainerfunction is passed through toopenUserProfile. The test setup follows the existing patterns in the file.packages/ui/src/elements/Modal.tsx (3)
1-1: LGTM!Import is correctly added for the new
usePortalRoothook.
30-30: LGTM!Correctly retrieves the portal container getter from context using the new hook.
56-64: LGTM! Clean fallback chain for portal root resolution.The priority order is correct: explicit
portalRootprop takes precedence, then context-provided container, thenundefined(which defaults todocument.body). The optional chaining onportalRootFromContext?.()correctly handles the case when no provider is present.packages/vue/src/keys.ts (1)
22-25: LGTM!The new
PortalInjectionKeyfollows the established pattern used by other injection keys in this file (ClerkInjectionKey,UserButtonInjectionKey, etc.). The type signature correctly matches the provider and consumer implementations.packages/vue/src/composables/__tests__/usePortalRoot.test.ts (4)
1-7: LGTM!Imports are correct and follow the project's testing conventions with
@testing-library/vueandvitest.
8-36: LGTM! Good test coverage for basic provider behavior.The first two tests properly verify:
usePortalRootreturns the provided container when inside the providerusePortalRootreturnsnullwhen outside the providerThese are the fundamental behaviors of the composable.
38-71: LGTM! Important test for provider scoping.This test correctly validates that the provider only affects descendant components, which is a key requirement for the
UNSAFE_PortalProviderfeature. Components outside the provider should get thenullfallback.
73-99: LGTM! Nested provider test validates correct precedence.This test ensures that when providers are nested, the innermost provider's
getContainertakes precedence. This is the expected behavior for Vue's provide/inject and matches the React implementation.packages/ui/src/components/OrganizationSwitcher/__tests__/OrganizationSwitcher.test.tsx (1)
545-589: LGTM! Comprehensive PortalProvider tests for OrganizationSwitcher.Both tests properly verify that
getContaineris passed through to the respective open handlers (openOrganizationProfileandopenCreateOrganization). The use ofwaitForfor menu item queries is appropriate given the async nature of menu rendering.packages/vue/src/components/PortalProvider.ts (2)
5-34: LGTM! Excellent JSDoc documentation.The documentation clearly explains:
- The purpose of the component
- The scope of affected components
- A practical example with Reka UI Dialog
This follows the coding guidelines for comprehensive JSDoc comments on public APIs.
35-52: LGTM with minor consideration for reactivity.The implementation is clean and follows Vue patterns correctly. One consideration: the
providecall passesprops.getContainerdirectly. If the parent component changes thegetContainerprop, consumers usinginjectwon't automatically receive the updated value sinceprovideis not reactive by default.However, since
getContaineris a function that typically returns a ref's value (e.g.,() => dialogContentRef?.$el), the actual container resolution happens at call-time, so this should work correctly in practice. The React equivalent also uses a similar pattern.packages/vue/src/components/ClerkHostRenderer.ts (4)
4-4: LGTM!Import correctly added for the new composable.
48-48: LGTM!Correctly retrieves the portal container getter from context using the composable at setup time.
57-67: LGTM! Clean integration of getContainer into mount/open flows.The
propsWithContainerpattern cleanly merges the context-providedgetContainerinto the props passed tomount()andopen(), enabling portal container customization for Clerk components.
71-83: LGTM! Consistent pattern for updateProps.The same
propsWithContainerpattern is correctly applied in the props watcher, ensuring that prop updates also include thegetContainerfunction.packages/ui/src/Components.tsx (1)
460-588: LGTM! Consistent portal container forwarding.The
getContainerprop is correctly forwarded from each modal's state to the correspondingLazyModalRendereracross all interactive modals (SignIn, SignUp, UserProfile, UserVerification, OrganizationProfile, CreateOrganization, Waitlist). The optional chaining handles null state appropriately, and the exclusion ofblankCaptchaModalis intentional given its special non-interactive styling.packages/shared/src/react/PortalProvider.tsx (2)
7-14: Well-defined type with clear documentation.The
PortalProviderPropstype is properly structured with a clear JSDoc comment explaining the purpose of thegetContainerfunction. The type signature correctly represents a function returningHTMLElement | null.
45-49: LGTM! Proper context provider implementation.The component correctly uses
useMemoto memoize the context value, preventing unnecessary re-renders of consumers. The memoization ongetContainerfollows React best practices, and the implementation is clean and straightforward.
| --- | ||
| '@clerk/shared': patch | ||
| --- |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🌐 Web query:
semantic versioning UNSAFE_ prefix new exports library npm package
💡 Result:
Short answer: use the UNSAFE_ prefix as a convention (like React) to mark an export as "unsafe/unstable/deprecated", but follow semver rules: adding a new UNSAFE_ export is a backward‑compatible new feature (bump minor); renaming/removing/changing existing exports (including replacing an old name with an UNSAFE_ name) is breaking (bump major). Also be careful when changing package "exports" because introducing or restricting exports itself can be breaking. [1][2][3]
Recommendations
- To mark a new unsafe API: export it under the UNSAFE_ name (example: export const UNSAFE_doThing = …) and publish a minor release. This gives users access without breaking callers that import the stable API. [1][3]
- To deprecate an existing export: do NOT rename or remove it in a minor/patch release. Renaming (e.g. foo → UNSAFE_foo) is a breaking change and requires a major version or a documented migration path (or keep the old name as an alias for one major cycle). [1][2]
- If you add an "exports" field or tighten existing exports, ensure you include all previous entry points or treat it as a breaking change. Node treats introducing/restricting "exports" as potentially breaking. [2]
- Document clearly (README/changelog/TS types) that UNSAFE_ exports are unstable and may be removed in a future major release.
Sources
- React blog (UNSAFE_ prefix rationale and usage). [1]
- Node.js docs on "exports" and breaking changes when introducing/restricting exports. [2]
- npm / semver guidance on version increments (patch/minor/major). [3]
🏁 Script executed:
# Find the actual changes in this changeset to see what was added
git log --oneline --all | head -20Repository: clerk/javascript
Length of output: 225
🏁 Script executed:
# Look for UNSAFE_PortalProvider in the codebase
rg "UNSAFE_PortalProvider" -t ts -t tsx -t js -t jsxRepository: clerk/javascript
Length of output: 87
🏁 Script executed:
# Fix the rg command - remove unrecognized type
rg "UNSAFE_PortalProvider"Repository: clerk/javascript
Length of output: 6459
🏁 Script executed:
# Check the commit that added UNSAFE_PortalProvider
git log --oneline | grep -i "unsafe_portal"Repository: clerk/javascript
Length of output: 124
🏁 Script executed:
# Get the detailed changes for this feature
git show fa6ef65a --statRepository: clerk/javascript
Length of output: 50373
🏁 Script executed:
# Check what files are in the @clerk/shared package
fd "package.json" | head -20Repository: clerk/javascript
Length of output: 947
🏁 Script executed:
cat .changeset/wet-phones-camp.mdRepository: clerk/javascript
Length of output: 90
🏁 Script executed:
# Find related changesets for context on versioning convention
find .changeset -name "*.md" -type f -exec grep -l "UNSAFE_" {} \;Repository: clerk/javascript
Length of output: 42
Mark this changeset as minor instead of patch.
Adding a new public export (UNSAFE_PortalProvider) is a backward-compatible feature that should trigger a minor version bump per semantic versioning standards. The UNSAFE_ prefix is a convention to indicate instability but does not change the fact that this is a new feature being added to the public API.
🤖 Prompt for AI Agents
In @.changeset/wet-phones-camp.md around lines 1 - 3, The changeset currently
declares a patch release for '@clerk/shared' but you added a new public export
(UNSAFE_PortalProvider); update the changeset header from 'patch' to 'minor' so
the release semver reflects a new public API export; edit the top YAML block in
.changeset/wet-phones-camp.md and replace 'patch' with 'minor' for the
'@clerk/shared' entry.
| it('applies scoped portal container styles when modalRoot provided', () => { | ||
| const modalRoot = React.createRef<HTMLDivElement>(); | ||
| const container = document.createElement('div'); | ||
| modalRoot.current = container; | ||
| document.body.appendChild(container); | ||
|
|
||
| const { container: testContainer } = render( | ||
| <APIKeyModal | ||
| modalRoot={modalRoot} | ||
| handleOpen={() => {}} | ||
| handleClose={() => {}} | ||
| canCloseModal={true} | ||
| > | ||
| <div>Test</div> | ||
| </APIKeyModal>, | ||
| ); | ||
|
|
||
| // The modal should have scoped styles (position: absolute) when modalRoot is provided | ||
| const modalElement = container.querySelector('[data-clerk-element="modalBackdrop"]'); | ||
| expect(modalElement).toBeTruthy(); | ||
|
|
||
| document.body.removeChild(container); | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Test assertion is weaker than its description suggests.
The test description states it "applies scoped portal container styles when modalRoot provided" and the comment mentions checking for position: absolute, but the assertion only verifies that the backdrop element exists, not that the styles are actually applied.
🔍 Suggested improvement
// The modal should have scoped styles (position: absolute) when modalRoot is provided
const modalElement = container.querySelector('[data-clerk-element="modalBackdrop"]');
- expect(modalElement).toBeTruthy();
+ expect(modalElement).toBeTruthy();
+ const computedStyle = window.getComputedStyle(modalElement);
+ expect(computedStyle.position).toBe('absolute');Alternatively, if style verification is intentionally omitted, update the test description and comment to match the actual assertion.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| it('applies scoped portal container styles when modalRoot provided', () => { | |
| const modalRoot = React.createRef<HTMLDivElement>(); | |
| const container = document.createElement('div'); | |
| modalRoot.current = container; | |
| document.body.appendChild(container); | |
| const { container: testContainer } = render( | |
| <APIKeyModal | |
| modalRoot={modalRoot} | |
| handleOpen={() => {}} | |
| handleClose={() => {}} | |
| canCloseModal={true} | |
| > | |
| <div>Test</div> | |
| </APIKeyModal>, | |
| ); | |
| // The modal should have scoped styles (position: absolute) when modalRoot is provided | |
| const modalElement = container.querySelector('[data-clerk-element="modalBackdrop"]'); | |
| expect(modalElement).toBeTruthy(); | |
| document.body.removeChild(container); | |
| }); | |
| it('applies scoped portal container styles when modalRoot provided', () => { | |
| const modalRoot = React.createRef<HTMLDivElement>(); | |
| const container = document.createElement('div'); | |
| modalRoot.current = container; | |
| document.body.appendChild(container); | |
| const { container: testContainer } = render( | |
| <APIKeyModal | |
| modalRoot={modalRoot} | |
| handleOpen={() => {}} | |
| handleClose={() => {}} | |
| canCloseModal={true} | |
| > | |
| <div>Test</div> | |
| </APIKeyModal>, | |
| ); | |
| // The modal should have scoped styles (position: absolute) when modalRoot is provided | |
| const modalElement = container.querySelector('[data-clerk-element="modalBackdrop"]'); | |
| expect(modalElement).toBeTruthy(); | |
| const computedStyle = window.getComputedStyle(modalElement); | |
| expect(computedStyle.position).toBe('absolute'); | |
| document.body.removeChild(container); | |
| }); |
🤖 Prompt for AI Agents
In @packages/ui/src/components/APIKeys/__tests__/APIKeyModal.test.tsx around
lines 34 - 56, The test description for APIKeyModal in APIKeyModal.test.tsx
claims scoped styles (position: absolute) are applied when modalRoot is provided
but only asserts the backdrop exists; either update the assertion to verify the
style (get the element selected by
container.querySelector('[data-clerk-element="modalBackdrop"]') and assert its
computed style position is 'absolute') or change the test description/comment to
reflect that it only checks presence of the backdrop; reference APIKeyModal,
modalRoot, and the queried modalElement when making the change.
| canCloseModal?: boolean; | ||
| modalId?: string; | ||
| modalStyle?: React.CSSProperties; | ||
| getContainer: () => HTMLElement | null; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
# First, let's look at the specific file and line mentioned
head -150 packages/ui/src/lazyModules/providers.tsx | tail -50Repository: clerk/javascript
Length of output: 1739
🏁 Script executed:
# Run the suggested script to find all LazyModalRenderer usages
rg -n -A 10 -B 3 'LazyModalRenderer' --type ts --type tsx -g '!*.test.*'Repository: clerk/javascript
Length of output: 87
🏁 Script executed:
# Search for LazyModalRenderer usage - fixed file type flags
rg -n -A 10 -B 3 'LazyModalRenderer' --type ts --type typescript -g '!*.test.*' -g '!*.spec.*'Repository: clerk/javascript
Length of output: 94
🏁 Script executed:
# Alternative: Search without type restrictions to find all usages
rg -n -A 5 '<LazyModalRenderer' -g '!*.test.*' -g '!*.spec.*'Repository: clerk/javascript
Length of output: 3820
🏁 Script executed:
# Get more context to see the full prop list including getContainer
rg -n -A 20 '<LazyModalRenderer' -g '!*.test.*' -g '!*.spec.*' packages/ui/src/Components.tsxRepository: clerk/javascript
Length of output: 7035
Make getContainer optional to match actual usage patterns.
The type definition requires getContainer (no ? modifier), but the blankCaptchaModal instance at line 594 in packages/ui/src/Components.tsx does not provide this prop. All other seven LazyModalRenderer call sites pass it via getContainer={<modalName>?.getContainer}. Change the type definition to getContainer?: () => HTMLElement | null; to align with the actual usage where some modals may not require a custom container.
🤖 Prompt for AI Agents
In @packages/ui/src/lazyModules/providers.tsx at line 119, The type for
getContainer is currently required but some usages (e.g., blankCaptchaModal in
Components.tsx and other LazyModalRenderer call sites) omit it; update the
declaration of getContainer in providers (the getter on the modal/provider
interface) to be optional by changing it to getContainer?: () => HTMLElement |
null so callers that don’t supply a custom container compile cleanly and
existing callers that pass getContainer continue to work.
Description
The
<UNSAFE_PortalProvider>component allows you to specify a custom container for Clerk floating UI elements (popovers, modals, tooltips, etc.) that use portals. Only Clerk components within the provider will be affected—components outside the provider will continue to use the default document.body for portals.This is particularly useful when using Clerk components inside external UI libraries like Radix Dialog or React Aria Components, where portaled elements need to render within the dialog's container to remain interactable.
Example usage
Docs: clerk/clerk-docs#2896
Checklist
pnpm testruns as expected.pnpm buildruns as expected.Type of change
Summary by CodeRabbit
New Features
Tests
✏️ Tip: You can customize this high-level summary in your review settings.