mirror of
https://github.com/logto-io/logto.git
synced 2024-12-30 20:33:54 -05:00
feat: init input and i18n
This commit is contained in:
parent
7bd68b5747
commit
6f88d1d1a5
16 changed files with 208 additions and 11 deletions
18
packages/ui/.vscode/tsx.code-snippets
vendored
Normal file
18
packages/ui/.vscode/tsx.code-snippets
vendored
Normal file
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
// Place your ui workspace snippets here. Each snippet is defined under a snippet name and has a scope, prefix, body and
|
||||
// description. Add comma separated ids of the languages where the snippet is applicable in the scope field. If scope
|
||||
// is left empty or omitted, the snippet gets applied to all languages. The prefix is what is
|
||||
// used to trigger the snippet and the body will be expanded and inserted. Possible variables are:
|
||||
// $1, $2 for tab stops, $0 for the final cursor position, and ${1:label}, ${2:another} for placeholders.
|
||||
// Placeholders with the same ids are connected.
|
||||
// Example:
|
||||
"Import SCSS styles": {
|
||||
"scope": "javascriptreact,typescriptreact",
|
||||
"prefix": "isc",
|
||||
"body": [
|
||||
"import styles from './index.module.scss';",
|
||||
"$0"
|
||||
],
|
||||
"description": "Import SCSS styles from the same directory."
|
||||
}
|
||||
}
|
|
@ -1,3 +1,3 @@
|
|||
# @logto/ui
|
||||
|
||||
The core register / sign in experience for end-users.
|
||||
The core register / sign-in experience for end-users.
|
||||
|
|
|
@ -15,8 +15,11 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"classnames": "^2.3.1",
|
||||
"i18next": "^20.3.3",
|
||||
"i18next-browser-languagedetector": "^6.1.2",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-i18next": "^11.11.3",
|
||||
"react-router-dom": "^5.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
import React from 'react';
|
||||
import { Route, Switch } from 'react-router-dom';
|
||||
import AppContent from './components/AppContent';
|
||||
import initI18n from './init/i18n';
|
||||
import Home from './pages/Home';
|
||||
import './scss/normalized.scss';
|
||||
|
||||
initI18n();
|
||||
|
||||
const App = () => (
|
||||
<AppContent theme="dark">
|
||||
<Switch>
|
||||
|
|
|
@ -55,7 +55,7 @@ $font-family: 'PingFang SC', 'SF Pro Text', sans-serif;
|
|||
|
||||
.mobile {
|
||||
--font-headline: 600 40px/56px #{$font-family};
|
||||
--font-heading-1: 600 28px/39.2px #{$font-family};
|
||||
--font-heading-1: 600 28px/39px #{$font-family};
|
||||
--font-heading-2: 600 20px/28px #{$font-family};
|
||||
--font-heading-3: 600 16px/22.4px #{$font-family};
|
||||
--font-body: 400 12px/16px #{$font-family};
|
||||
|
|
19
packages/ui/src/components/Input/index.module.scss
Normal file
19
packages/ui/src/components/Input/index.module.scss
Normal file
|
@ -0,0 +1,19 @@
|
|||
@use '/src/scss/underscore' as _;
|
||||
|
||||
.input {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: _.unit(3) _.unit(5);
|
||||
border-radius: _.unit(2);
|
||||
background: var(--color-control-background);
|
||||
color: var(--color-heading);
|
||||
font: var(--font-heading-3);
|
||||
border: none;
|
||||
outline: none;
|
||||
|
||||
&::placeholder {
|
||||
color: var(--color-placeholder);
|
||||
}
|
||||
}
|
27
packages/ui/src/components/Input/index.tsx
Normal file
27
packages/ui/src/components/Input/index.tsx
Normal file
|
@ -0,0 +1,27 @@
|
|||
import classNames from 'classnames';
|
||||
import React from 'react';
|
||||
import styles from './index.module.scss';
|
||||
|
||||
export type Props = {
|
||||
className?: string;
|
||||
placeholder?: string;
|
||||
type?: InputType;
|
||||
value: string;
|
||||
onChange: (value: string) => void;
|
||||
};
|
||||
|
||||
const Input = ({ className, placeholder, type = 'text', value, onChange }: Props) => {
|
||||
return (
|
||||
<input
|
||||
className={classNames(styles.input, className)}
|
||||
placeholder={placeholder}
|
||||
type={type}
|
||||
value={value}
|
||||
onChange={({ target: { value } }) => {
|
||||
onChange(value);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default Input;
|
23
packages/ui/src/include.d/dom.d.ts
vendored
Normal file
23
packages/ui/src/include.d/dom.d.ts
vendored
Normal file
|
@ -0,0 +1,23 @@
|
|||
type InputType =
|
||||
| 'button'
|
||||
| 'checkbox'
|
||||
| 'color'
|
||||
| 'date'
|
||||
| 'datetime-local'
|
||||
| 'email'
|
||||
| 'file'
|
||||
| 'hidden'
|
||||
| 'image'
|
||||
| 'month'
|
||||
| 'number'
|
||||
| 'password'
|
||||
| 'radio'
|
||||
| 'range'
|
||||
| 'reset'
|
||||
| 'search'
|
||||
| 'submit'
|
||||
| 'tel'
|
||||
| 'text'
|
||||
| 'time'
|
||||
| 'url'
|
||||
| 'week';
|
11
packages/ui/src/include.d/react-i18next.d.ts
vendored
Normal file
11
packages/ui/src/include.d/react-i18next.d.ts
vendored
Normal file
|
@ -0,0 +1,11 @@
|
|||
// https://react.i18next.com/latest/typescript#create-a-declaration-file
|
||||
|
||||
// eslint-disable-next-line import/no-unassigned-import
|
||||
import 'react-i18next';
|
||||
import en from '@/locales/en.json';
|
||||
|
||||
declare module 'react-i18next' {
|
||||
interface CustomTypeOptions {
|
||||
resources: typeof en;
|
||||
}
|
||||
}
|
23
packages/ui/src/init/i18n.ts
Normal file
23
packages/ui/src/init/i18n.ts
Normal file
|
@ -0,0 +1,23 @@
|
|||
import i18n from 'i18next';
|
||||
import LanguageDetector from 'i18next-browser-languagedetector';
|
||||
import { initReactI18next } from 'react-i18next';
|
||||
import en from '@/locales/en.json';
|
||||
import zhCN from '@/locales/zh-CN.json';
|
||||
|
||||
const initI18n = () => {
|
||||
void i18n
|
||||
.use(initReactI18next)
|
||||
.use(LanguageDetector)
|
||||
.init({
|
||||
resources: {
|
||||
en,
|
||||
'zh-CN': zhCN,
|
||||
},
|
||||
fallbackLng: 'en',
|
||||
interpolation: {
|
||||
escapeValue: false,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export default initI18n;
|
6
packages/ui/src/locales/en.json
Normal file
6
packages/ui/src/locales/en.json
Normal file
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"translation": {
|
||||
"sign-in.username": "Username",
|
||||
"sign-in.password": "Password"
|
||||
}
|
||||
}
|
6
packages/ui/src/locales/zh-CN.json
Normal file
6
packages/ui/src/locales/zh-CN.json
Normal file
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"translation": {
|
||||
"sign-in.username": "用户名",
|
||||
"sign-in.password": "密码"
|
||||
}
|
||||
}
|
|
@ -1,11 +1,18 @@
|
|||
@use '/src/scss/underscore' as _;
|
||||
|
||||
.wrapper {
|
||||
text-align: center;
|
||||
padding: _.unit(5);
|
||||
}
|
||||
padding: _.unit(8);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
.title {
|
||||
font: var(--font-headline);
|
||||
font: var(--font-heading-1);
|
||||
color: var(--color-heading);
|
||||
}
|
||||
|
||||
> input {
|
||||
align-self: stretch;
|
||||
margin: _.unit(1.5) 0;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,23 @@
|
|||
import React from 'react';
|
||||
import Input from '@/components/Input';
|
||||
import React, { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import styles from './index.module.scss';
|
||||
|
||||
const Home = () => {
|
||||
const { t } = useTranslation();
|
||||
const [username, setUsername] = useState('');
|
||||
const [password, setPassword] = useState('');
|
||||
|
||||
return (
|
||||
<div className={styles.wrapper}>
|
||||
<div className={styles.title}>Logto</div>
|
||||
<div className={styles.title}>登录 Logto</div>
|
||||
<Input placeholder={t('sign-in.username')} value={username} onChange={setUsername} />
|
||||
<Input
|
||||
placeholder={t('sign-in.password')}
|
||||
type="password"
|
||||
value={password}
|
||||
onChange={setPassword}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -3,3 +3,7 @@ body {
|
|||
padding: 0;
|
||||
font-family: sans-serif;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
|
|
@ -955,7 +955,7 @@
|
|||
"@babel/helper-validator-option" "^7.14.5"
|
||||
"@babel/plugin-transform-typescript" "^7.14.5"
|
||||
|
||||
"@babel/runtime@^7.1.2", "@babel/runtime@^7.12.1", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2":
|
||||
"@babel/runtime@^7.1.2", "@babel/runtime@^7.12.0", "@babel/runtime@^7.12.1", "@babel/runtime@^7.14.5", "@babel/runtime@^7.14.6", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2":
|
||||
version "7.14.6"
|
||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.14.6.tgz#535203bc0892efc7dec60bdc27b2ecf6e409062d"
|
||||
integrity sha512-/PCB2uJ7oM44tz8YhC4Z/6PeOKXp4K588f+5M3clr1M4zbqztlo0XEfJ2LEzj/FgwfgGcIdl8n7YYjTCI0BYwg==
|
||||
|
@ -5707,6 +5707,13 @@ html-minifier-terser@^5.0.1:
|
|||
relateurl "^0.2.7"
|
||||
terser "^4.6.3"
|
||||
|
||||
html-parse-stringify@^3.0.1:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz#dfc1017347ce9f77c8141a507f233040c59c55d2"
|
||||
integrity sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==
|
||||
dependencies:
|
||||
void-elements "3.1.0"
|
||||
|
||||
html-tags@^3.1.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/html-tags/-/html-tags-3.1.0.tgz#7b5e6f7e665e9fb41f30007ed9e0d41e97fb2140"
|
||||
|
@ -5847,6 +5854,20 @@ husky@^7.0.1:
|
|||
resolved "https://registry.yarnpkg.com/husky/-/husky-7.0.1.tgz#579f4180b5da4520263e8713cc832942b48e1f1c"
|
||||
integrity sha512-gceRaITVZ+cJH9sNHqx5tFwbzlLCVxtVZcusME8JYQ8Edy5mpGDOqD8QBCdMhpyo9a+JXddnujQ4rpY2Ff9SJA==
|
||||
|
||||
i18next-browser-languagedetector@^6.1.2:
|
||||
version "6.1.2"
|
||||
resolved "https://registry.yarnpkg.com/i18next-browser-languagedetector/-/i18next-browser-languagedetector-6.1.2.tgz#68565a28b929cbc98ab6a56826ef2faf0e927ff8"
|
||||
integrity sha512-YDzIGHhMRvr7M+c8B3EQUKyiMBhfqox4o1qkFvt4QXuu5V2cxf74+NCr+VEkUuU0y+RwcupA238eeolW1Yn80g==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.14.6"
|
||||
|
||||
i18next@^20.3.3:
|
||||
version "20.3.3"
|
||||
resolved "https://registry.yarnpkg.com/i18next/-/i18next-20.3.3.tgz#e3fdae045f9f0893e74826ce224715e43c62862b"
|
||||
integrity sha512-tx9EUhHeaipvZ5pFLTaN9Xdm5Ssal774MpujaTA1Wv/ST/1My5SnoBmliY1lOpyEP5Z51Dq1gXifk/y4Yt3agQ==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.12.0"
|
||||
|
||||
iconv-lite@0.4.24, iconv-lite@^0.4.24:
|
||||
version "0.4.24"
|
||||
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
|
||||
|
@ -9355,6 +9376,14 @@ react-error-overlay@^6.0.7, react-error-overlay@^6.0.9:
|
|||
resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.9.tgz#3c743010c9359608c375ecd6bc76f35d93995b0a"
|
||||
integrity sha512-nQTTcUu+ATDbrSD1BZHr5kgSD4oF8OFjxun8uAaL8RwPBacGBNPf/yAuVVdx17N8XNzRDMrZ9XcKZHCjPW+9ew==
|
||||
|
||||
react-i18next@^11.11.3:
|
||||
version "11.11.3"
|
||||
resolved "https://registry.yarnpkg.com/react-i18next/-/react-i18next-11.11.3.tgz#38d083bb079c3e6ee376b3321b0d6e409d798f68"
|
||||
integrity sha512-upzG5/SpyOlYP5oSF4K8TZBvDWVhnCo38JNV+KnWjrg0+IaJCBltyh6lRGZDO5ovLyA4dU6Ip0bwbUCjb6Yyxw==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.14.5"
|
||||
html-parse-stringify "^3.0.1"
|
||||
|
||||
react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.1:
|
||||
version "16.13.1"
|
||||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
|
||||
|
@ -11503,6 +11532,11 @@ vm-browserify@^1.0.1:
|
|||
resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.2.tgz#78641c488b8e6ca91a75f511e7a3b32a86e5dda0"
|
||||
integrity sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==
|
||||
|
||||
void-elements@3.1.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-3.1.0.tgz#614f7fbf8d801f0bb5f0661f5b2f5785750e4f09"
|
||||
integrity sha1-YU9/v42AHwu18GYfWy9XhXUOTwk=
|
||||
|
||||
w3c-hr-time@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz#0a89cdf5cc15822df9c360543676963e0cc308cd"
|
||||
|
|
Loading…
Reference in a new issue