Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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/user-profile-connected-accounts-section.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
---
---
3 changes: 3 additions & 0 deletions packages/swingset/src/components/DocsViewer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ const docModules: Record<string, Record<string, React.ComponentType>> = {
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')),
Expand Down
9 changes: 9 additions & 0 deletions packages/swingset/src/lib/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
@@ -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.

<Story name='Default' storyModule={Stories} composition={[{ name: 'SettingsGroup', href: '/blocks/settings-group', layer: 'Blocks' }]} />
Original file line number Diff line number Diff line change
@@ -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 (
<UserProfileConnectedAccountsSectionView
accounts={[
{
id: 'google',
provider: 'Google',
identifier: 'test@google.com',
iconUrl: 'https://img.clerk.com/static/google.svg',
connected: true,
},
{
id: 'apple',
provider: 'Apple',
iconUrl: 'https://img.clerk.com/static/apple.svg',
connected: false,
},
]}
onConnect={() => undefined}
onManage={() => undefined}
/>
);
}
8 changes: 8 additions & 0 deletions packages/ui/src/mosaic/icons/registry.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,13 @@ const Plus = glyph(
/>,
);

const ArrowRightTop = glyph(
<path
d='M6.35014 5.40727L10.8285 5.17157M10.8285 5.17157L10.5928 9.64991M10.8285 5.17157L5.17163 10.8284'
{...strokeProps}
/>,
);

const Pen = glyph(
<path
clipRule='evenodd'
Expand Down Expand Up @@ -143,6 +150,7 @@ const AlertCircle = glyph(
/** Runtime name → glyph map. `Icon`'s `name` prop is typed from these keys. */
export const iconRegistry = {
'alert-circle': AlertCircle,
'arrow-right-top': ArrowRightTop,
'chevron-right': ChevronRight,
'chevron-left': ChevronLeft,
'chevron-down': ChevronDown,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import * as stylex from '@stylexjs/stylex';

import { SettingsGroup } from '../block/settings-group';
import { Button } from '../components/button';
import { Icon } from '../components/icon';
import { space } from '../tokens.stylex';
import type { UserProfileMenuAction } from './user-profile-action-menu';
import { UserProfileActionMenu } from './user-profile-action-menu';
import { styles } from './user-profile-profile-panel.styles';

export interface UserProfileConnectedAccount {
id: string;
provider: string;
identifier?: string;
iconUrl?: string;
connected?: boolean;
canRemove?: boolean;
}

export interface UserProfileConnectedAccountsSectionViewProps {
accounts: UserProfileConnectedAccount[];
onConnect?: (id: string) => void;
onManage?: (id: string) => void;
onRemove?: (id: string) => void;
}

export function UserProfileConnectedAccountsSectionView({
accounts,
onConnect,
onManage,
onRemove,
}: UserProfileConnectedAccountsSectionViewProps) {
return (
<SettingsGroup.Root>
<SettingsGroup.Title>Connected accounts</SettingsGroup.Title>
<SettingsGroup.List>
{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 (
<SettingsGroup.Row
key={account.id}
style={{ columnGap: space['3'] }}
>
{account.iconUrl ? (
<SettingsGroup.Media>
<img
alt=''
src={account.iconUrl}
{...stylex.props(styles.providerIcon)}
/>
</SettingsGroup.Media>
) : null}
<SettingsGroup.Label description={account.identifier}>{account.provider}</SettingsGroup.Label>
<SettingsGroup.Control>
{connected ? (
<UserProfileActionMenu
actions={actions}
label={`Manage ${account.provider}`}
/>
) : null}
{!connected && onConnect ? (
<Button
color='neutral'
size='sm'
variant='outline'
onClick={() => onConnect(account.id)}
>
Connect
<Icon
name='arrow-right-top'
placement='inline-end'
size='sm'
/>
</Button>
) : null}
</SettingsGroup.Control>
</SettingsGroup.Row>
);
})}
</SettingsGroup.List>
</SettingsGroup.Root>
);
}
Loading