Skip to content

Commit cf5d0cd

Browse files
auto-resolve sso provider icons via simple icons (#8)
* auto-resolve sso provider icons via simple icons cdn * track errored slug to avoid stale fallback flash useEffect runs after paint, so when the provider prop changes the first render still sees errored=true and briefly shows the letter avatar. Tracking the errored slug directly makes the check happen during render. * Hybrid SSO icons: inline SVGs for top providers, Simple Icons CDN fallback Keep multicolor inline SVGs for Google, GitHub, GitLab, Microsoft, Facebook, and Apple. Fall back to Simple Icons CDN for any other provider, with a letter avatar as the final fallback. Co-authored-by: Cursor <cursoragent@cursor.com> * fix sso provider matching and stale error state --------- Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent d144cd4 commit cf5d0cd

3 files changed

Lines changed: 104 additions & 21 deletions

File tree

packages/managed-auth-react/src/components/icons.tsx

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,30 @@ export const GitHubMark = (p: IconProps) => (
227227
</svg>
228228
);
229229

230+
export const GitLabMark = (p: IconProps) => (
231+
<svg viewBox="0 0 24 24" aria-hidden="true" {...p}>
232+
<path d="M12 23.054l3.684-11.335H8.316L12 23.054z" fill="#E24329" />
233+
<path d="M12 23.054L8.316 11.719H1.647L12 23.054z" fill="#FC6D26" />
234+
<path
235+
d="M1.647 11.719L.253 16.01a.95.95 0 00.346 1.062L12 23.054 1.647 11.719z"
236+
fill="#FCA326"
237+
/>
238+
<path
239+
d="M1.647 11.719h6.669L5.633.926a.477.477 0 00-.908 0L1.647 11.719z"
240+
fill="#E24329"
241+
/>
242+
<path d="M12 23.054l3.684-11.335h6.669L12 23.054z" fill="#FC6D26" />
243+
<path
244+
d="M22.353 11.719l1.394 4.291a.95.95 0 01-.346 1.062L12 23.054l10.353-11.335z"
245+
fill="#FCA326"
246+
/>
247+
<path
248+
d="M22.353 11.719h-6.669l2.683-10.793a.477.477 0 01.908 0l3.078 10.793z"
249+
fill="#E24329"
250+
/>
251+
</svg>
252+
);
253+
230254
export const MicrosoftMark = (p: IconProps) => (
231255
<svg viewBox="0 0 24 24" aria-hidden="true" {...p}>
232256
<path d="M1 1h10.5v10.5H1V1z" fill="#F25022" />
Lines changed: 68 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
import type { ReactNode } from "react";
1+
import { useState, type ReactNode } from "react";
22
import {
33
AppleMark,
44
BuildingIcon,
55
FacebookMark,
66
GitHubMark,
7+
GitLabMark,
78
GoogleMark,
89
KeyIcon,
910
MicrosoftMark,
@@ -14,27 +15,73 @@ export interface SSOProviderInfo {
1415
icon: ReactNode;
1516
}
1617

18+
const BUILTIN_PROVIDERS: Record<
19+
string,
20+
{ label: string; Icon: (p: { className?: string }) => ReactNode }
21+
> = {
22+
google: { label: "Google", Icon: GoogleMark },
23+
github: { label: "GitHub", Icon: GitHubMark },
24+
gitlab: { label: "GitLab", Icon: GitLabMark },
25+
microsoft: { label: "Microsoft", Icon: MicrosoftMark },
26+
azure: { label: "Microsoft", Icon: MicrosoftMark },
27+
facebook: { label: "Facebook", Icon: FacebookMark },
28+
apple: { label: "Apple", Icon: AppleMark },
29+
passkey: { label: "Passkey", Icon: KeyIcon },
30+
sso: { label: "SSO", Icon: BuildingIcon },
31+
saml: { label: "SSO", Icon: BuildingIcon },
32+
};
33+
34+
function slugify(provider: string): string {
35+
return provider.toLowerCase().replace(/[^a-z0-9]/g, "");
36+
}
37+
38+
function titleCase(provider: string): string {
39+
return provider
40+
.split(/[\s_-]+/)
41+
.filter(Boolean)
42+
.map((w) => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase())
43+
.join(" ");
44+
}
45+
46+
function CDNProviderIcon({ provider }: { provider: string }) {
47+
const [erroredSlug, setErroredSlug] = useState<string | null>(null);
48+
const slug = slugify(provider);
49+
const letter = provider.trim().charAt(0).toUpperCase() || "?";
50+
51+
if (!slug || erroredSlug === slug) {
52+
return (
53+
<span className="kma-sso-icon kma-sso-icon--letter" aria-hidden="true">
54+
{letter}
55+
</span>
56+
);
57+
}
58+
59+
return (
60+
<img
61+
src={`https://cdn.simpleicons.org/${slug}`}
62+
alt=""
63+
className="kma-sso-icon"
64+
onError={() => setErroredSlug(slug)}
65+
/>
66+
);
67+
}
68+
1769
export function getSSOProviderInfo(provider: string): SSOProviderInfo {
18-
const p = provider.toLowerCase();
19-
if (p.includes("google"))
20-
return { label: "Google", icon: <GoogleMark className="kma-sso-icon" /> };
21-
if (p.includes("github"))
22-
return { label: "GitHub", icon: <GitHubMark className="kma-sso-icon" /> };
23-
if (p.includes("microsoft") || p.includes("azure"))
24-
return {
25-
label: "Microsoft",
26-
icon: <MicrosoftMark className="kma-sso-icon" />,
27-
};
28-
if (p.includes("facebook"))
70+
const slug = slugify(provider);
71+
72+
const builtinKey = Object.keys(BUILTIN_PROVIDERS).find((k) =>
73+
slug.includes(k),
74+
);
75+
if (builtinKey) {
76+
const builtin = BUILTIN_PROVIDERS[builtinKey];
2977
return {
30-
label: "Facebook",
31-
icon: <FacebookMark className="kma-sso-icon" />,
78+
label: builtin.label,
79+
icon: <builtin.Icon className="kma-sso-icon" />,
3280
};
33-
if (p.includes("apple"))
34-
return { label: "Apple", icon: <AppleMark className="kma-sso-icon" /> };
35-
if (p.includes("saml") || p.includes("sso"))
36-
return { label: "SSO", icon: <BuildingIcon className="kma-sso-icon" /> };
37-
if (p.includes("passkey"))
38-
return { label: "Passkey", icon: <KeyIcon className="kma-sso-icon" /> };
39-
return { label: provider, icon: null };
81+
}
82+
83+
return {
84+
label: titleCase(provider),
85+
icon: <CDNProviderIcon provider={provider} />,
86+
};
4087
}

packages/managed-auth-react/src/styles/styles.css

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -515,6 +515,18 @@
515515
height: 1.25rem;
516516
}
517517

518+
.kma-sso-icon--letter {
519+
display: inline-flex;
520+
align-items: center;
521+
justify-content: center;
522+
border-radius: 50%;
523+
background: var(--kma-color-muted, #6b7280);
524+
color: #fff;
525+
font-size: 0.75rem;
526+
font-weight: 600;
527+
line-height: 1;
528+
}
529+
518530
/* ---------- Form / Input ---------- */
519531

520532
.kma-form {

0 commit comments

Comments
 (0)