From b66af06102e8808fe56ca2dc608db183ebc791ad Mon Sep 17 00:00:00 2001 From: austincalvelage Date: Mon, 10 Aug 2026 11:23:57 -0600 Subject: [PATCH] feat(ui): add connected accounts section --- ...user-profile-connected-accounts-section.md | 2 + .../swingset/src/components/DocsViewer.tsx | 3 + packages/swingset/src/lib/registry.ts | 9 ++ ...ser-profile-connected-accounts-section.mdx | 7 ++ ...ile-connected-accounts-section.stories.tsx | 37 ++++++++ packages/ui/src/mosaic/icons/registry.tsx | 8 ++ ...rofile-connected-accounts-section.view.tsx | 90 +++++++++++++++++++ 7 files changed, 156 insertions(+) create mode 100644 .changeset/user-profile-connected-accounts-section.md create mode 100644 packages/swingset/src/stories/user-profile-connected-accounts-section.mdx create mode 100644 packages/swingset/src/stories/user-profile-connected-accounts-section.stories.tsx create mode 100644 packages/ui/src/mosaic/user-profile/user-profile-connected-accounts-section.view.tsx diff --git a/.changeset/user-profile-connected-accounts-section.md b/.changeset/user-profile-connected-accounts-section.md new file mode 100644 index 00000000000..a845151cc84 --- /dev/null +++ b/.changeset/user-profile-connected-accounts-section.md @@ -0,0 +1,2 @@ +--- +--- diff --git a/packages/swingset/src/components/DocsViewer.tsx b/packages/swingset/src/components/DocsViewer.tsx index 62d5d217307..d998e3a50dd 100644 --- a/packages/swingset/src/components/DocsViewer.tsx +++ b/packages/swingset/src/components/DocsViewer.tsx @@ -13,6 +13,9 @@ const docModules: Record> = { user: { 'user-button': dynamic(() => import('../stories/user-button.mdx')), 'user-profile-account-section': dynamic(() => import('../stories/user-profile-account-section.mdx')), + 'user-profile-connected-accounts-section': dynamic( + () => import('../stories/user-profile-connected-accounts-section.mdx'), + ), }, organization: { 'organization-profile': dynamic(() => import('../stories/organization-profile.mdx')), diff --git a/packages/swingset/src/lib/registry.ts b/packages/swingset/src/lib/registry.ts index 1cb1c1f010c..e35441d35c6 100644 --- a/packages/swingset/src/lib/registry.ts +++ b/packages/swingset/src/lib/registry.ts @@ -137,6 +137,10 @@ import { Default as UserProfileAccountSectionDefault, meta as userProfileAccountSectionMeta, } from '../stories/user-profile-account-section.stories'; +import { + Default as UserProfileConnectedAccountsSectionDefault, + meta as userProfileConnectedAccountsSectionMeta, +} from '../stories/user-profile-connected-accounts-section.stories'; import { toSlug } from './slug'; import type { StoryModule } from './types'; @@ -285,11 +289,16 @@ const userProfileAccountSectionModule: StoryModule = { meta: userProfileAccountSectionMeta, Default: UserProfileAccountSectionDefault, }; +const userProfileConnectedAccountsSectionModule: StoryModule = { + meta: userProfileConnectedAccountsSectionMeta, + Default: UserProfileConnectedAccountsSectionDefault, +}; export const registry: StoryModule[] = [ // User userButtonModule, userProfileAccountSectionModule, + userProfileConnectedAccountsSectionModule, // Organization organizationProfileModule, organizationProfileGeneralPanelModule, diff --git a/packages/swingset/src/stories/user-profile-connected-accounts-section.mdx b/packages/swingset/src/stories/user-profile-connected-accounts-section.mdx new file mode 100644 index 00000000000..e4a451a3584 --- /dev/null +++ b/packages/swingset/src/stories/user-profile-connected-accounts-section.mdx @@ -0,0 +1,7 @@ +import * as Stories from './user-profile-connected-accounts-section.stories'; + +# UserProfileConnectedAccountsSection + +Connected and available external identity providers with provider-specific actions. + + diff --git a/packages/swingset/src/stories/user-profile-connected-accounts-section.stories.tsx b/packages/swingset/src/stories/user-profile-connected-accounts-section.stories.tsx new file mode 100644 index 00000000000..f2ed497579f --- /dev/null +++ b/packages/swingset/src/stories/user-profile-connected-accounts-section.stories.tsx @@ -0,0 +1,37 @@ +/** @jsxImportSource @emotion/react */ +import { UserProfileConnectedAccountsSectionView } from '@clerk/ui/mosaic/user-profile/user-profile-connected-accounts-section.view'; + +import type { StoryMeta } from '@/lib/types'; + +export { default as __source } from './user-profile-connected-accounts-section.stories?raw'; + +export const meta: StoryMeta = { + group: 'User', + title: 'UserProfileConnectedAccountsSection', + source: 'packages/ui/src/mosaic/user-profile/user-profile-connected-accounts-section.view.tsx', + styleEngine: 'stylex', +}; + +export function Default() { + return ( + undefined} + onManage={() => undefined} + /> + ); +} diff --git a/packages/ui/src/mosaic/icons/registry.tsx b/packages/ui/src/mosaic/icons/registry.tsx index 763d4562237..b5f27bc2175 100644 --- a/packages/ui/src/mosaic/icons/registry.tsx +++ b/packages/ui/src/mosaic/icons/registry.tsx @@ -94,6 +94,13 @@ const Plus = glyph( />, ); +const ArrowRightTop = glyph( + , +); + const Pen = glyph( void; + onManage?: (id: string) => void; + onRemove?: (id: string) => void; +} + +export function UserProfileConnectedAccountsSectionView({ + accounts, + onConnect, + onManage, + onRemove, +}: UserProfileConnectedAccountsSectionViewProps) { + return ( + + Connected accounts + + {accounts.map(account => { + const connected = account.connected ?? Boolean(account.identifier); + const actions: UserProfileMenuAction[] = []; + if (onRemove && account.canRemove !== false) { + actions.push({ label: 'Remove', color: 'negative', onClick: () => onRemove(account.id) }); + } else if (!onRemove && onManage) { + actions.push({ label: 'Manage', onClick: () => onManage(account.id) }); + } + + return ( + + {account.iconUrl ? ( + + + + ) : null} + {account.provider} + + {connected ? ( + + ) : null} + {!connected && onConnect ? ( + + ) : null} + + + ); + })} + + + ); +}