Skip to content

Commit 74ba288

Browse files
feat(ui): add connected accounts section
1 parent 97f411e commit 74ba288

7 files changed

Lines changed: 168 additions & 4 deletions
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
---
2+
---

packages/swingset/src/components/DocsViewer.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ const docModules: Record<string, Record<string, React.ComponentType>> = {
1313
user: {
1414
'user-button': dynamic(() => import('../stories/user-button.mdx')),
1515
'user-profile-account-section': dynamic(() => import('../stories/user-profile-account-section.mdx')),
16+
'user-profile-connected-accounts-section': dynamic(
17+
() => import('../stories/user-profile-connected-accounts-section.mdx'),
18+
),
1619
},
1720
components: {
1821
avatar: dynamic(() => import('../stories/avatar.mdx')),

packages/swingset/src/lib/registry.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,10 @@ import {
100100
Default as UserProfileAccountSectionDefault,
101101
meta as userProfileAccountSectionMeta,
102102
} from '../stories/user-profile-account-section.stories';
103+
import {
104+
Default as UserProfileConnectedAccountsSectionDefault,
105+
meta as userProfileConnectedAccountsSectionMeta,
106+
} from '../stories/user-profile-connected-accounts-section.stories';
103107
import { toSlug } from './slug';
104108
import type { StoryModule } from './types';
105109

@@ -212,11 +216,16 @@ const userProfileAccountSectionModule: StoryModule = {
212216
meta: userProfileAccountSectionMeta,
213217
Default: UserProfileAccountSectionDefault,
214218
};
219+
const userProfileConnectedAccountsSectionModule: StoryModule = {
220+
meta: userProfileConnectedAccountsSectionMeta,
221+
Default: UserProfileConnectedAccountsSectionDefault,
222+
};
215223

216224
export const registry: StoryModule[] = [
217225
// User
218226
userButtonModule,
219227
userProfileAccountSectionModule,
228+
userProfileConnectedAccountsSectionModule,
220229
// Components
221230
avatarModule,
222231
badgeModule,
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import * as Stories from './user-profile-connected-accounts-section.stories';
2+
3+
# UserProfileConnectedAccountsSection
4+
5+
Connected and available external identity providers with provider-specific actions.
6+
7+
<Story
8+
name='Default'
9+
storyModule={Stories}
10+
composition={[
11+
{ name: 'Section', href: '/components/section', layer: 'Components' },
12+
{ name: 'Button', href: '/components/button', layer: 'Components' },
13+
{ name: 'Icon', href: '/components/icon', layer: 'Components' },
14+
]}
15+
/>
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { UserProfileConnectedAccountsSectionView } from '@clerk/ui/mosaic/user-profile/user-profile-connected-accounts-section.view';
2+
3+
import type { StoryMeta } from '@/lib/types';
4+
5+
export { default as __source } from './user-profile-connected-accounts-section.stories?raw';
6+
7+
export const meta: StoryMeta = {
8+
group: 'User',
9+
title: 'UserProfileConnectedAccountsSection',
10+
source: 'packages/ui/src/mosaic/user-profile/user-profile-connected-accounts-section.view.tsx',
11+
};
12+
13+
export function Default() {
14+
return (
15+
<UserProfileConnectedAccountsSectionView
16+
accounts={[
17+
{
18+
id: 'google',
19+
provider: 'Google',
20+
identifier: 'test@google.com',
21+
iconUrl: 'https://img.clerk.com/static/google.svg',
22+
connected: true,
23+
},
24+
{
25+
id: 'apple',
26+
provider: 'Apple',
27+
iconUrl: 'https://img.clerk.com/static/apple.svg',
28+
connected: false,
29+
},
30+
]}
31+
onConnect={() => undefined}
32+
onManage={() => undefined}
33+
/>
34+
);
35+
}
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import * as stylex from '@stylexjs/stylex';
2+
3+
import { Button } from '../components/button';
4+
import { Icon } from '../components/icon';
5+
import { Section } from '../components/section';
6+
import type { UserProfileMenuAction } from './user-profile-action-menu';
7+
import { UserProfileActionMenu } from './user-profile-action-menu';
8+
import { styles } from './user-profile-profile-panel.styles';
9+
10+
export interface UserProfileConnectedAccount {
11+
id: string;
12+
provider: string;
13+
identifier?: string;
14+
iconUrl?: string;
15+
connected?: boolean;
16+
canRemove?: boolean;
17+
}
18+
19+
export interface UserProfileConnectedAccountsSectionViewProps {
20+
accounts: UserProfileConnectedAccount[];
21+
onConnect?: (id: string) => void;
22+
onManage?: (id: string) => void;
23+
onRemove?: (id: string) => void;
24+
}
25+
26+
export function UserProfileConnectedAccountsSectionView({
27+
accounts,
28+
onConnect,
29+
onManage,
30+
onRemove,
31+
}: UserProfileConnectedAccountsSectionViewProps) {
32+
return (
33+
<Section.Root>
34+
<Section.Title>Connected accounts</Section.Title>
35+
<Section.Group>
36+
{accounts.map(account => {
37+
const connected = account.connected ?? Boolean(account.identifier);
38+
const actions: UserProfileMenuAction[] = [];
39+
if (onRemove && account.canRemove !== false) {
40+
actions.push({ label: 'Remove', color: 'negative', onClick: () => onRemove(account.id) });
41+
} else if (!onRemove && onManage) {
42+
actions.push({ label: 'Manage', onClick: () => onManage(account.id) });
43+
}
44+
45+
return (
46+
<Section.Row key={account.id}>
47+
<Section.Item>
48+
{account.iconUrl ? (
49+
<Section.Media
50+
size='xl'
51+
{...stylex.props(styles.providerMedia)}
52+
>
53+
<img
54+
alt=''
55+
src={account.iconUrl}
56+
{...stylex.props(styles.providerIcon)}
57+
/>
58+
</Section.Media>
59+
) : null}
60+
<Section.Content>
61+
<Section.Label>{account.provider}</Section.Label>
62+
{account.identifier ? <Section.Description>{account.identifier}</Section.Description> : null}
63+
</Section.Content>
64+
<Section.Actions>
65+
{connected ? (
66+
<UserProfileActionMenu
67+
actions={actions}
68+
label={`Manage ${account.provider}`}
69+
/>
70+
) : null}
71+
{!connected && onConnect ? (
72+
<Button
73+
color='neutral'
74+
size='sm'
75+
variant='outline'
76+
onClick={() => onConnect(account.id)}
77+
>
78+
Connect
79+
<Icon
80+
name='arrow-right-top'
81+
placement='inline-end'
82+
size='sm'
83+
/>
84+
</Button>
85+
) : null}
86+
</Section.Actions>
87+
</Section.Item>
88+
</Section.Row>
89+
);
90+
})}
91+
</Section.Group>
92+
</Section.Root>
93+
);
94+
}

packages/ui/src/mosaic/user-profile/user-profile-profile-panel.styles.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,17 @@ export const styles = stylex.create({
99
display: 'flex',
1010
minWidth: 0,
1111
},
12+
providerMedia: {
13+
backgroundColor: 'var(--cl-color-background)',
14+
borderColor: 'light-dark(var(--cl-color-border-faded), var(--cl-color-background))',
15+
borderRadius: 'var(--cl-radius-lg)',
16+
borderStyle: 'solid',
17+
borderWidth: '1px',
18+
},
1219
providerIcon: {
13-
flexShrink: 0,
14-
objectFit: 'contain',
15-
height: space['6'],
16-
width: space['6'],
20+
display: 'block',
21+
height: '20px',
22+
width: '20px',
1723
},
1824
root: {
1925
gap: space['6'],

0 commit comments

Comments
 (0)