Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
70 commits
Select commit Hold shift + click to select a range
5854db7
feat(ui): add UserButton account/org switcher
alexcarpenter Jul 16, 2026
46ebff2
feat(ui): give UserButton its own User section above Organization
alexcarpenter Jul 16, 2026
bfb25a1
refactor(ui): move UserButton into its own user-button/ folder
alexcarpenter Jul 16, 2026
3403c8f
fix(ui): resolve UserButton lint errors
alexcarpenter Jul 16, 2026
ef7084d
refactor(ui): compose UserButton view from Mosaic Popover, Card, Item…
alexcarpenter Jul 31, 2026
f569458
fix(ui): show additional account emails on md rows in UserButton
alexcarpenter Jul 31, 2026
bca964e
fix(ui): drop the popover positioner z-index
alexcarpenter Jul 31, 2026
4695ecb
feat(ui): render UserButton accounts as email rows with a menu
alexcarpenter Jul 31, 2026
4ecb298
fix(headless): stop menu Escape from bubbling to the parent floating …
alexcarpenter Jul 31, 2026
048f14e
fix(ui): make the UserButton trigger follow the avatar's shape
alexcarpenter Jul 31, 2026
13513ef
feat(ui): give the Mosaic Popover trigger a themeable slot
alexcarpenter Jul 31, 2026
6f9b525
refactor(ui): name the Mosaic radius tokens by size
alexcarpenter Jul 31, 2026
3d7e1ef
avatar disable dragging
alexcarpenter Jul 31, 2026
33fa922
default item color to neutral faded
alexcarpenter Jul 31, 2026
3fae407
Update item.styles.ts
alexcarpenter Jul 31, 2026
694bfcf
feat(headless): add alignOffset to Popover.Root
alexcarpenter Jul 31, 2026
9b5ca15
feat(ui): add UserButton mode prop for combined, orgs, and user switc…
alexcarpenter Jul 31, 2026
b2f47ad
docs(ui): note UserButton mode in the changeset
alexcarpenter Jul 31, 2026
b509cfd
docs(repo): make the swingset UserButton examples live prototypes
alexcarpenter Jul 31, 2026
348164b
fix(ui): stop rendering the active UserButton workspace as a button
alexcarpenter Jul 31, 2026
6bcfd80
fix(ui): drop the separator above additional UserButton accounts
alexcarpenter Jul 31, 2026
129e32f
fix(ui): put every UserButton switch row in one group
alexcarpenter Jul 31, 2026
d15bd3c
fix(ui): keep the account at the head of the combined UserButton surface
alexcarpenter Jul 31, 2026
140374a
feat(ui): list every account's organizations in the UserButton
alexcarpenter Jul 31, 2026
12a0482
fix(ui): give the Avatar fallback the image's radius
alexcarpenter Jul 31, 2026
689c090
fix(ui): promote Item icons with the row, on interactive rows only
alexcarpenter Jul 31, 2026
6f7ef1c
docs(swingset): render Item docs icons with the Icon component
alexcarpenter Jul 31, 2026
7f2e343
style(ui): weight the UserButton account row email
alexcarpenter Jul 31, 2026
bc2a391
feat(ui): sign the UserButton footer with the Clerk logo
alexcarpenter Jul 31, 2026
c7f11f9
style(ui): give the UserButton branding row more breathing room
alexcarpenter Jul 31, 2026
e0c904c
feat(headless): hold Popover contents while it closes (#9310)
alexcarpenter Jul 31, 2026
db32919
refactor(ui): scope the UserButton switcher to fetchable data
alexcarpenter Aug 3, 2026
f1a4657
chore(ui): drop the UserButton changeset
alexcarpenter Aug 3, 2026
0ede646
fix(ui): follow the StyleX Card migration
alexcarpenter Aug 3, 2026
65c8cfc
docs(swingset): follow the Mosaic radius token rename
alexcarpenter Aug 3, 2026
a74d307
refactor(ui): rename the presentational UserButton to UserButtonView
alexcarpenter Aug 3, 2026
87fcb8c
feat(ui): add Mosaic Spinner and a spin-delay hook
alexcarpenter Aug 3, 2026
a595062
feat(ui): add loading and busy states to UserButton
alexcarpenter Aug 3, 2026
db80b6a
fix(ui): keep pending workspaces reachable and avatars out of row names
alexcarpenter Aug 3, 2026
6d3337c
feat(ui): close the UserButton popover only when a workspace is picked
alexcarpenter Aug 3, 2026
0d8fd8e
refactor(ui): migrate the Mosaic Spinner to StyleX and give it a sm size
alexcarpenter Aug 3, 2026
8ceeb57
feat(ui): rework the account-only UserButton surface
alexcarpenter Aug 3, 2026
bb43348
refactor(ui): sign out from a labelled header button and check the ac…
alexcarpenter Aug 3, 2026
6b544e8
docs(swingset): trim the UserButton page
alexcarpenter Aug 3, 2026
3bf207b
fix(ui): check the active account in the combined Accounts list, not …
alexcarpenter Aug 3, 2026
0093193
feat(ui): name the active workspace in the UserButton trigger
alexcarpenter Aug 3, 2026
ba983c3
refactor(ui): split the UserButton trigger label into two props
alexcarpenter Aug 3, 2026
d71b3b8
feat(ui): let combined UserButton lead with the organization or the a…
alexcarpenter Aug 3, 2026
4c60b70
docs(swingset): drop the Styling section from the UserButton page
alexcarpenter Aug 3, 2026
92915c7
feat(ui): name the active organization from itself, and list accepted…
alexcarpenter Aug 4, 2026
48d53ff
fix(ui): show the UserButton workspace list in one go rather than a l…
alexcarpenter Aug 4, 2026
c54c08e
fix(ui): take the user button trigger corner from its workspace
alexcarpenter Aug 4, 2026
95d0034
fix(ui): drop the padding from the labelled UserButton trigger
alexcarpenter Aug 4, 2026
7a97288
refactor(ui): render the UserButton trigger skeleton with StyleX
alexcarpenter Aug 5, 2026
9a5fb7b
fix(ui): keep a UserButton row's avatar through its busy state
alexcarpenter Aug 5, 2026
6a72f6b
feat(ui): scroll the UserButton workspace list through the shared scr…
alexcarpenter Aug 4, 2026
56a7822
fix(ui): keep the UserButton account row on an account with no organi…
alexcarpenter Aug 4, 2026
b12b5c0
fix(ui): keep the UserButton account menu mounted while another actio…
alexcarpenter Aug 4, 2026
e82c73a
feat(ui): render the UserButton personal workspace row
alexcarpenter Aug 5, 2026
cd9a8a5
feat(ui): name the UserButton personal row "Personal account"
alexcarpenter Aug 5, 2026
23fb11b
fix(ui): withhold the UserButton personal row when handed none
alexcarpenter Aug 5, 2026
7fede63
test(ui): cover the UserButton rendering cases in the view test
alexcarpenter Aug 5, 2026
c435381
fix(ui): shape the UserButton loading placeholder like the workspace …
alexcarpenter Aug 4, 2026
4f5d76b
fix(ui): apply the loading placeholder's shape to the block that pain…
alexcarpenter Aug 4, 2026
35b52a9
fix(ui): keep a scrolled-into-view workspace row clear of the list's …
alexcarpenter Aug 5, 2026
e4e8c07
refactor(ui): name the UserButton account line by identifier, not email
alexcarpenter Aug 5, 2026
698ad01
fix(ui): drop the UserButton identifier line when it repeats the title
alexcarpenter Aug 5, 2026
e242344
fix(repo): rename the swingset UserButton session fixture field to id…
alexcarpenter Aug 5, 2026
c9d10f3
fix(ui): use the scroll-padding longhands stylex accepts
alexcarpenter Aug 6, 2026
bb3a877
fix(ui): name the active UserButton row as the current one
alexcarpenter Aug 6, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .changeset/mosaic-radius-scale.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
---
---
2 changes: 2 additions & 0 deletions .changeset/mosaic-user-button-trigger-label.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
---
---
2 changes: 2 additions & 0 deletions .changeset/popover-freeze-on-close.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
---
---
2 changes: 1 addition & 1 deletion packages/headless/src/primitives/menu/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ Accepts all `FloatingArrow` props. `ref` and `context` are injected automaticall
- Nested menus open on hover (75ms delay) with a `safePolygon` safe zone.
- Only one sibling submenu can be open at a time.
- Clicking any item with `closeOnClick={true}` (default) closes the entire menu tree via a tree event.
- `Escape` closes the innermost menu first, bubbling up through the tree.
- `Escape` closes one level: the innermost open menu, leaving its parent — a parent menu, or a `Popover` the menu is rendered inside — open. Pressing it again closes the next level up. An outside press is the opposite: it dismisses the whole stack at once.

## Important Notes

Expand Down
4 changes: 3 additions & 1 deletion packages/headless/src/primitives/menu/menu-root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,9 @@ function MenuInner(props: MenuProps) {
delete reference.role;
return { ...baseRole, reference };
}, [baseRole, isNested]);
const dismiss = useDismiss(floatingContext, { bubbles: true });
// Escape must not bubble: it closes this menu and leaves whatever it sits inside — a parent menu,
// or a popover — open. An outside press is the opposite, and dismisses the whole stack.
const dismiss = useDismiss(floatingContext, { bubbles: { escapeKey: false, outsidePress: true } });
const listNavigation = useListNavigation(floatingContext, {
listRef: elementsRef,
activeIndex,
Expand Down
74 changes: 74 additions & 0 deletions packages/headless/src/primitives/menu/menu.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -643,6 +643,80 @@ describe('Menu', () => {

expect(onClick).toHaveBeenCalledTimes(1);
});

it('Escape closes only the submenu', async () => {
const user = userEvent.setup();
render(
<Menu.Root>
<Menu.Trigger>Actions</Menu.Trigger>
<Menu.Positioner>
<Menu.Popup>
<Menu.Root>
<Menu.Trigger>Share</Menu.Trigger>
<Menu.Positioner>
<Menu.Popup>
<Menu.Item label='Email'>Email</Menu.Item>
</Menu.Popup>
</Menu.Positioner>
</Menu.Root>
</Menu.Popup>
</Menu.Positioner>
</Menu.Root>,
);

await user.click(screen.getByText('Actions'));
await new Promise(r => requestAnimationFrame(r));
await user.keyboard('{ArrowDown}');
await user.keyboard('{ArrowRight}');
await user.keyboard('{Escape}');

expect(screen.getByText('Share')).toHaveAttribute('data-closed', '');
expect(screen.getByText('Actions')).toHaveAttribute('data-open', '');
});
});

describe('inside a popover', () => {
function renderMenuInPopover() {
return render(
<Popover.Root defaultOpen>
<Popover.Trigger>Open popover</Popover.Trigger>
<Popover.Positioner>
<Popover.Popup>
<Menu.Root>
<Menu.Trigger>Actions</Menu.Trigger>
<Menu.Positioner>
<Menu.Popup>
<Menu.Item label='Cut'>Cut</Menu.Item>
</Menu.Popup>
</Menu.Positioner>
</Menu.Root>
</Popover.Popup>
</Popover.Positioner>
</Popover.Root>,
);
}

it('Escape closes only the menu', async () => {
const user = userEvent.setup();
renderMenuInPopover();

await user.click(screen.getByText('Actions'));
await user.keyboard('{Escape}');

expect(screen.getByText('Actions')).toHaveAttribute('data-closed', '');
expect(screen.getByText('Open popover')).toHaveAttribute('data-open', '');
});

it('Escape closes the popover once the menu is closed', async () => {
const user = userEvent.setup();
renderMenuInPopover();

await user.click(screen.getByText('Actions'));
await user.keyboard('{Escape}');
await user.keyboard('{Escape}');

expect(screen.getByText('Open popover')).toHaveAttribute('data-closed', '');
});
});

describe('positioner', () => {
Expand Down
18 changes: 10 additions & 8 deletions packages/headless/src/primitives/popover/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,14 +63,15 @@ const [open, setOpen] = useState(false);

### `Popover.Root`

| Prop | Type | Default | Description |
| -------------- | ------------------------- | ---------- | ---------------------------------- |
| `open` | `boolean` | — | Controlled open state |
| `defaultOpen` | `boolean` | `false` | Initial open state (uncontrolled) |
| `onOpenChange` | `(open: boolean) => void` | — | Called when open state changes |
| `placement` | `Placement` | `"bottom"` | Floating UI placement |
| `sideOffset` | `number` | `4` | Gap between trigger and popup (px) |
| `modal` | `boolean` | `false` | Traps focus within the popover |
| Prop | Type | Default | Description |
| -------------- | ------------------------- | ---------- | ----------------------------------- |
| `open` | `boolean` | — | Controlled open state |
| `defaultOpen` | `boolean` | `false` | Initial open state (uncontrolled) |
| `onOpenChange` | `(open: boolean) => void` | — | Called when open state changes |
| `placement` | `Placement` | `"bottom"` | Floating UI placement |
| `sideOffset` | `number` | `4` | Gap between trigger and popup (px) |
| `alignOffset` | `number` | `0` | Nudge along the alignment axis (px) |
| `modal` | `boolean` | `false` | Traps focus within the popover |

### `Popover.Trigger`, `Popover.Positioner`, `Popover.Popup`, `Popover.Title`, `Popover.Description`, `Popover.Close`

Expand Down Expand Up @@ -104,6 +105,7 @@ Middleware stack: `offset` -> `flip` -> `shift` -> `arrow` -> CSS vars. The popu
- **Title and Description are optional but recommended.** They wire `aria-labelledby` and `aria-describedby` to the positioner. If omitted, those attributes are simply absent.
- **Non-modal by default.** Unlike Dialog, the page remains interactive behind the popover. Set `modal={true}` for a stricter focus trap.
- **Nested popovers are supported.** The `FloatingTree` pattern handles nesting automatically.
- **Popup contents freeze while closing.** The popup outlives `open` by its exit animation, so its children are wrapped in `Freeze` (`@clerk/headless/utils`) and hold their last frame instead of re-rendering under the animation. The popup element itself keeps updating, so `data-closed` / `data-ending-style` still land. Freezing wraps the children in a `display: contents` element and detaches refs inside them until the popup reopens.

## ARIA

Expand Down
11 changes: 8 additions & 3 deletions packages/headless/src/primitives/popover/popover-popup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,22 @@

import React from 'react';

import { type ComponentProps, mergeProps, useRender } from '../../utils';
import { type ComponentProps, Freeze, mergeProps, useRender } from '../../utils';
import { usePopoverContext } from './popover-context';

export type PopoverPopupProps = ComponentProps<'div'>;

export const PopoverPopup = React.forwardRef<HTMLDivElement, PopoverPopupProps>(function PopoverPopup(props, ref) {
const { render, ...otherProps } = props;
const { popupRef, transitionProps } = usePopoverContext();
const { render, children, ...otherProps } = props;
const { open, popupRef, transitionProps } = usePopoverContext();

const defaultProps = {
...transitionProps,
// The popup outlives `open` by the length of its exit animation. Whatever closed it has
// usually changed the data behind it (switching account, picking an item), so the contents
// hold their last frame on the way out instead of swapping under the animation. The popup
// element itself stays live, so `data-closed` / `data-ending-style` still land.
children: <Freeze frozen={!open}>{children}</Freeze>,
};

return useRender({
Expand Down
5 changes: 3 additions & 2 deletions packages/headless/src/primitives/popover/popover-root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,14 @@ export interface PopoverProps {
onOpenChange?: (open: boolean) => void;
placement?: Placement;
sideOffset?: number;
alignOffset?: number;
modal?: boolean;
children: ReactNode;
}

function PopoverInner(props: PopoverProps) {
const nodeId = useFloatingNodeId();
const { placement: placementProp = 'bottom', sideOffset = 4, modal = false, children } = props;
const { placement: placementProp = 'bottom', sideOffset = 4, alignOffset = 0, modal = false, children } = props;

const [open, setOpen] = useControllableState(props.open, props.defaultOpen ?? false, props.onOpenChange);

Expand All @@ -61,7 +62,7 @@ function PopoverInner(props: PopoverProps) {
onOpenChange: setOpen,
placement: placementProp,
middleware: [
offset(sideOffset),
offset({ mainAxis: sideOffset, alignmentAxis: alignOffset }),
flip({
crossAxis: placementProp.includes('-'),
fallbackAxisSideDirection: 'end',
Expand Down
72 changes: 72 additions & 0 deletions packages/headless/src/utils/freeze.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { cleanup, render, screen } from '@testing-library/react';
import { afterEach, describe, expect, it } from 'vitest';

import { Freeze } from './freeze';

afterEach(() => {
cleanup();
});

describe('Freeze', () => {
it('renders children while not frozen', () => {
render(<Freeze frozen={false}>Acme</Freeze>);

expect(screen.getByText('Acme')).toBeInTheDocument();
});

it('holds the committed DOM when children change while frozen', () => {
const { rerender } = render(<Freeze frozen={false}>Acme</Freeze>);

rerender(<Freeze frozen>Globex</Freeze>);

expect(screen.getByText('Acme')).toBeInTheDocument();
expect(screen.queryByText('Globex')).toBeNull();
});

it('keeps the held DOM visible', () => {
const { rerender } = render(<Freeze frozen={false}>Acme</Freeze>);

rerender(<Freeze frozen>Globex</Freeze>);

expect(screen.getByText('Acme')).toBeVisible();
});

it('keeps the held DOM visible across further updates while frozen', () => {
const { rerender } = render(<Freeze frozen={false}>Acme</Freeze>);

rerender(<Freeze frozen>Globex</Freeze>);
rerender(<Freeze frozen>Initech</Freeze>);

expect(screen.getByText('Acme')).toBeVisible();
});

it('commits the pending children once unfrozen', () => {
const { rerender } = render(<Freeze frozen={false}>Acme</Freeze>);

rerender(<Freeze frozen>Globex</Freeze>);
rerender(<Freeze frozen={false}>Globex</Freeze>);

expect(screen.getByText('Globex')).toBeInTheDocument();
expect(screen.queryByText('Acme')).toBeNull();
});

it('holds state updates raised from inside the frozen subtree', () => {
function Counter({ count }: { count: number }) {
return <span>count: {count}</span>;
}

const { rerender } = render(
<Freeze frozen={false}>
<Counter count={0} />
</Freeze>,
);

rerender(
<Freeze frozen>
<Counter count={1} />
</Freeze>,
);

expect(screen.getByText('count: 0')).toBeInTheDocument();
});
});
64 changes: 64 additions & 0 deletions packages/headless/src/utils/freeze.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
'use client';

import * as React from 'react';

/**
* Never settles. Throwing it suspends the enclosing boundary indefinitely: React keeps
* rendering the subtree but holds the commit, so the DOM keeps painting its last frame.
*/
const never = new Promise<never>(() => {});

function Suspend(): null {
// eslint-disable-next-line @typescript-eslint/only-throw-error -- Suspending is React's thrown-thenable protocol, not an error. `React.use()` would say this more plainly but needs React 19.2; this package supports React 18.
throw never;
}

export interface FreezeProps {
/** While `true`, the DOM below holds whatever it last committed. */
frozen: boolean;
children?: React.ReactNode;
}

/**
* Holds its subtree's DOM at the last committed frame while `frozen`. Renders keep
* happening, they just don't reach the DOM; the pending one commits when `frozen` flips
* back to `false`.
*
* Use it to stop content from visibly changing under an exit animation — a popover that
* closes because the thing it was showing changed would otherwise swap its contents on the
* way out.
*/
export function Freeze({ frozen, children }: FreezeProps) {
const contentRef = React.useRef<HTMLDivElement | null>(null);

// Hold onto the node ourselves rather than reading a plain ref: hiding a boundary's children
// detaches their refs, so by the time the effect below runs a normal ref reads `null`.
const setContent = React.useCallback((node: HTMLDivElement | null) => {
if (node) {
contentRef.current = node;
}
}, []);

// React hides a suspended boundary's host children with `display: none !important`, which is
// the opposite of what this is for. Undo it on the commit that applies it: insertion effects
// run after the boundary's mutation and before paint, so the held frame never blinks out.
// `display: contents` is also what the wrapper renders with, so React puts it back on unfreeze
// and the wrapper stays out of the layout it is spliced into.
React.useInsertionEffect(() => {
if (frozen) {
contentRef.current?.style.setProperty('display', 'contents');
}
}, [frozen]);

return (
<React.Suspense fallback={null}>
{frozen ? <Suspend /> : null}
<div
ref={setContent}
style={{ display: 'contents' }}
>
{children}
</div>
</React.Suspense>
);
}
1 change: 1 addition & 0 deletions packages/headless/src/utils/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export { cssVars } from './css-vars';
export { Freeze, type FreezeProps } from './freeze';
export { resetLayoutStyles } from './reset-layout-styles';
export {
type ComponentProps,
Expand Down
3 changes: 3 additions & 0 deletions packages/swingset/src/components/DocsViewer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ import { ViewSource } from './ViewSource';
// MDX docs keyed by `group` slug → `component` slug. Group-aware so identically-named
// entries (the headless `Dialog` primitive vs. the styled `Dialog` component) stay distinct.
const docModules: Record<string, Record<string, React.ComponentType>> = {
user: {
'user-button': dynamic(() => import('../stories/user-button.mdx')),
},
organization: {
'organization-profile': dynamic(() => import('../stories/organization-profile.mdx')),
'organization-profile-general-panel': dynamic(() => import('../stories/organization-profile-general-panel.mdx')),
Expand Down
15 changes: 15 additions & 0 deletions packages/swingset/src/lib/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,12 @@ import {
} from '../stories/text.stories';
import { meta as tooltipMeta } from '../stories/tooltip.stories';
import { meta as useDataTableMeta } from '../stories/use-data-table.stories';
import {
Combined as UserButtonCombined,
meta as userButtonMeta,
Organizations as UserButtonOrganizations,
User as UserButtonUser,
} from '../stories/user-button.stories';
Comment on lines +118 to +123

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

Preserve __source when constructing userButtonModule.

user-button.stories.tsx exports __source via its ?raw self-import, but this import list (Lines 108-113) omits it, and userButtonModule (Lines 185-190) never sets it. Any consumer of StoryModule.__source for the code-footer feature won't have it for UserButton.

This mirrors the same unresolved concern raised previously for accountButtonModule.

🛠️ Proposed fix
 import {
+  __source as userButtonSource,
   Default as UserButtonDefault,
   meta as userButtonMeta,
   MultipleSessions as UserButtonMultipleAccounts,
   Personal as UserButtonPersonal,
 } from '../stories/user-button.stories';
...
 const userButtonModule: StoryModule = {
   meta: userButtonMeta,
+  __source: userButtonSource,
   Default: UserButtonDefault,
   Personal: UserButtonPersonal,
   MultipleSessions: UserButtonMultipleAccounts,
 };

Also applies to: 185-190

🤖 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/lib/registry.ts` around lines 108 - 113, Update the
UserButton story imports to include the exported __source value, then assign it
to the __source property when constructing userButtonModule alongside the
existing story metadata. Preserve the current module construction behavior for
all other fields.

import { toSlug } from './slug';
import type { StoryModule } from './types';

Expand Down Expand Up @@ -187,6 +193,13 @@ const itemModule: StoryModule = {
Scrolling: ItemScrolling,
};

const userButtonModule: StoryModule = {
meta: userButtonMeta,
Combined: UserButtonCombined,
Organizations: UserButtonOrganizations,
User: UserButtonUser,
};

const headingModule: StoryModule = {
meta: headingMeta,
Default: HeadingDefault,
Expand Down Expand Up @@ -237,6 +250,8 @@ const scrollAreaModule: StoryModule = {
const useDataTableModule: StoryModule = { meta: useDataTableMeta };

export const registry: StoryModule[] = [
// User
userButtonModule,
// Organization
organizationProfileModule,
organizationProfileGeneralPanelModule,
Expand Down
Loading