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

feat(ui): update input field (#342)

* feat(ui): update input field

update input field using latest design

* feat(ui):  add error status border

add error status border

* fix(ui): cr fix

cr fix

* fix(ui): fix icon center bug

fix icon center bug
This commit is contained in:
simeng-li 2022-03-09 15:35:45 +08:00 committed by GitHub
parent 8f94a0d76b
commit 6fcc682f4f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 121 additions and 33 deletions

View file

@ -1,3 +1,5 @@
@use '@/scss/underscore' as _;
.content {
position: absolute;
inset: 0;
@ -48,19 +50,29 @@
--color-primary: #6139f6;
--color-gradient: linear-gradient(69.73deg, #492ef3 5.52%, #cf69ff 94.22%);
--color-background: #fdfdff;
--color-control-background: #f4f4f4;
--color-control-focus: #a48dfa;
--color-neutral-100: #111;
--color-neutral-90: #666;
--color-neutral-70: #999;
--color-neutral-50: #aeaeae;
--color-neutral-30: #d8d8d8;
--color-neutral-10: #f4f4f4;
--color-neutral-0: #fff;
/* Font Color */
--color-font-primary: #111;
--color-font-secondary: #444;
--color-font-tertiary: #777;
--color-font-tertiary-1: #777;
--color-font-tertiary-2: #999;
--color-font-tertiary-3: #aaa;
/* ===== Legacy Styling ===== */
--color-heading: #333;
--color-body: #555;
--color-secondary: #888;
--color-placeholder: #aaa;
--color-control-background: #f5f8fa;
--color-control-background-disabled: #eaeaea;
/* Shadow */
@ -68,17 +80,18 @@
--shadow-control: 1px 1px 2px rgb(221, 221, 221, 25%);
}
$font-family: 'PingFang SC', 'SF Pro Display', sans-serif;
$font-family: 'PingFang SC', 'SF Pro Text', sans-serif;
.mobile {
--font-subtitle-18-medium: 500 18px/21px #{$font-family};
--font-title: 600 32px/40px #{$font-family};
--font-heading-2: 400 18px/21px #{$font-family};
--font-heading-2-bold: 600 18px/21px #{$font-family};
--font-control: 400 18px/21px #{$font-family};
/* ===== Legacy Styling ===== */
--font-headline: 600 40px/56px #{$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-control: 500 16px/22.4px #{$font-family};
--font-body: 400 12px/16px #{$font-family};
--font-body-bold: 500 12px/16px #{$font-family};
}

View file

@ -18,6 +18,6 @@ $logo-height: 60px;
}
.headline {
font: var(--font-subtitle-18-medium);
font: var(--font-heading-2-bold);
color: var(--color-font-secondary);
}

View file

@ -0,0 +1,9 @@
import React, { SVGProps } from 'react';
const CloseIcon = (props: SVGProps<SVGSVGElement>) => (
<svg width="22" height="22" viewBox="0 0 22 22" xmlns="http://www.w3.org/2000/svg" {...props}>
<path d="M14.7098 7.29008C14.6169 7.19635 14.5063 7.12196 14.3844 7.07119C14.2626 7.02042 14.1318 6.99428 13.9998 6.99428C13.8678 6.99428 13.7371 7.02042 13.6153 7.07119C13.4934 7.12196 13.3828 7.19635 13.2898 7.29008L10.9998 9.59008L8.70984 7.29008C8.52153 7.10178 8.26614 6.99599 7.99984 6.99599C7.73353 6.99599 7.47814 7.10178 7.28983 7.29008C7.10153 7.47838 6.99574 7.73378 6.99574 8.00008C6.99574 8.26638 7.10153 8.52178 7.28983 8.71008L9.58984 11.0001L7.28983 13.2901C7.19611 13.383 7.12171 13.4936 7.07094 13.6155C7.02017 13.7374 6.99404 13.8681 6.99404 14.0001C6.99404 14.1321 7.02017 14.2628 7.07094 14.3847C7.12171 14.5065 7.19611 14.6171 7.28983 14.7101C7.3828 14.8038 7.4934 14.8782 7.61526 14.929C7.73712 14.9797 7.86782 15.0059 7.99984 15.0059C8.13185 15.0059 8.26255 14.9797 8.38441 14.929C8.50627 14.8782 8.61687 14.8038 8.70984 14.7101L10.9998 12.4101L13.2898 14.7101C13.3828 14.8038 13.4934 14.8782 13.6153 14.929C13.7371 14.9797 13.8678 15.0059 13.9998 15.0059C14.1318 15.0059 14.2626 14.9797 14.3844 14.929C14.5063 14.8782 14.6169 14.8038 14.7098 14.7101C14.8036 14.6171 14.878 14.5065 14.9287 14.3847C14.9795 14.2628 15.0056 14.1321 15.0056 14.0001C15.0056 13.8681 14.9795 13.7374 14.9287 13.6155C14.878 13.4936 14.8036 13.383 14.7098 13.2901L12.4098 11.0001L14.7098 8.71008C14.8036 8.61712 14.878 8.50652 14.9287 8.38466C14.9795 8.2628 15.0056 8.13209 15.0056 8.00008C15.0056 7.86807 14.9795 7.73736 14.9287 7.6155C14.878 7.49364 14.8036 7.38304 14.7098 7.29008ZM18.0698 3.93008C17.1474 2.97498 16.0439 2.21316 14.8239 1.68907C13.6038 1.16498 12.2916 0.889113 10.9638 0.877575C9.63605 0.866037 8.31926 1.11905 7.09029 1.62186C5.86133 2.12467 4.74481 2.8672 3.80589 3.80613C2.86696 4.74506 2.12443 5.86158 1.62162 7.09054C1.11881 8.3195 0.865793 9.6363 0.877331 10.9641C0.888869 12.2919 1.16473 13.6041 1.68882 14.8241C2.21291 16.0442 2.97473 17.1476 3.92984 18.0701C4.8523 19.0252 5.95575 19.787 7.17579 20.3111C8.39583 20.8352 9.70803 21.111 11.0358 21.1226C12.3636 21.1341 13.6804 20.8811 14.9094 20.3783C16.1383 19.8755 17.2549 19.133 18.1938 18.194C19.1327 17.2551 19.8752 16.1386 20.3781 14.9096C20.8809 13.6807 21.1339 12.3639 21.1223 11.0361C21.1108 9.70827 20.8349 8.39607 20.3109 7.17603C19.7868 5.95599 19.0249 4.85255 18.0698 3.93008ZM16.6598 16.6601C15.3519 17.9695 13.6304 18.7849 11.7886 18.9674C9.94688 19.1499 8.09884 18.6881 6.55936 17.6609C5.01987 16.6336 3.88419 15.1043 3.34581 13.3336C2.80742 11.5628 2.89964 9.66022 3.60675 7.94986C4.31386 6.23951 5.59211 4.82723 7.22373 3.95364C8.85534 3.08006 10.7394 2.79921 12.5548 3.15895C14.3703 3.5187 16.0049 4.49677 17.1801 5.92654C18.3553 7.35631 18.9984 9.14932 18.9998 11.0001C19.0034 12.0514 18.7984 13.0929 18.3968 14.0645C17.9951 15.036 17.4047 15.9182 16.6598 16.6601Z" />
</svg>
);
export default CloseIcon;

View file

@ -1,23 +1,38 @@
@use '@/scss/underscore' as _;
.wrapper {
position: relative;
}
.input {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
padding: _.unit(3) _.unit(5);
width: 100%;
padding: _.unit(3) _.unit(12) _.unit(3) _.unit(5);
border-radius: _.unit(2);
border: _.border();
background: var(--color-control-background);
color: var(--color-heading);
color: var(--color-font-primary);
caret-color: var(--color-primary);
font: var(--font-control);
transition: var(--transition-default-control);
&::placeholder {
color: var(--color-placeholder);
color: var(--color-font-tertiary-3);
}
&:disabled {
background: var(--color-control-background-disabled);
color: var(--color-secondary);
&:focus {
border: _.border(var(--color-control-focus));
}
}
.error,
.error:focus {
border: _.border(var(--color-error));
}
.clearButton {
position: absolute;
right: _.unit(5);
bottom: 50%;
transform: translateY(50%);
fill: var(--color-neutral-70);
}

View file

@ -0,0 +1,33 @@
import { render, fireEvent } from '@testing-library/react';
import React from 'react';
import Input from '.';
describe('Input Field UI Component', () => {
const text = 'foo';
const onChange = jest.fn();
test('render with input value', () => {
const { container } = render(<Input name="foo" value={text} onChange={onChange} />);
const inputEle = container.querySelector('input');
expect(inputEle).not.toBeNull();
expect(inputEle?.value).toEqual(text);
expect(container.querySelector('svg')).not.toBeNull();
if (inputEle) {
fireEvent.change(inputEle, { target: { value: 'update' } });
expect(onChange).toBeCalledWith('update');
}
});
test('click on clear button', () => {
const { container } = render(<Input name="foo" value={text} onChange={onChange} />);
const closeIcon = container.querySelector('svg');
expect(closeIcon).not.toBeNull();
if (closeIcon) {
fireEvent.click(closeIcon);
expect(onChange).toBeCalledWith('');
}
});
});

View file

@ -1,6 +1,7 @@
import classNames from 'classnames';
import React from 'react';
import CloseIcon from '../Icons/CloseIcon';
import * as styles from './index.module.scss';
export type Props = {
@ -11,6 +12,7 @@ export type Props = {
placeholder?: string;
type?: InputType;
value: string;
hasError?: boolean;
onChange: (value: string) => void;
};
@ -22,21 +24,32 @@ const Input = ({
placeholder,
type = 'text',
value,
hasError = false,
onChange,
}: Props) => {
return (
<input
name={name}
disabled={isDisabled}
className={classNames(styles.input, className)}
placeholder={placeholder}
type={type}
value={value}
autoComplete={autoComplete}
onChange={({ target: { value } }) => {
onChange(value);
}}
/>
<div className={classNames(styles.wrapper, className)}>
<input
name={name}
disabled={isDisabled}
className={classNames(styles.input, hasError && styles.error)}
placeholder={placeholder}
type={type}
value={value}
autoComplete={autoComplete}
onChange={({ target: { value } }) => {
onChange(value);
}}
/>
{value && (
<CloseIcon
className={classNames(styles.clearButton)}
onClick={() => {
onChange('');
}}
/>
)}
</div>
);
};

View file

@ -25,10 +25,9 @@
margin-bottom: _.unit(-6);
}
.box,
> input:not([type='button']) {
margin-top: _.unit(3);
.inputField {
width: 100%;
margin-top: _.unit(3);
max-width: 320px;
}

View file

@ -44,6 +44,7 @@ const SignIn: FC = () => {
isDisabled={loading}
placeholder={t('sign_in.username')}
value={username}
className={styles.inputField}
onChange={setUsername}
/>
<Input
@ -53,6 +54,7 @@ const SignIn: FC = () => {
placeholder={t('sign_in.password')}
type="password"
value={password}
className={styles.inputField}
onChange={setPassword}
/>
{error && (

View file

@ -13,3 +13,7 @@
object-fit: contain;
object-position: center;
}
@function border($color: transparent, $width: 1) {
@return #{$width}px solid #{$color};
}