From b3b1e03ca6fcc4c7e522417b91bf207fedef7121 Mon Sep 17 00:00:00 2001 From: simeng-li Date: Wed, 15 Feb 2023 13:45:04 +0800 Subject: [PATCH] refactor(ui): clean up legacy code clean up legacy code left of input components --- .../components/Input/PasswordInput.test.tsx | 57 --------- .../ui/src/components/Input/PasswordInput.tsx | 64 ---------- .../src/components/Input/PhoneInput.test.tsx | 97 --------------- .../ui/src/components/Input/PhoneInput.tsx | 112 ------------------ .../ui/src/components/Input/index.module.scss | 91 -------------- .../ui/src/components/Input/index.test.tsx | 56 --------- packages/ui/src/components/Input/index.tsx | 48 -------- .../components/Input/phoneInput.module.scss | 62 ---------- packages/ui/src/hooks/use-phone-number.ts | 55 --------- 9 files changed, 642 deletions(-) delete mode 100644 packages/ui/src/components/Input/PasswordInput.test.tsx delete mode 100644 packages/ui/src/components/Input/PasswordInput.tsx delete mode 100644 packages/ui/src/components/Input/PhoneInput.test.tsx delete mode 100644 packages/ui/src/components/Input/PhoneInput.tsx delete mode 100644 packages/ui/src/components/Input/index.module.scss delete mode 100644 packages/ui/src/components/Input/index.test.tsx delete mode 100644 packages/ui/src/components/Input/index.tsx delete mode 100644 packages/ui/src/components/Input/phoneInput.module.scss delete mode 100644 packages/ui/src/hooks/use-phone-number.ts diff --git a/packages/ui/src/components/Input/PasswordInput.test.tsx b/packages/ui/src/components/Input/PasswordInput.test.tsx deleted file mode 100644 index 7a68b8a7d..000000000 --- a/packages/ui/src/components/Input/PasswordInput.test.tsx +++ /dev/null @@ -1,57 +0,0 @@ -import { render, fireEvent } from '@testing-library/react'; - -import PasswordInput from './PasswordInput'; - -describe('Input Field UI Component', () => { - const text = 'foo'; - const onChange = jest.fn(); - - test('render password input', () => { - const { container } = render( - { - if (target instanceof HTMLInputElement) { - onChange(target.value); - } - }} - /> - ); - - const inputEle = container.querySelector('input'); - expect(inputEle?.value).toEqual(text); - expect(inputEle?.type).toEqual('password'); - - if (inputEle) { - fireEvent.change(inputEle, { target: { value: 'update' } }); - expect(onChange).toBeCalledWith('update'); - } - }); - - test('render error message', () => { - const errorCode = 'password_required'; - const { queryByText } = render(); - expect(queryByText(errorCode)).not.toBeNull(); - }); - - test('click on toggle visibility button', () => { - const { container } = render(); - - const inputEle = container.querySelector('input'); - - if (!inputEle) { - return; - } - - fireEvent.focus(inputEle); - - const visibilityButton = container.querySelector('svg'); - expect(visibilityButton).not.toBeNull(); - - if (visibilityButton) { - fireEvent.mouseDown(visibilityButton); - expect(inputEle.type).toEqual('text'); - } - }); -}); diff --git a/packages/ui/src/components/Input/PasswordInput.tsx b/packages/ui/src/components/Input/PasswordInput.tsx deleted file mode 100644 index 6f22af369..000000000 --- a/packages/ui/src/components/Input/PasswordInput.tsx +++ /dev/null @@ -1,64 +0,0 @@ -import classNames from 'classnames'; -import type { HTMLProps } from 'react'; -import { useState, useRef } from 'react'; - -import PasswordHideIcon from '@/assets/icons/password-hide-icon.svg'; -import PasswordShowIcon from '@/assets/icons/password-show-icon.svg'; -import type { ErrorType } from '@/components/ErrorMessage'; -import ErrorMessage from '@/components/ErrorMessage'; - -import * as styles from './index.module.scss'; - -export type Props = Omit, 'type'> & { - className?: string; - error?: ErrorType; -}; - -const PasswordInput = ({ className, value, error, onFocus, onBlur, ...rest }: Props) => { - // Toggle the password visibility - const [type, setType] = useState('password'); - const [onInputFocus, setOnInputFocus] = useState(false); - const inputElement = useRef(null); - const Icon = type === 'password' ? PasswordHideIcon : PasswordShowIcon; - - return ( -
-
- { - setOnInputFocus(true); - onFocus?.(event); - }} - onBlur={(event) => { - setOnInputFocus(false); - onBlur?.(event); - }} - {...rest} - /> - {value && onInputFocus && ( - { - event.preventDefault(); - setType(type === 'password' ? 'text' : 'password'); - - if (inputElement.current) { - const { length } = inputElement.current.value; - // Force async render, move cursor to the end of the input - setTimeout(() => { - inputElement.current?.setSelectionRange(length, length); - }); - } - }} - /> - )} -
- {error && } -
- ); -}; - -export default PasswordInput; diff --git a/packages/ui/src/components/Input/PhoneInput.test.tsx b/packages/ui/src/components/Input/PhoneInput.test.tsx deleted file mode 100644 index 71953086b..000000000 --- a/packages/ui/src/components/Input/PhoneInput.test.tsx +++ /dev/null @@ -1,97 +0,0 @@ -import { render, fireEvent } from '@testing-library/react'; - -import { getCountryList, getDefaultCountryCallingCode } from '@/utils/country-code'; - -import PhoneInput from './PhoneInput'; - -jest.mock('i18next', () => ({ - language: 'en', -})); - -describe('Phone Input Field UI Component', () => { - const onChange = jest.fn(); - - beforeEach(() => { - onChange.mockClear(); - }); - - const defaultCountryCallingCode = getDefaultCountryCallingCode(); - - it('render empty PhoneInput', () => { - const { queryByText, container } = render( - - ); - expect(queryByText(`+${defaultCountryCallingCode}`)).toBeNull(); - expect(container.querySelector('input')?.value).toBe(''); - }); - - it('render with country list', () => { - const { queryAllByText, container } = render( - - ); - - const countryCode = queryAllByText(`+${defaultCountryCallingCode}`); - expect(countryCode).toHaveLength(2); - - const selector = container.querySelector('select'); - expect(selector).not.toBeNull(); - - if (selector) { - fireEvent.change(selector, { target: { value: '1' } }); - expect(onChange).toBeCalledWith({ countryCallingCode: '1' }); - } - }); - - it('render input update', () => { - const { container } = render( - - ); - - const inputField = container.querySelector('input'); - expect(inputField?.value).toBe('911'); - - if (inputField) { - fireEvent.change(inputField, { target: { value: '110' } }); - expect(onChange).toBeCalledWith({ nationalNumber: '110' }); - fireEvent.focus(inputField); - } - }); - - it('render input error', () => { - const { queryByText } = render( - - ); - expect(queryByText('invalid_phone')).not.toBeNull(); - }); - - it('render input clear', () => { - const { container } = render( - - ); - - const inputField = container.querySelector('input'); - - if (inputField) { - fireEvent.focus(inputField); - } - - const clearButton = container.querySelector('svg'); - expect(clearButton).not.toBeNull(); - - if (clearButton) { - fireEvent.mouseDown(clearButton); - expect(onChange).toBeCalledWith({ nationalNumber: '' }); - } - }); -}); diff --git a/packages/ui/src/components/Input/PhoneInput.tsx b/packages/ui/src/components/Input/PhoneInput.tsx deleted file mode 100644 index fa7fed594..000000000 --- a/packages/ui/src/components/Input/PhoneInput.tsx +++ /dev/null @@ -1,112 +0,0 @@ -import classNames from 'classnames'; -import { useState, useMemo, useRef } from 'react'; - -import DownArrowIcon from '@/assets/icons/arrow-down.svg'; -import ClearIcon from '@/assets/icons/clear-icon.svg'; -import type { ErrorType } from '@/components/ErrorMessage'; -import ErrorMessage from '@/components/ErrorMessage'; -import type { CountryCallingCode, CountryMetaData } from '@/hooks/use-phone-number'; - -import * as styles from './index.module.scss'; -import * as phoneInputStyles from './phoneInput.module.scss'; - -type Value = { countryCallingCode?: CountryCallingCode; nationalNumber?: string }; - -export type Props = { - name: string; - // eslint-disable-next-line react/boolean-prop-naming - autoFocus?: boolean; - className?: string; - placeholder?: string; - countryCallingCode?: CountryCallingCode; - nationalNumber: string; - countryList?: CountryMetaData[]; - error?: ErrorType; - onChange: (value: Value) => void; -}; - -const PhoneInput = ({ - name, - autoFocus, - className, - placeholder, - countryCallingCode, - nationalNumber, - countryList, - error, - onChange, -}: Props) => { - const [onFocus, setOnFocus] = useState(false); - const inputReference = useRef(null); - - const countrySelector = useMemo(() => { - if (countryCallingCode === undefined || !countryList?.length) { - return null; - } - - return ( -
- {`+${countryCallingCode}`} - - -
- ); - }, [countryCallingCode, countryList, onChange]); - - return ( -
-
- {countrySelector} - { - setOnFocus(true); - }} - onBlur={() => { - setOnFocus(false); - }} - onChange={({ target: { value } }) => { - onChange({ nationalNumber: value.replace(/\D/g, '') }); - }} - /> - {nationalNumber && onFocus && ( - { - event.preventDefault(); - onChange({ nationalNumber: '' }); - }} - /> - )} -
- {error && } -
- ); -}; - -export default PhoneInput; diff --git a/packages/ui/src/components/Input/index.module.scss b/packages/ui/src/components/Input/index.module.scss deleted file mode 100644 index 7f6ab9959..000000000 --- a/packages/ui/src/components/Input/index.module.scss +++ /dev/null @@ -1,91 +0,0 @@ -@use '@/scss/underscore' as _; - -.wrapper { - position: relative; - @include _.flex-row; - border: _.border(var(--color-line-border)); - border-radius: var(--radius); - // fix in safari input field line-height issue - height: 44px; - overflow: hidden; - transition-property: outline, border; - transition-timing-function: ease-in-out; - transition-duration: 0.2s; - - .actionButton { - position: absolute; - right: _.unit(4); - top: 50%; - transform: translateY(-50%); - display: none; - color: var(--color-type-secondary); - width: 24px; - height: 24px; - cursor: pointer; - } - - input { - padding: 0 _.unit(4); - flex: 1; - background: none; - caret-color: var(--color-brand-default); - font: var(--font-body-1); - color: var(--color-type-primary); - align-self: stretch; - - &:not(:first-child) { - padding-left: _.unit(1); - } - - &::placeholder { - color: var(--color-type-secondary); - } - } - - &:focus-within { - border: _.border(var(--color-brand-default)); - - .actionButton { - display: block; - } - - input { - padding-right: calc(24px + _.unit(4)); - outline: none; - } - } - - &.error { - border: _.border(var(--color-danger-default)); - } -} - -.errorMessage { - margin-left: _.unit(0.5); - margin-top: _.unit(1); -} - -:global(body.desktop) { - .wrapper { - border-radius: 6px; - outline: 3px solid transparent; - - input { - font: var(--font-body-2); - } - - .actionButton { - width: 22px; - height: 22px; - } - - &.error { - border: _.border(var(--color-danger-default)); - } - - &:focus-within { - border: _.border(var(--color-brand-default)); - outline-color: var(--color-overlay-brand-focused); - } - } -} diff --git a/packages/ui/src/components/Input/index.test.tsx b/packages/ui/src/components/Input/index.test.tsx deleted file mode 100644 index ba01ba8b4..000000000 --- a/packages/ui/src/components/Input/index.test.tsx +++ /dev/null @@ -1,56 +0,0 @@ -import { render, fireEvent } from '@testing-library/react'; - -import Input from '.'; - -describe('Input Field UI Component', () => { - const text = 'foo'; - const onChange = jest.fn(); - const onClear = jest.fn(); - - test('render plain text input with value', () => { - const { container } = render( - { - if (target instanceof HTMLInputElement) { - onChange(target.value); - } - }} - /> - ); - const inputEle = container.querySelector('input'); - expect(inputEle).not.toBeNull(); - expect(inputEle?.value).toEqual(text); - - if (inputEle) { - fireEvent.change(inputEle, { target: { value: 'update' } }); - expect(onChange).toBeCalledWith('update'); - } - }); - - test('render error message', () => { - const errorCode = 'invalid_email'; - const { queryByText } = render(); - expect(queryByText(errorCode)).not.toBeNull(); - }); - - test('click on clear button', () => { - const { container } = render( - - ); - const inputField = container.querySelector('input'); - - if (inputField) { - fireEvent.focus(inputField); - } - - const clearIcon = container.querySelector('svg'); - expect(clearIcon).not.toBeNull(); - - if (clearIcon) { - fireEvent.mouseDown(clearIcon); - expect(onClear).toBeCalledWith(); - } - }); -}); diff --git a/packages/ui/src/components/Input/index.tsx b/packages/ui/src/components/Input/index.tsx deleted file mode 100644 index fe639c22b..000000000 --- a/packages/ui/src/components/Input/index.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import classNames from 'classnames'; -import type { HTMLProps } from 'react'; - -import ClearIcon from '@/assets/icons/clear-icon.svg'; -import type { ErrorType } from '@/components/ErrorMessage'; -import ErrorMessage from '@/components/ErrorMessage'; - -import * as styles from './index.module.scss'; - -export { default as PasswordInput } from './PasswordInput'; -export { default as PhoneInput } from './PhoneInput'; - -export type Props = HTMLProps & { - className?: string; - error?: ErrorType; - onClear?: () => void; - isErrorStyling?: boolean; -}; - -const Input = ({ - className, - type = 'text', - value, - error, - isErrorStyling = true, - onClear, - ...rest -}: Props) => { - return ( -
-
- - {value && onClear && ( - { - event.preventDefault(); - onClear(); - }} - /> - )} -
- {error && } -
- ); -}; - -export default Input; diff --git a/packages/ui/src/components/Input/phoneInput.module.scss b/packages/ui/src/components/Input/phoneInput.module.scss deleted file mode 100644 index e6f1fce6d..000000000 --- a/packages/ui/src/components/Input/phoneInput.module.scss +++ /dev/null @@ -1,62 +0,0 @@ -@use '@/scss/underscore' as _; - -.countryCodeSelector { - color: var(--color-type-primary); - border: none; - background: none; - width: auto; - @include _.flex-row; - position: relative; - margin-right: _.unit(1); - margin-left: _.unit(4); - - > select { - appearance: none; - border: none; - outline: none; - background: none; - position: absolute; - width: 100%; - height: 100%; - font-size: 0; - } -} - -:global(body.mobile) { - .countryCodeSelector { - font: var(--font-label-1); - - > select option { - font: var(--font-label-1); - } - - > svg { - color: var(--color-type-secondary); - margin-left: _.unit(1); - width: 16px; - height: 16px; - } - - + input { - // hot fix unknown android bug of input width - width: 0; - } - } -} - -:global(body.desktop) { - .countryCodeSelector { - font: var(--font-body-2); - - > select option { - font: var(--font-body-2); - } - - > svg { - color: var(--color-type-secondary); - margin-left: _.unit(2); - width: 20px; - height: 20px; - } - } -} diff --git a/packages/ui/src/hooks/use-phone-number.ts b/packages/ui/src/hooks/use-phone-number.ts deleted file mode 100644 index b8de5ee6b..000000000 --- a/packages/ui/src/hooks/use-phone-number.ts +++ /dev/null @@ -1,55 +0,0 @@ -/** - * Provide PhoneNumber Format support - * Reference [libphonenumber-js](https://gitlab.com/catamphetamine/libphonenumber-js) - */ - -import type { CountryCallingCode, CountryCode } from 'libphonenumber-js/mobile'; -import { parsePhoneNumberWithError, ParseError } from 'libphonenumber-js/mobile'; -import { useState } from 'react'; - -import { - getDefaultCountryCallingCode, - getCountryList, - parseE164Number, -} from '@/utils/country-code'; - -export type { CountryCallingCode } from 'libphonenumber-js/mobile'; - -export type CountryMetaData = { - countryCode: CountryCode; - countryCallingCode: CountryCallingCode; -}; - -type PhoneNumberData = { - countryCallingCode: string; - nationalNumber: string; -}; - -const isValidPhoneNumber = (value: string): boolean => { - try { - const phoneNumber = parsePhoneNumberWithError(parseE164Number(value)); - - return phoneNumber.isValid(); - } catch (error: unknown) { - if (error instanceof ParseError) { - return false; - } - throw error; - } -}; - -const usePhoneNumber = () => { - const [phoneNumber, setPhoneNumber] = useState({ - countryCallingCode: getDefaultCountryCallingCode(), - nationalNumber: '', - }); - - return { - countryList: getCountryList(), - phoneNumber, - setPhoneNumber, - isValidPhoneNumber, - }; -}; - -export default usePhoneNumber;