Skip to content

feat(ui): add Mosaic Popover component#9241

Open
alexcarpenter wants to merge 2 commits into
mainfrom
carp/mosaic-popover
Open

feat(ui): add Mosaic Popover component#9241
alexcarpenter wants to merge 2 commits into
mainfrom
carp/mosaic-popover

Conversation

@alexcarpenter

@alexcarpenter alexcarpenter commented Jul 24, 2026

Copy link
Copy Markdown
Member

Description

Adds a reusable Mosaic Popover component to @clerk/ui, styled with StyleX on top of the @clerk/headless popover primitive.

Preview: https://swingset-git-carp-mosaic-popover.clerkstage.dev/components/popover

The popup is a flexible floating card: consumers drop any inner content into Popover.Content plus an optional Popover.Footer. The convenience Popover wraps trigger + portalled, positioned popup; the compound parts (Popover.Root, Popover.Positioner, Popover.Popup, …) are exposed for custom layouts.

Styling notes

  • Themed with StyleX (not slot recipes). Each styled part carries a stable .cl-<slot> class; consumers override from a later CSS layer.
  • Enter/exit transition (opacity + scale) is driven off the popup's own data-starting-style / data-ending-style attributes via StyleX :where([data-*]) conditional keys, and disabled under prefers-reduced-motion: reduce.
  • Only styles.css ships from the isolated mosaic build; the mosaic JS is not publicly importable yet, so the changeset is empty.

Docs

  • Swingset docs added (popover.component.stories.tsx + .mdx, archetype A · compound) and wired into the registry + docModules.

How to test

  • pnpm --filter @clerk/ui test src/mosaic/components/popover (5 tests)
  • pnpm --filter @clerk/ui build:mosaic — verifies the transition + reduced-motion CSS lands in styles.css
  • pnpm run dev:swingset → Components → Popover

Checklist

  • pnpm test runs as expected.
  • pnpm build runs as expected.
  • (If applicable) JSDoc comments have been added or updated for any package exports
  • (If applicable) Documentation has been updated

Type of change

  • 🌟 New feature

Reusable StyleX-styled popover built on the @clerk/headless popover
primitive: a flexible popup card with content + footer slots, enter/exit
transition driven off self data-attributes, and swingset docs.
@vercel

vercel Bot commented Jul 24, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
clerk-js-sandbox Ready Ready Preview, Comment Jul 24, 2026 2:00pm
swingset Ready Ready Preview, Comment Jul 24, 2026 2:00pm

Request Review

@changeset-bot

changeset-bot Bot commented Jul 24, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 27e9be7

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 0 packages

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

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

@pkg-pr-new

pkg-pr-new Bot commented Jul 24, 2026

Copy link
Copy Markdown

Open in StackBlitz

@clerk/astro

npm i https://pkg.pr.new/@clerk/astro@9241

@clerk/backend

npm i https://pkg.pr.new/@clerk/backend@9241

@clerk/chrome-extension

npm i https://pkg.pr.new/@clerk/chrome-extension@9241

@clerk/clerk-js

npm i https://pkg.pr.new/@clerk/clerk-js@9241

@clerk/electron

npm i https://pkg.pr.new/@clerk/electron@9241

@clerk/electron-passkeys

npm i https://pkg.pr.new/@clerk/electron-passkeys@9241

@clerk/eslint-plugin

npm i https://pkg.pr.new/@clerk/eslint-plugin@9241

@clerk/expo

npm i https://pkg.pr.new/@clerk/expo@9241

@clerk/expo-google-signin

npm i https://pkg.pr.new/@clerk/expo-google-signin@9241

@clerk/expo-passkeys

npm i https://pkg.pr.new/@clerk/expo-passkeys@9241

@clerk/express

npm i https://pkg.pr.new/@clerk/express@9241

@clerk/fastify

npm i https://pkg.pr.new/@clerk/fastify@9241

@clerk/hono

npm i https://pkg.pr.new/@clerk/hono@9241

@clerk/localizations

npm i https://pkg.pr.new/@clerk/localizations@9241

@clerk/nextjs

npm i https://pkg.pr.new/@clerk/nextjs@9241

@clerk/nuxt

npm i https://pkg.pr.new/@clerk/nuxt@9241

@clerk/react

npm i https://pkg.pr.new/@clerk/react@9241

@clerk/react-router

npm i https://pkg.pr.new/@clerk/react-router@9241

@clerk/shared

npm i https://pkg.pr.new/@clerk/shared@9241

@clerk/tanstack-react-start

npm i https://pkg.pr.new/@clerk/tanstack-react-start@9241

@clerk/testing

npm i https://pkg.pr.new/@clerk/testing@9241

@clerk/ui

npm i https://pkg.pr.new/@clerk/ui@9241

@clerk/upgrade

npm i https://pkg.pr.new/@clerk/upgrade@9241

@clerk/vue

npm i https://pkg.pr.new/@clerk/vue@9241

commit: 27e9be7

@github-actions

Copy link
Copy Markdown
Contributor

API Changes Report

Generated by Break Check on 2026-07-24T14:02:51.287Z

Summary

Metric Count
Packages analyzed 19
Packages with changes 1
🔴 Breaking changes 1
🟡 Non-breaking changes 1
🟢 Additions 0

Warning
1 breaking change(s) detected - Major version bump required

🤖 This report was reviewed by claude-sonnet-4-6.

🔴 Breaking changes index (1)

Every breaking change, up front. Full diffs are in the package sections below.

Package Subpath Change
@clerk/ui ./experimental ./experimental

@clerk/ui

Current version: 1.25.8
Recommended bump: MAJOR → 2.0.0

Subpath ./experimental

🔴 Breaking Changes (1)

Changed: ./experimental

Subpath export ./experimental was removed

Subpath ./internal

🟡 Non-breaking Changes (1)

Modified: ElementsConfig
// ... 392 unchanged lines elided ...
    profileSectionPrimaryButton: WithOptions<ProfileSectionId>;
    profileSectionButtonGroup: WithOptions<ProfileSectionId>;
    profilePage: WithOptions<ProfilePageId>;
-   profilePageContent: WithOptions;
    formattedPhoneNumber: WithOptions;
    formattedPhoneNumberFlag: WithOptions;
    formattedPhoneNumberText: WithOptions;
// ... 158 unchanged lines elided ...

Static analyzer: Breaking change in type alias ElementsConfig: Type changed: {button:import("@clerk/ui").~WithOptions<string>;input:import("@clerk/ui").~WithOptions;checkbox:import("@clerk/ui").~W…{button:import("@clerk/ui").~WithOptions<string>;input:import("@clerk/ui").~WithOptions;checkbox:import("@clerk/ui").~W…

🤖 AI review (reclassified as non-breaking) (80%): The before and after snippets show the same type shape with only a minor structural difference (476 vs 477 lines elided in the middle), suggesting one property was removed or renamed; however, ElementsConfig is used only as an index type in the output-mapped type Elements, not as a consumer input type, so any removed/added keys only affect the derived Elements union members — which is an output type consumers read rather than construct. Without the exact diff of the removed line being identifiable from the elided sections, and given the usage site is purely output, this is non-breaking for consumers.


Report generated by Break Check

Last ran on 27e9be7.

@coderabbitai

coderabbitai Bot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Adds a StyleX-based Mosaic Popover with headless primitive composition, compound subcomponents, styling, exports, and behavioral tests. Updates the Mosaic build configuration to externalize the headless dependency. Adds Storybook and MDX documentation, then registers the component in Swingset documentation lookup and story discovery.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly and concisely summarizes the main change: adding a Mosaic Popover component.
Description check ✅ Passed The description directly matches the changeset, covering the new Popover component, docs, styling, and tests.

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/swingset/src/stories/popover.component.mdx`:
- Around line 5-15: Update the introductory paragraph in the Popover MDX to
state that the composed Mosaic layer owns open/placement state and wires
together the `@clerk/headless` popover primitive with StyleX theming. Add a
composition array to the sole Story component naming each direct dependency.
- Around line 41-42: Update the controlled popover example in the story to
import useState from React, and add a composition array to its Story declaration
listing the direct dependencies required by the composed layer.

In `@packages/ui/src/mosaic/components/popover/popover.styles.ts`:
- Around line 47-58: Update the popup style container around the visible
maxWidth/minWidth declarations to add a viewport-relative maxHeight, ensuring
the overall popup remains within the viewport so the content overflow can scroll
without displacing the footer. Adjust the minWidth constraint to remain
compatible with maxWidth on narrow viewports, using a responsive width
constraint rather than forcing 18rem below the available viewport width.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro Plus

Run ID: c5cd8a53-2b61-4f94-a5c6-7a936df2a45f

📥 Commits

Reviewing files that changed from the base of the PR and between 01f2c12 and 23353f2.

📒 Files selected for processing (11)
  • .changeset/mosaic-popover.md
  • packages/swingset/src/components/DocsViewer.tsx
  • packages/swingset/src/lib/registry.ts
  • packages/swingset/src/stories/popover.component.mdx
  • packages/swingset/src/stories/popover.component.stories.tsx
  • packages/ui/src/mosaic/components/popover/index.ts
  • packages/ui/src/mosaic/components/popover/popover.styles.ts
  • packages/ui/src/mosaic/components/popover/popover.test.tsx
  • packages/ui/src/mosaic/components/popover/popover.tsx
  • packages/ui/src/mosaic/styles/index.ts
  • packages/ui/tsdown.mosaic.config.mts
🔗 Linked repositories identified

CodeRabbit considers these linked repositories for cross-repo context during reviews:

  • clerk/clerk_go (manual)
  • clerk/dashboard (manual)
  • clerk/accounts (manual)
  • clerk/backoffice (manual)
  • clerk/clerk (manual)
  • clerk/clerk-docs (manual)
  • clerk/cloudflare-workers (manual)
  • clerk/cli (auto-detected)
  • clerk/clerk-ios (auto-detected)
  • clerk/clerk-android (auto-detected)

Comment on lines +5 to +15
The Mosaic `Popover` — the styled Mosaic component composed from the `@clerk/headless` popover
primitive and themed with StyleX. It is a reusable floating card anchored to a trigger: drop any
inner content into `Popover.Content` and an optional `Popover.Footer`. It inherits the primitive's
positioning, focus management, and ARIA wiring.

## Example

<Story
name='Default'
storyModule={PopoverStories}
/>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Add the composed-layer documentation metadata.

Document which layer owns open/placement state and which lower-layer pieces are wired together, then pass a composition array to this sole <Story>.

As per coding guidelines, “For Archetype C (composed layer) MDX files, lead with a single paragraph on what state is owned and which lower-layer pieces are wired together, then render a single <Story> with a composition array naming each direct dependency.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/swingset/src/stories/popover.component.mdx` around lines 5 - 15,
Update the introductory paragraph in the Popover MDX to state that the composed
Mosaic layer owns open/placement state and wires together the `@clerk/headless`
popover primitive with StyleX theming. Add a composition array to the sole Story
component naming each direct dependency.

Source: Coding guidelines

Comment on lines +41 to +42
```tsx
const [open, setOpen] = useState(false);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

file="packages/swingset/src/stories/popover.component.mdx"

echo "== File outline =="
wc -l "$file"
echo
nl -ba "$file" | sed -n '1,220p'

echo
echo "== Search for composition usage in similar stories =="
rg -n "composition\s*:" packages/swingset/src/stories -g '*.mdx' || true

echo
echo "== Search for useState import in this file =="
rg -n "useState|from 'react'|from \"react\"" "$file" || true

Repository: clerk/javascript

Length of output: 270


🏁 Script executed:

#!/bin/bash
set -euo pipefail

file="packages/swingset/src/stories/popover.component.mdx"

echo "== File contents =="
cat -n "$file"

echo
echo "== MDX stories with composition arrays =="
rg -n "composition\s*:" packages/swingset/src/stories -g '*.mdx' || true

echo
echo "== Popover references elsewhere =="
rg -n "popover" packages/swingset/src/stories -g '*.mdx' -g '*.ts' -g '*.tsx' || true

Repository: clerk/javascript

Length of output: 9929


🏁 Script executed:

#!/bin/bash
set -euo pipefail

file="packages/swingset/src/stories/popover.component.mdx"

echo "== File contents =="
cat -n "$file"

Repository: clerk/javascript

Length of output: 5749


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '1,140p' packages/swingset/src/stories/popover.component.mdx

Repository: clerk/javascript

Length of output: 4924


Import useState and add the composition metadata

  • The controlled example uses useState without importing it, so the snippet won’t compile as written.
  • This composed-layer story also needs a composition array on <Story> to document the direct dependencies.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/swingset/src/stories/popover.component.mdx` around lines 41 - 42,
Update the controlled popover example in the story to import useState from
React, and add a composition array to its Story declaration listing the direct
dependencies required by the composed layer.

Comment on lines +47 to +58
maxWidth: 'calc(100vw - 2rem)',
minWidth: '18rem',
},

// Flexible inner content region. Scrolls on overflow so tall content never
// pushes the footer out of view.
content: {
padding: space['4'],
display: 'flex',
flexDirection: 'column',
minHeight: 0,
overflowY: 'auto',

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the target file and surrounding popover styles.
FILE="packages/ui/src/mosaic/components/popover/popover.styles.ts"
echo "== file outline =="
ast-grep outline "$FILE" --view expanded || true

echo
echo "== relevant lines =="
nl -ba "$FILE" | sed -n '1,220p'

echo
echo "== search for related popover sizing/positioning styles =="
rg -n "maxHeight|maxWidth|minWidth|overflowY|popover|content:" packages/ui/src/mosaic/components/popover -S || true

Repository: clerk/javascript

Length of output: 337


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="packages/ui/src/mosaic/components/popover/popover.styles.ts"

echo "== file with line numbers =="
awk '{printf "%4d: %s\n", NR, $0}' "$FILE" | sed -n '1,220p'

echo
echo "== surrounding popover files =="
fd -t f . packages/ui/src/mosaic/components/popover

echo
echo "== related references =="
rg -n "maxHeight|maxWidth|minWidth|overflowY|overflow|popover" packages/ui/src/mosaic/components/popover -S || true

Repository: clerk/javascript

Length of output: 6065


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="packages/ui/src/mosaic/components/popover/popover.tsx"

echo "== file with line numbers =="
awk '{printf "%4d: %s\n", NR, $0}' "$FILE" | sed -n '1,220p'

echo
echo "== popover tests =="
awk '{printf "%4d: %s\n", NR, $0}' "packages/ui/src/mosaic/components/popover/popover.test.tsx" | sed -n '1,220p'

Repository: clerk/javascript

Length of output: 8904


Constrain the popup to the viewport.
overflowY: 'auto' on the content won’t help unless the popup itself has a height cap, so tall content can still push the footer off-screen. On narrow screens, minWidth: '18rem' also overrides maxWidth: 'calc(100vw - 2rem)' once the viewport drops below 20rem.

Proposed fix
     transitionTimingFunction: 'ease-out',
+    maxHeight: 'calc(100dvh - 2rem)',
     maxWidth: 'calc(100vw - 2rem)',
-    minWidth: '18rem',
+    minWidth: 'min(18rem, calc(100vw - 2rem))',
📝 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.

Suggested change
maxWidth: 'calc(100vw - 2rem)',
minWidth: '18rem',
},
// Flexible inner content region. Scrolls on overflow so tall content never
// pushes the footer out of view.
content: {
padding: space['4'],
display: 'flex',
flexDirection: 'column',
minHeight: 0,
overflowY: 'auto',
transitionTimingFunction: 'ease-out',
maxHeight: 'calc(100dvh - 2rem)',
maxWidth: 'calc(100vw - 2rem)',
minWidth: 'min(18rem, calc(100vw - 2rem))',
},
// Flexible inner content region. Scrolls on overflow so tall content never
// pushes the footer out of view.
content: {
padding: space['4'],
display: 'flex',
flexDirection: 'column',
minHeight: 0,
overflowY: 'auto',
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ui/src/mosaic/components/popover/popover.styles.ts` around lines 47
- 58, Update the popup style container around the visible maxWidth/minWidth
declarations to add a viewport-relative maxHeight, ensuring the overall popup
remains within the viewport so the content overflow can scroll without
displacing the footer. Adjust the minWidth constraint to remain compatible with
maxWidth on narrow viewports, using a responsive width constraint rather than
forcing 18rem below the available viewport width.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant