0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2024-12-16 20:26:19 -05:00

refactor: reorg elements project (#6708)

This commit is contained in:
Xiao Yijun 2024-10-22 15:29:21 +08:00 committed by GitHub
parent 95c7d8b5be
commit 98bd9bb4e0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
62 changed files with 143 additions and 2413 deletions

View file

@ -1,3 +0,0 @@
# Logto account elements
TBD

View file

@ -1,17 +0,0 @@
<!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>
<logto-account-provider>
Logto Account Provider
</logto-account-provider>
</body>
</html>

View file

@ -1,83 +0,0 @@
{
"name": "@logto/elements",
"version": "0.0.1",
"description": "Logto account elements.",
"author": "Silverhand Inc. <contact@silverhand.io>",
"homepage": "https://github.com/logto-io/logto#readme",
"license": "MPL-2.0",
"type": "module",
"private": true,
"main": "dist/index.js",
"module": "dist/index.js",
"exports": {
".": {
"import": "./dist/index.js",
"types": "./dist/index.d.ts"
},
"./react": {
"import": "./dist/react.js",
"types": "./dist/react.d.ts"
}
},
"files": [
"dist"
],
"repository": {
"type": "git",
"url": "git+https://github.com/logto-io/logto.git"
},
"scripts": {
"precommit": "lint-staged",
"build": "tsup",
"start": "web-dev-server",
"dev": "tsup --watch --no-splitting",
"lint": "eslint --ext .ts src",
"lint:report": "pnpm lint --format json --output-file report.json",
"test": "echo \"No tests yet.\"",
"test:ci": "pnpm run test --silent --coverage"
},
"engines": {
"node": "^20.9.0"
},
"bugs": {
"url": "https://github.com/logto-io/logto/issues"
},
"dependencies": {
"@lit/context": "^1.1.2",
"@lit/react": "^1.0.5",
"@silverhand/essentials": "^2.9.1",
"ky": "^1.2.3",
"lit": "^3.1.4"
},
"devDependencies": {
"@logto/schemas": "workspace:^1.20.0",
"@silverhand/eslint-config": "6.0.1",
"@silverhand/ts-config": "6.0.0",
"@types/node": "^20.9.5",
"@web/dev-server": "^0.4.6",
"@web/dev-server-esbuild": "^1.0.2",
"eslint": "^8.56.0",
"lint-staged": "^15.0.0",
"prettier": "^3.0.0",
"tsup": "^8.1.0"
},
"eslintConfig": {
"extends": "@silverhand",
"ignorePatterns": [
"src/generated/"
],
"rules": {
"no-console": "error",
"unicorn/prevent-abbreviations": [
"error",
{
"replacements": {
"var": false,
"vars": false
}
}
]
}
},
"prettier": "@silverhand/eslint-config/.prettierrc"
}

View file

@ -1,39 +0,0 @@
import { type UserProfileResponse } from '@logto/schemas';
import ky, { type KyInstance } from 'ky';
export type AccessTokenFetcher = () => Promise<string>;
/**
* The API client for the Logto account elements
*
* Used internally to interact with Account-related backend APIs, including the Profile API.
*/
export class LogtoAccountApi {
private readonly ky: KyInstance;
constructor(
/**
* The endpoint of the Logto instance.
*/
logtoEndpoint: string,
/**
* The function to fetch the account access token.
*/
accessTokenFetcher: AccessTokenFetcher
) {
this.ky = ky.create({
prefixUrl: logtoEndpoint,
hooks: {
beforeRequest: [
async (request) => {
request.headers.set('Authorization', `Bearer ${await accessTokenFetcher()}`);
},
],
},
});
}
async fetchUserProfile() {
return this.ky.get('api/profile').json<UserProfileResponse>();
}
}

View file

@ -1,3 +0,0 @@
export type { AccessTokenFetcher } from './api/index.js';
export * from './providers/logto-account-provider.js';

View file

@ -1,12 +0,0 @@
{
"extends": "@silverhand/ts-config/tsconfig.base",
"compilerOptions": {
"experimentalDecorators": true,
"useDefineForClassFields": false,
"noEmit": true
},
"include": [
"src",
"*.config.ts"
]
}

View file

@ -1,23 +0,0 @@
import fs from 'node:fs/promises';
import { defineConfig } from 'tsup';
export default defineConfig({
entry: ['src/index.ts', 'src/react.ts'],
format: 'esm',
dts: true,
clean: true,
esbuildPlugins: [
{
name: 'transform-svg',
setup: (build) => {
build.onLoad({ filter: /\.svg$/ }, async (arguments_) => {
const text = await fs.readFile(arguments_.path, 'utf8');
return {
contents: `import { html } from 'lit';\nexport default html\`${text}\`;`,
};
});
},
},
],
});

View file

@ -1,33 +0,0 @@
// eslint-disable-next-line unicorn/prevent-abbreviations
import { fileURLToPath } from 'node:url';
import { esbuildPlugin } from '@web/dev-server-esbuild';
const config = {
open: true,
watch: true,
appIndex: 'index.html',
nodeResolve: {
exportConditions: ['development'],
},
plugins: [
esbuildPlugin({
ts: true,
tsconfig: fileURLToPath(new URL('tsconfig.json', import.meta.url)),
}),
// Transform SVG files into Lit templates
{
name: 'transform-svg',
transform(context) {
if (context.path.endsWith('.svg')) {
return {
body: `import { html } from 'lit';\nexport default html\`${context.body}\`;`,
headers: { 'content-type': 'application/javascript' },
};
}
},
},
],
};
export default config;

View file

@ -30,7 +30,6 @@
"@logto/cloud": "0.2.5-5e334eb",
"@logto/connector-kit": "workspace:^4.0.0",
"@logto/core-kit": "workspace:^2.5.0",
"@logto/elements": "workspace:^0.0.1",
"@logto/language-kit": "workspace:^1.1.0",
"@logto/phrases": "workspace:^1.14.0",
"@logto/phrases-experience": "workspace:^1.8.0",

View file

@ -1,6 +1,5 @@
import { createReactComponents, initLocalization } from '@logto/elements/react';
import type { ConnectorResponse } from '@logto/schemas';
import React, { useCallback, useState } from 'react';
import { useCallback, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useRoutes } from 'react-router-dom';
import useSWRImmutable from 'swr/immutable';
@ -9,7 +8,7 @@ import FormCard from '@/components/FormCard';
import PageMeta from '@/components/PageMeta';
import Topbar from '@/components/Topbar';
import { adminTenantEndpoint, meApi } from '@/consts';
import { isCloud, isDevFeaturesEnabled } from '@/consts/env';
import { isCloud } from '@/consts/env';
import AppBoundary from '@/containers/AppBoundary';
import Button from '@/ds-components/Button';
import CardTitle from '@/ds-components/CardTitle';
@ -32,12 +31,6 @@ import Skeleton from './components/Skeleton';
import DeleteAccountModal from './containers/DeleteAccountModal';
import styles from './index.module.scss';
if (isDevFeaturesEnabled) {
initLocalization();
}
const { LogtoProfileCard, LogtoThemeProvider, LogtoUserProvider } = createReactComponents(React);
function Profile() {
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
const { navigate } = useTenantPathname();
@ -69,64 +62,59 @@ function Profile() {
return (
<AppBoundary>
<LogtoThemeProvider theme="dark">
<LogtoUserProvider api={api}>
<div className={styles.pageContainer}>
<Topbar hideTenantSelector hideTitle />
<OverlayScrollbar className={styles.scrollable}>
<div className={styles.wrapper}>
<PageMeta titleKey="profile.page_title" />
<div className={pageLayout.headline}>
<CardTitle title="profile.title" subtitle="profile.description" />
</div>
{showLoadingSkeleton && <Skeleton />}
{isDevFeaturesEnabled && <LogtoProfileCard />}
{user && !showLoadingSkeleton && (
<div className={styles.content}>
<BasicUserInfoSection user={user} onUpdate={reload} />
{isCloud && (
<LinkAccountSection user={user} connectors={connectors} onUpdate={reload} />
)}
<FormCard title="profile.password.title">
<CardContent
title="profile.password.password_setting"
data={[
{
key: 'password',
label: 'profile.password.password',
value: user.hasPassword,
renderer: (value) => (value ? <span>********</span> : <NotSet />),
action: {
name: 'profile.change',
handler: () => {
navigate(user.hasPassword ? 'verify-password' : 'change-password', {
state: { email: user.primaryEmail, action: 'changePassword' },
});
},
},
<div className={styles.pageContainer}>
<Topbar hideTenantSelector hideTitle />
<OverlayScrollbar className={styles.scrollable}>
<div className={styles.wrapper}>
<PageMeta titleKey="profile.page_title" />
<div className={pageLayout.headline}>
<CardTitle title="profile.title" subtitle="profile.description" />
</div>
{showLoadingSkeleton && <Skeleton />}
{user && !showLoadingSkeleton && (
<div className={styles.content}>
<BasicUserInfoSection user={user} onUpdate={reload} />
{isCloud && (
<LinkAccountSection user={user} connectors={connectors} onUpdate={reload} />
)}
<FormCard title="profile.password.title">
<CardContent
title="profile.password.password_setting"
data={[
{
key: 'password',
label: 'profile.password.password',
value: user.hasPassword,
renderer: (value) => (value ? <span>********</span> : <NotSet />),
action: {
name: 'profile.change',
handler: () => {
navigate(user.hasPassword ? 'verify-password' : 'change-password', {
state: { email: user.primaryEmail, action: 'changePassword' },
});
},
]}
/>
</FormCard>
{isCloud && (
<FormCard title="profile.delete_account.title">
<div className={styles.deleteAccount}>
<div className={styles.description}>
{t('profile.delete_account.description')}
</div>
<Button title="profile.delete_account.button" onClick={show} />
</div>
<DeleteAccountModal isOpen={showDeleteAccountModal} onClose={hide} />
</FormCard>
)}
</div>
},
},
]}
/>
</FormCard>
{isCloud && (
<FormCard title="profile.delete_account.title">
<div className={styles.deleteAccount}>
<div className={styles.description}>
{t('profile.delete_account.description')}
</div>
<Button title="profile.delete_account.button" onClick={show} />
</div>
<DeleteAccountModal isOpen={showDeleteAccountModal} onClose={hide} />
</FormCard>
)}
</div>
</OverlayScrollbar>
{childrenRoutes}
)}
</div>
</LogtoUserProvider>
</LogtoThemeProvider>
</OverlayScrollbar>
{childrenRoutes}
</div>
</AppBoundary>
);
}

View file

@ -1,7 +0,0 @@
# @logto/elements
## 0.0.1
### Patch Changes
- fae8725a4: improve RTL language support

View file

@ -5,15 +5,13 @@
<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>
<script type="module" src="./src/account/index.ts"></script>
</head>
<body style="background: #111;">
<logto-theme-provider theme="dark">
<logto-user-provider>
<logto-profile-card></logto-profile-card>
</logto-user-provider>
</logto-theme-provider>
<body style="background: #ecebf5;">
<logto-account-provider>
Logto Account Provider
</logto-account-provider>
</body>
</html>

View file

@ -1,31 +0,0 @@
{
"$schema": "https://raw.githubusercontent.com/lit/lit/main/packages/localize-tools/config.schema.json",
"sourceLocale": "en",
"targetLocales": [
"ar-AR",
"de",
"es",
"fr",
"it",
"ja",
"ko",
"pl-PL",
"pt-BR",
"pt-PT",
"ru",
"tr-TR",
"zh-CN",
"zh-HK",
"zh-TW"
],
"tsConfig": "./tsconfig.json",
"output": {
"mode": "runtime",
"outputDir": "./src/generated/locales",
"localeCodesModule": "./src/generated/locale-codes.ts"
},
"interchange": {
"format": "xliff",
"xliffDir": "./xliff/"
}
}

View file

@ -10,13 +10,13 @@
"main": "dist/index.js",
"module": "dist/index.js",
"exports": {
".": {
"import": "./dist/index.js",
"types": "./dist/index.d.ts"
"./account": {
"import": "./dist/account/index.js",
"types": "./dist/account/index.d.ts"
},
"./react": {
"import": "./dist/react.js",
"types": "./dist/react.d.ts"
"./account/react": {
"import": "./dist/account/react.js",
"types": "./dist/account/react.d.ts"
}
},
"files": [
@ -28,17 +28,14 @@
},
"scripts": {
"precommit": "lint-staged",
"build:only": "lit-localize build && tsup",
"build": "pnpm check && pnpm build:only",
"prepack": "pnpm build",
"build": "tsup",
"start": "web-dev-server",
"dev": "lit-localize build && tsup --watch --no-splitting",
"dev": "tsup --watch --no-splitting",
"lint": "eslint --ext .ts src",
"lint:report": "pnpm lint --format json --output-file report.json",
"test": "echo \"No tests yet.\"",
"test:ci": "pnpm run test --silent --coverage",
"prepack": "pnpm build:only",
"localize": "lit-localize",
"check": "if command -v git &> /dev/null; then lit-localize extract && git add . -N && git diff --exit-code; fi"
"test:ci": "pnpm run test --silent --coverage"
},
"engines": {
"node": "^20.9.0"

View file

@ -0,0 +1,49 @@
import ky, { type KyInstance } from 'ky';
import { type UserProfile } from '../types.js';
export type GetAccessToken = () => Promise<string>;
/**
* The API client for the Logto account elements
*
* Used to interact with Account-related backend APIs, including the Profile API.
*/
export class LogtoAccountApi {
private readonly ky: KyInstance;
constructor(
/**
* The endpoint URL of the Logto service.
*
* Example: 'https://your-tenant-id.logto.app'
*/
logtoEndpoint: string,
/**
* Obtains the access token for Account-related API interactions.
*
* Called every time the account elements make a request to the backend API.
*
* Should handle access token expiration and request a new token when needed.
*
* Note: If using the `getAccessToken` method provided by the Logto SDK,
* it already ensures a valid access token is obtained.
*/
getAccessToken: GetAccessToken
) {
this.ky = ky.create({
prefixUrl: logtoEndpoint,
hooks: {
beforeRequest: [
async (request) => {
request.headers.set('Authorization', `Bearer ${await getAccessToken()}`);
},
],
},
});
}
async fetchUserProfile() {
return this.ky.get('api/profile').json<UserProfile>();
}
}

View file

@ -0,0 +1,3 @@
export * from './api/index.js';
export * from './providers/logto-account-provider.js';

View file

@ -2,7 +2,7 @@ import { html, LitElement } from 'lit';
import { customElement } from 'lit/decorators.js';
const tagName = 'logto-account-provider';
@customElement('logto-account-provider')
@customElement(tagName)
export class LogtoAccountProvider extends LitElement {
static tagName = tagName;

View file

@ -2,7 +2,7 @@ import { createComponent } from '@lit/react';
import { LogtoAccountProvider } from './index.js';
export type { AccessTokenFetcher } from './api/index.js';
export * from './api/index.js';
export const createReactComponents = (react: Parameters<typeof createComponent>[0]['react']) => {
return {

View file

@ -0,0 +1,3 @@
import { type UserProfileResponse } from '@logto/schemas';
export type UserProfile = Partial<UserProfileResponse>;

View file

@ -1,53 +0,0 @@
import { msg } from '@lit/localize';
import { LitElement, css } from 'lit';
import { customElement, property } from 'lit/decorators.js';
import { unit } from '../utils/css.js';
const tagName = 'logto-avatar';
const sizes = Object.freeze({
medium: unit(8),
large: unit(10),
});
@customElement(tagName)
export class LogtoAvatar extends LitElement {
static tagName = tagName;
static styles = css`
:host {
display: block;
border-radius: ${unit(2)};
background-size: cover;
background-position: center;
background-repeat: no-repeat;
}
`;
@property({ reflect: true })
size: 'medium' | 'large' = 'medium';
@property({ reflect: true })
src = '';
@property({ reflect: true })
alt = msg('Avatar', {
id: 'account.profile.personal-info.avatar',
desc: 'The avatar of the user.',
});
connectedCallback(): void {
super.connectedCallback();
this.style.setProperty('width', sizes[this.size].cssText);
this.style.setProperty('height', sizes[this.size].cssText);
if (this.src) {
// Show the image holder with the provided image.
this.style.setProperty('background-color', '#adaab422');
this.style.setProperty('background-image', `url(${this.src})`);
} else {
// A temporary default fallback color. Need to implement the relevant logic in `<UserAvatar />` later.
this.style.setProperty('background-color', '#e74c3c');
}
}
}

View file

@ -1,106 +0,0 @@
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

@ -1,58 +0,0 @@
import { LitElement, css, html } from 'lit';
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)};
border-radius: ${unit(2)};
cursor: pointer;
}
:host(:disabled) {
cursor: not-allowed;
}
`,
buttonSizes,
textButton,
defaultButton,
primaryButton,
];
@property({ reflect: true })
type: 'default' | 'text' | 'primary' = 'default';
@property({ reflect: true })
size: 'small' | 'medium' | 'large' = 'medium';
connectedCallback(): void {
super.connectedCallback();
this.role = 'button';
this.tabIndex = 0;
}
render() {
return html`<slot></slot>`;
}
}

View file

@ -1,33 +0,0 @@
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';
const tagName = 'logto-card-section';
/** A section in a form card with a heading. It is used to group related content. */
@customElement(tagName)
@localized()
export class LogtoCardSection extends LitElement {
static tagName = tagName;
static styles = css`
header {
font: ${vars.fontLabel2};
color: ${vars.colorTextPrimary};
margin-bottom: ${unit(1)};
}
`;
@property()
heading = notSet;
render() {
return html`
<header>${this.heading}</header>
<slot></slot>
`;
}
}

View file

@ -1,33 +0,0 @@
import { LitElement, css, html } from 'lit';
import { customElement } from 'lit/decorators.js';
import { unit } from '../utils/css.js';
import { vars } from '../utils/theme.js';
const tagName = 'logto-card';
/**
* A card with background, padding, and border radius.
*
* @example
* ```html
* <logto-card>
* <!-- Content goes here -->
* </logto-card>
* ```
*/
@customElement(tagName)
export class LogtoCard extends LitElement {
static tagName = tagName;
static styles = css`
:host {
background: ${vars.colorLayer1};
border-radius: ${unit(4)};
padding: ${unit(6)};
}
`;
render() {
return html`<slot></slot>`;
}
}

View file

@ -1,78 +0,0 @@
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';
const tagName = 'logto-form-card';
/**
* A card that contains a form or form-like content. A heading and an optional description can be
* provided to describe the purpose of the content.
*
* To group related content in a form card, use the `logto-card-section` element.
*
* @example
* ```tsx
* html`
* <logto-form-card heading=${msg('Account', ...)}>
* <logto-card-section heading=${msg('Personal information', ...)}>
* <!-- Content goes here -->
* </logto-card-section>
* <logto-card-section heading=${msg('Account settings', ...)}>
* <!-- Content goes here -->
* </logto-card-section>
* </logto-form-card>
* `
* ```
*/
@customElement(tagName)
@localized()
export class LogtoFormCard extends LitElement {
static tagName = tagName;
static styles = css`
logto-card {
display: flex;
padding: ${unit(6, 8)};
}
header {
flex: 7;
font: ${vars.fontSectionHeading1};
color: ${vars.colorCardTitle};
letter-spacing: 0.1em;
text-transform: uppercase;
}
div.spacer {
flex: 1;
}
slot {
display: block;
flex: 16;
}
`;
@property()
heading = notSet;
@property()
description = '';
render() {
return html`
<logto-card>
<header>
<div role="heading">${this.heading}</div>
${cond(this.description && html`<p>${this.description}</p>`)}
</header>
<div class="spacer"></div>
<slot></slot>
</logto-card>
`;
}
}

View file

@ -1,51 +0,0 @@
import { LitElement, html, css } from 'lit';
import { customElement } from 'lit/decorators.js';
import { unit } from '../utils/css.js';
import { vars } from '../utils/theme.js';
const tagName = 'logto-icon-button';
@customElement(tagName)
export class LogtoIconButton extends LitElement {
static tagName = tagName;
static styles = css`
:host {
all: unset;
border-radius: ${unit(1.5)};
transition: background 0.2s ease-in-out;
padding: ${unit(1)};
display: inline-flex;
justify-content: center;
align-items: center;
cursor: pointer;
}
:host(:disabled) {
cursor: not-allowed;
}
:host(:focus-visible) {
background: ${vars.colorFocused};
}
:host(:not(:disabled):hover) {
background: ${vars.colorHover};
}
::slotted(svg) {
color: ${vars.colorTextSecondary};
}
`;
connectedCallback(): void {
super.connectedCallback();
this.role = 'button';
this.tabIndex = 0;
}
render() {
return html`<slot></slot>`;
}
}

View file

@ -1,55 +0,0 @@
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';
const tagName = 'logto-list-row';
@customElement(tagName)
@localized()
export class LogtoListRow extends LitElement {
static tagName = tagName;
static styles = css`
:host {
box-sizing: border-box;
display: grid;
height: ${unit(16)};
padding: ${unit(2, 6)};
grid-template-columns: 1fr 2fr 1fr;
align-items: center;
color: ${vars.colorTextPrimary};
font: ${vars.fontBody2};
}
:host(:not(:last-child)) {
border-bottom: 1px solid ${vars.colorLineDivider};
}
slot {
display: block;
}
slot[name='title'] {
font: ${vars.fontLabel2};
}
slot[name='actions'] {
text-align: end;
}
span.not-set {
color: ${vars.colorTextSecondary};
}
`;
render() {
return html`
<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

@ -1,23 +0,0 @@
import { LitElement, css, html } from 'lit';
import { customElement } from 'lit/decorators.js';
import { unit } from '../utils/css.js';
import { vars } from '../utils/theme.js';
const tagName = 'logto-list';
@customElement(tagName)
export class LogtoList extends LitElement {
static tagName = tagName;
static styles = css`
:host {
display: block;
border-radius: ${unit(2)};
border: 1px solid ${vars.colorLineDivider};
}
`;
render() {
return html`<slot></slot>`;
}
}

View file

@ -1,75 +0,0 @@
import { consume } from '@lit/context';
import { localized, msg } from '@lit/localize';
import { cond, noop } from '@silverhand/essentials';
import { LitElement, html, css } from 'lit';
import { customElement, property } from 'lit/decorators.js';
import closeIcon from '../icons/close.svg';
import { unit } from '../utils/css.js';
import { vars } from '../utils/theme.js';
import { ModalContext, type ModalContextType } from './logto-modal.context.js';
const tagName = 'logto-modal-layout';
/**
* A typical layout for a modal. It includes a header, a footer, and a slot for the content.
*
* Note: A consumable modal context ({@link ModalContext}) is required to use this component.
*/
@customElement(tagName)
@localized()
export class LogtoModalLayout extends LitElement {
static tagName = tagName;
static styles = css`
header {
font: ${vars.fontTitle1};
color: ${vars.colorTextPrimary};
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: ${unit(6)};
}
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 })
heading = msg('Not set', {
id: 'general.fallback-title',
desc: 'A fallback title when the title or heading of a component is not provided.',
});
@consume({ context: ModalContext })
context?: ModalContextType;
render() {
const { onClose } = this.context ?? {};
return html`
<header>
<h1>${this.heading}</h1>
${cond(
onClose &&
onClose !== noop &&
html`<logto-icon-button @click=${onClose}>${closeIcon}</logto-icon-button>`
)}
</header>
<slot></slot>
<footer>
<slot name="footer"></slot>
</footer>
`;
}
}

View file

@ -1,16 +0,0 @@
import { createContext } from '@lit/context';
import { noop } from '@silverhand/essentials';
/** @see {@link ModalContext} */
export type ModalContextType = { onClose: () => void };
/**
* Context for the modal component. It's useful for operating the modal from deep in the component
* tree. For example, closing the modal from a child component.
*/
export const ModalContext = createContext<ModalContextType>('modal-context');
/** The default value for the modal context. */
export const modalContext: ModalContextType = {
onClose: noop,
};

View file

@ -1,88 +0,0 @@
import { provide } from '@lit/context';
import { noop } from '@silverhand/essentials';
import { LitElement, html, css, type PropertyValues } from 'lit';
import { customElement, property } from 'lit/decorators.js';
import { type Ref, createRef, ref } from 'lit/directives/ref.js';
import { unit } from '../utils/css.js';
import { vars } from '../utils/theme.js';
import { ModalContext, modalContext } from './logto-modal.context.js';
const tagName = 'logto-modal';
@customElement(tagName)
export class LogtoModal extends LitElement {
static tagName = tagName;
static styles = css`
dialog {
padding: 0;
border: none;
color: ${vars.colorTextPrimary};
background: ${vars.colorLayer1};
border-radius: ${unit(4)};
padding: ${unit(6)};
width: 95%;
max-width: 600px;
min-width: 300px;
}
dialog::backdrop {
background-color: ${vars.colorOverlay};
}
`;
@property({ type: Boolean, reflect: true })
open = false;
@property()
onClose = noop;
@provide({ context: ModalContext })
context = modalContext;
protected dialogRef: Ref<HTMLDialogElement> = createRef();
render() {
return html`<dialog
${ref(this.dialogRef)}
@keydown=${(event: KeyboardEvent) => {
// The "right" way should be to use the `@cancel` event, but it doesn't always work and the
// browser support is unknown. See: https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/cancel_event#browser_compatibility
if (event.key === 'Escape') {
event.preventDefault();
this.onClose();
}
}}
>
<slot></slot>
</dialog> `;
}
protected toggleDialog() {
if (this.open) {
this.dialogRef.value?.showModal();
} else {
this.dialogRef.value?.close();
}
}
protected handlePropertiesChange(changedProperties: PropertyValues) {
if (changedProperties.has('open')) {
this.toggleDialog();
}
if (changedProperties.has('onClose')) {
this.context.onClose = this.onClose;
}
}
protected firstUpdated(changedProperties: PropertyValues): void {
this.handlePropertiesChange(changedProperties);
}
protected updated(changedProperties: PropertyValues): void {
this.handlePropertiesChange(changedProperties);
}
}

View file

@ -1,85 +0,0 @@
import { LitElement, css, html } from 'lit';
import { customElement, property, queryAssignedElements } from 'lit/decorators.js';
import { unit } from '../utils/css.js';
import { vars } from '../utils/theme.js';
const tagName = 'logto-text-input';
@customElement(tagName)
export class LogtoTextInput extends LitElement {
static tagName = tagName;
static styles = css`
:host {
display: flex;
position: relative;
border-radius: ${unit(1.5)};
border: 1px solid ${vars.colorBorder};
outline: 3px solid transparent;
transition-property: outline, border;
transition-timing-function: ease-in-out;
transition-duration: 0.2s;
height: 36px;
background: ${vars.colorLayer1};
font: ${vars.fontBody2};
padding: 0 ${unit(3)};
}
:host(:focus-within) {
border-color: ${vars.colorPrimary};
outline-color: ${vars.colorFocusedVariant};
}
:host([disabled]) {
background: ${vars.colorDisabledBackground};
color: ${vars.colorTextSecondary};
border-color: ${vars.colorBorder};
cursor: not-allowed;
}
:host([readonly]) {
background: ${vars.colorLayer2};
}
:host([readonly]:focus-within) {
border-color: ${vars.colorBorder};
outline-color: transparent;
}
::slotted(input) {
all: unset;
flex: 1;
color: ${vars.colorTextPrimary};
}
::slotted(input::placeholder) {
color: ${vars.colorPlaceholder};
}
::slotted(input:webkit-autofill) {
box-shadow: 0 0 0 ${unit(6)} ${vars.colorLayer1} inset;
-webkit-text-fill-color: ${vars.colorTextPrimary};
caret-color: ${vars.colorTextPrimary};
}
`;
@property({ type: Boolean, reflect: true })
disabled = false;
@property({ type: Boolean, reflect: true })
readonly = false;
@queryAssignedElements({ selector: 'input' })
slotInputs!: HTMLInputElement[];
render() {
return html`<slot @slotchange=${this.handleSlotChange}></slot>`;
}
protected handleSlotChange() {
if (this.slotInputs[0] && this.slotInputs.length === 1) {
this.disabled = this.slotInputs[0].disabled;
this.readonly = this.slotInputs[0].readOnly;
}
}
}

View file

@ -1,129 +0,0 @@
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';
@customElement(tagName)
@localized()
export class LogtoProfileCard extends LitElement {
static tagName = tagName;
static styles = css`
p.dev {
color: ${vars.colorTextSecondary};
}
`;
@consume({ context: UserContext, subscribe: true })
userContext?: UserContextType;
@state()
updateNameOpened = false;
@state()
name = '';
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>
<logto-card-section
heading=${msg('Personal information', { id: 'account.profile.personal-info.title' })}
>
<logto-list>
<logto-list-row>
<div slot="title">
${msg('Avatar', {
id: 'account.profile.personal-info.avatar',
desc: 'The avatar of the user.',
})}
</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" size="small">
${msg('Change', { id: 'general.change' })}
</logto-button>
</div>
</logto-list-row>
<logto-list-row>
<div slot="title">
${msg('Name', {
id: 'account.profile.personal-info.name',
desc: 'The name of the user.',
})}
</div>
${cond(user.name && html`<div slot="content">${user.name}</div>`)}
<div slot="actions">
<logto-button
type="text"
size="small"
@click=${() => {
this.updateNameOpened = true;
this.name = user.name ?? '';
}}
>
${msg('Update', { id: 'general.update' })}
</logto-button>
</div>
</logto-list-row>
</logto-list>
</logto-card-section>
</logto-form-card>
<logto-modal
?open=${this.updateNameOpened}
.onClose=${() => {
this.updateNameOpened = false;
}}
>
<logto-modal-layout
heading=${msg('Update name', {
id: 'account.profile.personal-info.update-name',
})}
>
<logto-text-input>
<input
placeholder=${msg('Person Doe', {
id: 'account.profile.personal-info.name-placeholder',
desc: 'The placeholder for the name input field.',
})}
.value=${this.name}
@input=${(event: InputEvent) => {
// eslint-disable-next-line no-restricted-syntax
this.name = (event.target as HTMLInputElement).value;
}}
/>
</logto-text-input>
<logto-button
slot="footer"
size="large"
type="primary"
@click=${async () => {
await this.userContext?.updateUser({ name: this.name });
this.updateNameOpened = false;
}}
>
${msg('Save', { id: 'general.save' })}
</logto-button>
</logto-modal-layout>
</logto-modal>
`;
}
}

View file

@ -1,5 +0,0 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M13.4099 12.0002L19.7099 5.71019C19.8982 5.52188 20.004 5.26649 20.004 5.00019C20.004 4.73388 19.8982 4.47849 19.7099 4.29019C19.5216 4.10188 19.2662 3.99609 18.9999 3.99609C18.7336 3.99609 18.4782 4.10188 18.2899 4.29019L11.9999 10.5902L5.70994 4.29019C5.52164 4.10188 5.26624 3.99609 4.99994 3.99609C4.73364 3.99609 4.47824 4.10188 4.28994 4.29019C4.10164 4.47849 3.99585 4.73388 3.99585 5.00019C3.99585 5.26649 4.10164 5.52188 4.28994 5.71019L10.5899 12.0002L4.28994 18.2902C4.19621 18.3831 4.12182 18.4937 4.07105 18.6156C4.02028 18.7375 3.99414 18.8682 3.99414 19.0002C3.99414 19.1322 4.02028 19.2629 4.07105 19.3848C4.12182 19.5066 4.19621 19.6172 4.28994 19.7102C4.3829 19.8039 4.4935 19.8783 4.61536 19.9291C4.73722 19.9798 4.86793 20.006 4.99994 20.006C5.13195 20.006 5.26266 19.9798 5.38452 19.9291C5.50638 19.8783 5.61698 19.8039 5.70994 19.7102L11.9999 13.4102L18.2899 19.7102C18.3829 19.8039 18.4935 19.8783 18.6154 19.9291C18.7372 19.9798 18.8679 20.006 18.9999 20.006C19.132 20.006 19.2627 19.9798 19.3845 19.9291C19.5064 19.8783 19.617 19.8039 19.7099 19.7102C19.8037 19.6172 19.8781 19.5066 19.9288 19.3848C19.9796 19.2629 20.0057 19.1322 20.0057 19.0002C20.0057 18.8682 19.9796 18.7375 19.9288 18.6156C19.8781 18.4937 19.8037 18.3831 19.7099 18.2902L13.4099 12.0002Z"
fill="currentColor" />
</svg>

Before

Width:  |  Height:  |  Size: 1.4 KiB

View file

@ -1,4 +0,0 @@
declare module '*.svg' {
const value: string;
export default value;
}

View file

@ -1,18 +0,0 @@
export * from './components/logto-avatar.js';
export * from './components/logto-button.js';
export * from './components/logto-card-section.js';
export * from './components/logto-card.js';
export * from './components/logto-form-card.js';
export * from './components/logto-icon-button.js';
export * from './components/logto-list-row.js';
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 './elements/logto-profile-card.js';
export * from './providers/logto-theme-provider.js';
export * from './providers/logto-user-provider.js';
export * from './utils/api.js';
export * from './utils/locale.js';

View file

@ -1,6 +0,0 @@
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

@ -1,22 +0,0 @@
import { LitElement, css, html } from 'lit';
import { customElement, property } from 'lit/decorators.js';
import { defaultTheme, darkTheme, toLitCss } from '../utils/theme.js';
const tagName = 'logto-theme-provider';
@customElement(tagName)
export class LogtoThemeProvider extends LitElement {
static tagName = tagName;
static styles = css`
${toLitCss(defaultTheme)}
${toLitCss(darkTheme, 'dark')}
`;
@property({ reflect: true })
theme: 'default' | 'dark' = 'default';
render() {
return html`<slot></slot>`;
}
}

View file

@ -1,61 +0,0 @@
import { createContext, provide } from '@lit/context';
import { type UserInfo } from '@logto/schemas';
import { noop } from '@silverhand/essentials';
import { LitElement, type PropertyValues, html } from 'lit';
import { customElement, property } from 'lit/decorators.js';
import { LogtoAccountApi } from '../utils/api.js';
/** @see {@link UserContext} */
export type UserContextType = Readonly<{
user?: UserInfo;
updateUser: (user: Partial<UserInfo>) => void | Promise<void>;
}>;
/**
* Context for the current user. It's a fundamental context for the account-related elements.
*/
export const UserContext = createContext<UserContextType>('user-context');
/** The default value for the user context. */
export const userContext: UserContextType = Object.freeze({
updateUser: noop,
});
const tagName = 'logto-user-provider';
@customElement(tagName)
export class LogtoUserProvider extends LitElement {
static tagName = tagName;
@provide({ context: UserContext })
context = userContext;
@property({ type: Object })
api!: LogtoAccountApi | ConstructorParameters<typeof LogtoAccountApi>[0];
render() {
return html`<slot></slot>`;
}
protected updateContext(context: Partial<UserContextType>) {
this.context = Object.freeze({ ...this.context, ...context });
}
protected async handlePropertiesChange(changedProperties: PropertyValues) {
if (changedProperties.has('api')) {
const api = this.api instanceof LogtoAccountApi ? this.api : new LogtoAccountApi(this.api);
this.updateContext({
updateUser: async (user) => {
const updated = await api.updateUser(user);
this.updateContext({ user: updated });
},
user: await api.getUser(),
});
}
}
protected firstUpdated(changedProperties: PropertyValues): void {
void this.handlePropertiesChange(changedProperties);
}
}

View file

@ -1,48 +0,0 @@
import { createComponent } from '@lit/react';
import {
LogtoThemeProvider,
LogtoCard,
LogtoFormCard,
LogtoProfileCard,
LogtoList,
LogtoUserProvider,
} from './index.js';
export * from './utils/locale.js';
export * from './utils/api.js';
export const createReactComponents = (react: Parameters<typeof createComponent>[0]['react']) => {
return {
LogtoFormCard: createComponent({
tagName: LogtoFormCard.tagName,
elementClass: LogtoFormCard,
react,
}),
LogtoList: createComponent({
tagName: LogtoList.tagName,
elementClass: LogtoList,
react,
}),
LogtoProfileCard: createComponent({
tagName: LogtoProfileCard.tagName,
elementClass: LogtoProfileCard,
react,
}),
LogtoCard: createComponent({
tagName: LogtoCard.tagName,
elementClass: LogtoCard,
react,
}),
LogtoThemeProvider: createComponent({
tagName: LogtoThemeProvider.tagName,
elementClass: LogtoThemeProvider,
react,
}),
LogtoUserProvider: createComponent({
tagName: LogtoUserProvider.tagName,
elementClass: LogtoUserProvider,
react,
}),
};
};

View file

@ -1,22 +0,0 @@
import { type UserInfo } from '@logto/schemas';
import originalKy, { type Options, type KyInstance } from 'ky';
/**
* CAUTION: The current implementation is based on the admin tenant's `/me` API which is interim.
* The final implementation should be based on the Account API.
*/
export class LogtoAccountApi {
protected ky: KyInstance;
constructor(init: KyInstance | Options) {
this.ky = 'create' in init ? init : originalKy.create(init);
}
async getUser() {
return this.ky('me').json<UserInfo>();
}
async updateUser(user: Partial<UserInfo>) {
return this.ky.patch('me', { json: user }).json<UserInfo>();
}
}

View file

@ -1,37 +0,0 @@
import { type CSSResult, unsafeCSS } from 'lit';
type Unit = {
/**
* @example unit(1) // '4px'
*/
(value: number): CSSResult;
/**
* @example unit(1, 2) // '4px 8px'
*/
(value1: number, value2: number): CSSResult;
/**
* @example unit(1, 2, 3) // '4px 8px 12px'
*/
// eslint-disable-next-line @typescript-eslint/unified-signatures -- for better readability
(value1: number, value2: number, value3: number): CSSResult;
/**
* @example unit(1, 2, 3, 4) // '4px 8px 12px 16px'
*/
// eslint-disable-next-line @typescript-eslint/unified-signatures -- for better readability
(value1: number, value2: number, value3: number, value4: number): CSSResult;
};
/**
* Returns a `CSSResult` that represents the given values in pixels. The values are multiplied by 4.
*/
export const unit: Unit = (...values: number[]) => {
if (values.length === 0 || values.length > 4) {
throw new Error('unit() accepts 1 to 4 arguments');
}
if (values.some((value) => typeof value !== 'number')) {
throw new Error('unit() accepts only numbers');
}
return unsafeCSS(values.map((value) => `${value * 4}px`).join(' '));
};

View file

@ -1,11 +0,0 @@
import { configureLocalization } from '@lit/localize';
// Generated via output.localeCodesModule
import { sourceLocale, targetLocales } from '../generated/locale-codes.js';
export const initLocalization = () =>
configureLocalization({
sourceLocale,
targetLocales,
loadLocale: async (locale) => import(`/locales/${locale}.js`),
});

View file

@ -1,19 +0,0 @@
export type KebabCase<T extends string> = T extends `${infer L}${infer M}${infer R}`
? L extends Lowercase<L>
? M extends Lowercase<M>
? `${L}${KebabCase<`${M}${R}`>}`
: `${L}-${KebabCase<`${M}${R}`>}`
: M extends Lowercase<M>
? `${Lowercase<L>}${KebabCase<`${M}${R}`>}`
: R extends Uncapitalize<R>
? `${Lowercase<L>}-${Lowercase<M>}${KebabCase<R>}`
: `${Lowercase<L>}${KebabCase<`${M}${R}`>}`
: T;
export const kebabCase = <T extends string>(value: T): KebabCase<T> => {
// eslint-disable-next-line no-restricted-syntax -- `as` assertion is needed to make TS happy
return value
.replaceAll(/([^A-Z])([A-Z])/g, '$1-$2')
.replaceAll(/([A-Z])([A-Z][^A-Z])/g, '$1-$2')
.toLowerCase() as KebabCase<T>;
};

View file

@ -1,174 +0,0 @@
import { type CSSResult, unsafeCSS } from 'lit';
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;
colorBorder: string;
colorCardTitle: string;
colorLayer1: string;
colorLayer2: string;
colorLineDivider: string;
colorDisabled: string;
colorDisabledBackground: string;
colorHover: string;
colorHoverVariant: string;
colorPressed: string;
colorFocused: string;
colorFocusedVariant: string;
colorOverlay: string;
colorPlaceholder: string;
};
/** All the fonts to be used in the Logto components and elements. */
export type Font = {
fontTitle1: string;
fontTitle2: string;
fontTitle3: string;
fontLabel1: string;
fontLabel2: string;
fontLabel3: string;
fontBody1: string;
fontBody2: string;
fontBody3: string;
fontSectionHeading1: string;
fontSectionHeading2: string;
};
/** The complete styling properties to be used in the Logto components and elements. */
export type Theme = Color & Font;
export const defaultFontFamily =
'-apple-system, system-ui, BlinkMacSystemFont, Segoe UI, Roboto, Helvetica Neue, Helvetica, Arial, sans-serif, Apple Color Emoji';
export const defaultFont: Readonly<Font> = Object.freeze({
fontTitle1: `600 20px / 28px ${defaultFontFamily}`,
fontTitle2: `600 16px / 24px ${defaultFontFamily}`,
fontTitle3: `600 14px / 20px ${defaultFontFamily}`,
fontLabel1: `500 16px / 24px ${defaultFontFamily}`,
fontLabel2: `500 14px / 20px ${defaultFontFamily}`,
fontLabel3: `500 12px / 16px ${defaultFontFamily}`,
fontBody1: `400 16px / 24px ${defaultFontFamily}`,
fontBody2: `400 14px / 20px ${defaultFontFamily}`,
fontBody3: `400 12px / 16px ${defaultFontFamily}`,
fontSectionHeading1: `700 12px / 16px ${defaultFontFamily}`,
fontSectionHeading2: `700 10px / 16px ${defaultFontFamily}`,
});
export const defaultTheme: Readonly<Theme> = Object.freeze({
...defaultFont,
colorPrimary: '#5d34f2',
colorOnPrimary: '#000',
colorPrimaryPressed: '#e6deff',
colorPrimaryHover: '#af9eff',
colorTextPrimary: '#191c1d',
colorTextLink: '#5d34f2',
colorTextSecondary: '#747778',
colorBorder: '#c4c7c7',
colorCardTitle: '#928f9a',
colorLayer1: '#000',
colorLayer2: '#2d3132',
colorLineDivider: '#191c1d1f',
colorDisabled: '#5c5f60',
colorDisabledBackground: '#2d3132',
colorHover: '#191c1d14',
colorHoverVariant: '#5d34f214',
colorPressed: 'rgba(25, 28, 29, 12%)',
colorFocused: 'rgba(25, 28, 29, 16%)',
colorFocusedVariant: '#5d34f229',
colorOverlay: '#000000b3',
colorPlaceholder: '#747778',
});
export const darkTheme: Readonly<Theme> = Object.freeze({
...defaultFont,
colorPrimary: '#7958ff',
colorOnPrimary: '#fff',
colorPrimaryPressed: '#5d34f2',
colorPrimaryHover: '#947dff',
colorTextPrimary: '#f7f8f8',
colorTextLink: '#cabeff',
colorTextSecondary: '#a9acac',
colorBorder: '#5c5f60',
colorCardTitle: '#928f9a',
colorLayer1: '#2a2c32',
colorLayer2: '#34353f',
colorLineDivider: '#f7f8f824',
colorDisabled: '#5c5f60',
colorDisabledBackground: '#2d3132',
colorHover: '#f7f8f814',
colorHoverVariant: '#cabeff14',
colorPressed: 'rgba(247, 248, 248, 12%)',
colorFocused: 'rgba(247, 248, 248, 16%)',
colorFocusedVariant: '#cabeff29',
colorOverlay: '#0000003c',
colorPlaceholder: '#747778',
});
/**
* Converts the theme object to a list of CSS custom properties entries. Each key is prefixed
* with `--logto-`.
*
* @example
* toLogtoCssEntries(defaultTheme) // [['--logto-color-primary', '#5d34f2'], ...]
*/
export const toLogtoCssEntries = (theme: Theme) =>
Object.entries(theme).map(([key, value]) =>
Object.freeze([`--logto-${kebabCase(key)}`, value] as const)
);
export type ToLogtoCssProperties<T extends Record<string, unknown>> = {
[K in keyof T as K extends string ? `--logto-${KebabCase<K>}` : never]: T[K];
};
/**
* Converts the theme object to a map of CSS custom properties. Each key is prefixed with
* `--logto-`.
*
* @example
* toLogtoCssProperties(defaultTheme) // { '--logto-color-primary': '#5d34f2', ... }
*/
export const toLogtoCssProperties = (theme: Theme): ToLogtoCssProperties<Theme> => {
// eslint-disable-next-line no-restricted-syntax -- `Object.fromEntries` will lose the type
return Object.fromEntries(toLogtoCssEntries(theme)) as ToLogtoCssProperties<Theme>;
};
/**
* Converts the given value to a logto CSS custom property prefixed with `--logto-`.
*
* @example
* toVar('colorPrimary') // '--logto-color-primary' in `CSSResult`
*/
export const toVar = (value: string) => unsafeCSS(`var(--logto-${kebabCase(value)})`);
/**
* The CSS custom properties in `CSSResult` format for a theme object. You can use this object
* to apply a custom property from the theme.
*
* @example
* css`
* p {
* color: ${vars.colorPrimary};
* }
* `
*/
// eslint-disable-next-line no-restricted-syntax -- `Object.fromEntries` will lose the type
export const vars: Record<keyof Theme, CSSResult> = Object.freeze(
Object.fromEntries(Object.keys(defaultTheme).map((key) => [key, toVar(key)]))
) as Record<keyof Theme, CSSResult>;
export const toLitCss = (theme: Theme, name?: string) =>
unsafeCSS(
`:host${typeof name === 'string' ? `([theme=${name}])` : ''} {\n` +
toLogtoCssEntries(theme)
.map(([key, value]) => `${key}: ${value};`)
.join('\n') +
'\n}'
);

View file

@ -3,7 +3,10 @@ import fs from 'node:fs/promises';
import { defineConfig } from 'tsup';
export default defineConfig({
entry: ['src/index.ts', 'src/react.ts'],
entry: {
'account/index': 'src/account/index.ts',
'account/react': 'src/account/react.ts',
},
format: 'esm',
dts: true,
clean: true,

View file

@ -1,50 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
<file target-language="ar-AR" source-language="en" original="lit-localize-inputs" datatype="plaintext">
<body>
<trans-unit id="general.title">
<source>Title</source>
</trans-unit>
<trans-unit id="general.actions">
<source>Actions</source>
</trans-unit>
<trans-unit id="account.profile.title">
<source>Profile</source>
</trans-unit>
<trans-unit id="account.profile.no-user">
<source>No user provided.</source>
</trans-unit>
<trans-unit id="account.profile.personal-info.title">
<source>Personal information</source>
</trans-unit>
<trans-unit id="general.change">
<source>Change</source>
</trans-unit>
<trans-unit id="general.update">
<source>Update</source>
</trans-unit>
<trans-unit id="account.profile.personal-info.update-name">
<source>Update name</source>
</trans-unit>
<trans-unit id="general.save">
<source>Save</source>
</trans-unit>
<trans-unit id="general.fallback-title">
<source>Not set</source>
<note from="lit-localize">A fallback title when the title or heading of a component is not provided.</note>
</trans-unit>
<trans-unit id="account.profile.personal-info.avatar">
<source>Avatar</source>
<note from="lit-localize">The avatar of the user.</note>
</trans-unit>
<trans-unit id="account.profile.personal-info.name">
<source>Name</source>
<note from="lit-localize">The name of the user.</note>
</trans-unit>
<trans-unit id="account.profile.personal-info.name-placeholder">
<source>Person Doe</source>
<note from="lit-localize">The placeholder for the name input field.</note>
</trans-unit>
</body>
</file>
</xliff>

View file

@ -1,50 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
<file target-language="de" source-language="en" original="lit-localize-inputs" datatype="plaintext">
<body>
<trans-unit id="general.title">
<source>Title</source>
</trans-unit>
<trans-unit id="general.actions">
<source>Actions</source>
</trans-unit>
<trans-unit id="account.profile.title">
<source>Profile</source>
</trans-unit>
<trans-unit id="account.profile.personal-info.title">
<source>Personal information</source>
</trans-unit>
<trans-unit id="general.change">
<source>Change</source>
</trans-unit>
<trans-unit id="general.update">
<source>Update</source>
</trans-unit>
<trans-unit id="account.profile.personal-info.update-name">
<source>Update name</source>
</trans-unit>
<trans-unit id="general.fallback-title">
<source>Not set</source>
<note from="lit-localize">A fallback title when the title or heading of a component is not provided.</note>
</trans-unit>
<trans-unit id="account.profile.personal-info.avatar">
<source>Avatar</source>
<note from="lit-localize">The avatar of the user.</note>
</trans-unit>
<trans-unit id="account.profile.personal-info.name">
<source>Name</source>
<note from="lit-localize">The name of the user.</note>
</trans-unit>
<trans-unit id="account.profile.personal-info.name-placeholder">
<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

@ -1,50 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
<file target-language="es" source-language="en" original="lit-localize-inputs" datatype="plaintext">
<body>
<trans-unit id="general.title">
<source>Title</source>
</trans-unit>
<trans-unit id="general.actions">
<source>Actions</source>
</trans-unit>
<trans-unit id="account.profile.title">
<source>Profile</source>
</trans-unit>
<trans-unit id="account.profile.personal-info.title">
<source>Personal information</source>
</trans-unit>
<trans-unit id="general.change">
<source>Change</source>
</trans-unit>
<trans-unit id="general.update">
<source>Update</source>
</trans-unit>
<trans-unit id="account.profile.personal-info.update-name">
<source>Update name</source>
</trans-unit>
<trans-unit id="general.fallback-title">
<source>Not set</source>
<note from="lit-localize">A fallback title when the title or heading of a component is not provided.</note>
</trans-unit>
<trans-unit id="account.profile.personal-info.avatar">
<source>Avatar</source>
<note from="lit-localize">The avatar of the user.</note>
</trans-unit>
<trans-unit id="account.profile.personal-info.name">
<source>Name</source>
<note from="lit-localize">The name of the user.</note>
</trans-unit>
<trans-unit id="account.profile.personal-info.name-placeholder">
<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

@ -1,50 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
<file target-language="fr" source-language="en" original="lit-localize-inputs" datatype="plaintext">
<body>
<trans-unit id="general.title">
<source>Title</source>
</trans-unit>
<trans-unit id="general.actions">
<source>Actions</source>
</trans-unit>
<trans-unit id="account.profile.title">
<source>Profile</source>
</trans-unit>
<trans-unit id="account.profile.personal-info.title">
<source>Personal information</source>
</trans-unit>
<trans-unit id="general.change">
<source>Change</source>
</trans-unit>
<trans-unit id="general.update">
<source>Update</source>
</trans-unit>
<trans-unit id="account.profile.personal-info.update-name">
<source>Update name</source>
</trans-unit>
<trans-unit id="general.fallback-title">
<source>Not set</source>
<note from="lit-localize">A fallback title when the title or heading of a component is not provided.</note>
</trans-unit>
<trans-unit id="account.profile.personal-info.avatar">
<source>Avatar</source>
<note from="lit-localize">The avatar of the user.</note>
</trans-unit>
<trans-unit id="account.profile.personal-info.name">
<source>Name</source>
<note from="lit-localize">The name of the user.</note>
</trans-unit>
<trans-unit id="account.profile.personal-info.name-placeholder">
<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

@ -1,50 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
<file target-language="it" source-language="en" original="lit-localize-inputs" datatype="plaintext">
<body>
<trans-unit id="general.title">
<source>Title</source>
</trans-unit>
<trans-unit id="general.actions">
<source>Actions</source>
</trans-unit>
<trans-unit id="account.profile.title">
<source>Profile</source>
</trans-unit>
<trans-unit id="account.profile.personal-info.title">
<source>Personal information</source>
</trans-unit>
<trans-unit id="general.change">
<source>Change</source>
</trans-unit>
<trans-unit id="general.update">
<source>Update</source>
</trans-unit>
<trans-unit id="account.profile.personal-info.update-name">
<source>Update name</source>
</trans-unit>
<trans-unit id="general.fallback-title">
<source>Not set</source>
<note from="lit-localize">A fallback title when the title or heading of a component is not provided.</note>
</trans-unit>
<trans-unit id="account.profile.personal-info.avatar">
<source>Avatar</source>
<note from="lit-localize">The avatar of the user.</note>
</trans-unit>
<trans-unit id="account.profile.personal-info.name">
<source>Name</source>
<note from="lit-localize">The name of the user.</note>
</trans-unit>
<trans-unit id="account.profile.personal-info.name-placeholder">
<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

@ -1,50 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
<file target-language="ja" source-language="en" original="lit-localize-inputs" datatype="plaintext">
<body>
<trans-unit id="general.title">
<source>Title</source>
</trans-unit>
<trans-unit id="general.actions">
<source>Actions</source>
</trans-unit>
<trans-unit id="account.profile.title">
<source>Profile</source>
</trans-unit>
<trans-unit id="account.profile.personal-info.title">
<source>Personal information</source>
</trans-unit>
<trans-unit id="general.change">
<source>Change</source>
</trans-unit>
<trans-unit id="general.update">
<source>Update</source>
</trans-unit>
<trans-unit id="account.profile.personal-info.update-name">
<source>Update name</source>
</trans-unit>
<trans-unit id="general.fallback-title">
<source>Not set</source>
<note from="lit-localize">A fallback title when the title or heading of a component is not provided.</note>
</trans-unit>
<trans-unit id="account.profile.personal-info.avatar">
<source>Avatar</source>
<note from="lit-localize">The avatar of the user.</note>
</trans-unit>
<trans-unit id="account.profile.personal-info.name">
<source>Name</source>
<note from="lit-localize">The name of the user.</note>
</trans-unit>
<trans-unit id="account.profile.personal-info.name-placeholder">
<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

@ -1,50 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
<file target-language="ko" source-language="en" original="lit-localize-inputs" datatype="plaintext">
<body>
<trans-unit id="general.title">
<source>Title</source>
</trans-unit>
<trans-unit id="general.actions">
<source>Actions</source>
</trans-unit>
<trans-unit id="account.profile.title">
<source>Profile</source>
</trans-unit>
<trans-unit id="account.profile.personal-info.title">
<source>Personal information</source>
</trans-unit>
<trans-unit id="general.change">
<source>Change</source>
</trans-unit>
<trans-unit id="general.update">
<source>Update</source>
</trans-unit>
<trans-unit id="account.profile.personal-info.update-name">
<source>Update name</source>
</trans-unit>
<trans-unit id="general.fallback-title">
<source>Not set</source>
<note from="lit-localize">A fallback title when the title or heading of a component is not provided.</note>
</trans-unit>
<trans-unit id="account.profile.personal-info.avatar">
<source>Avatar</source>
<note from="lit-localize">The avatar of the user.</note>
</trans-unit>
<trans-unit id="account.profile.personal-info.name">
<source>Name</source>
<note from="lit-localize">The name of the user.</note>
</trans-unit>
<trans-unit id="account.profile.personal-info.name-placeholder">
<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

@ -1,50 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
<file target-language="pl-PL" source-language="en" original="lit-localize-inputs" datatype="plaintext">
<body>
<trans-unit id="general.title">
<source>Title</source>
</trans-unit>
<trans-unit id="general.actions">
<source>Actions</source>
</trans-unit>
<trans-unit id="account.profile.title">
<source>Profile</source>
</trans-unit>
<trans-unit id="account.profile.personal-info.title">
<source>Personal information</source>
</trans-unit>
<trans-unit id="general.change">
<source>Change</source>
</trans-unit>
<trans-unit id="general.update">
<source>Update</source>
</trans-unit>
<trans-unit id="account.profile.personal-info.update-name">
<source>Update name</source>
</trans-unit>
<trans-unit id="general.fallback-title">
<source>Not set</source>
<note from="lit-localize">A fallback title when the title or heading of a component is not provided.</note>
</trans-unit>
<trans-unit id="account.profile.personal-info.avatar">
<source>Avatar</source>
<note from="lit-localize">The avatar of the user.</note>
</trans-unit>
<trans-unit id="account.profile.personal-info.name">
<source>Name</source>
<note from="lit-localize">The name of the user.</note>
</trans-unit>
<trans-unit id="account.profile.personal-info.name-placeholder">
<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

@ -1,50 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
<file target-language="pt-BR" source-language="en" original="lit-localize-inputs" datatype="plaintext">
<body>
<trans-unit id="general.title">
<source>Title</source>
</trans-unit>
<trans-unit id="general.actions">
<source>Actions</source>
</trans-unit>
<trans-unit id="account.profile.title">
<source>Profile</source>
</trans-unit>
<trans-unit id="account.profile.personal-info.title">
<source>Personal information</source>
</trans-unit>
<trans-unit id="general.change">
<source>Change</source>
</trans-unit>
<trans-unit id="general.update">
<source>Update</source>
</trans-unit>
<trans-unit id="account.profile.personal-info.update-name">
<source>Update name</source>
</trans-unit>
<trans-unit id="general.fallback-title">
<source>Not set</source>
<note from="lit-localize">A fallback title when the title or heading of a component is not provided.</note>
</trans-unit>
<trans-unit id="account.profile.personal-info.avatar">
<source>Avatar</source>
<note from="lit-localize">The avatar of the user.</note>
</trans-unit>
<trans-unit id="account.profile.personal-info.name">
<source>Name</source>
<note from="lit-localize">The name of the user.</note>
</trans-unit>
<trans-unit id="account.profile.personal-info.name-placeholder">
<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

@ -1,50 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
<file target-language="pt-PT" source-language="en" original="lit-localize-inputs" datatype="plaintext">
<body>
<trans-unit id="general.title">
<source>Title</source>
</trans-unit>
<trans-unit id="general.actions">
<source>Actions</source>
</trans-unit>
<trans-unit id="account.profile.title">
<source>Profile</source>
</trans-unit>
<trans-unit id="account.profile.personal-info.title">
<source>Personal information</source>
</trans-unit>
<trans-unit id="general.change">
<source>Change</source>
</trans-unit>
<trans-unit id="general.update">
<source>Update</source>
</trans-unit>
<trans-unit id="account.profile.personal-info.update-name">
<source>Update name</source>
</trans-unit>
<trans-unit id="general.fallback-title">
<source>Not set</source>
<note from="lit-localize">A fallback title when the title or heading of a component is not provided.</note>
</trans-unit>
<trans-unit id="account.profile.personal-info.avatar">
<source>Avatar</source>
<note from="lit-localize">The avatar of the user.</note>
</trans-unit>
<trans-unit id="account.profile.personal-info.name">
<source>Name</source>
<note from="lit-localize">The name of the user.</note>
</trans-unit>
<trans-unit id="account.profile.personal-info.name-placeholder">
<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

@ -1,50 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
<file target-language="ru" source-language="en" original="lit-localize-inputs" datatype="plaintext">
<body>
<trans-unit id="general.title">
<source>Title</source>
</trans-unit>
<trans-unit id="general.actions">
<source>Actions</source>
</trans-unit>
<trans-unit id="account.profile.title">
<source>Profile</source>
</trans-unit>
<trans-unit id="account.profile.personal-info.title">
<source>Personal information</source>
</trans-unit>
<trans-unit id="general.change">
<source>Change</source>
</trans-unit>
<trans-unit id="general.update">
<source>Update</source>
</trans-unit>
<trans-unit id="account.profile.personal-info.update-name">
<source>Update name</source>
</trans-unit>
<trans-unit id="general.fallback-title">
<source>Not set</source>
<note from="lit-localize">A fallback title when the title or heading of a component is not provided.</note>
</trans-unit>
<trans-unit id="account.profile.personal-info.avatar">
<source>Avatar</source>
<note from="lit-localize">The avatar of the user.</note>
</trans-unit>
<trans-unit id="account.profile.personal-info.name">
<source>Name</source>
<note from="lit-localize">The name of the user.</note>
</trans-unit>
<trans-unit id="account.profile.personal-info.name-placeholder">
<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

@ -1,50 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
<file target-language="tr-TR" source-language="en" original="lit-localize-inputs" datatype="plaintext">
<body>
<trans-unit id="general.title">
<source>Title</source>
</trans-unit>
<trans-unit id="general.actions">
<source>Actions</source>
</trans-unit>
<trans-unit id="account.profile.title">
<source>Profile</source>
</trans-unit>
<trans-unit id="account.profile.personal-info.title">
<source>Personal information</source>
</trans-unit>
<trans-unit id="general.change">
<source>Change</source>
</trans-unit>
<trans-unit id="general.update">
<source>Update</source>
</trans-unit>
<trans-unit id="account.profile.personal-info.update-name">
<source>Update name</source>
</trans-unit>
<trans-unit id="general.fallback-title">
<source>Not set</source>
<note from="lit-localize">A fallback title when the title or heading of a component is not provided.</note>
</trans-unit>
<trans-unit id="account.profile.personal-info.avatar">
<source>Avatar</source>
<note from="lit-localize">The avatar of the user.</note>
</trans-unit>
<trans-unit id="account.profile.personal-info.name">
<source>Name</source>
<note from="lit-localize">The name of the user.</note>
</trans-unit>
<trans-unit id="account.profile.personal-info.name-placeholder">
<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

@ -1,50 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
<file target-language="zh-CN" source-language="en" original="lit-localize-inputs" datatype="plaintext">
<body>
<trans-unit id="general.title">
<source>Title</source>
</trans-unit>
<trans-unit id="general.actions">
<source>Actions</source>
</trans-unit>
<trans-unit id="account.profile.title">
<source>Profile</source>
</trans-unit>
<trans-unit id="account.profile.personal-info.title">
<source>Personal information</source>
</trans-unit>
<trans-unit id="general.change">
<source>Change</source>
</trans-unit>
<trans-unit id="general.update">
<source>Update</source>
</trans-unit>
<trans-unit id="account.profile.personal-info.update-name">
<source>Update name</source>
</trans-unit>
<trans-unit id="general.fallback-title">
<source>Not set</source>
<note from="lit-localize">A fallback title when the title or heading of a component is not provided.</note>
</trans-unit>
<trans-unit id="account.profile.personal-info.avatar">
<source>Avatar</source>
<note from="lit-localize">The avatar of the user.</note>
</trans-unit>
<trans-unit id="account.profile.personal-info.name">
<source>Name</source>
<note from="lit-localize">The name of the user.</note>
</trans-unit>
<trans-unit id="account.profile.personal-info.name-placeholder">
<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

@ -1,50 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
<file target-language="zh-HK" source-language="en" original="lit-localize-inputs" datatype="plaintext">
<body>
<trans-unit id="general.title">
<source>Title</source>
</trans-unit>
<trans-unit id="general.actions">
<source>Actions</source>
</trans-unit>
<trans-unit id="account.profile.title">
<source>Profile</source>
</trans-unit>
<trans-unit id="account.profile.personal-info.title">
<source>Personal information</source>
</trans-unit>
<trans-unit id="general.change">
<source>Change</source>
</trans-unit>
<trans-unit id="general.update">
<source>Update</source>
</trans-unit>
<trans-unit id="account.profile.personal-info.update-name">
<source>Update name</source>
</trans-unit>
<trans-unit id="general.fallback-title">
<source>Not set</source>
<note from="lit-localize">A fallback title when the title or heading of a component is not provided.</note>
</trans-unit>
<trans-unit id="account.profile.personal-info.avatar">
<source>Avatar</source>
<note from="lit-localize">The avatar of the user.</note>
</trans-unit>
<trans-unit id="account.profile.personal-info.name">
<source>Name</source>
<note from="lit-localize">The name of the user.</note>
</trans-unit>
<trans-unit id="account.profile.personal-info.name-placeholder">
<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

@ -1,50 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
<file target-language="zh-TW" source-language="en" original="lit-localize-inputs" datatype="plaintext">
<body>
<trans-unit id="general.title">
<source>Title</source>
</trans-unit>
<trans-unit id="general.actions">
<source>Actions</source>
</trans-unit>
<trans-unit id="account.profile.title">
<source>Profile</source>
</trans-unit>
<trans-unit id="account.profile.personal-info.title">
<source>Personal information</source>
</trans-unit>
<trans-unit id="general.change">
<source>Change</source>
</trans-unit>
<trans-unit id="general.update">
<source>Update</source>
</trans-unit>
<trans-unit id="account.profile.personal-info.update-name">
<source>Update name</source>
</trans-unit>
<trans-unit id="general.fallback-title">
<source>Not set</source>
<note from="lit-localize">A fallback title when the title or heading of a component is not provided.</note>
</trans-unit>
<trans-unit id="account.profile.personal-info.avatar">
<source>Avatar</source>
<note from="lit-localize">The avatar of the user.</note>
</trans-unit>
<trans-unit id="account.profile.personal-info.name">
<source>Name</source>
<note from="lit-localize">The name of the user.</note>
</trans-unit>
<trans-unit id="account.profile.personal-info.name-placeholder">
<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

@ -2738,9 +2738,6 @@ importers:
'@logto/core-kit':
specifier: workspace:^2.5.0
version: link:../toolkit/core-kit
'@logto/elements':
specifier: workspace:^0.0.1
version: link:../elements
'@logto/language-kit':
specifier: workspace:^1.1.0
version: link:../toolkit/language-kit
@ -16064,10 +16061,10 @@ snapshots:
eslint-config-prettier: 9.1.0(eslint@8.57.0)
eslint-config-xo: 0.44.0(eslint@8.57.0)
eslint-config-xo-typescript: 4.0.0(@typescript-eslint/eslint-plugin@7.7.0(@typescript-eslint/parser@7.7.0(eslint@8.57.0)(typescript@5.5.3))(eslint@8.57.0)(typescript@5.5.3))(@typescript-eslint/parser@7.7.0(eslint@8.57.0)(typescript@5.5.3))(eslint@8.57.0)(typescript@5.5.3)
eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@7.7.0(eslint@8.57.0)(typescript@5.5.3))(eslint-plugin-import@2.29.1)(eslint@8.57.0)
eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@7.7.0(eslint@8.57.0)(typescript@5.5.3))(eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.7.0(eslint@8.57.0)(typescript@5.5.3))(eslint@8.57.0))(eslint@8.57.0)
eslint-plugin-consistent-default-export-name: 0.0.15
eslint-plugin-eslint-comments: 3.2.0(eslint@8.57.0)
eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.7.0(eslint@8.57.0)(typescript@5.5.3))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0)
eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.7.0(eslint@8.57.0)(typescript@5.5.3))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.7.0(eslint@8.57.0)(typescript@5.5.3))(eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.7.0(eslint@8.57.0)(typescript@5.5.3))(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0)
eslint-plugin-n: 17.2.1(eslint@8.57.0)
eslint-plugin-no-use-extend-native: 0.5.0
eslint-plugin-prettier: 5.1.3(eslint-config-prettier@9.1.0(eslint@8.57.0))(eslint@8.57.0)(prettier@3.0.0)
@ -18556,6 +18553,10 @@ snapshots:
debounce@1.2.1: {}
debug@3.2.7:
dependencies:
ms: 2.1.3
debug@3.2.7(supports-color@5.5.0):
dependencies:
ms: 2.1.3
@ -19029,19 +19030,19 @@ snapshots:
eslint-import-resolver-node@0.3.9:
dependencies:
debug: 3.2.7(supports-color@5.5.0)
debug: 3.2.7
is-core-module: 2.13.1
resolve: 1.22.8
transitivePeerDependencies:
- supports-color
eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.7.0(eslint@8.57.0)(typescript@5.5.3))(eslint-plugin-import@2.29.1)(eslint@8.57.0):
eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.7.0(eslint@8.57.0)(typescript@5.5.3))(eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.7.0(eslint@8.57.0)(typescript@5.5.3))(eslint@8.57.0))(eslint@8.57.0):
dependencies:
debug: 4.3.5
enhanced-resolve: 5.16.0
eslint: 8.57.0
eslint-module-utils: 2.8.1(@typescript-eslint/parser@7.7.0(eslint@8.57.0)(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0)
eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.7.0(eslint@8.57.0)(typescript@5.5.3))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0)
eslint-module-utils: 2.8.1(@typescript-eslint/parser@7.7.0(eslint@8.57.0)(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.7.0(eslint@8.57.0)(typescript@5.5.3))(eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.7.0(eslint@8.57.0)(typescript@5.5.3))(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0)
eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.7.0(eslint@8.57.0)(typescript@5.5.3))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.7.0(eslint@8.57.0)(typescript@5.5.3))(eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.7.0(eslint@8.57.0)(typescript@5.5.3))(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0)
fast-glob: 3.3.2
get-tsconfig: 4.7.3
is-core-module: 2.13.1
@ -19052,14 +19053,14 @@ snapshots:
- eslint-import-resolver-webpack
- supports-color
eslint-module-utils@2.8.1(@typescript-eslint/parser@7.7.0(eslint@8.57.0)(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0):
eslint-module-utils@2.8.1(@typescript-eslint/parser@7.7.0(eslint@8.57.0)(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.7.0(eslint@8.57.0)(typescript@5.5.3))(eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.7.0(eslint@8.57.0)(typescript@5.5.3))(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0):
dependencies:
debug: 3.2.7(supports-color@5.5.0)
debug: 3.2.7
optionalDependencies:
'@typescript-eslint/parser': 7.7.0(eslint@8.57.0)(typescript@5.5.3)
eslint: 8.57.0
eslint-import-resolver-node: 0.3.9
eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@7.7.0(eslint@8.57.0)(typescript@5.5.3))(eslint-plugin-import@2.29.1)(eslint@8.57.0)
eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@7.7.0(eslint@8.57.0)(typescript@5.5.3))(eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.7.0(eslint@8.57.0)(typescript@5.5.3))(eslint@8.57.0))(eslint@8.57.0)
transitivePeerDependencies:
- supports-color
@ -19081,17 +19082,17 @@ snapshots:
eslint: 8.57.0
ignore: 5.3.1
eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.7.0(eslint@8.57.0)(typescript@5.5.3))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0):
eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.7.0(eslint@8.57.0)(typescript@5.5.3))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.7.0(eslint@8.57.0)(typescript@5.5.3))(eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.7.0(eslint@8.57.0)(typescript@5.5.3))(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0):
dependencies:
array-includes: 3.1.8
array.prototype.findlastindex: 1.2.5
array.prototype.flat: 1.3.2
array.prototype.flatmap: 1.3.2
debug: 3.2.7(supports-color@5.5.0)
debug: 3.2.7
doctrine: 2.1.0
eslint: 8.57.0
eslint-import-resolver-node: 0.3.9
eslint-module-utils: 2.8.1(@typescript-eslint/parser@7.7.0(eslint@8.57.0)(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0)
eslint-module-utils: 2.8.1(@typescript-eslint/parser@7.7.0(eslint@8.57.0)(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.7.0(eslint@8.57.0)(typescript@5.5.3))(eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.7.0(eslint@8.57.0)(typescript@5.5.3))(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0)
hasown: 2.0.2
is-core-module: 2.13.1
is-glob: 4.0.3