-
Notifications
You must be signed in to change notification settings - Fork 465
feat(ui): add UserButton view component #9184
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
Open
Open
Changes from all commits
Commits
Show all changes
80 commits
Select commit
Hold shift + click to select a range
95d4160
fix(headless): stop menu Escape from bubbling to the parent floating …
alexcarpenter 9653a8f
feat(headless): add alignOffset to Popover.Root
alexcarpenter bfe1013
feat(headless): hold Popover contents while it closes (#9310)
alexcarpenter 3366a9f
feat(ui): add UserButton account/org switcher
alexcarpenter 6a41d4b
feat(ui): give UserButton its own User section above Organization
alexcarpenter f2d5042
refactor(ui): move UserButton into its own user-button/ folder
alexcarpenter 1de672e
fix(ui): resolve UserButton lint errors
alexcarpenter 273c049
refactor(ui): compose UserButton view from Mosaic Popover, Card, Item…
alexcarpenter ff53246
fix(ui): show additional account emails on md rows in UserButton
alexcarpenter cac185d
fix(ui): drop the popover positioner z-index
alexcarpenter 306b29b
feat(ui): render UserButton accounts as email rows with a menu
alexcarpenter c72393e
fix(ui): make the UserButton trigger follow the avatar's shape
alexcarpenter 519c2f8
feat(ui): give the Mosaic Popover trigger a themeable slot
alexcarpenter 13ad2f0
refactor(ui): name the Mosaic radius tokens by size
alexcarpenter 33f1ed8
avatar disable dragging
alexcarpenter 9be1134
default item color to neutral faded
alexcarpenter a120c43
Update item.styles.ts
alexcarpenter c54d913
feat(ui): add UserButton mode prop for combined, orgs, and user switc…
alexcarpenter f6b46f5
docs(ui): note UserButton mode in the changeset
alexcarpenter 86722bb
docs(repo): make the swingset UserButton examples live prototypes
alexcarpenter bbcb512
fix(ui): stop rendering the active UserButton workspace as a button
alexcarpenter b6a15fc
fix(ui): drop the separator above additional UserButton accounts
alexcarpenter d2ab847
fix(ui): put every UserButton switch row in one group
alexcarpenter ff1f564
fix(ui): keep the account at the head of the combined UserButton surface
alexcarpenter 17a3345
feat(ui): list every account's organizations in the UserButton
alexcarpenter 4263f67
fix(ui): give the Avatar fallback the image's radius
alexcarpenter 3010f2c
fix(ui): promote Item icons with the row, on interactive rows only
alexcarpenter 8091fe0
docs(swingset): render Item docs icons with the Icon component
alexcarpenter 4daf002
style(ui): weight the UserButton account row email
alexcarpenter b1115da
feat(ui): sign the UserButton footer with the Clerk logo
alexcarpenter d20a90b
style(ui): give the UserButton branding row more breathing room
alexcarpenter c0300c3
feat(headless): hold Popover contents while it closes (#9310)
alexcarpenter 0b5bc52
refactor(ui): scope the UserButton switcher to fetchable data
alexcarpenter fd486c0
chore(ui): drop the UserButton changeset
alexcarpenter 49e85f8
fix(ui): follow the StyleX Card migration
alexcarpenter 813f3c3
docs(swingset): follow the Mosaic radius token rename
alexcarpenter 84c37fe
refactor(ui): rename the presentational UserButton to UserButtonView
alexcarpenter 13733ac
feat(ui): add Mosaic Spinner and a spin-delay hook
alexcarpenter cab33fb
feat(ui): add loading and busy states to UserButton
alexcarpenter 0bbd958
fix(ui): keep pending workspaces reachable and avatars out of row names
alexcarpenter 4ccfd5f
feat(ui): close the UserButton popover only when a workspace is picked
alexcarpenter be87de7
refactor(ui): migrate the Mosaic Spinner to StyleX and give it a sm size
alexcarpenter baf2970
feat(ui): rework the account-only UserButton surface
alexcarpenter 0e21a01
refactor(ui): sign out from a labelled header button and check the ac…
alexcarpenter 891c240
docs(swingset): trim the UserButton page
alexcarpenter b80985c
fix(ui): check the active account in the combined Accounts list, not …
alexcarpenter 02b6b9a
feat(ui): name the active workspace in the UserButton trigger
alexcarpenter 5f67627
refactor(ui): split the UserButton trigger label into two props
alexcarpenter 8aa6218
feat(ui): let combined UserButton lead with the organization or the a…
alexcarpenter 345d106
docs(swingset): drop the Styling section from the UserButton page
alexcarpenter c9ca7c9
feat(ui): name the active organization from itself, and list accepted…
alexcarpenter 8fb642f
fix(ui): show the UserButton workspace list in one go rather than a l…
alexcarpenter ae87a55
fix(ui): take the user button trigger corner from its workspace
alexcarpenter 4f72a22
fix(ui): drop the padding from the labelled UserButton trigger
alexcarpenter cf271c9
refactor(ui): render the UserButton trigger skeleton with StyleX
alexcarpenter 3397233
fix(ui): keep a UserButton row's avatar through its busy state
alexcarpenter e39db40
feat(ui): scroll the UserButton workspace list through the shared scr…
alexcarpenter 6001e9e
fix(ui): keep the UserButton account row on an account with no organi…
alexcarpenter debb6d7
fix(ui): keep the UserButton account menu mounted while another actio…
alexcarpenter 461084d
feat(ui): render the UserButton personal workspace row
alexcarpenter d0bc588
feat(ui): name the UserButton personal row "Personal account"
alexcarpenter e710de0
fix(ui): withhold the UserButton personal row when handed none
alexcarpenter 492e37f
test(ui): cover the UserButton rendering cases in the view test
alexcarpenter 39d49df
fix(ui): shape the UserButton loading placeholder like the workspace …
alexcarpenter 9fba035
fix(ui): apply the loading placeholder's shape to the block that pain…
alexcarpenter b7c7e9d
fix(ui): keep a scrolled-into-view workspace row clear of the list's …
alexcarpenter f00b17c
refactor(ui): name the UserButton account line by identifier, not email
alexcarpenter 0ded411
fix(ui): drop the UserButton identifier line when it repeats the title
alexcarpenter cc3d79e
fix(repo): rename the swingset UserButton session fixture field to id…
alexcarpenter 5c109ee
fix(ui): use the scroll-padding longhands stylex accepts
alexcarpenter d90577e
fix(ui): name the active UserButton row as the current one
alexcarpenter 4fb6030
refactor(ui): follow the Mosaic radius token rename in Input
alexcarpenter 836db8d
fix(ui): spin the UserButton join button in place
alexcarpenter fd14fec
feat(ui): add customMenuItems and menuItemOrder to the UserButton view
alexcarpenter 5e86372
refactor(ui): move the UserButton contract into user-button.types
alexcarpenter 8d1b2ef
refactor(ui): drop the UserButton trigger placeholder
alexcarpenter 2d25475
refactor(ui): drop comments the test names already carry
alexcarpenter 58cc1e7
refactor(ui): name the organization mode in full
alexcarpenter 9f1148b
feat(ui): link the UserButton's Clerk logo and pull its strings into …
alexcarpenter fd4475a
refactor(ui): rename renderPlanBadge to renderTriggerBadge
alexcarpenter File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| --- | ||
| --- |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| --- | ||
| --- |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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(); | ||
| }); | ||
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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> | ||
| ); | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.