Skip to content

Commit c337a2b

Browse files
feat(ui): Add support for <SignInChooseSession /> (clerk#3936)
1 parent 50639e5 commit c337a2b

3 files changed

Lines changed: 188 additions & 0 deletions

File tree

.changeset/spicy-icons-swim.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
---
2+
---
Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
import { useClerk } from '@clerk/clerk-react';
2+
import { cva } from 'cva';
3+
import { Button } from 'react-aria-components';
4+
5+
import { useAppearance } from '~/hooks/use-appearance';
6+
import { useDisplayConfig } from '~/hooks/use-display-config';
7+
import { useLocalizations } from '~/hooks/use-localizations';
8+
import * as Card from '~/primitives/card';
9+
import * as Icon from '~/primitives/icon';
10+
11+
function getInitials({
12+
firstName,
13+
lastName,
14+
identifier,
15+
}: {
16+
firstName?: string | null;
17+
lastName?: string | null;
18+
identifier?: string | null;
19+
}): string {
20+
let initials: string = '';
21+
if (firstName) {
22+
initials += firstName[0].toUpperCase();
23+
}
24+
if (lastName) {
25+
initials += lastName[0].toUpperCase();
26+
}
27+
if (!initials && identifier) {
28+
initials += identifier[0].toUpperCase();
29+
}
30+
return initials;
31+
}
32+
33+
function getTitleAndSubtitle({
34+
firstName,
35+
lastName,
36+
identifier,
37+
}: {
38+
firstName?: string | null;
39+
lastName?: string | null;
40+
identifier?: string | null;
41+
}) {
42+
let title = '';
43+
let subtitle = '';
44+
if (firstName || lastName) {
45+
title = `${firstName} ${lastName}`;
46+
subtitle = identifier || '';
47+
} else {
48+
title = identifier || '';
49+
}
50+
return { title, subtitle };
51+
}
52+
53+
const sessionAction = cva({
54+
base: 'text-start text-gray-11 hover:bg-gray-2 data-[focus-visible]:bg-gray-2 flex w-full items-center gap-x-3 px-10 py-4 text-base outline-none [--session-icon-opacity:0] data-[hovered]:[--session-icon-opacity:1]',
55+
});
56+
57+
export function SignInChooseSession() {
58+
const clerk = useClerk();
59+
const { t } = useLocalizations();
60+
const { layout } = useAppearance();
61+
// const renderDevModeWarning = useDevModeWarning();
62+
const { branded } = useDisplayConfig();
63+
64+
const cardFooterProps = {
65+
branded,
66+
helpPageUrl: layout?.helpPageUrl,
67+
privacyPageUrl: layout?.privacyPageUrl,
68+
termsPageUrl: layout?.termsPageUrl,
69+
};
70+
71+
const activeSessions = clerk.client.activeSessions;
72+
73+
return (
74+
<Card.Root>
75+
<Card.Content>
76+
<Card.Header>
77+
<Card.Title>{t('signIn.accountSwitcher.title')}</Card.Title>
78+
<Card.Description>{t('signIn.accountSwitcher.subtitle')}</Card.Description>
79+
</Card.Header>
80+
<Card.Body>
81+
<ul className='-mx-[--card-body-padding] -mb-[--card-body-padding] overflow-hidden rounded-b-[--card-content-rounded-b]'>
82+
{activeSessions?.map(session => {
83+
const { userId, identifier, firstName, lastName, hasImage, imageUrl } = session.publicUserData;
84+
const { title, subtitle } = getTitleAndSubtitle({ firstName, lastName, identifier });
85+
return (
86+
<li
87+
key={userId}
88+
className='border-gray-a4 border-t'
89+
>
90+
<Button
91+
type='button'
92+
className={sessionAction()}
93+
>
94+
<span className='bg-gray-2 border-gray-a4 relative grid size-9 shrink-0 place-content-center overflow-hidden rounded-full border'>
95+
{hasImage ? (
96+
<img
97+
src={imageUrl}
98+
className='absolute inset-0 object-cover'
99+
alt={`Avatar for ${title}`}
100+
/>
101+
) : (
102+
<span className='text-gray-11 text-base font-medium'>
103+
{getInitials({ firstName, lastName, identifier })}
104+
</span>
105+
)}
106+
</span>
107+
<span className='flex min-w-0 flex-1 flex-col'>
108+
{title ? <span className='text-gray-12 font-medium'>{title}</span> : null}
109+
{subtitle ? (
110+
<span className='text-gray-11 w-full overflow-hidden text-ellipsis'>{subtitle}</span>
111+
) : null}
112+
</span>
113+
<span className='text-gray-11 w-4 shrink-0 opacity-[--session-icon-opacity] transition-opacity'>
114+
<Icon.RightArrowSm />
115+
</span>
116+
</Button>
117+
</li>
118+
);
119+
})}
120+
<li className='border-gray-a4 border-t'>
121+
<Button
122+
type='button'
123+
className={sessionAction()}
124+
>
125+
<span className='grid size-9 place-content-center'>
126+
<svg
127+
viewBox='0 0 36 24'
128+
className='w-9'
129+
aria-hidden
130+
>
131+
<circle
132+
cx='18'
133+
cy='12'
134+
r='11.5'
135+
strokeDasharray='2 2'
136+
className='fill-gray-2 stroke-gray-7'
137+
/>
138+
<path
139+
d='M18.75 7.75a.75.75 0 1 0-1.5 0v3.5h-3.5a.75.75 0 1 0 0 1.5h3.5v3.5a.75.75 0 1 0 1.5 0v-3.5h3.5a.75.75 0 1 0 0-1.5h-3.5v-3.5Z'
140+
className='fill-gray-11'
141+
/>
142+
</svg>
143+
</span>
144+
{t('signIn.accountSwitcher.action__addAccount')}
145+
</Button>
146+
</li>
147+
</ul>
148+
</Card.Body>
149+
{/* {renderDevModeWarning ? <Card.Banner>Development mode</Card.Banner> : null} */}
150+
</Card.Content>
151+
<Card.Footer {...cardFooterProps}>
152+
<Card.FooterAction>
153+
<Button className='text-gray-11 hover:bg-gray-3 data-[focus-visible]:bg-gray-3 -my-1 flex w-full items-center gap-x-3 rounded-md px-4 py-1 text-base font-medium outline-none'>
154+
<span className='grid w-9 place-content-center'>
155+
<svg
156+
xmlns='http://www.w3.org/2000/svg'
157+
fill='none'
158+
viewBox='0 0 36 24'
159+
className='w-9 rtl:rotate-180'
160+
>
161+
<path
162+
fill='currentColor'
163+
fillRule='evenodd'
164+
d='M12.6 6.604A2.045 2.045 0 0 1 14.052 6h3.417c.544 0 1.066.217 1.45.604.385.387.601.911.601 1.458v.69c0 .413-.334.75-.746.75a.748.748 0 0 1-.745-.75v-.69a.564.564 0 0 0-.56-.562h-3.417a.558.558 0 0 0-.56.563v7.874a.564.564 0 0 0 .56.563h3.417a.558.558 0 0 0 .56-.563v-.671c0-.415.334-.75.745-.75.412 0 .746.335.746.75v.671c0 .548-.216 1.072-.6 1.459a2.046 2.046 0 0 1-1.45.604H14.05a2.045 2.045 0 0 1-1.45-.604A2.068 2.068 0 0 1 12 15.937V8.064c0-.548.216-1.072.6-1.459Zm8.386 3.116a.743.743 0 0 1 1.055 0l1.74 1.75a.753.753 0 0 1 0 1.06l-1.74 1.75a.743.743 0 0 1-1.055 0 .753.753 0 0 1 0-1.06l.467-.47h-5.595a.748.748 0 0 1-.746-.75c0-.414.334-.75.746-.75h5.595l-.467-.47a.753.753 0 0 1 0-1.06Z'
165+
clipRule='evenodd'
166+
/>
167+
</svg>
168+
</span>
169+
{t('signIn.accountSwitcher.action__signOutAll')}
170+
</Button>
171+
</Card.FooterAction>
172+
</Card.Footer>
173+
</Card.Root>
174+
);
175+
}

packages/ui/src/primitives/icon.tsx

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -760,3 +760,14 @@ export const SMSSm = createIcon({
760760
/>
761761
),
762762
});
763+
764+
export const RightArrowSm = createIcon({
765+
displayName: 'IconRightArrowSm',
766+
viewBox: '0 0 16 16',
767+
path: (
768+
<path
769+
d='M9.53033 4.94233C9.23744 4.64943 8.76256 4.64943 8.46967 4.94233C8.17678 5.23522 8.17678 5.71009 8.46967 6.00299L10.1893 7.72266H4C3.58579 7.72266 3.25 8.05844 3.25 8.47266C3.25 8.88687 3.58579 9.22266 4 9.22266H10.1893L8.46967 10.9423C8.17678 11.2352 8.17678 11.7101 8.46967 12.003C8.76256 12.2959 9.23744 12.2959 9.53033 12.003L12.5303 9.00299C12.8232 8.71009 12.8232 8.23522 12.5303 7.94233L9.53033 4.94233Z'
770+
fill='currentColor'
771+
/>
772+
),
773+
});

0 commit comments

Comments
 (0)