-
Notifications
You must be signed in to change notification settings - Fork 45
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(core-components-money-input): add money-input, story, base test
- Loading branch information
stepancar
committed
Jun 11, 2020
1 parent
2d82709
commit aab595a
Showing
12 changed files
with
624 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
{ | ||
"name": "@alfalab/core-components-money-input", | ||
"version": "1.0.0", | ||
"description": "", | ||
"keywords": [], | ||
"license": "ISC", | ||
"main": "dist/index.js", | ||
"files": [ | ||
"dist" | ||
], | ||
"publishConfig": { | ||
"access": "public" | ||
}, | ||
"peerDependencies": { | ||
"classnames": "^2.2.6", | ||
"react": "^16.9.0" | ||
}, | ||
"dependencies": { | ||
"@alfalab/core-components-input": "^1.3.1", | ||
"text-mask-core": "^5.1.2" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
import { Meta, Props, Title } from '@storybook/addon-docs/blocks'; | ||
import { text, select, boolean } from '@storybook/addon-knobs'; | ||
import { Container, Row, Col } from 'storybook/blocks/grid'; | ||
|
||
|
||
import { MoneyInput } from './Component'; | ||
import { name, version } from '../package.json'; | ||
|
||
|
||
<!-- prettier-ignore --> | ||
|
||
|
||
<Meta title='Компоненты|MoneyInput' component={MoneyInput} /> | ||
|
||
<Title> | ||
MoneyInput ({name}@{version}) | ||
</Title> | ||
|
||
```tsx | ||
import { MoneyInput } from '@alfalab/core-components-money-input'; | ||
``` | ||
|
||
## Описание | ||
|
||
Компонент текстового поля для ввода денежных значений | ||
|
||
<div style={{ width: '240px' }}> | ||
<MoneyInput /> | ||
</div> | ||
|
||
<Props of={MoneyInput} /> | ||
|
||
<Story name='Песочница'> | ||
<MoneyInput | ||
block={boolean('block', false)} | ||
size={select('size', ['s', 'm', 'l'], 's')} | ||
disabled={boolean('disabled', false)} | ||
placeholder={text('placeholder', '')} | ||
label={text('label', '')} | ||
hint={text('hint', '')} | ||
error={text('error', '')} | ||
leftAddons={boolean('leftAddons', false) && <Icon />} | ||
bottomAddons={boolean('bottomAddons', false) && <span>bottom text</span>} | ||
/> | ||
</Story> | ||
|
||
|
||
Компонент может быть использован как controlled с помощью `onChange` и `selected`: | ||
|
||
<Preview> | ||
{React.createElement(() => { | ||
const [amount, setAmount] = React.useState({ | ||
value: 100000, | ||
currency: 'RUR', | ||
minorUnits: 100 | ||
}); | ||
const handleChange = (event, payload) => { | ||
setAmount(payload.amount); | ||
}; | ||
return ( | ||
<Container> | ||
<Row align="middle"> | ||
<Col> | ||
<MoneyInput | ||
amount={amount} | ||
onChange={handleChange} | ||
/> | ||
</Col> | ||
<Col> | ||
{ JSON.stringify(amount) } | ||
</Col> | ||
</Row> | ||
</Container> | ||
); | ||
})} | ||
</Preview> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
import React from 'react'; | ||
import { render } from '@testing-library/react'; | ||
|
||
import { MoneyInput } from './index'; | ||
|
||
const THINSP = String.fromCharCode(8201); | ||
// prettier-ignore | ||
|
||
describe('MoneyInput', () => { | ||
describe('Snapshots tests', () => { | ||
it('should match snapshot', () => { | ||
expect(render(<MoneyInput />)).toMatchSnapshot(); | ||
}); | ||
}); | ||
|
||
it('should render passed amount', () => { | ||
const dataTestId = 'test-id'; | ||
const { getByTestId } = render( | ||
<MoneyInput | ||
amount={{ | ||
value: 1234500, | ||
currency: 'RUR', | ||
minorUnits: 100, | ||
}} | ||
dataTestId={dataTestId} | ||
/>, | ||
); | ||
|
||
const input = getByTestId(dataTestId) as HTMLInputElement; | ||
|
||
expect(input.value).toBe(`12${THINSP}345,00`); | ||
}); | ||
|
||
/** | ||
* 100003 вставить ',' после 100 | ||
* 100003 вставить '.' после 100 | ||
* максимум 12 символов в мажорной части и не более 2х в минорной | ||
* 1234 каретка перед двойкой backspace | ||
* Тест на то что если amount зацикливать то все ок | ||
* Тест на то что если задать amount1 раз - все работает | ||
* Тест при вставке невалидного символа каретка не двигается | ||
*/ | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,156 @@ | ||
import cn from 'classnames'; | ||
import React, { useState, useEffect } from 'react'; | ||
import { Input, InputProps } from '@alfalab/core-components-input'; | ||
|
||
import { CURRENCY_CODES } from './utils/currencyCodes'; | ||
|
||
import { getFormatedValue, getAmountValueFromStr, formatAmount } from './utils'; | ||
import styles from './index.module.css'; | ||
|
||
type AmountType = { | ||
/** Сумма в минорных единицах */ | ||
value: number; | ||
/** Валюта */ | ||
currency: string; | ||
/** Минорные единицы */ | ||
minorUnits: number; | ||
}; | ||
|
||
export type MoneyInputProps = Omit<InputProps, 'label' | 'onChange' | 'rightAddons'> & { | ||
/** | ||
* Денежное значение | ||
*/ | ||
amount?: AmountType; | ||
|
||
/** | ||
* Label для input | ||
*/ | ||
label?: string; | ||
|
||
/** | ||
* Дополнительный класс | ||
*/ | ||
className?: string; | ||
|
||
/** | ||
* Обработчик события изменения значения | ||
*/ | ||
onChange?: ( | ||
e: React.ChangeEvent<HTMLInputElement>, | ||
payload: { amount: AmountType; value: string }, | ||
) => void; | ||
|
||
/** | ||
* Идентификатор для систем автоматизированного тестирования | ||
*/ | ||
dataTestId?: string; | ||
}; | ||
|
||
export const MoneyInput: React.FC<MoneyInputProps> = ({ | ||
amount = { | ||
value: 0, | ||
minorUnits: 100, | ||
currency: 'RUR', | ||
}, | ||
label = 'Сумма', | ||
className, | ||
dataTestId, | ||
onChange, | ||
...restProps | ||
}: MoneyInputProps) => { | ||
const { value, minorUnits, currency } = amount; | ||
const [inputValue, setInputValue] = useState<string>(value === 0 ? '' : value.toString()); | ||
const currencySymbol = CURRENCY_CODES[currency]; | ||
|
||
useEffect(() => { | ||
const currentAmountValue = getAmountValueFromStr(inputValue, minorUnits); | ||
if (currentAmountValue !== value) { | ||
const { majorPart, minorPart } = formatAmount({ | ||
value, | ||
currency: { code: currency, minority: minorUnits }, | ||
}); | ||
|
||
const newFormatedValue = `${majorPart},${minorPart}`; | ||
return setInputValue(newFormatedValue); | ||
} | ||
|
||
return () => undefined; | ||
// eslint-disable-next-line react-hooks/exhaustive-deps | ||
}, [value, currency, minorUnits]); | ||
|
||
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => { | ||
const enteredValue = e.target.value.replace(/\s/g, '').replace('.', ','); | ||
const isCorrectEnteredValue = /(^[0-9]{1,12}(,([0-9]+)?)?$|^\s*$)/.test(enteredValue); | ||
|
||
if (isCorrectEnteredValue) { | ||
const newFormatedValue = getFormatedValue(enteredValue, currency, minorUnits); | ||
|
||
if (newFormatedValue === inputValue) { | ||
const caret = e.target.selectionStart; | ||
const element = e.target; | ||
window.requestAnimationFrame(() => { | ||
element.selectionStart = caret; | ||
element.selectionEnd = caret; | ||
}); | ||
} else { | ||
/** | ||
* Поддержка положения картки | ||
* Поскольку при форматировании введенного значения могут появляться символы типа пробела | ||
* или запятая - каретка прыгает в конец и ее необходимо ставить в правильное место | ||
*/ | ||
|
||
// TODO: тут полная жопа | ||
|
||
// Узнаем длину оригинального инпута с учловием обрезания лишних символов | ||
|
||
const [head, tail] = e.target.value.split(/\.|,/); | ||
let l = head.length; | ||
if (tail) { | ||
l += 1; | ||
l += tail.slice(0, 2).length; | ||
} | ||
|
||
const diff = newFormatedValue.length - l; | ||
const caret = (e.target.selectionStart as number) + diff; | ||
const element = e.target; | ||
window.requestAnimationFrame(() => { | ||
element.selectionStart = caret; | ||
element.selectionEnd = caret; | ||
}); | ||
} | ||
|
||
setInputValue(newFormatedValue); | ||
if (onChange) { | ||
const enteredAmount = { | ||
...amount, | ||
value: getAmountValueFromStr(newFormatedValue, minorUnits), | ||
}; | ||
onChange(e, { | ||
amount: enteredAmount, | ||
value: '', | ||
}); | ||
} | ||
} else { | ||
// Не двигаем каретку когда вставляется невалидный символ | ||
const caret = (e.target.selectionStart as number) - 1; | ||
const element = e.target; | ||
window.requestAnimationFrame(() => { | ||
element.selectionStart = caret; | ||
element.selectionEnd = caret; | ||
}); | ||
} | ||
}; | ||
|
||
return ( | ||
<Input | ||
{...restProps} | ||
label={label} | ||
value={inputValue} | ||
rightAddons={<span className={styles.currency}>{currencySymbol}</span>} | ||
className={className} | ||
inputClassName={cn(styles.input)} | ||
onChange={handleChange} | ||
dataTestId={dataTestId} | ||
/> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
@import '../../themes/src/default.css'; | ||
|
||
:root { | ||
--money-input-currency-color: var(--color-dark-indigo-60-flat); | ||
} | ||
|
||
.input { | ||
font-weight: bold; | ||
} | ||
|
||
.currency { | ||
width: 24px; | ||
display: flex; | ||
justify-content: center; | ||
color: var(--money-input-currency-color); | ||
@mixin styrene_20-24_medium; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export * from './Component'; |
Oops, something went wrong.