mirror of
https://github.com/logto-io/logto.git
synced 2024-12-16 20:26:19 -05:00
feat: init sign-in button
This commit is contained in:
parent
6f88d1d1a5
commit
3ddc180d65
11 changed files with 141 additions and 8 deletions
|
@ -13,6 +13,7 @@
|
|||
--color-gradient: linear-gradient(12.07deg, #3c4ce3 8.81%, #717ce0 93.49%);
|
||||
--color-button-background: #3c4ce3;
|
||||
--color-button-background-disabled: #626fe8;
|
||||
--color-button-background-hover: #2234df;
|
||||
--color-button-text: #f5f5f5;
|
||||
--color-button-text-disabled: #eee;
|
||||
--color-error: #ff6b66;
|
||||
|
|
25
packages/ui/src/components/Button/index.module.scss
Normal file
25
packages/ui/src/components/Button/index.module.scss
Normal file
|
@ -0,0 +1,25 @@
|
|||
@use '/src/scss/underscore' as _;
|
||||
|
||||
.button {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: _.unit(3) _.unit(8);
|
||||
border-radius: 12px;
|
||||
background: var(--color-button-background);
|
||||
color: var(--color-button-text);
|
||||
font: var(--font-heading-3);
|
||||
box-shadow: var(--shadow-button);
|
||||
transition: background 0.2s ease-in-out, color 0.2s ease-in-out;
|
||||
cursor: pointer;
|
||||
|
||||
&:not(.disabled):hover {
|
||||
background: var(--color-button-background-hover);
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
background: var(--color-button-background-disabled);
|
||||
color: var(--color-button-text-disabled);
|
||||
}
|
||||
}
|
27
packages/ui/src/components/Button/index.tsx
Normal file
27
packages/ui/src/components/Button/index.tsx
Normal file
|
@ -0,0 +1,27 @@
|
|||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import styles from './index.module.scss';
|
||||
|
||||
export type Props = {
|
||||
isDisabled?: boolean;
|
||||
className?: string;
|
||||
value?: string;
|
||||
onClick?: React.MouseEventHandler;
|
||||
};
|
||||
|
||||
const Button = ({ isDisabled = false, className, value, onClick }: Props) => {
|
||||
return (
|
||||
<input
|
||||
className={classNames(styles.button, isDisabled && styles.disabled, className)}
|
||||
type="button"
|
||||
value={value}
|
||||
onClick={(event) => {
|
||||
if (!isDisabled) {
|
||||
onClick?.(event);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default Button;
|
|
@ -10,8 +10,6 @@
|
|||
background: var(--color-control-background);
|
||||
color: var(--color-heading);
|
||||
font: var(--font-heading-3);
|
||||
border: none;
|
||||
outline: none;
|
||||
|
||||
&::placeholder {
|
||||
color: var(--color-placeholder);
|
||||
|
|
|
@ -3,6 +3,7 @@ import React from 'react';
|
|||
import styles from './index.module.scss';
|
||||
|
||||
export type Props = {
|
||||
autoComplete?: AutoCompleteType;
|
||||
className?: string;
|
||||
placeholder?: string;
|
||||
type?: InputType;
|
||||
|
@ -10,13 +11,14 @@ export type Props = {
|
|||
onChange: (value: string) => void;
|
||||
};
|
||||
|
||||
const Input = ({ className, placeholder, type = 'text', value, onChange }: Props) => {
|
||||
const Input = ({ autoComplete, className, placeholder, type = 'text', value, onChange }: Props) => {
|
||||
return (
|
||||
<input
|
||||
className={classNames(styles.input, className)}
|
||||
placeholder={placeholder}
|
||||
type={type}
|
||||
value={value}
|
||||
autoComplete={autoComplete}
|
||||
onChange={({ target: { value } }) => {
|
||||
onChange(value);
|
||||
}}
|
||||
|
|
48
packages/ui/src/include.d/dom.d.ts
vendored
48
packages/ui/src/include.d/dom.d.ts
vendored
|
@ -1,3 +1,4 @@
|
|||
// https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#input_types
|
||||
type InputType =
|
||||
| 'button'
|
||||
| 'checkbox'
|
||||
|
@ -21,3 +22,50 @@ type InputType =
|
|||
| 'time'
|
||||
| 'url'
|
||||
| 'week';
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#autofilling-form-controls:-the-autocomplete-attribute
|
||||
type AutoCompleteType =
|
||||
| 'name'
|
||||
| 'honorific-prefix'
|
||||
| 'given-name'
|
||||
| 'additional-name'
|
||||
| 'family-name'
|
||||
| 'honorific-suffix'
|
||||
| 'nickname'
|
||||
| 'username'
|
||||
| 'new-password'
|
||||
| 'current-password'
|
||||
| 'one-time-code'
|
||||
| 'organization-title'
|
||||
| 'organization'
|
||||
| 'street-address'
|
||||
| 'address-line1'
|
||||
| 'address-line2'
|
||||
| 'address-line3'
|
||||
| 'address-level4'
|
||||
| 'address-level3'
|
||||
| 'address-level2'
|
||||
| 'address-level1'
|
||||
| 'country'
|
||||
| 'country-name'
|
||||
| 'postal-code'
|
||||
| 'cc-name'
|
||||
| 'cc-given-name'
|
||||
| 'cc-additional-name'
|
||||
| 'cc-family-name'
|
||||
| 'cc-number'
|
||||
| 'cc-exp'
|
||||
| 'cc-exp-month'
|
||||
| 'cc-exp-year'
|
||||
| 'cc-csc'
|
||||
| 'cc-type'
|
||||
| 'transaction-currency'
|
||||
| 'transaction-amount'
|
||||
| 'language'
|
||||
| 'bday'
|
||||
| 'bday-day'
|
||||
| 'bday-month'
|
||||
| 'bday-year'
|
||||
| 'sex'
|
||||
| 'url'
|
||||
| 'photo';
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
{
|
||||
"translation": {
|
||||
"sign-in": "Sign In",
|
||||
"sign-in.loading": "Signing in...",
|
||||
"sign-in.username": "Username",
|
||||
"sign-in.password": "Password"
|
||||
}
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
{
|
||||
"translation": {
|
||||
"sign-in": "登录",
|
||||
"sign-in.loading": "登录中...",
|
||||
"sign-in.username": "用户名",
|
||||
"sign-in.password": "密码"
|
||||
}
|
||||
|
|
|
@ -5,14 +5,21 @@
|
|||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
|
||||
.title {
|
||||
font: var(--font-heading-1);
|
||||
color: var(--color-heading);
|
||||
margin-bottom: _.unit(9);
|
||||
}
|
||||
|
||||
> input {
|
||||
> input:not([type='button']) {
|
||||
align-self: stretch;
|
||||
margin: _.unit(1.5) 0;
|
||||
margin-top: _.unit(3);
|
||||
}
|
||||
|
||||
> input[type='button'] {
|
||||
margin-top: _.unit(6);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import Button from '@/components/Button';
|
||||
import Input from '@/components/Input';
|
||||
import React, { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
@ -7,18 +8,33 @@ const Home = () => {
|
|||
const { t } = useTranslation();
|
||||
const [username, setUsername] = useState('');
|
||||
const [password, setPassword] = useState('');
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
return (
|
||||
<div className={styles.wrapper}>
|
||||
<form className={styles.wrapper}>
|
||||
<div className={styles.title}>登录 Logto</div>
|
||||
<Input placeholder={t('sign-in.username')} value={username} onChange={setUsername} />
|
||||
|
||||
<Input
|
||||
autoComplete="username"
|
||||
placeholder={t('sign-in.username')}
|
||||
value={username}
|
||||
onChange={setUsername}
|
||||
/>
|
||||
<Input
|
||||
autoComplete="current-password"
|
||||
placeholder={t('sign-in.password')}
|
||||
type="password"
|
||||
value={password}
|
||||
onChange={setPassword}
|
||||
/>
|
||||
</div>
|
||||
<Button
|
||||
isDisabled={isLoading}
|
||||
value={isLoading ? t('sign-in.loading') : t('sign-in')}
|
||||
onClick={() => {
|
||||
setIsLoading(true);
|
||||
}}
|
||||
/>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -7,3 +7,8 @@ body {
|
|||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
input {
|
||||
border: none;
|
||||
outline: none;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue