0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2025-03-17 22:31:28 -05:00

feat(elements): init user provider

This commit is contained in:
Gao Sun 2024-07-19 12:43:05 +08:00
parent a5a8570d7a
commit 2c1e326949
No known key found for this signature in database
GPG key ID: 13EBE123E4773688
29 changed files with 352 additions and 108 deletions

View file

@ -1,14 +1,19 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Logto elements dev page</title>
<script type="module" src="./src/index.ts"></script>
</head>
<body style="background: #111;">
<logto-theme-provider theme="dark">
<logto-profile-card></logto-profile-card>
<logto-user-provider user='{"name": "Johnny Silverhand", "avatar": "https://github.com/logto-io.png"}'>
<logto-profile-card></logto-profile-card>
</logto-user-provider>
</logto-theme-provider>
</body>
</html>

View file

@ -55,6 +55,7 @@
},
"devDependencies": {
"@lit/localize-tools": "^0.7.2",
"@logto/schemas": "workspace:^1.18.0",
"@silverhand/eslint-config": "6.0.1",
"@silverhand/ts-config": "6.0.0",
"@types/node": "^20.9.5",

View file

@ -0,0 +1,106 @@
import { css } from 'lit';
import { unit } from '../utils/css.js';
import { vars } from '../utils/theme.js';
export const buttonSizes = css`
:host([size='small']) {
height: 30px;
padding: ${unit(0, 3)};
}
:host([size='small'][type='text']) {
height: 24px;
}
:host([size='medium']) {
height: 36px;
padding: ${unit(0, 4)};
}
:host([size='medium'][type='text']) {
height: 28px;
font: ${vars.fontLabel1};
}
:host([size='large']) {
height: 44px;
padding: ${unit(0, 6)};
}
:host([size='large'][type='text']) {
height: 28px;
font: ${vars.fontLabel1};
}
`;
export const textButton = css`
:host([type='text']) {
background: none;
border-color: transparent;
font: ${vars.fontLabel2};
color: ${vars.colorTextLink};
padding: ${unit(0.5, 1)};
border-radius: ${unit(1)};
}
:host([type='text']:disabled) {
color: ${vars.colorDisabled};
}
:host([type='text']:focus-visible) {
outline: 2px solid ${vars.colorFocusedVariant};
}
:host([type='text']:not(:disabled):not(:active):hover) {
background: ${vars.colorHoverVariant};
}
`;
export const defaultButton = css`
:host([type='default']) {
background: ${vars.colorLayer1};
color: ${vars.colorTextPrimary};
border: 1px solid ${vars.colorBorder};
}
:host([type='default']:disabled) {
color: ${vars.colorPlaceholder};
}
:host([type='default']:focus-visible) {
outline: 3px solid ${vars.colorFocused};
}
:host([type='default']:active) {
background: ${vars.colorPressed};
}
:host([type='default']:not(:disabled):not(:active):hover) {
background: ${vars.colorHover};
}
`;
export const primaryButton = css`
:host([type='primary']) {
background: ${vars.colorPrimary};
color: ${vars.colorOnPrimary};
}
:host([type='primary']:disabled) {
background: ${vars.colorDisabledBackground};
color: ${vars.colorPlaceholder};
}
:host([type='primary']:focus-visible) {
outline: 3px solid ${vars.colorFocusedVariant};
}
:host([type='primary']:active) {
background: ${vars.colorPrimaryPressed};
}
:host([type='primary']:not(:disabled):not(:active):hover) {
background: ${vars.colorPrimaryHover};
}
`;

View file

@ -4,56 +4,47 @@ import { customElement, property } from 'lit/decorators.js';
import { unit } from '../utils/css.js';
import { vars } from '../utils/theme.js';
import { buttonSizes, defaultButton, primaryButton, textButton } from './logto-button.styles.js';
const tagName = 'logto-button';
@customElement(tagName)
export class LogtoButton extends LitElement {
static tagName = tagName;
static styles = css`
:host {
display: inline-flex;
align-items: center;
justify-content: center;
border: none;
outline: none;
font: ${vars.fontLabel2};
transition: background-color 0.2s ease-in-out;
white-space: nowrap;
user-select: none;
position: relative;
text-decoration: none;
gap: ${unit(2)};
cursor: pointer;
}
static styles = [
css`
:host {
display: inline-flex;
align-items: center;
justify-content: center;
border: none;
outline: none;
font: ${vars.fontLabel2};
transition: background-color 0.2s ease-in-out;
white-space: nowrap;
user-select: none;
position: relative;
text-decoration: none;
gap: ${unit(2)};
border-radius: ${unit(2)};
cursor: pointer;
}
:host(:disabled) {
cursor: not-allowed;
}
:host(:disabled) {
cursor: not-allowed;
}
`,
buttonSizes,
textButton,
defaultButton,
primaryButton,
];
:host([type='text']) {
background: none;
border-color: transparent;
font: ${vars.fontLabel2};
color: ${vars.colorTextLink};
padding: ${unit(0.5, 1)};
border-radius: ${unit(1)};
}
@property({ reflect: true })
type: 'default' | 'text' | 'primary' = 'default';
:host([type='text']:disabled) {
color: ${vars.colorDisabled};
}
:host([type='text']:focus-visible) {
outline: 2px solid ${vars.colorFocusedVariant};
}
:host([type='text']:not(:disabled):hover) {
background: ${vars.colorHoverVariant};
}
`;
@property()
type: 'default' | 'text' = 'default';
@property({ reflect: true })
size: 'small' | 'medium' | 'large' = 'medium';
connectedCallback(): void {
super.connectedCallback();

View file

@ -1,7 +1,8 @@
import { localized, msg } from '@lit/localize';
import { localized } from '@lit/localize';
import { LitElement, css, html } from 'lit';
import { customElement, property } from 'lit/decorators.js';
import { notSet } from '../phrases/index.js';
import { unit } from '../utils/css.js';
import { vars } from '../utils/theme.js';
@ -21,10 +22,7 @@ export class LogtoCardSection extends LitElement {
`;
@property()
heading = msg('Not set', {
id: 'general.fallback-title',
desc: 'A fallback title when the title or heading of a component is not provided.',
});
heading = notSet;
render() {
return html`

View file

@ -1,8 +1,9 @@
import { localized, msg } from '@lit/localize';
import { localized } from '@lit/localize';
import { cond } from '@silverhand/essentials';
import { LitElement, css, html } from 'lit';
import { customElement, property } from 'lit/decorators.js';
import { notSet } from '../phrases/index.js';
import { unit } from '../utils/css.js';
import { vars } from '../utils/theme.js';
@ -57,10 +58,7 @@ export class LogtoFormCard extends LitElement {
`;
@property()
heading = msg('Not set', {
id: 'general.fallback-title',
desc: 'A fallback title when the title or heading of a component is not provided.',
});
heading = notSet;
@property()
description = '';

View file

@ -2,6 +2,7 @@ import { localized, msg } from '@lit/localize';
import { LitElement, css, html } from 'lit';
import { customElement } from 'lit/decorators.js';
import { notSet } from '../phrases/index.js';
import { unit } from '../utils/css.js';
import { vars } from '../utils/theme.js';
@ -38,13 +39,17 @@ export class LogtoListRow extends LitElement {
slot[name='actions'] {
text-align: right;
}
span.not-set {
color: ${vars.colorTextSecondary};
}
`;
render() {
return html`
<slot name="title">{${msg('Title', { id: 'general.title' })}}</slot>
<slot name="content">{${msg('Content', { id: 'general.content' })}}</slot>
<slot name="actions">{${msg('Actions', { id: 'general.actions' })}}</slot>
<slot name="title">${msg('Title', { id: 'general.title' })}</slot>
<slot name="content"><span class="not-set">${notSet}</span></slot>
<slot name="actions">${msg('Actions', { id: 'general.actions' })}</slot>
`;
}
}

View file

@ -35,6 +35,14 @@ export class LogtoModalLayout extends LitElement {
h1 {
all: unset;
}
footer:not(:empty) {
margin-top: ${unit(6)};
display: flex;
justify-content: flex-end;
gap: ${unit(4)};
align-items: center;
}
`;
@property({ reflect: true })
@ -44,19 +52,24 @@ export class LogtoModalLayout extends LitElement {
});
@consume({ context: ModalContext })
context!: ModalContextType;
context?: ModalContextType;
render() {
const { onClose } = this.context ?? {};
return html`
<header>
<h1>${this.heading}</h1>
${cond(
this.context.onClose !== noop &&
html`<logto-icon-button @click=${this.context.onClose}>${closeIcon}</logto-icon-button>`
onClose &&
onClose !== noop &&
html`<logto-icon-button @click=${onClose}>${closeIcon}</logto-icon-button>`
)}
</header>
<slot></slot>
<footer></footer>
<footer>
<slot name="footer"></slot>
</footer>
`;
}
}

View file

@ -1,7 +1,10 @@
import { consume } from '@lit/context';
import { localized, msg } from '@lit/localize';
import { cond } from '@silverhand/essentials';
import { LitElement, css, html } from 'lit';
import { customElement, state } from 'lit/decorators.js';
import { UserContext, type UserContextType } from '../providers/logto-user-provider.js';
import { vars } from '../utils/theme.js';
const tagName = 'logto-profile-card';
@ -16,10 +19,21 @@ export class LogtoProfileCard extends LitElement {
}
`;
@consume({ context: UserContext, subscribe: true })
userContext?: UserContextType;
@state()
updateNameOpened = false;
render() {
const user = this.userContext?.user;
if (!user) {
return html`<logto-form-card heading=${msg('Profile', { id: 'account.profile.title' })}>
<p class="dev"> ${msg('No user provided.', { id: 'account.profile.no-user' })}</p>
</logto-form-card>`;
}
return html`
<logto-form-card heading=${msg('Profile', { id: 'account.profile.title' })}>
<p class="dev">🚧 This section is a dev feature that is still working in progress.</p>
@ -34,11 +48,14 @@ export class LogtoProfileCard extends LitElement {
desc: 'The avatar of the user.',
})}
</div>
<div slot="content">
<logto-avatar size="large" src="https://github.com/logto-io.png"></logto-avatar>
</div>
${cond(
user.avatar &&
html`<div slot="content">
<logto-avatar size="large" src=${user.avatar}></logto-avatar>
</div>`
)}
<div slot="actions">
<logto-button type="text">
<logto-button type="text" size="small">
${msg('Change', { id: 'general.change' })}
</logto-button>
</div>
@ -50,10 +67,11 @@ export class LogtoProfileCard extends LitElement {
desc: 'The name of the user.',
})}
</div>
<div slot="content">John Doe</div>
${cond(user.name && html`<div slot="content">${user.name}</div>`)}
<div slot="actions">
<logto-button
type="text"
size="small"
@click=${() => {
this.updateNameOpened = true;
}}
@ -85,6 +103,9 @@ export class LogtoProfileCard extends LitElement {
value=""
/>
</logto-text-input>
<logto-button slot="footer" size="large" type="primary">
${msg('Save', { id: 'general.save' })}
</logto-button>
</logto-modal-layout>
</logto-modal>
`;

View file

@ -9,6 +9,6 @@ export * from './components/logto-list.js';
export * from './components/logto-modal-layout.js';
export * from './components/logto-modal.js';
export * from './components/logto-text-input.js';
export * from './components/logto-theme-provider.js';
export * from './elements/logto-profile-card.js';
export * from './utils/locale.js';
export * from './providers/logto-theme-provider.js';

View file

@ -0,0 +1,6 @@
import { msg } from '@lit/localize';
export const notSet = msg('Not set', {
id: 'general.fallback-title',
desc: 'A fallback title when the title or heading of a component is not provided.',
});

View file

@ -0,0 +1,46 @@
import { createContext, provide } from '@lit/context';
import { type UserInfo } from '@logto/schemas';
import { LitElement, type PropertyValues, html } from 'lit';
import { customElement, property } from 'lit/decorators.js';
/** @see {@link UserContext} */
export type UserContextType = { user?: UserInfo };
/**
* Context for the current user. It's a fundamental context for the account-related elements.
*/
export const UserContext = createContext<UserContextType>('modal-context');
/** The default value for the user context. */
export const userContext: UserContextType = {};
const tagName = 'logto-user-provider';
@customElement(tagName)
export class LogtoUserProvider extends LitElement {
static tagName = tagName;
@provide({ context: UserContext })
context = userContext;
@property({ type: Object })
user?: UserInfo;
render() {
return html`<slot></slot>`;
}
protected handlePropertiesChange(changedProperties: PropertyValues) {
if (changedProperties.has('user')) {
this.context.user = this.user;
}
}
protected firstUpdated(changedProperties: PropertyValues): void {
this.handlePropertiesChange(changedProperties);
}
protected updated(changedProperties: PropertyValues): void {
this.handlePropertiesChange(changedProperties);
}
}

View file

@ -5,6 +5,9 @@ import { type KebabCase, kebabCase } from './string.js';
/** All the colors to be used in the Logto components and elements. */
export type Color = {
colorPrimary: string;
colorOnPrimary: string;
colorPrimaryPressed: string;
colorPrimaryHover: string;
colorTextPrimary: string;
colorTextLink: string;
colorTextSecondary: string;
@ -62,6 +65,9 @@ export const defaultFont: Readonly<Font> = Object.freeze({
export const defaultTheme: Readonly<Theme> = Object.freeze({
...defaultFont,
colorPrimary: '#5d34f2',
colorOnPrimary: '#000',
colorPrimaryPressed: '#e6deff',
colorPrimaryHover: '#af9eff',
colorTextPrimary: '#191c1d',
colorTextLink: '#5d34f2',
colorTextSecondary: '#747778',
@ -84,6 +90,9 @@ export const defaultTheme: Readonly<Theme> = Object.freeze({
export const darkTheme: Readonly<Theme> = Object.freeze({
...defaultFont,
colorPrimary: '#7958ff',
colorOnPrimary: '#fff',
colorPrimaryPressed: '#5d34f2',
colorPrimaryHover: '#947dff',
colorTextPrimary: '#f7f8f8',
colorTextLink: '#cabeff',
colorTextSecondary: '#a9acac',

View file

@ -5,9 +5,6 @@
<trans-unit id="general.title">
<source>Title</source>
</trans-unit>
<trans-unit id="general.content">
<source>Content</source>
</trans-unit>
<trans-unit id="general.actions">
<source>Actions</source>
</trans-unit>
@ -42,6 +39,12 @@
<source>Person Doe</source>
<note from="lit-localize">The placeholder for the name input field.</note>
</trans-unit>
<trans-unit id="account.profile.no-user">
<source>No user provided.</source>
</trans-unit>
<trans-unit id="general.save">
<source>Save</source>
</trans-unit>
</body>
</file>
</xliff>

View file

@ -5,9 +5,6 @@
<trans-unit id="general.title">
<source>Title</source>
</trans-unit>
<trans-unit id="general.content">
<source>Content</source>
</trans-unit>
<trans-unit id="general.actions">
<source>Actions</source>
</trans-unit>
@ -42,6 +39,12 @@
<source>Person Doe</source>
<note from="lit-localize">The placeholder for the name input field.</note>
</trans-unit>
<trans-unit id="account.profile.no-user">
<source>No user provided.</source>
</trans-unit>
<trans-unit id="general.save">
<source>Save</source>
</trans-unit>
</body>
</file>
</xliff>

View file

@ -5,9 +5,6 @@
<trans-unit id="general.title">
<source>Title</source>
</trans-unit>
<trans-unit id="general.content">
<source>Content</source>
</trans-unit>
<trans-unit id="general.actions">
<source>Actions</source>
</trans-unit>
@ -42,6 +39,12 @@
<source>Person Doe</source>
<note from="lit-localize">The placeholder for the name input field.</note>
</trans-unit>
<trans-unit id="account.profile.no-user">
<source>No user provided.</source>
</trans-unit>
<trans-unit id="general.save">
<source>Save</source>
</trans-unit>
</body>
</file>
</xliff>

View file

@ -5,9 +5,6 @@
<trans-unit id="general.title">
<source>Title</source>
</trans-unit>
<trans-unit id="general.content">
<source>Content</source>
</trans-unit>
<trans-unit id="general.actions">
<source>Actions</source>
</trans-unit>
@ -42,6 +39,12 @@
<source>Person Doe</source>
<note from="lit-localize">The placeholder for the name input field.</note>
</trans-unit>
<trans-unit id="account.profile.no-user">
<source>No user provided.</source>
</trans-unit>
<trans-unit id="general.save">
<source>Save</source>
</trans-unit>
</body>
</file>
</xliff>

View file

@ -5,9 +5,6 @@
<trans-unit id="general.title">
<source>Title</source>
</trans-unit>
<trans-unit id="general.content">
<source>Content</source>
</trans-unit>
<trans-unit id="general.actions">
<source>Actions</source>
</trans-unit>
@ -42,6 +39,12 @@
<source>Person Doe</source>
<note from="lit-localize">The placeholder for the name input field.</note>
</trans-unit>
<trans-unit id="account.profile.no-user">
<source>No user provided.</source>
</trans-unit>
<trans-unit id="general.save">
<source>Save</source>
</trans-unit>
</body>
</file>
</xliff>

View file

@ -5,9 +5,6 @@
<trans-unit id="general.title">
<source>Title</source>
</trans-unit>
<trans-unit id="general.content">
<source>Content</source>
</trans-unit>
<trans-unit id="general.actions">
<source>Actions</source>
</trans-unit>
@ -42,6 +39,12 @@
<source>Person Doe</source>
<note from="lit-localize">The placeholder for the name input field.</note>
</trans-unit>
<trans-unit id="account.profile.no-user">
<source>No user provided.</source>
</trans-unit>
<trans-unit id="general.save">
<source>Save</source>
</trans-unit>
</body>
</file>
</xliff>

View file

@ -5,9 +5,6 @@
<trans-unit id="general.title">
<source>Title</source>
</trans-unit>
<trans-unit id="general.content">
<source>Content</source>
</trans-unit>
<trans-unit id="general.actions">
<source>Actions</source>
</trans-unit>
@ -42,6 +39,12 @@
<source>Person Doe</source>
<note from="lit-localize">The placeholder for the name input field.</note>
</trans-unit>
<trans-unit id="account.profile.no-user">
<source>No user provided.</source>
</trans-unit>
<trans-unit id="general.save">
<source>Save</source>
</trans-unit>
</body>
</file>
</xliff>

View file

@ -5,9 +5,6 @@
<trans-unit id="general.title">
<source>Title</source>
</trans-unit>
<trans-unit id="general.content">
<source>Content</source>
</trans-unit>
<trans-unit id="general.actions">
<source>Actions</source>
</trans-unit>
@ -42,6 +39,12 @@
<source>Person Doe</source>
<note from="lit-localize">The placeholder for the name input field.</note>
</trans-unit>
<trans-unit id="account.profile.no-user">
<source>No user provided.</source>
</trans-unit>
<trans-unit id="general.save">
<source>Save</source>
</trans-unit>
</body>
</file>
</xliff>

View file

@ -5,9 +5,6 @@
<trans-unit id="general.title">
<source>Title</source>
</trans-unit>
<trans-unit id="general.content">
<source>Content</source>
</trans-unit>
<trans-unit id="general.actions">
<source>Actions</source>
</trans-unit>
@ -42,6 +39,12 @@
<source>Person Doe</source>
<note from="lit-localize">The placeholder for the name input field.</note>
</trans-unit>
<trans-unit id="account.profile.no-user">
<source>No user provided.</source>
</trans-unit>
<trans-unit id="general.save">
<source>Save</source>
</trans-unit>
</body>
</file>
</xliff>

View file

@ -5,9 +5,6 @@
<trans-unit id="general.title">
<source>Title</source>
</trans-unit>
<trans-unit id="general.content">
<source>Content</source>
</trans-unit>
<trans-unit id="general.actions">
<source>Actions</source>
</trans-unit>
@ -42,6 +39,12 @@
<source>Person Doe</source>
<note from="lit-localize">The placeholder for the name input field.</note>
</trans-unit>
<trans-unit id="account.profile.no-user">
<source>No user provided.</source>
</trans-unit>
<trans-unit id="general.save">
<source>Save</source>
</trans-unit>
</body>
</file>
</xliff>

View file

@ -5,9 +5,6 @@
<trans-unit id="general.title">
<source>Title</source>
</trans-unit>
<trans-unit id="general.content">
<source>Content</source>
</trans-unit>
<trans-unit id="general.actions">
<source>Actions</source>
</trans-unit>
@ -42,6 +39,12 @@
<source>Person Doe</source>
<note from="lit-localize">The placeholder for the name input field.</note>
</trans-unit>
<trans-unit id="account.profile.no-user">
<source>No user provided.</source>
</trans-unit>
<trans-unit id="general.save">
<source>Save</source>
</trans-unit>
</body>
</file>
</xliff>

View file

@ -5,9 +5,6 @@
<trans-unit id="general.title">
<source>Title</source>
</trans-unit>
<trans-unit id="general.content">
<source>Content</source>
</trans-unit>
<trans-unit id="general.actions">
<source>Actions</source>
</trans-unit>
@ -42,6 +39,12 @@
<source>Person Doe</source>
<note from="lit-localize">The placeholder for the name input field.</note>
</trans-unit>
<trans-unit id="account.profile.no-user">
<source>No user provided.</source>
</trans-unit>
<trans-unit id="general.save">
<source>Save</source>
</trans-unit>
</body>
</file>
</xliff>

View file

@ -5,9 +5,6 @@
<trans-unit id="general.title">
<source>Title</source>
</trans-unit>
<trans-unit id="general.content">
<source>Content</source>
</trans-unit>
<trans-unit id="general.actions">
<source>Actions</source>
</trans-unit>
@ -42,6 +39,12 @@
<source>Person Doe</source>
<note from="lit-localize">The placeholder for the name input field.</note>
</trans-unit>
<trans-unit id="account.profile.no-user">
<source>No user provided.</source>
</trans-unit>
<trans-unit id="general.save">
<source>Save</source>
</trans-unit>
</body>
</file>
</xliff>

View file

@ -5,9 +5,6 @@
<trans-unit id="general.title">
<source>Title</source>
</trans-unit>
<trans-unit id="general.content">
<source>Content</source>
</trans-unit>
<trans-unit id="general.actions">
<source>Actions</source>
</trans-unit>
@ -42,6 +39,12 @@
<source>Person Doe</source>
<note from="lit-localize">The placeholder for the name input field.</note>
</trans-unit>
<trans-unit id="account.profile.no-user">
<source>No user provided.</source>
</trans-unit>
<trans-unit id="general.save">
<source>Save</source>
</trans-unit>
</body>
</file>
</xliff>

3
pnpm-lock.yaml generated
View file

@ -3579,6 +3579,9 @@ importers:
'@lit/localize-tools':
specifier: ^0.7.2
version: 0.7.2
'@logto/schemas':
specifier: workspace:^1.18.0
version: link:../schemas
'@silverhand/eslint-config':
specifier: 6.0.1
version: 6.0.1(eslint@8.57.0)(prettier@3.0.0)(typescript@5.5.3)