re-structure to monorepo.

This commit is contained in:
a.bouhuolia
2023-02-03 01:02:31 +02:00
parent 8242ec64ba
commit 7a0a13f9d5
10400 changed files with 46966 additions and 17223 deletions

View File

@@ -0,0 +1,27 @@
// @ts-nocheck
import {
FormGroup,
InputGroup,
NumericInput,
Checkbox,
RadioGroup,
Switch,
EditableText,
TextArea,
} from '@blueprintjs-formik/core';
import { Select, MultiSelect } from '@blueprintjs-formik/select';
import { DateInput } from '@blueprintjs-formik/datetime';
export {
FormGroup as FFormGroup,
InputGroup as FInputGroup,
NumericInput as FNumericInput,
Checkbox as FCheckbox,
RadioGroup as FRadioGroup,
Switch as FSwitch,
Select as FSelect,
MultiSelect as FMultiSelect,
EditableText as FEditableText,
TextArea as FTextArea,
DateInput as FDateInput,
};

View File

@@ -0,0 +1,28 @@
// @ts-nocheck
import React, { useState } from 'react';
import {
Checkbox as BPCheckbox,
} from '@blueprintjs/core';
export default function CheckboxComponent(props: any) {
const { field, form, ...rest } = props;
const [value, setValue] = useState(field.value || false);
const handleChange = () => {
const checked = !value;
form.setFieldValue(field.name, checked);
setValue(checked);
};
const handleBlur = () => {
form.setFieldTouched(field.name);
};
const checkboxProps = {
...rest,
onChange: handleChange,
onBlur: handleBlur,
checked: value,
}
return <BPCheckbox {...checkboxProps} />;
}

View File

@@ -0,0 +1,37 @@
// @ts-nocheck
import React from 'react';
import { Intent } from '@blueprintjs/core';
import { Field, getIn } from 'formik';
import { CurrencyInput } from './MoneyInputGroup';
const fieldToMoneyInputGroup = ({
field: { onBlur: onFieldBlur, ...field },
form: { setFieldValue, touched, errors },
onBlur,
...props
}) => {
const fieldError = getIn(errors, field.name);
const showError = getIn(touched, field.name) && !!fieldError;
return {
intent: showError ? Intent.DANGER : Intent.NONE,
onBlurValue:
onBlur ??
function (e) {
onFieldBlur(e ?? field.name);
},
...field,
onChange: (value) => {
setFieldValue(field.name, value);
},
...props,
};
};
function FieldToMoneyInputGroup({ ...props }) {
return <CurrencyInput {...fieldToMoneyInputGroup(props)} />;
}
export function FMoneyInputGroup({ ...props }) {
return <Field {...props} component={FieldToMoneyInputGroup} />;
}

View File

@@ -0,0 +1,15 @@
// @ts-nocheck
import { useEffect } from 'react'
export function FormObserver({ onChange, values }) {
useEffect(() => {
onChange(values);
}, [Object.values(values).join(', ')]);
return null;
}
FormObserver.defaultProps = {
onChange: () => null,
};

View File

@@ -0,0 +1,17 @@
// @ts-nocheck
import { useFormikContext } from 'formik';
import { useDeepCompareEffect } from '@/hooks/utils';
export function FormikObserver({ onChange }) {
const { values } = useFormikContext();
useDeepCompareEffect(() => {
onChange(values);
}, [values]);
return null;
}
FormikObserver.defaultProps = {
onChange: () => null,
};

View File

@@ -0,0 +1,10 @@
// @ts-nocheck
import React from 'react';
export default function InputPrepend({ children }) {
return (
<div class="input-prepend">
{ children }
</div>
);
}

View File

@@ -0,0 +1,31 @@
// @ts-nocheck
import React, { useMemo } from 'react';
import classNames from 'classnames';
import { Button, Tooltip, Classes } from '@blueprintjs/core';
export function InputPrependButton({
buttonProps = {},
tooltip = false,
tooltipProps = {},
}) {
const appendButton = useMemo(
() => (
<Button
className={classNames('input-prepend__button', Classes.SMALL)}
{...buttonProps}
/>
),
[buttonProps],
);
const appendButtonWithTooltip = useMemo(
() => <Tooltip {...tooltipProps}>{appendButton}</Tooltip>,
[tooltipProps, appendButton],
);
return (
<div class="input-prepend">
{tooltip ? appendButtonWithTooltip : appendButton}
</div>
);
}

View File

@@ -0,0 +1,10 @@
// @ts-nocheck
import React from 'react';
export function InputPrependText({ text, children }) {
return (
<div class="input-group-prepend">
<span class="input-group-text">{text}</span>
</div>
);
}

View File

@@ -0,0 +1,121 @@
// @ts-nocheck
type Overwrite<T, U> = Pick<T, Exclude<keyof T, keyof U>> & U;
export type Separator = ',' | '.';
export type CurrencyInputProps = Overwrite<
React.InputHTMLAttributes<HTMLInputElement>,
{
/**
* Allow decimals
*
* Default = true
*/
allowDecimals?: boolean;
/**
* Allow user to enter negative value
*
* Default = true
*/
allowNegativeValue?: boolean;
/**
* Component id
*/
id?: string;
/**
* Maximum characters the user can enter
*/
maxLength?: number;
/**
* Class names
*/
className?: string;
/**
* Limit length of decimals allowed
*
* Default = 2
*/
decimalsLimit?: number;
/**
* Default value
*/
defaultValue?: number | string;
/**
* Disabled
*
* Default = false
*/
disabled?: boolean;
/**
* Value will always have the specified length of decimals
*/
fixedDecimalLength?: number;
/**
* Handle change in value
*/
onChange?: (value: string | undefined, name?: string) => void;
/**
* Handle value onBlur
*
*/
onBlurValue?: (value: string | undefined, name?: string) => void;
/**
* Placeholder
*/
placeholder?: string;
/**
* Specify decimal precision for padding/trimming
*/
precision?: number;
/**
* Include a prefix eg. £
*/
prefix?: string;
/**
* Incremental value change on arrow down and arrow up key press
*/
step?: number;
/**
* Separator between integer part and fractional part of value. Cannot be a number
*
* Default = "."
*/
decimalSeparator?: string;
/**
* Separator between thousand, million and billion. Cannot be a number
*
* Default = ","
*/
groupSeparator?: string;
/**
* Disable auto adding separator between values eg. 1000 > 1,000
*
* Default = false
*/
turnOffSeparators?: boolean;
/**
* Disable abbreviations eg. 1k > 1,000, 2m > 2,000,000
*
* Default = false
*/
turnOffAbbreviations?: boolean;
}
>;

View File

@@ -0,0 +1,213 @@
// @ts-nocheck
import React, { FC, useState, useEffect, useRef } from 'react';
import { InputGroup } from '@blueprintjs/core';
import { CurrencyInputProps } from './CurrencyInputProps';
import {
isNumber,
cleanValue,
fixedDecimalValue,
formatValue,
padTrimValue,
CleanValueOptions,
} from './utils';
export const CurrencyInput: FC<CurrencyInputProps> = ({
allowDecimals = true,
allowNegativeValue = true,
id,
name,
className,
decimalsLimit,
defaultValue,
disabled = false,
maxLength: userMaxLength,
value: userValue,
onChange,
onBlurValue,
fixedDecimalLength,
placeholder,
precision,
prefix,
step,
decimalSeparator = '.',
groupSeparator = ',',
turnOffSeparators = false,
turnOffAbbreviations = false,
...props
}: CurrencyInputProps) => {
if (decimalSeparator === groupSeparator) {
throw new Error('decimalSeparator cannot be the same as groupSeparator');
}
if (isNumber(decimalSeparator)) {
throw new Error('decimalSeparator cannot be a number');
}
if (isNumber(groupSeparator)) {
throw new Error('groupSeparator cannot be a number');
}
const formatValueOptions = {
decimalSeparator,
groupSeparator,
turnOffSeparators,
prefix,
};
const cleanValueOptions: Partial<CleanValueOptions> = {
decimalSeparator,
groupSeparator,
allowDecimals,
decimalsLimit: decimalsLimit || fixedDecimalLength || 2,
allowNegativeValue,
turnOffAbbreviations,
prefix,
};
const _defaultValue =
defaultValue !== undefined
? formatValue({ value: String(defaultValue), ...formatValueOptions })
: '';
const [stateValue, setStateValue] = useState(_defaultValue);
const [cursor, setCursor] = useState(0);
const inputRef = useRef<HTMLInputElement | null>(null);
const onFocus = (): number => (stateValue ? stateValue.length : 0);
const processChange = (
value: string,
selectionStart?: number | null,
): void => {
const valueOnly = cleanValue({ value, ...cleanValueOptions });
if (!valueOnly) {
onChange && onChange(undefined, name);
setStateValue('');
return;
}
if (userMaxLength && valueOnly.replace(/-/g, '').length > userMaxLength) {
return;
}
if (valueOnly === '-') {
onChange && onChange(undefined, name);
setStateValue(value);
return;
}
const formattedValue = formatValue({
value: valueOnly,
...formatValueOptions,
});
/* istanbul ignore next */
if (selectionStart !== undefined && selectionStart !== null) {
const cursor =
selectionStart + (formattedValue.length - value.length) || 1;
setCursor(cursor);
}
setStateValue(formattedValue);
onChange && onChange(valueOnly, name);
};
const handleOnChange = ({
target: { value, selectionStart },
}: React.ChangeEvent<HTMLInputElement>): void => {
processChange(value, selectionStart);
};
const handleOnBlur = ({
target: { value },
}: React.ChangeEvent<HTMLInputElement>): void => {
const valueOnly = cleanValue({ value, ...cleanValueOptions });
if (valueOnly === '-' || !valueOnly) {
onBlurValue && onBlurValue(undefined, name);
setStateValue('');
return;
}
const fixedDecimals = fixedDecimalValue(
valueOnly,
decimalSeparator,
fixedDecimalLength,
);
// Add padding or trim value to precision
const newValue = padTrimValue(
fixedDecimals,
decimalSeparator,
precision || fixedDecimalLength,
);
onChange && onChange(newValue, name);
onBlurValue && onBlurValue(newValue, name);
const formattedValue = formatValue({
value: newValue,
...formatValueOptions,
});
setStateValue(formattedValue);
};
const handleOnKeyDown = ({ key }: React.KeyboardEvent<HTMLInputElement>) => {
if (step && (key === 'ArrowUp' || key === 'ArrowDown')) {
const currentValue =
Number(
userValue !== undefined
? userValue
: cleanValue({ value: stateValue, ...cleanValueOptions }),
) || 0;
const newValue =
key === 'ArrowUp'
? String(currentValue + Number(step))
: String(currentValue - Number(step));
processChange(newValue);
}
};
/* istanbul ignore next */
useEffect(() => {
if (inputRef && inputRef.current) {
inputRef.current.setSelectionRange(cursor, cursor);
}
}, [cursor, inputRef]);
const formattedPropsValue =
userValue !== undefined
? formatValue({ value: String(userValue), ...formatValueOptions })
: undefined;
const handleInputRef = (ref: HTMLInputElement | null) => {
inputRef.current = ref;
return null;
};
return (
<InputGroup
type="text"
inputMode="decimal"
id={id}
name={name}
className={className}
onChange={handleOnChange}
onBlur={handleOnBlur}
onFocus={onFocus}
onKeyDown={handleOnKeyDown}
placeholder={placeholder}
disabled={disabled}
value={
formattedPropsValue !== undefined && stateValue !== '-'
? formattedPropsValue
: stateValue
}
inputRef={handleInputRef}
{...props}
/>
);
};
export { CurrencyInput as MoneyInputGroup };

View File

@@ -0,0 +1,12 @@
// @ts-nocheck
import { addSeparators } from '../addSeparators';
describe('Separators', () => {
it('should add separator in string', () => {
expect(addSeparators('1000000')).toEqual('1,000,000');
});
it('should use custom separator when provided', () => {
expect(addSeparators('1000000', '.')).toEqual('1.000.000');
});
});

View File

@@ -0,0 +1,221 @@
// @ts-nocheck
import { cleanValue } from '../cleanValue';
describe('cleanValue', () => {
it('should remove group separator in string', () => {
expect(
cleanValue({
value: '1,000,000',
})
).toEqual('1000000');
});
it('should handle period decimal separator in string', () => {
expect(
cleanValue({
value: '1.000.000,12',
decimalSeparator: ',',
groupSeparator: '.',
})
).toEqual('1000000,12');
});
it('should remove prefix', () => {
expect(
cleanValue({
value: '£1000000',
prefix: '£',
})
).toEqual('1000000');
expect(
cleanValue({
value: '$5.5',
prefix: '$',
})
).toEqual('5.5');
});
it('should remove extra decimals', () => {
expect(
cleanValue({
value: '100.0000',
})
).toEqual('100.00');
});
it('should remove decimals if not allowed', () => {
expect(
cleanValue({
value: '100.0000',
allowDecimals: false,
decimalsLimit: 0,
})
).toEqual('100');
});
it('should include decimals if allowed', () => {
expect(
cleanValue({
value: '100.123',
allowDecimals: true,
decimalsLimit: 0,
})
).toEqual('100.123');
});
it('should format value', () => {
expect(
cleanValue({
value: '£1,234,567.89',
prefix: '£',
})
).toEqual('1234567.89');
});
describe('negative values', () => {
it('should handle negative value', () => {
expect(
cleanValue({
value: '-£1,000',
decimalSeparator: '.',
groupSeparator: ',',
allowDecimals: true,
decimalsLimit: 2,
prefix: '£',
})
).toEqual('-1000');
});
it('should handle negative value with decimal', () => {
expect(
cleanValue({
value: '-£99,999.99',
decimalSeparator: '.',
groupSeparator: ',',
allowDecimals: true,
decimalsLimit: 2,
prefix: '£',
})
).toEqual('-99999.99');
});
it('should handle not allow negative value if allowNegativeValue is false', () => {
expect(
cleanValue({
value: '-£1,000',
decimalSeparator: '.',
groupSeparator: ',',
allowDecimals: true,
decimalsLimit: 2,
allowNegativeValue: false,
prefix: '£',
})
).toEqual('1000');
});
});
it('should handle values placed before prefix', () => {
expect(
cleanValue({
value: '2£1',
prefix: '£',
})
).toEqual('12');
expect(
cleanValue({
value: '-2£1',
prefix: '£',
})
).toEqual('-12');
expect(
cleanValue({
value: '2-£1',
prefix: '£',
})
).toEqual('-12');
expect(
cleanValue({
value: '2-£1.99',
prefix: '£',
decimalsLimit: 5,
})
).toEqual('-1.992');
});
describe('abbreviations', () => {
it('should return empty string if abbreviation only', () => {
expect(
cleanValue({
value: 'k',
turnOffAbbreviations: true,
})
).toEqual('');
expect(
cleanValue({
value: 'm',
turnOffAbbreviations: true,
})
).toEqual('');
expect(
cleanValue({
value: 'b',
turnOffAbbreviations: true,
})
).toEqual('');
});
it('should return empty string if prefix and abbreviation only', () => {
expect(
cleanValue({
value: '$k',
prefix: '$',
turnOffAbbreviations: true,
})
).toEqual('');
expect(
cleanValue({
value: '£m',
prefix: '£',
turnOffAbbreviations: true,
})
).toEqual('');
});
it('should ignore abbreviations if turnOffAbbreviations is true', () => {
expect(
cleanValue({
value: '1k',
turnOffAbbreviations: true,
})
).toEqual('1');
expect(
cleanValue({
value: '-2k',
turnOffAbbreviations: true,
})
).toEqual('-2');
expect(
cleanValue({
value: '25.6m',
turnOffAbbreviations: true,
})
).toEqual('25.6');
expect(
cleanValue({
value: '9b',
turnOffAbbreviations: true,
})
).toEqual('9');
});
});
});

View File

@@ -0,0 +1,30 @@
// @ts-nocheck
import { fixedDecimalValue } from '../fixedDecimalValue';
describe('fixedDecimalValue', () => {
it('should return original value if no match', () => {
expect(fixedDecimalValue('abc', '.', 2)).toEqual('abc');
});
it('should work with 2 fixed decimal length', () => {
expect(fixedDecimalValue('1', '.', 2)).toEqual('1');
expect(fixedDecimalValue('12', '.', 2)).toEqual('1.2');
expect(fixedDecimalValue('123', '.', 2)).toEqual('1.23');
expect(fixedDecimalValue('12345', '.', 2)).toEqual('123.45');
expect(fixedDecimalValue('123.4567', '.', 2)).toEqual('123.45');
});
it('should work with 4 fixed decimal length', () => {
expect(fixedDecimalValue('12', ',', 4)).toEqual('1,2');
expect(fixedDecimalValue('123', ',', 4)).toEqual('1,23');
expect(fixedDecimalValue('1234', ',', 4)).toEqual('1,234');
expect(fixedDecimalValue('12345', ',', 4)).toEqual('1,2345');
});
it('should trim decimals if too long', () => {
expect(fixedDecimalValue('1.23', '.', 2)).toEqual('1.23');
expect(fixedDecimalValue('1.2345', '.', 2)).toEqual('1.23');
expect(fixedDecimalValue('1,2345678', ',', 3)).toEqual('1,234');
expect(fixedDecimalValue('123,45678', ',', 3)).toEqual('123,456');
});
});

View File

@@ -0,0 +1,173 @@
// @ts-nocheck
import { formatValue } from '../formatValue';
describe('formatValue', () => {
it('should return empty if blank value', () => {
expect(
formatValue({
value: '',
})
).toEqual('');
});
it('should add separator', () => {
expect(
formatValue({
value: '1234567',
})
).toEqual('1,234,567');
});
it('should handle period separator', () => {
expect(
formatValue({
value: '1234567',
decimalSeparator: '.',
groupSeparator: '.',
})
).toEqual('1.234.567');
});
it('should handle comma separator for decimals', () => {
expect(
formatValue({
value: '1234567,89',
decimalSeparator: '.',
groupSeparator: '.',
})
).toEqual('1.234.567,89');
});
it('should handle - as separator for decimals', () => {
expect(
formatValue({
value: '1234567-89',
decimalSeparator: '-',
groupSeparator: '.',
})
).toEqual('1.234.567-89');
});
it('should handle empty decimal separator', () => {
expect(
formatValue({
value: '1234567-89',
decimalSeparator: '',
groupSeparator: '.',
})
).toEqual('1.234.567-89');
});
it('should NOT add separator if "turnOffSeparators" is true', () => {
expect(
formatValue({
value: '1234567',
turnOffSeparators: true,
})
).toEqual('1234567');
});
it('should NOT add separator if "turnOffSeparators" is true even if decimal and group separators specified', () => {
expect(
formatValue({
value: '1234567',
decimalSeparator: '.',
groupSeparator: ',',
turnOffSeparators: true,
})
).toEqual('1234567');
});
it('should add prefix', () => {
expect(
formatValue({
value: '123',
prefix: '£',
})
).toEqual('£123');
});
it('should include "."', () => {
expect(
formatValue({
value: '1234567.',
})
).toEqual('1,234,567.');
});
it('should include decimals', () => {
expect(
formatValue({
value: '1234.567',
})
).toEqual('1,234.567');
});
it('should format value', () => {
expect(
formatValue({
value: '1234567.89',
prefix: '£',
})
).toEqual('£1,234,567.89');
});
it('should handle 0 value', () => {
expect(
formatValue({
value: '0',
prefix: '£',
})
).toEqual('£0');
});
describe('negative values', () => {
it('should handle negative values', () => {
expect(
formatValue({
value: '-1234',
prefix: '£',
})
).toEqual('-£1,234');
});
it('should return negative sign if only negative sign', () => {
expect(
formatValue({
value: '-',
prefix: '£',
})
).toEqual('-');
});
});
it('should handle negative value and "-" as groupSeparator', () => {
expect(
formatValue({
value: '-1234',
groupSeparator: '-',
prefix: '£',
})
).toEqual('-£1-234');
});
it('should handle negative value and "-" as decimalSeparator', () => {
expect(
formatValue({
value: '-12-34',
decimalSeparator: '-',
prefix: '£',
})
).toEqual('-£12-34');
});
it('should handle negative value and "-" as groupSeparator', () => {
expect(
formatValue({
value: '-123456',
groupSeparator: '-',
prefix: '£',
})
).toEqual('-£123-456');
});
});

View File

@@ -0,0 +1,44 @@
// @ts-nocheck
import { isNumber } from '../isNumber';
describe('isNumber', () => {
it('should return true for 0', () => {
expect(isNumber('0')).toBe(true);
});
it('should return true for -3', () => {
expect(isNumber('-3')).toBe(true);
});
it('should return true for 9', () => {
expect(isNumber('9')).toBe(true);
});
it('should return true for abc1', () => {
expect(isNumber('abc1')).toBe(true);
});
it('should return true for a.1', () => {
expect(isNumber('a.1')).toBe(true);
});
it('should return false for space', () => {
expect(isNumber(' ')).toBe(false);
});
it('should return false for comma', () => {
expect(isNumber(',')).toBe(false);
});
it('should return false for period', () => {
expect(isNumber('.')).toBe(false);
});
it('should return false for -', () => {
expect(isNumber('-')).toBe(false);
});
it('should return false for +', () => {
expect(isNumber('+')).toBe(false);
});
});

View File

@@ -0,0 +1,39 @@
// @ts-nocheck
import { padTrimValue } from '../padTrimValue';
describe('padTrimValue', () => {
it('should return original value if no precision', () => {
const value = padTrimValue('1000000');
expect(value).toEqual('1000000');
});
it('should return blank value if no value', () => {
const value = padTrimValue('', '.', 2);
expect(value).toEqual('');
});
it('should return blank value if no only negative', () => {
const value = padTrimValue('-', '.', 2);
expect(value).toEqual('');
});
it('should pad with 0 if no decimals', () => {
const value = padTrimValue('99', '.', 3);
expect(value).toEqual('99.000');
});
it('should pad with 0 if decimal length is less than precision', () => {
const value = padTrimValue('10.5', '.', 5);
expect(value).toEqual('10.50000');
});
it('should trim if decimal length is larger than precision', () => {
const value = padTrimValue('10.599', '.', 2);
expect(value).toEqual('10.59');
});
it('should trim handle comma as decimal separator', () => {
const value = padTrimValue('9,9', ',', 3);
expect(value).toEqual('9,900');
});
});

View File

@@ -0,0 +1,78 @@
// @ts-nocheck
import { abbrValue, parseAbbrValue } from '../parseAbbrValue';
describe('abbrValue', () => {
it('should not convert value under 1000', () => {
expect(abbrValue(999)).toEqual('999');
});
it('should convert thousand to k', () => {
expect(abbrValue(1000)).toEqual('1k');
expect(abbrValue(1500)).toEqual('1.5k');
expect(abbrValue(10000)).toEqual('10k');
});
it('should work with comma as decimal separator', () => {
expect(abbrValue(1500, ',')).toEqual('1,5k');
});
it('should work with decimal places option', () => {
expect(abbrValue(123456, '.')).toEqual('0.123456M');
expect(abbrValue(123456, '.', 2)).toEqual('0.12M');
});
});
describe('parseAbbrValue', () => {
it('should return undefined if cannot parse', () => {
expect(parseAbbrValue('1km')).toEqual(undefined);
expect(parseAbbrValue('2mb')).toEqual(undefined);
expect(parseAbbrValue('3a')).toEqual(undefined);
});
it('should return undefined if no abbreviation', () => {
expect(parseAbbrValue('1.23')).toEqual(undefined);
expect(parseAbbrValue('100')).toEqual(undefined);
expect(parseAbbrValue('20000')).toEqual(undefined);
});
it('should return undefined for only letter', () => {
expect(parseAbbrValue('k')).toBeUndefined();
expect(parseAbbrValue('m')).toBeUndefined();
expect(parseAbbrValue('b')).toBeUndefined();
});
it('should return 0 for 0', () => {
expect(parseAbbrValue('0k')).toEqual(0);
expect(parseAbbrValue('0m')).toEqual(0);
expect(parseAbbrValue('0b')).toEqual(0);
});
it('should parse k', () => {
expect(parseAbbrValue('1k')).toEqual(1000);
expect(parseAbbrValue('2K')).toEqual(2000);
expect(parseAbbrValue('1.1239999k')).toEqual(1123.9999);
expect(parseAbbrValue('1.5k')).toEqual(1500);
expect(parseAbbrValue('50.12K')).toEqual(50120);
expect(parseAbbrValue('100K')).toEqual(100000);
});
it('should parse m', () => {
expect(parseAbbrValue('1m')).toEqual(1000000);
expect(parseAbbrValue('1.5m')).toEqual(1500000);
expect(parseAbbrValue('45.123456m')).toEqual(45123456);
expect(parseAbbrValue('83.5m')).toEqual(83500000);
expect(parseAbbrValue('100M')).toEqual(100000000);
});
it('should parse b', () => {
expect(parseAbbrValue('1b')).toEqual(1000000000);
expect(parseAbbrValue('1.5b')).toEqual(1500000000);
expect(parseAbbrValue('65.5513b')).toEqual(65551300000);
expect(parseAbbrValue('100B')).toEqual(100000000000);
});
it('should work with comma as decimal separator', () => {
expect(parseAbbrValue('1,2k', ',')).toEqual(1200);
expect(parseAbbrValue('2,3m', ',')).toEqual(2300000);
});
});

View File

@@ -0,0 +1,24 @@
// @ts-nocheck
import { removeInvalidChars } from '../removeInvalidChars';
describe('removeInvalidChars', () => {
it('should remove letters in string', () => {
expect(removeInvalidChars('1,000ab,0cd00.99', [',', '.'])).toEqual('1,000,000.99');
});
it('should remove special characters in string', () => {
expect(removeInvalidChars('1.00ji0.0*&0^0', ['.'])).toEqual('1.000.000');
});
it('should keep abbreviations', () => {
expect(removeInvalidChars('9k', ['k'])).toEqual('9k');
expect(removeInvalidChars('1m', ['m'])).toEqual('1m');
expect(removeInvalidChars('5b', ['b'])).toEqual('5b');
});
it('should keep abbreviations (case insensitive)', () => {
expect(removeInvalidChars('9K', ['k'])).toEqual('9K');
expect(removeInvalidChars('1M', ['m'])).toEqual('1M');
expect(removeInvalidChars('5B', ['b'])).toEqual('5B');
});
});

View File

@@ -0,0 +1,12 @@
// @ts-nocheck
import { removeSeparators } from '../removeSeparators';
describe('removeSeparators', () => {
it('should remove separators in string', () => {
expect(removeSeparators('1,000,000')).toEqual('1000000');
});
it('should use custom separator when provided', () => {
expect(removeSeparators('1.000.000', '.')).toEqual('1000000');
});
});

View File

@@ -0,0 +1,7 @@
// @ts-nocheck
/**
* Add group separator to value eg. 1000 > 1,000
*/
export const addSeparators = (value: string, separator = ','): string => {
return value.replace(/\B(?=(\d{3})+(?!\d))/g, separator);
};

View File

@@ -0,0 +1,67 @@
// @ts-nocheck
import { parseAbbrValue } from './parseAbbrValue';
import { removeSeparators } from './removeSeparators';
import { removeInvalidChars } from './removeInvalidChars';
import { escapeRegExp } from './escapeRegExp';
export type CleanValueOptions = {
value: string;
decimalSeparator?: string;
groupSeparator?: string;
allowDecimals?: boolean;
decimalsLimit?: number;
allowNegativeValue?: boolean;
turnOffAbbreviations?: boolean;
prefix?: string;
};
/**
* Remove prefix, separators and extra decimals from value
*/
export const cleanValue = ({
value,
groupSeparator = ',',
decimalSeparator = '.',
allowDecimals = true,
decimalsLimit = 2,
allowNegativeValue = true,
turnOffAbbreviations = false,
prefix = '',
}: CleanValueOptions): string => {
const abbreviations = turnOffAbbreviations ? [] : ['k', 'm', 'b'];
const isNegative = value.includes('-');
const [prefixWithValue, preValue] = RegExp(`(\\d+)-?${escapeRegExp(prefix)}`).exec(value) || [];
const withoutPrefix = prefix ? value.replace(prefixWithValue, '').concat(preValue) : value;
const withoutSeparators = removeSeparators(withoutPrefix, groupSeparator);
const withoutInvalidChars = removeInvalidChars(withoutSeparators, [
groupSeparator,
decimalSeparator,
...abbreviations,
]);
let valueOnly = withoutInvalidChars;
if (!turnOffAbbreviations) {
// disallow letter without number
if (abbreviations.some((letter) => letter === withoutInvalidChars.toLowerCase())) {
return '';
}
const parsed = parseAbbrValue(withoutInvalidChars, decimalSeparator);
if (parsed) {
valueOnly = String(parsed);
}
}
const includeNegative = isNegative && allowNegativeValue ? '-' : '';
if (String(valueOnly).includes(decimalSeparator)) {
const [int, decimals] = withoutInvalidChars.split(decimalSeparator);
const trimmedDecimals = decimalsLimit ? decimals.slice(0, decimalsLimit) : decimals;
const includeDecimals = allowDecimals ? `${decimalSeparator}${trimmedDecimals}` : '';
return `${includeNegative}${int}${includeDecimals}`;
}
return `${includeNegative}${valueOnly}`;
};

View File

@@ -0,0 +1,9 @@
// @ts-nocheck
/**
* Escape regex char
*
* See: https://stackoverflow.com/questions/17885855/use-dynamic-variable-string-as-regex-pattern-in-javascript
*/
export const escapeRegExp = (stringToGoIntoTheRegex: string): string => {
return stringToGoIntoTheRegex.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
};

View File

@@ -0,0 +1,28 @@
// @ts-nocheck
export const fixedDecimalValue = (
value: string,
decimalSeparator: string,
fixedDecimalLength?: number
): string => {
if (fixedDecimalLength && value.length > 1) {
if (value.includes(decimalSeparator)) {
const [int, decimals] = value.split(decimalSeparator);
if (decimals.length > fixedDecimalLength) {
return `${int}${decimalSeparator}${decimals.slice(0, fixedDecimalLength)}`;
}
}
const reg =
value.length > fixedDecimalLength
? new RegExp(`(\\d+)(\\d{${fixedDecimalLength}})`)
: new RegExp(`(\\d)(\\d+)`);
const match = value.match(reg);
if (match) {
const [, int, decimals] = match;
return `${int}${decimalSeparator}${decimals}`;
}
}
return value;
};

View File

@@ -0,0 +1,79 @@
// @ts-nocheck
import { addSeparators } from './addSeparators';
type Props = {
/**
* Value to format
*/
value: number | string | undefined;
/**
* Decimal separator
*
* Default = '.'
*/
decimalSeparator?: string;
/**
* Group separator
*
* Default = ','
*/
groupSeparator?: string;
/**
* Turn off separators
*
* This will override Group separators
*
* Default = false
*/
turnOffSeparators?: boolean;
/**
* Prefix
*/
prefix?: string;
};
/**
* Format value with decimal separator, group separator and prefix
*/
export const formatValue = (props: Props): string => {
const {
value: _value,
groupSeparator = ',',
decimalSeparator = '.',
turnOffSeparators = false,
prefix,
} = props;
if (_value === '' || _value === undefined) {
return '';
}
const value = String(_value);
if (value === '-') {
return '-';
}
const isNegative = RegExp('^-\\d+').test(value);
const hasDecimalSeparator = decimalSeparator && value.includes(decimalSeparator);
const valueOnly = isNegative ? value.replace('-', '') : value;
const [int, decimals] = hasDecimalSeparator ? valueOnly.split(decimalSeparator) : [valueOnly];
const formattedInt = turnOffSeparators ? int : addSeparators(int, groupSeparator);
const includePrefix = prefix ? prefix : '';
const includeNegative = isNegative ? '-' : '';
const includeDecimals =
hasDecimalSeparator && decimals
? `${decimalSeparator}${decimals}`
: hasDecimalSeparator
? `${decimalSeparator}`
: '';
return `${includeNegative}${includePrefix}${formattedInt}${includeDecimals}`;
};

View File

@@ -0,0 +1,6 @@
// @ts-nocheck
export * from './cleanValue';
export * from './fixedDecimalValue';
export * from './formatValue';
export * from './isNumber';
export * from './padTrimValue';

View File

@@ -0,0 +1,2 @@
// @ts-nocheck
export const isNumber = (input: string): boolean => RegExp(/\d/, 'gi').test(input);

View File

@@ -0,0 +1,23 @@
// @ts-nocheck
export const padTrimValue = (value: string, decimalSeparator = '.', precision?: number): string => {
if (!precision || value === '' || value === undefined) {
return value;
}
if (!value.match(/\d/g)) {
return '';
}
const [int, decimals] = value.split(decimalSeparator);
let newValue = decimals || '';
if (newValue.length < precision) {
while (newValue.length < precision) {
newValue += '0';
}
} else {
newValue = newValue.slice(0, precision);
}
return `${int}${decimalSeparator}${newValue}`;
};

View File

@@ -0,0 +1,43 @@
// @ts-nocheck
import { escapeRegExp } from './escapeRegExp';
/**
* Abbreviate number eg. 1000 = 1k
*
* Source: https://stackoverflow.com/a/9345181
*/
export const abbrValue = (value: number, decimalSeparator = '.', _decimalPlaces = 10): string => {
if (value > 999) {
let valueLength = ('' + value).length;
const p = Math.pow;
const d = p(10, _decimalPlaces);
valueLength -= valueLength % 3;
const abbrValue = Math.round((value * d) / p(10, valueLength)) / d + ' kMGTPE'[valueLength / 3];
return abbrValue.replace('.', decimalSeparator);
}
return String(value);
};
type AbbrMap = { [key: string]: number };
const abbrMap: AbbrMap = { k: 1000, m: 1000000, b: 1000000000 };
/**
* Parse a value with abbreviation e.g 1k = 1000
*/
export const parseAbbrValue = (value: string, decimalSeparator = '.'): number | undefined => {
const reg = new RegExp(`(\\d+(${escapeRegExp(decimalSeparator)}\\d+)?)([kmb])$`, 'i');
const match = value.match(reg);
if (match) {
const [, digits, , abbr] = match;
const multiplier = abbrMap[abbr.toLowerCase()];
if (digits && multiplier) {
return Number(digits.replace(decimalSeparator, '.')) * multiplier;
}
}
return undefined;
};

View File

@@ -0,0 +1,11 @@
// @ts-nocheck
import { escapeRegExp } from './escapeRegExp';
/**
* Remove invalid characters
*/
export const removeInvalidChars = (value: string, validChars: ReadonlyArray<string>): string => {
const chars = escapeRegExp(validChars.join(''));
const reg = new RegExp(`[^\\d${chars}]`, 'gi');
return value.replace(reg, '');
};

View File

@@ -0,0 +1,10 @@
// @ts-nocheck
import { escapeRegExp } from './escapeRegExp';
/**
* Remove group separator from value eg. 1,000 > 1000
*/
export const removeSeparators = (value: string, separator = ','): string => {
const reg = new RegExp(escapeRegExp(separator), 'g');
return value.replace(reg, '');
};

View File

@@ -0,0 +1,7 @@
export * from './FormObserver';
export * from './FormikObserver';
export * from './FMoneyInputGroup';
export * from './BlueprintFormik';
export * from './InputPrependText';
export * from './InputPrependButton';
export * from './MoneyInputGroup';