Skip to content

Commit

Permalink
feat(core-components-money-input): add money-input, story, base test
Browse files Browse the repository at this point in the history
  • Loading branch information
stepancar committed Jun 11, 2020
1 parent 2d82709 commit aab595a
Show file tree
Hide file tree
Showing 12 changed files with 624 additions and 0 deletions.
22 changes: 22 additions & 0 deletions packages/money-input/package.json
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"
}
}
76 changes: 76 additions & 0 deletions packages/money-input/src/Component.stories.mdx
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>
43 changes: 43 additions & 0 deletions packages/money-input/src/Component.test.tsx
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 раз - все работает
* Тест при вставке невалидного символа каретка не двигается
*/
});
156 changes: 156 additions & 0 deletions packages/money-input/src/Component.tsx
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}
/>
);
};
17 changes: 17 additions & 0 deletions packages/money-input/src/index.module.css
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;
}
1 change: 1 addition & 0 deletions packages/money-input/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './Component';

0 comments on commit aab595a

Please sign in to comment.