Skip to content

Commit b7d19d3

Browse files
committed
feat(select-idp): add LinkedIn and Microsoft icon components
feat(select-idp): add LinkedIn and Microsoft theme button classes feat(select-idp): add LinkedIn and Microsoft provider branches chore(select-idp): add LinkedIn and Microsoft entries to mock fixtures feat(select-idp): add LinkedIn and Microsoft Storybook stories test(select-idp): add LinkedIn and Microsoft E2E social redirect tests chore(release): add changeset for LinkedIn and Microsoft IDP support test(select-idp): drop LinkedIn/Microsoft E2E pending AM journeys fix(select-idp): use official Microsoft brand colors for icon fix(select-idp): align Microsoft button bg/border with brand guidelines
1 parent f503f45 commit b7d19d3

7 files changed

Lines changed: 304 additions & 2 deletions

File tree

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
'@forgerock/login-widget': minor
3+
---
4+
5+
Add LinkedIn and Microsoft Entra ID as OOTB identity providers in the SelectIdP callback.
6+
7+
- LinkedIn: matches `buttonDisplayName` containing `'LinkedIn'`, renders branded button with `#0077b5` background and the LinkedIn "in" logo.
8+
- Microsoft/Entra ID: matches `buttonDisplayName` containing `'Microsoft'`, renders branded button with `#0072c6` background and the 4-color Microsoft squares logo.
9+
- Dark mode variants for both providers follow the existing pattern (white background, brand-color text).
10+
- Unknown providers continue to render nothing — no change to existing fallback behavior.
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<!--
2+
3+
Copyright © 2026 Ping Identity Corporation. All right reserved.
4+
5+
This software may be modified and distributed under the terms
6+
of the MIT license. See the LICENSE file for details.
7+
8+
-->
9+
10+
<script lang="ts">
11+
export let classes = '';
12+
export let size = '24px';
13+
</script>
14+
15+
<svg
16+
xmlns="http://www.w3.org/2000/svg"
17+
width={size}
18+
height={size}
19+
fill="currentColor"
20+
class={classes}
21+
viewBox="0 0 24 24"
22+
>
23+
<path
24+
d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433a2.062 2.062 0 0 1-2.063-2.065 2.064 2.064 0 1 1 2.063 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z"
25+
/>
26+
</svg>
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<!--
2+
3+
Copyright © 2026 Ping Identity Corporation. All right reserved.
4+
5+
This software may be modified and distributed under the terms
6+
of the MIT license. See the LICENSE file for details.
7+
8+
-->
9+
10+
<script lang="ts">
11+
export let classes = '';
12+
export let size = '24px';
13+
</script>
14+
15+
<svg
16+
xmlns="http://www.w3.org/2000/svg"
17+
width={size}
18+
height={size}
19+
viewBox="0 0 21 21"
20+
class={classes}
21+
>
22+
<path fill="#f25022" d="M1 1h9v9H1z" />
23+
<path fill="#7fba00" d="M11 1h9v9H11z" />
24+
<path fill="#00a4ef" d="M1 11h9v9H1z" />
25+
<path fill="#ffb900" d="M11 11h9v9H11z" />
26+
</svg>

core/journey/callbacks/select-idp/select-idp.mock.ts

Lines changed: 159 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/**
22
*
3-
* Copyright © 2025 Ping Identity Corporation. All right reserved.
3+
* Copyright © 2025-2026 Ping Identity Corporation. All right reserved.
44
*
55
* This software may be modified and distributed under the terms
66
* of the MIT license. See the LICENSE file for details.
@@ -60,6 +60,36 @@ export const multipleProvidersLocalAuthFormStep = {
6060
iconBackground: '#4184f3',
6161
},
6262
},
63+
{
64+
provider: 'linkedin-web',
65+
uiConfig: {
66+
buttonCustomStyle:
67+
'background-color: #0077b5; color: white; border-color: #0077b5;',
68+
buttonImage: '',
69+
buttonClass: '',
70+
buttonCustomStyleHover:
71+
'background-color: #006097; color: white; border-color: #006097;',
72+
buttonDisplayName: 'LinkedIn',
73+
iconClass: '',
74+
iconFontColor: 'white',
75+
iconBackground: '#0077b5',
76+
},
77+
},
78+
{
79+
provider: 'microsoft-web',
80+
uiConfig: {
81+
buttonCustomStyle:
82+
'background-color: #0072c6; color: white; border-color: #0072c6;',
83+
buttonImage: '',
84+
buttonClass: '',
85+
buttonCustomStyleHover:
86+
'background-color: #005a9e; color: white; border-color: #005a9e;',
87+
buttonDisplayName: 'Microsoft',
88+
iconClass: '',
89+
iconFontColor: 'white',
90+
iconBackground: '#0072c6',
91+
},
92+
},
6393
{ provider: 'localAuthentication' },
6494
],
6595
},
@@ -136,6 +166,36 @@ export const multipleProvidersLocalAuthNoFormStep = {
136166
iconBackground: '#4184f3',
137167
},
138168
},
169+
{
170+
provider: 'linkedin-web',
171+
uiConfig: {
172+
buttonCustomStyle:
173+
'background-color: #0077b5; color: white; border-color: #0077b5;',
174+
buttonImage: '',
175+
buttonClass: '',
176+
buttonCustomStyleHover:
177+
'background-color: #006097; color: white; border-color: #006097;',
178+
buttonDisplayName: 'LinkedIn',
179+
iconClass: '',
180+
iconFontColor: 'white',
181+
iconBackground: '#0077b5',
182+
},
183+
},
184+
{
185+
provider: 'microsoft-web',
186+
uiConfig: {
187+
buttonCustomStyle:
188+
'background-color: #0072c6; color: white; border-color: #0072c6;',
189+
buttonImage: '',
190+
buttonClass: '',
191+
buttonCustomStyleHover:
192+
'background-color: #005a9e; color: white; border-color: #005a9e;',
193+
buttonDisplayName: 'Microsoft',
194+
iconClass: '',
195+
iconFontColor: 'white',
196+
iconBackground: '#0072c6',
197+
},
198+
},
139199
{ provider: 'localAuthentication' },
140200
],
141201
},
@@ -200,6 +260,36 @@ export const multipleProvidersNoLocalAuthStep = {
200260
iconBackground: '#4184f3',
201261
},
202262
},
263+
{
264+
provider: 'linkedin-web',
265+
uiConfig: {
266+
buttonCustomStyle:
267+
'background-color: #0077b5; color: white; border-color: #0077b5;',
268+
buttonImage: '',
269+
buttonClass: '',
270+
buttonCustomStyleHover:
271+
'background-color: #006097; color: white; border-color: #006097;',
272+
buttonDisplayName: 'LinkedIn',
273+
iconClass: '',
274+
iconFontColor: 'white',
275+
iconBackground: '#0077b5',
276+
},
277+
},
278+
{
279+
provider: 'microsoft-web',
280+
uiConfig: {
281+
buttonCustomStyle:
282+
'background-color: #0072c6; color: white; border-color: #0072c6;',
283+
buttonImage: '',
284+
buttonClass: '',
285+
buttonCustomStyleHover:
286+
'background-color: #005a9e; color: white; border-color: #005a9e;',
287+
buttonDisplayName: 'Microsoft',
288+
iconClass: '',
289+
iconFontColor: 'white',
290+
iconBackground: '#0072c6',
291+
},
292+
},
203293
],
204294
},
205295
{ name: 'value', value: '' },
@@ -327,6 +417,74 @@ export const singleProviderNoLocalAuthStep = {
327417
],
328418
};
329419

420+
export const singleLinkedInProviderStep = {
421+
authId:
422+
'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhdXRoSW5kZXhWYWx1ZSI6IlNvY2lhbExvZ2luLXdlYiIsIm90ayI6ImFpMzZ2NHZwZ243c2JnZGo1YTN2aHZiM3V0IiwiYXV0aEluZGV4VHlwZSI6InNlcnZpY2UiLCJyZWFsbSI6Ii9hbHBoYSIsInNlc3Npb25JZCI6IipBQUpUU1FBQ01ESUFCSFI1Y0dVQUNFcFhWRjlCVlZSSUFBSlRNUUFDTURFLipleUowZVhBaU9pSktWMVFpTENKamRIa2lPaUpLVjFRaUxDSmhiR2NpT2lKSVV6STFOaUo5LlpYbEtNR1ZZUVdsUGFVcExWakZSYVV4RFNteGliVTFwVDJsS1FrMVVTVFJSTUVwRVRGVm9WRTFxVlRKSmFYZHBXVmQ0YmtscWIybGFSMng1U1c0d0xpNW5hRmRCWlVweVRtOTNYekZFY1RJNU4wUkhka2gzTGxJemVsWlJiblJWVkZFMVozWmFjVVZJTkdVMWJUTlFiRUZOVjNwU1RWZzNTV0ZVYld4c1RqRlpjakZQY0U5T1EySlViVEEyTFRsVlQwbDBNbEpQY0RsV2NXWlNUM2xtYWtSWldGWnhTV0l4UVZGdlpreFFRamhPVG10ak1Xb3pMVXBhUVVkNFlrRlRXRTR5U1daaVdFSlVOVlp3Y2xCMWVHMDNOMGcwV2tKSlNYcHRVek5mWVU1clJtRkNSbkJEYzNOUWNteERWM1F3YWxVNVMxTlFRVUpFUkhkRVRFbFRiMnBET1hCQ1NGbzVTRUZxTWxsUlRVUmFialk1VWpORE0xYzBNekZCYTB4d1ZWTmpSSFZEUkdacFEzVm5hVTlDYzJaemR6Tk1OWGt3TVhoZk5rUkVTMnBwT0ZWSGRIVkZWa3B4VGxGcVVUaExWMEZGZFZWSFNVbEthVkZIWjFOa2F6bHRXR1F3ZFhJeU5GcFFNV2hZVkROWVIxWnlaMmc0U2pGU2RUVlZaWFk0WjBobk5FTTNjR2R2ZEVoUFIyVnBWMk5QUTBoSlpXSndTalJWWm5odU1XVnVkVEpaVjFaSFVWTTFiMFo1WVRGalZWSlBPVTA0TUdadVZFTTJZVGh2YkV4c09XVkhZbFE1TkUxVFJYZG5NVk5QTFc1MGJsaHliR1J6TWxvMmVXMXlhR2hxTlc4MGRucEhTV1JvWDIxMU5rVkROMUpMWTFReFlXZGtkRXRUWDBZdFFXaHBZMVpCUWtGc1JrdDZTWGxSTFRRdExUSmpRMDFXWVRoRFRVbHlVV0U1YXpjMlRrWk9Sak5IY2twaGJIZFpiVzlrUnpGc0xWRnRRVmQyZVZrelRVSnJRa1l4UmxKMmFuaDZMVkZHZUd4d1J6SlJOR1p6VkZSVkxYVjFhakl5VWpneWJqQkhhVnBVZDFkbVZVbE9WV2x0VUdWSWVrSnBZbEU1TjJ4dlYwaHZkMUoxVEV3NE5rUjZOVTFVZDB0eFNUQTJNak42V0UweVJuVkJTRUp0V1ROc1NHdHhkWEptWnpOTmRrUjRZVE5mVTNFMmRXZEdPWG8wU2xCVVp6UTRWWE5rUWtwdVduZEdlRlF4TXpoTFdGbFphMUV5Y0dvdU4wWkhRa2QwZG5STE1FRlVibWxHY1ZacVIwTlJady5xa1NtUTM1X253X0lCUU11RlRueW5NMTN1NmFoTGR2bkVYRVhSaTNFSFBjIiwiZXhwIjoxNjY5OTc3OTg1LCJpYXQiOjE2Njk5Nzc2ODV9.iV-WX2dk_8oTug-zlou7SvL29CwfaCSlzR5N0huRsxc',
423+
callbacks: [
424+
{
425+
type: 'SelectIdPCallback',
426+
output: [
427+
{
428+
name: 'providers',
429+
value: [
430+
{
431+
provider: 'linkedin-web',
432+
uiConfig: {
433+
buttonCustomStyle:
434+
'background-color: #0077b5; color: white; border-color: #0077b5;',
435+
buttonImage: '',
436+
buttonClass: '',
437+
buttonCustomStyleHover:
438+
'background-color: #006097; color: white; border-color: #006097;',
439+
buttonDisplayName: 'LinkedIn',
440+
iconClass: '',
441+
iconFontColor: 'white',
442+
iconBackground: '#0077b5',
443+
},
444+
},
445+
],
446+
},
447+
{ name: 'value', value: '' },
448+
],
449+
input: [{ name: 'IDToken1', value: '' }],
450+
},
451+
],
452+
};
453+
454+
export const singleMicrosoftProviderStep = {
455+
authId:
456+
'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhdXRoSW5kZXhWYWx1ZSI6IlNvY2lhbExvZ2luLXdlYiIsIm90ayI6ImFpMzZ2NHZwZ243c2JnZGo1YTN2aHZiM3V0IiwiYXV0aEluZGV4VHlwZSI6InNlcnZpY2UiLCJyZWFsbSI6Ii9hbHBoYSIsInNlc3Npb25JZCI6IipBQUpUU1FBQ01ESUFCSFI1Y0dVQUNFcFhWRjlCVlZSSUFBSlRNUUFDTURFLipleUowZVhBaU9pSktWMVFpTENKamRIa2lPaUpLVjFRaUxDSmhiR2NpT2lKSVV6STFOaUo5LlpYbEtNR1ZZUVdsUGFVcExWakZSYVV4RFNteGliVTFwVDJsS1FrMVVTVFJSTUVwRVRGVm9WRTFxVlRKSmFYZHBXVmQ0YmtscWIybGFSMng1U1c0d0xpNW5hRmRCWlVweVRtOTNYekZFY1RJNU4wUkhka2gzTGxJemVsWlJiblJWVkZFMVozWmFjVVZJTkdVMWJUTlFiRUZOVjNwU1RWZzNTV0ZVYld4c1RqRlpjakZQY0U5T1EySlViVEEyTFRsVlQwbDBNbEpQY0RsV2NXWlNUM2xtYWtSWldGWnhTV0l4UVZGdlpreFFRamhPVG10ak1Xb3pMVXBhUVVkNFlrRlRXRTR5U1daaVdFSlVOVlp3Y2xCMWVHMDNOMGcwV2tKSlNYcHRVek5mWVU1clJtRkNSbkJEYzNOUWNteERWM1F3YWxVNVMxTlFRVUpFUkhkRVRFbFRiMnBET1hCQ1NGbzVTRUZxTWxsUlRVUmFialk1VWpORE0xYzBNekZCYTB4d1ZWTmpSSFZEUkdacFEzVm5hVTlDYzJaemR6Tk1OWGt3TVhoZk5rUkVTMnBwT0ZWSGRIVkZWa3B4VGxGcVVUaExWMEZGZFZWSFNVbEthVkZIWjFOa2F6bHRXR1F3ZFhJeU5GcFFNV2hZVkROWVIxWnlaMmc0U2pGU2RUVlZaWFk0WjBobk5FTTNjR2R2ZEVoUFIyVnBWMk5QUTBoSlpXSndTalJWWm5odU1XVnVkVEpaVjFaSFVWTTFiMFo1WVRGalZWSlBPVTA0TUdadVZFTTJZVGh2YkV4c09XVkhZbFE1TkUxVFJYZG5NVk5QTFc1MGJsaHliR1J6TWxvMmVXMXlhR2hxTlc4MGRucEhTV1JvWDIxMU5rVkROMUpMWTFReFlXZGtkRXRUWDBZdFFXaHBZMVpCUWtGc1JrdDZTWGxSTFRRdExUSmpRMDFXWVRoRFRVbHlVV0U1YXpjMlRrWk9Sak5IY2twaGJIZFpiVzlrUnpGc0xWRnRRVmQyZVZrelRVSnJRa1l4UmxKMmFuaDZMVkZHZUd4d1J6SlJOR1p6VkZSVkxYVjFhakl5VWpneWJqQkhhVnBVZDFkbVZVbE9WV2x0VUdWSWVrSnBZbEU1TjJ4dlYwaHZkMUoxVEV3NE5rUjZOVTFVZDB0eFNUQTJNak42V0UweVJuVkJTRUp0V1ROc1NHdHhkWEptWnpOTmRrUjRZVE5mVTNFMmRXZEdPWG8wU2xCVVp6UTRWWE5rUWtwdVduZEdlRlF4TXpoTFdGbFphMUV5Y0dvdU4wWkhRa2QwZG5STE1FRlVibWxHY1ZacVIwTlJady5xa1NtUTM1X253X0lCUU11RlRueW5NMTN1NmFoTGR2bkVYRVhSaTNFSFBjIiwiZXhwIjoxNjY5OTc3OTg1LCJpYXQiOjE2Njk5Nzc2ODV9.iV-WX2dk_8oTug-zlou7SvL29CwfaCSlzR5N0huRsxc',
457+
callbacks: [
458+
{
459+
type: 'SelectIdPCallback',
460+
output: [
461+
{
462+
name: 'providers',
463+
value: [
464+
{
465+
provider: 'microsoft-web',
466+
uiConfig: {
467+
buttonCustomStyle:
468+
'background-color: #0072c6; color: white; border-color: #0072c6;',
469+
buttonImage: '',
470+
buttonClass: '',
471+
buttonCustomStyleHover:
472+
'background-color: #005a9e; color: white; border-color: #005a9e;',
473+
buttonDisplayName: 'Microsoft',
474+
iconClass: '',
475+
iconFontColor: 'white',
476+
iconBackground: '#0072c6',
477+
},
478+
},
479+
],
480+
},
481+
{ name: 'value', value: '' },
482+
],
483+
input: [{ name: 'IDToken1', value: '' }],
484+
},
485+
],
486+
};
487+
330488
export const docsExample = {
331489
type: 'SelectIdPCallback',
332490
output: [

core/journey/callbacks/select-idp/select-idp.stories.js

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,16 @@
1010
import { callbackType } from '@forgerock/journey-client';
1111

1212
import { createJourneyStep } from '$journey/_utilities/step.mock';
13-
import { singleProviderNoLocalAuthStep } from './select-idp.mock';
13+
import {
14+
singleLinkedInProviderStep,
15+
singleMicrosoftProviderStep,
16+
singleProviderNoLocalAuthStep,
17+
} from './select-idp.mock';
1418
import Input from './select-idp.story.svelte';
1519

1620
const singleProviderNoLocalAuth = createJourneyStep(singleProviderNoLocalAuthStep);
21+
const singleLinkedInProvider = createJourneyStep(singleLinkedInProviderStep);
22+
const singleMicrosoftProvider = createJourneyStep(singleMicrosoftProviderStep);
1723

1824
export default {
1925
argTypes: {
@@ -32,3 +38,17 @@ export const Base = {
3238
localAuth: true,
3339
},
3440
};
41+
42+
export const LinkedIn = {
43+
args: {
44+
socialCallback: singleLinkedInProvider.getCallbackOfType(callbackType.SelectIdPCallback),
45+
localAuth: false,
46+
},
47+
};
48+
49+
export const Microsoft = {
50+
args: {
51+
socialCallback: singleMicrosoftProvider.getCallbackOfType(callbackType.SelectIdPCallback),
52+
localAuth: false,
53+
},
54+
};

core/journey/callbacks/select-idp/select-idp.svelte

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
import AppleIcon from '../../../components/icons/apple-icon.svelte';
1515
import FacebookIcon from '../../../components/icons/facebook-icon.svelte';
1616
import GoogleIcon from '../../../components/icons/google-icon.svelte';
17+
import LinkedinIcon from '../../../components/icons/linkedin-icon.svelte';
18+
import MicrosoftIcon from '../../../components/icons/microsoft-icon.svelte';
1719
1820
import type { SelectIdPCallback } from '@forgerock/journey-client/types';
1921
import type { z } from 'zod';
@@ -107,6 +109,28 @@
107109
<T key="continueWith" />
108110
{idp.text}
109111
</Button>
112+
{:else if idp.text.toUpperCase().includes('LINKEDIN')}
113+
<Button
114+
classes="tw_button-linkedin dark:tw_button-linkedin_dark"
115+
type="button"
116+
width="auto"
117+
onClick={() => setBtnValue(idp.value)}
118+
>
119+
<LinkedinIcon classes="tw_inline-block tw_fill-current" />
120+
<T key="continueWith" />
121+
{idp.text}
122+
</Button>
123+
{:else if idp.text.toUpperCase().includes('MICROSOFT')}
124+
<Button
125+
classes="tw_button-microsoft dark:tw_button-microsoft_dark"
126+
type="button"
127+
width="auto"
128+
onClick={() => setBtnValue(idp.value)}
129+
>
130+
<MicrosoftIcon classes="tw_inline-block tw_fill-current" />
131+
<T key="continueWith" />
132+
{idp.text}
133+
</Button>
110134
{/if}
111135
</Grid>
112136
{/each}

themes/default/journey.cjs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,44 @@ module.exports = function (theme) {
7171
opacity: `0.1`,
7272
},
7373
},
74+
'.button-linkedin': {
75+
'--border-color':
76+
'hsl(var(--tw-colors-secondary-light-hs),var(--tw-colors-secondary-light-l))',
77+
borderColor: `var(--border-color, ${theme('colors.secondary.light')})`,
78+
backgroundColor: '#0077b5',
79+
color: theme('colors.white'),
80+
'&:hover::before, &:focus::before': {
81+
opacity: `0.1`,
82+
},
83+
},
84+
'.button-linkedin_dark': {
85+
'--border-color':
86+
'hsl(var(--tw-colors-secondary-light-hs),var(--tw-colors-secondary-light-l))',
87+
borderColor: `var(--border-color, ${theme('colors.secondary.light')})`,
88+
backgroundColor: theme('colors.white'),
89+
color: '#0077b5',
90+
'&:hover::before, &:focus::before': {
91+
opacity: `0.1`,
92+
},
93+
},
94+
'.button-microsoft': {
95+
'--border-color': '#8C8C8C',
96+
borderColor: `var(--border-color, #8C8C8C)`,
97+
backgroundColor: theme('colors.white'),
98+
color: theme('colors.black'),
99+
'&:hover::before, &:focus::before': {
100+
opacity: `0.1`,
101+
},
102+
},
103+
'.button-microsoft_dark': {
104+
'--border-color': '#8C8C8C',
105+
borderColor: `var(--border-color, #8C8C8C)`,
106+
backgroundColor: theme('colors.white'),
107+
color: theme('colors.black'),
108+
'&:hover::before, &:focus::before': {
109+
opacity: `0.1`,
110+
},
111+
},
74112
'.input-policies': {
75113
'--font-size': 'var(--tw-font-size-sm-type)',
76114
fontSize: `var(--font-size, ${theme('fontSize.sm')})`,

0 commit comments

Comments
 (0)