-
-
Notifications
You must be signed in to change notification settings - Fork 440
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(elements): add logto-social-identity element (#6736)
- Loading branch information
Showing
6 changed files
with
249 additions
and
0 deletions.
There are no files selected for viewing
88 changes: 88 additions & 0 deletions
88
packages/elements/src/account/components/logto-identity-info.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
import { css, html, LitElement } from 'lit'; | ||
import { customElement, property, state } from 'lit/decorators.js'; | ||
|
||
import fallbackAvatar from '../icons/fallback-avatar.svg'; | ||
|
||
const tagName = 'logto-identity-info'; | ||
|
||
@customElement(tagName) | ||
export class LogtoIdentityInfo extends LitElement { | ||
static tagName = tagName; | ||
|
||
static styles = css` | ||
:host { | ||
display: flex; | ||
align-items: center; | ||
gap: var(--logto-spacing-sm); | ||
} | ||
.avatar { | ||
--logto-icon-size: var(--logto-identity-info-avatar-size, 36px); | ||
> img { | ||
display: block; | ||
width: var(--logto-identity-info-avatar-size, 36px); | ||
height: var(--logto-identity-info-avatar-size, 36px); | ||
border-radius: var(--logto-identity-info-avatar-shape, var(--logto-shape-corner-md)); | ||
} | ||
} | ||
.info { | ||
flex: 1; | ||
flex-direction: column; | ||
.name { | ||
font: var(--logto-identity-info-name-font-size, var(--logto-font-body-md)); | ||
color: var( | ||
--logto-identity-info-name-color, | ||
var(--logto-color---logto-color-typeface-primary) | ||
); | ||
} | ||
.email { | ||
font: var(--logto-identity-info-email-font, var(--logto-font-body-sm)); | ||
color: var( | ||
--logto-identity-info-email-color, | ||
var(--logto-color---logto-color-typeface-primary) | ||
); | ||
} | ||
} | ||
`; | ||
|
||
@property({ type: String }) | ||
avatar = ''; | ||
|
||
@property({ type: String }) | ||
name = ''; | ||
|
||
@property({ type: String }) | ||
email = ''; | ||
|
||
@state() | ||
failedToLoadAvatar = false; | ||
|
||
render() { | ||
return html` | ||
<div class="avatar"> | ||
${this.avatar && !this.failedToLoadAvatar | ||
? html`<img src="${this.avatar}" alt="user avatar" @error=${this.handleAvatarError} />` | ||
: html`<logto-icon>${fallbackAvatar}</logto-icon>`} | ||
</div> | ||
<div class="info"> | ||
<div class="name">${this.name}</div> | ||
<div class="email">${this.email}</div> | ||
</div> | ||
`; | ||
} | ||
|
||
private handleAvatarError() { | ||
this.failedToLoadAvatar = true; | ||
} | ||
} | ||
|
||
declare global { | ||
// eslint-disable-next-line @typescript-eslint/consistent-type-definitions | ||
interface HTMLElementTagNameMap { | ||
[tagName]: LogtoIdentityInfo; | ||
} | ||
} |
85 changes: 85 additions & 0 deletions
85
packages/elements/src/account/elements/logto-social-identity.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
import { assert, fixture, html, waitUntil } from '@open-wc/testing'; | ||
|
||
import { createMockAccountApi } from '../__mocks__/account-api.js'; | ||
import { LogtoIdentityInfo } from '../components/logto-identity-info.js'; | ||
import { type LogtoAccountProvider } from '../providers/logto-account-provider.js'; | ||
|
||
import { LogtoSocialIdentity } from './logto-social-identity.js'; | ||
|
||
suite('logto-social-identity', () => { | ||
test('is defined', () => { | ||
const element = document.createElement(LogtoSocialIdentity.tagName); | ||
assert.instanceOf(element, LogtoSocialIdentity); | ||
}); | ||
|
||
test('should render error message when account context is not available', async () => { | ||
const element = await fixture<LogtoSocialIdentity>( | ||
html`<logto-social-identity></logto-social-identity>` | ||
); | ||
await element.updateComplete; | ||
|
||
assert.equal(element.shadowRoot?.textContent, 'Unable to retrieve account context.'); | ||
}); | ||
|
||
test('should render correctly when user has permission to view social identity information', async () => { | ||
const mockAccountApi = createMockAccountApi({ | ||
fetchUserProfile: async () => ({ | ||
identities: { | ||
github: { | ||
userId: '123', | ||
details: { | ||
name: 'John Doe', | ||
email: '[email protected]', | ||
}, | ||
}, | ||
}, | ||
}), | ||
}); | ||
|
||
const provider = await fixture<LogtoAccountProvider>( | ||
html`<logto-account-provider .accountApi=${mockAccountApi}> | ||
<logto-social-identity target="github"></logto-social-identity> | ||
</logto-account-provider>` | ||
); | ||
|
||
await provider.updateComplete; | ||
|
||
const logtoSocialIdentity = provider.querySelector<LogtoSocialIdentity>( | ||
LogtoSocialIdentity.tagName | ||
); | ||
|
||
const identityInfo = logtoSocialIdentity?.shadowRoot?.querySelector<LogtoIdentityInfo>( | ||
LogtoIdentityInfo.tagName | ||
); | ||
|
||
await waitUntil( | ||
() => | ||
identityInfo?.shadowRoot?.querySelector('div[class=name]')?.textContent === 'John Doe' && | ||
identityInfo.shadowRoot.querySelector('div[class=email]')?.textContent === | ||
'[email protected]', | ||
'Unable to get social identity information from account context' | ||
); | ||
}); | ||
|
||
test('should render nothing if the user lacks permission to view social identity information', async () => { | ||
const mockAccountApi = createMockAccountApi({ | ||
fetchUserProfile: async () => ({ | ||
identities: undefined, | ||
}), | ||
}); | ||
|
||
const provider = await fixture<LogtoAccountProvider>( | ||
html`<logto-account-provider .accountApi=${mockAccountApi}> | ||
<logto-social-identity target="github" labelText="GitHub"></logto-social-identity> | ||
</logto-account-provider>` | ||
); | ||
|
||
await provider.updateComplete; | ||
const logtoSocialIdentity = provider.querySelector<LogtoSocialIdentity>( | ||
LogtoSocialIdentity.tagName | ||
); | ||
|
||
await logtoSocialIdentity?.updateComplete; | ||
assert.equal(logtoSocialIdentity?.shadowRoot?.children.length, 0); | ||
}); | ||
}); |
55 changes: 55 additions & 0 deletions
55
packages/elements/src/account/elements/logto-social-identity.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
import { html, type TemplateResult } from 'lit'; | ||
import { customElement, property } from 'lit/decorators.js'; | ||
import { when } from 'lit/directives/when.js'; | ||
|
||
import usernameIcon from '../icons/username.svg'; | ||
|
||
import { LogtoProfileItemElement } from './LogtoProfileItemElement.js'; | ||
|
||
const tagName = 'logto-social-identity'; | ||
|
||
@customElement(tagName) | ||
export class LogtoSocialIdentity extends LogtoProfileItemElement { | ||
static tagName = tagName; | ||
|
||
@property({ type: String }) | ||
target = ''; | ||
|
||
protected isAccessible(): boolean { | ||
return this.accountContext?.userProfile.identities !== undefined; | ||
} | ||
|
||
protected getItemLabelInfo() { | ||
// Todo: @xiaoyijun replace with correct label text and icon when related connector API is ready | ||
return { | ||
icon: usernameIcon, | ||
label: this.target, | ||
}; | ||
} | ||
|
||
protected renderContent(): TemplateResult { | ||
const { identities } = this.accountContext?.userProfile ?? {}; | ||
|
||
const identity = identities?.[this.target]; | ||
// Todo: @xiaoyijun support identifier fallback logic | ||
const { avatar = '', name = '', email = '' } = identity?.details ?? {}; | ||
|
||
return when( | ||
identity, | ||
() => | ||
html`<logto-identity-info | ||
slot="content" | ||
.avatar=${String(avatar)} | ||
.name=${String(name)} | ||
.email=${String(email)} | ||
></logto-identity-info>` | ||
); | ||
} | ||
} | ||
|
||
declare global { | ||
// eslint-disable-next-line @typescript-eslint/consistent-type-definitions | ||
interface HTMLElementTagNameMap { | ||
[tagName]: LogtoSocialIdentity; | ||
} | ||
} |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters