From 2c1e3269499873f0441b2d48734627de931c2d7e Mon Sep 17 00:00:00 2001 From: Gao Sun Date: Fri, 19 Jul 2024 12:43:05 +0800 Subject: [PATCH] feat(elements): init user provider --- packages/elements/index.html | 7 +- packages/elements/package.json | 1 + .../src/components/logto-button.styles.ts | 106 ++++++++++++++++++ .../elements/src/components/logto-button.ts | 75 ++++++------- .../src/components/logto-card-section.ts | 8 +- .../src/components/logto-form-card.ts | 8 +- .../elements/src/components/logto-list-row.ts | 11 +- .../src/components/logto-modal-layout.ts | 21 +++- .../src/elements/logto-profile-card.ts | 31 ++++- packages/elements/src/index.ts | 2 +- packages/elements/src/phrases/index.ts | 6 + .../logto-theme-provider.ts | 0 .../src/providers/logto-user-provider.ts | 46 ++++++++ packages/elements/src/utils/theme.ts | 9 ++ packages/elements/xliff/de.xlf | 9 +- packages/elements/xliff/es.xlf | 9 +- packages/elements/xliff/fr.xlf | 9 +- packages/elements/xliff/it.xlf | 9 +- packages/elements/xliff/ja.xlf | 9 +- packages/elements/xliff/ko.xlf | 9 +- packages/elements/xliff/pl-PL.xlf | 9 +- packages/elements/xliff/pt-BR.xlf | 9 +- packages/elements/xliff/pt-PT.xlf | 9 +- packages/elements/xliff/ru.xlf | 9 +- packages/elements/xliff/tr-TR.xlf | 9 +- packages/elements/xliff/zh-CN.xlf | 9 +- packages/elements/xliff/zh-HK.xlf | 9 +- packages/elements/xliff/zh-TW.xlf | 9 +- pnpm-lock.yaml | 3 + 29 files changed, 352 insertions(+), 108 deletions(-) create mode 100644 packages/elements/src/components/logto-button.styles.ts create mode 100644 packages/elements/src/phrases/index.ts rename packages/elements/src/{components => providers}/logto-theme-provider.ts (100%) create mode 100644 packages/elements/src/providers/logto-user-provider.ts diff --git a/packages/elements/index.html b/packages/elements/index.html index 93d3782ab..ecc395bd1 100644 --- a/packages/elements/index.html +++ b/packages/elements/index.html @@ -1,14 +1,19 @@ + Logto elements dev page + - + + + + diff --git a/packages/elements/package.json b/packages/elements/package.json index d1bcbded3..e08c8a5b2 100644 --- a/packages/elements/package.json +++ b/packages/elements/package.json @@ -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", diff --git a/packages/elements/src/components/logto-button.styles.ts b/packages/elements/src/components/logto-button.styles.ts new file mode 100644 index 000000000..a3998cfc0 --- /dev/null +++ b/packages/elements/src/components/logto-button.styles.ts @@ -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}; + } +`; diff --git a/packages/elements/src/components/logto-button.ts b/packages/elements/src/components/logto-button.ts index 5f6d7c4a0..09b31ffb1 100644 --- a/packages/elements/src/components/logto-button.ts +++ b/packages/elements/src/components/logto-button.ts @@ -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(); diff --git a/packages/elements/src/components/logto-card-section.ts b/packages/elements/src/components/logto-card-section.ts index 2f3a365d1..4fb57dfec 100644 --- a/packages/elements/src/components/logto-card-section.ts +++ b/packages/elements/src/components/logto-card-section.ts @@ -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` diff --git a/packages/elements/src/components/logto-form-card.ts b/packages/elements/src/components/logto-form-card.ts index 329295ca2..776d32658 100644 --- a/packages/elements/src/components/logto-form-card.ts +++ b/packages/elements/src/components/logto-form-card.ts @@ -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 = ''; diff --git a/packages/elements/src/components/logto-list-row.ts b/packages/elements/src/components/logto-list-row.ts index 5faf2c45f..866b6003b 100644 --- a/packages/elements/src/components/logto-list-row.ts +++ b/packages/elements/src/components/logto-list-row.ts @@ -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` - {${msg('Title', { id: 'general.title' })}} - {${msg('Content', { id: 'general.content' })}} - {${msg('Actions', { id: 'general.actions' })}} + ${msg('Title', { id: 'general.title' })} + ${notSet} + ${msg('Actions', { id: 'general.actions' })} `; } } diff --git a/packages/elements/src/components/logto-modal-layout.ts b/packages/elements/src/components/logto-modal-layout.ts index 0035ffa86..04786f4fe 100644 --- a/packages/elements/src/components/logto-modal-layout.ts +++ b/packages/elements/src/components/logto-modal-layout.ts @@ -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`

${this.heading}

${cond( - this.context.onClose !== noop && - html`${closeIcon}` + onClose && + onClose !== noop && + html`${closeIcon}` )}
- + `; } } diff --git a/packages/elements/src/elements/logto-profile-card.ts b/packages/elements/src/elements/logto-profile-card.ts index abe1baaa8..568897f3d 100644 --- a/packages/elements/src/elements/logto-profile-card.ts +++ b/packages/elements/src/elements/logto-profile-card.ts @@ -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` +

⚠️ ${msg('No user provided.', { id: 'account.profile.no-user' })}

+
`; + } + return html`

🚧 This section is a dev feature that is still working in progress.

@@ -34,11 +48,14 @@ export class LogtoProfileCard extends LitElement { desc: 'The avatar of the user.', })} -
- -
+ ${cond( + user.avatar && + html`
+ +
` + )}
- + ${msg('Change', { id: 'general.change' })}
@@ -50,10 +67,11 @@ export class LogtoProfileCard extends LitElement { desc: 'The name of the user.', })} -
John Doe
+ ${cond(user.name && html`
${user.name}
`)}
{ this.updateNameOpened = true; }} @@ -85,6 +103,9 @@ export class LogtoProfileCard extends LitElement { value="" /> + + ${msg('Save', { id: 'general.save' })} + `; diff --git a/packages/elements/src/index.ts b/packages/elements/src/index.ts index 3817fc0d7..566659ed9 100644 --- a/packages/elements/src/index.ts +++ b/packages/elements/src/index.ts @@ -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'; diff --git a/packages/elements/src/phrases/index.ts b/packages/elements/src/phrases/index.ts new file mode 100644 index 000000000..ee1687f51 --- /dev/null +++ b/packages/elements/src/phrases/index.ts @@ -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.', +}); diff --git a/packages/elements/src/components/logto-theme-provider.ts b/packages/elements/src/providers/logto-theme-provider.ts similarity index 100% rename from packages/elements/src/components/logto-theme-provider.ts rename to packages/elements/src/providers/logto-theme-provider.ts diff --git a/packages/elements/src/providers/logto-user-provider.ts b/packages/elements/src/providers/logto-user-provider.ts new file mode 100644 index 000000000..08ad1b1b6 --- /dev/null +++ b/packages/elements/src/providers/logto-user-provider.ts @@ -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('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``; + } + + 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); + } +} diff --git a/packages/elements/src/utils/theme.ts b/packages/elements/src/utils/theme.ts index a30d54c46..48b827011 100644 --- a/packages/elements/src/utils/theme.ts +++ b/packages/elements/src/utils/theme.ts @@ -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 = Object.freeze({ export const defaultTheme: Readonly = 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 = Object.freeze({ export const darkTheme: Readonly = Object.freeze({ ...defaultFont, colorPrimary: '#7958ff', + colorOnPrimary: '#fff', + colorPrimaryPressed: '#5d34f2', + colorPrimaryHover: '#947dff', colorTextPrimary: '#f7f8f8', colorTextLink: '#cabeff', colorTextSecondary: '#a9acac', diff --git a/packages/elements/xliff/de.xlf b/packages/elements/xliff/de.xlf index 44dc02786..c04fcde9c 100644 --- a/packages/elements/xliff/de.xlf +++ b/packages/elements/xliff/de.xlf @@ -5,9 +5,6 @@ Title - - Content - Actions @@ -42,6 +39,12 @@ Person Doe The placeholder for the name input field. + + No user provided. + + + Save + diff --git a/packages/elements/xliff/es.xlf b/packages/elements/xliff/es.xlf index e523e4559..31ee1a63b 100644 --- a/packages/elements/xliff/es.xlf +++ b/packages/elements/xliff/es.xlf @@ -5,9 +5,6 @@ Title - - Content - Actions @@ -42,6 +39,12 @@ Person Doe The placeholder for the name input field. + + No user provided. + + + Save + diff --git a/packages/elements/xliff/fr.xlf b/packages/elements/xliff/fr.xlf index 3abe2d129..5b421a459 100644 --- a/packages/elements/xliff/fr.xlf +++ b/packages/elements/xliff/fr.xlf @@ -5,9 +5,6 @@ Title - - Content - Actions @@ -42,6 +39,12 @@ Person Doe The placeholder for the name input field. + + No user provided. + + + Save + diff --git a/packages/elements/xliff/it.xlf b/packages/elements/xliff/it.xlf index a80d9c2f9..9ce355697 100644 --- a/packages/elements/xliff/it.xlf +++ b/packages/elements/xliff/it.xlf @@ -5,9 +5,6 @@ Title - - Content - Actions @@ -42,6 +39,12 @@ Person Doe The placeholder for the name input field. + + No user provided. + + + Save + diff --git a/packages/elements/xliff/ja.xlf b/packages/elements/xliff/ja.xlf index f2d6614a9..7e465f296 100644 --- a/packages/elements/xliff/ja.xlf +++ b/packages/elements/xliff/ja.xlf @@ -5,9 +5,6 @@ Title - - Content - Actions @@ -42,6 +39,12 @@ Person Doe The placeholder for the name input field. + + No user provided. + + + Save + diff --git a/packages/elements/xliff/ko.xlf b/packages/elements/xliff/ko.xlf index 191b84845..d019aaf27 100644 --- a/packages/elements/xliff/ko.xlf +++ b/packages/elements/xliff/ko.xlf @@ -5,9 +5,6 @@ Title - - Content - Actions @@ -42,6 +39,12 @@ Person Doe The placeholder for the name input field. + + No user provided. + + + Save + diff --git a/packages/elements/xliff/pl-PL.xlf b/packages/elements/xliff/pl-PL.xlf index b358ffe8a..54efd13f2 100644 --- a/packages/elements/xliff/pl-PL.xlf +++ b/packages/elements/xliff/pl-PL.xlf @@ -5,9 +5,6 @@ Title - - Content - Actions @@ -42,6 +39,12 @@ Person Doe The placeholder for the name input field. + + No user provided. + + + Save + diff --git a/packages/elements/xliff/pt-BR.xlf b/packages/elements/xliff/pt-BR.xlf index 5ee50f852..7b323b0e0 100644 --- a/packages/elements/xliff/pt-BR.xlf +++ b/packages/elements/xliff/pt-BR.xlf @@ -5,9 +5,6 @@ Title - - Content - Actions @@ -42,6 +39,12 @@ Person Doe The placeholder for the name input field. + + No user provided. + + + Save + diff --git a/packages/elements/xliff/pt-PT.xlf b/packages/elements/xliff/pt-PT.xlf index c90939caf..b2fbc483f 100644 --- a/packages/elements/xliff/pt-PT.xlf +++ b/packages/elements/xliff/pt-PT.xlf @@ -5,9 +5,6 @@ Title - - Content - Actions @@ -42,6 +39,12 @@ Person Doe The placeholder for the name input field. + + No user provided. + + + Save + diff --git a/packages/elements/xliff/ru.xlf b/packages/elements/xliff/ru.xlf index ed631df88..63ad3ac59 100644 --- a/packages/elements/xliff/ru.xlf +++ b/packages/elements/xliff/ru.xlf @@ -5,9 +5,6 @@ Title - - Content - Actions @@ -42,6 +39,12 @@ Person Doe The placeholder for the name input field. + + No user provided. + + + Save + diff --git a/packages/elements/xliff/tr-TR.xlf b/packages/elements/xliff/tr-TR.xlf index 14eddb612..9cbaed6ca 100644 --- a/packages/elements/xliff/tr-TR.xlf +++ b/packages/elements/xliff/tr-TR.xlf @@ -5,9 +5,6 @@ Title - - Content - Actions @@ -42,6 +39,12 @@ Person Doe The placeholder for the name input field. + + No user provided. + + + Save + diff --git a/packages/elements/xliff/zh-CN.xlf b/packages/elements/xliff/zh-CN.xlf index b4b558b6e..0f8b1a4bf 100644 --- a/packages/elements/xliff/zh-CN.xlf +++ b/packages/elements/xliff/zh-CN.xlf @@ -5,9 +5,6 @@ Title - - Content - Actions @@ -42,6 +39,12 @@ Person Doe The placeholder for the name input field. + + No user provided. + + + Save + diff --git a/packages/elements/xliff/zh-HK.xlf b/packages/elements/xliff/zh-HK.xlf index ccec972b9..823a36418 100644 --- a/packages/elements/xliff/zh-HK.xlf +++ b/packages/elements/xliff/zh-HK.xlf @@ -5,9 +5,6 @@ Title - - Content - Actions @@ -42,6 +39,12 @@ Person Doe The placeholder for the name input field. + + No user provided. + + + Save + diff --git a/packages/elements/xliff/zh-TW.xlf b/packages/elements/xliff/zh-TW.xlf index 718b58ddc..169de1214 100644 --- a/packages/elements/xliff/zh-TW.xlf +++ b/packages/elements/xliff/zh-TW.xlf @@ -5,9 +5,6 @@ Title - - Content - Actions @@ -42,6 +39,12 @@ Person Doe The placeholder for the name input field. + + No user provided. + + + Save + diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8593912e6..ecdde142e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -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)