@weareconceptstudio/form 0.2.7 → 0.2.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/form/BaseForm.js +13 -1
- package/dist/form/Builder/index.js +1 -2
- package/dist/form/Item/index.js +3 -4
- package/dist/form/hooks/rules.js +6 -3
- package/dist/form/hooks/useInput.js +2 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.js +0 -1
- package/dist/input/Input.js +1 -1
- package/dist/input/PhoneNumber/index.d.ts +16 -0
- package/dist/input/PhoneNumber/index.js +72 -0
- package/dist/input/index.d.ts +2 -0
- package/dist/input/index.js +2 -0
- package/package.json +3 -3
- package/dist/phone-number/index.d.ts +0 -3
- package/dist/phone-number/index.js +0 -36
package/dist/form/BaseForm.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useCallback, forwardRef, useImperativeHandle } from 'react';
|
|
1
|
+
import React, { useCallback, forwardRef, useImperativeHandle, useEffect } from 'react';
|
|
2
2
|
import { FormProvider, useForm } from 'react-hook-form';
|
|
3
3
|
import { Text } from '@weareconceptstudio/core';
|
|
4
4
|
import classNames from 'classnames';
|
|
@@ -44,6 +44,18 @@ const BaseForm = forwardRef(({ initialValues: defaultValues, values, children, o
|
|
|
44
44
|
isFormTouched: Object.keys(methods.formState.touchedFields).length > 0,
|
|
45
45
|
submit: () => methods.handleSubmit(handleSubmit)(),
|
|
46
46
|
}));
|
|
47
|
+
useEffect(() => {
|
|
48
|
+
let errors = methods.formState.errors;
|
|
49
|
+
if (Object.keys(errors).length > 0) {
|
|
50
|
+
const firstErrorField = Object.keys(errors)[0];
|
|
51
|
+
const errorElement = document.querySelector(`[name="${firstErrorField}"]`);
|
|
52
|
+
if (errorElement) {
|
|
53
|
+
errorElement.scrollIntoView({ behavior: "smooth", block: "center" });
|
|
54
|
+
// @ts-ignore
|
|
55
|
+
errors[firstErrorField].ref.select();
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}, [methods.formState.errors]);
|
|
47
59
|
return (React.createElement(FormProvider, { ...methods },
|
|
48
60
|
methods.formState.errors.root?.serverError && (React.createElement("div", { className: 'global-error-wrap' },
|
|
49
61
|
React.createElement(Text, { className: 'backend-error', text: methods.formState.errors.root?.serverError.message }),
|
|
@@ -5,7 +5,6 @@ import Form from '../';
|
|
|
5
5
|
import Input from '../../input';
|
|
6
6
|
import Checkbox from '../../checkbox';
|
|
7
7
|
import Radio from '../../radio';
|
|
8
|
-
import PhoneNumber from '../../phone-number';
|
|
9
8
|
import Select from '../../select';
|
|
10
9
|
import Upload from '../../upload';
|
|
11
10
|
import DatePicker from '../../date-picker';
|
|
@@ -17,9 +16,9 @@ const formElements = {
|
|
|
17
16
|
'password': React.createElement(Input.Password, null),
|
|
18
17
|
'textarea': React.createElement(Input.TextArea, null),
|
|
19
18
|
'number': React.createElement(Input.Number, null),
|
|
19
|
+
'phone': React.createElement(Input.PhoneNumber, null),
|
|
20
20
|
'checkbox': React.createElement(Checkbox, null),
|
|
21
21
|
'radio': React.createElement(Radio, null),
|
|
22
|
-
'phone': React.createElement(PhoneNumber, null),
|
|
23
22
|
'select': React.createElement(Select, null),
|
|
24
23
|
'upload': React.createElement(Upload, null),
|
|
25
24
|
'date': React.createElement(DatePicker, null),
|
package/dist/form/Item/index.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import React, { cloneElement, isValidElement, useRef } from 'react';
|
|
2
|
-
import lodashGet from 'lodash.get';
|
|
3
2
|
import classNames from 'classnames';
|
|
4
3
|
import { isForwardRefChild, useTranslation } from '@weareconceptstudio/core';
|
|
5
4
|
import ErrorMessage from '../../error-message';
|
|
@@ -11,7 +10,7 @@ const FormItem = ({ label, name, children, className, errorKey, required = true,
|
|
|
11
10
|
}
|
|
12
11
|
const childRef = useRef(null);
|
|
13
12
|
const { translate } = useTranslation();
|
|
14
|
-
const {
|
|
13
|
+
const { fieldState } = useInput({
|
|
15
14
|
name,
|
|
16
15
|
rules: useRules({
|
|
17
16
|
rules,
|
|
@@ -23,10 +22,10 @@ const FormItem = ({ label, name, children, className, errorKey, required = true,
|
|
|
23
22
|
}),
|
|
24
23
|
errorKey: errorKey || label,
|
|
25
24
|
});
|
|
26
|
-
const
|
|
25
|
+
const { error, invalid } = fieldState;
|
|
27
26
|
const classString = classNames('form-item', {
|
|
28
27
|
[className]: className,
|
|
29
|
-
'has-error':
|
|
28
|
+
'has-error': error || invalid,
|
|
30
29
|
// @ts-ignore
|
|
31
30
|
disabled: children.props.disabled,
|
|
32
31
|
});
|
package/dist/form/hooks/rules.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React, { useMemo } from 'react';
|
|
2
|
-
import { isValidPhoneNumber } from '
|
|
2
|
+
import { isValidPhoneNumber } from 'libphonenumber-js';
|
|
3
3
|
import { useTranslation } from '@weareconceptstudio/core';
|
|
4
4
|
export const validationPatterns = ({ type, pattern, message, translate }) => {
|
|
5
5
|
switch (type) {
|
|
@@ -22,7 +22,10 @@ export const useRules = ({ rules, required, requiredMessage, childType, errorKey
|
|
|
22
22
|
if (childType.toLowerCase() === 'input' || childType.toLowerCase() === 'textarea') {
|
|
23
23
|
r.push({
|
|
24
24
|
validator: (value) => {
|
|
25
|
-
|
|
25
|
+
if (!value || typeof value !== 'string' || value.trim().length === 0) {
|
|
26
|
+
return translate('form.validateMessages.whitespace', { errorKey: translate(errorKey) });
|
|
27
|
+
}
|
|
28
|
+
return true;
|
|
26
29
|
},
|
|
27
30
|
});
|
|
28
31
|
}
|
|
@@ -45,7 +48,7 @@ export const mapValidationRules = (errorKey, rules) => {
|
|
|
45
48
|
const { translate } = useTranslation();
|
|
46
49
|
return rules?.reduce((acc, rule) => {
|
|
47
50
|
if (rule.required) {
|
|
48
|
-
acc.required = rule.message || translate('form.validateMessages.required', { errorKey: translate(errorKey) });
|
|
51
|
+
acc.required = { value: rule.required, message: rule.message || translate('form.validateMessages.required', { errorKey: translate(errorKey) }) };
|
|
49
52
|
}
|
|
50
53
|
if (rule.pattern || rule.type) {
|
|
51
54
|
acc.pattern = validationPatterns({ ...rule, translate });
|
|
@@ -4,11 +4,12 @@ import { mapValidationRules } from './rules';
|
|
|
4
4
|
const defaultFormat = (value) => (value == null ? '' : value);
|
|
5
5
|
const defaultParse = (value) => (value === '' ? null : value);
|
|
6
6
|
export const useInput = (props) => {
|
|
7
|
-
const { defaultValue, name, onBlur: initialOnBlur, onChange: initialOnChange, format = defaultFormat, parse = defaultParse, multiple, errorKey, rules = [] } = props;
|
|
7
|
+
const { defaultValue, name, onBlur: initialOnBlur, onChange: initialOnChange, format = defaultFormat, parse = defaultParse, multiple, errorKey, rules = [], disabled } = props;
|
|
8
8
|
const { field: controllerField, fieldState, formState, } = useController({
|
|
9
9
|
name,
|
|
10
10
|
defaultValue,
|
|
11
11
|
rules: mapValidationRules(errorKey, rules),
|
|
12
|
+
disabled: disabled || undefined,
|
|
12
13
|
});
|
|
13
14
|
const onBlur = useEvent((...event) => {
|
|
14
15
|
controllerField.onBlur();
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
package/dist/input/Input.js
CHANGED
|
@@ -13,7 +13,7 @@ const Input = forwardRef((props, ref) => {
|
|
|
13
13
|
onBlur,
|
|
14
14
|
});
|
|
15
15
|
//! On Uppercase
|
|
16
|
-
const inputFirstLatterUppercase = type == 'text' && uppercase
|
|
16
|
+
const inputFirstLatterUppercase = type == 'text' && (name == 'first_name' || name == 'firstName' || name == 'last_name' || name == 'lastName' || name == 'name') && uppercase
|
|
17
17
|
? (e) => {
|
|
18
18
|
let inputValue = e.target.value.trimStart();
|
|
19
19
|
if (e.target.value.length > 0) {
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { CountryCode } from 'libphonenumber-js';
|
|
3
|
+
interface PhoneNumberProps {
|
|
4
|
+
name: string;
|
|
5
|
+
onChange?: (value: string) => void;
|
|
6
|
+
onBlur?: (event: React.FocusEvent<HTMLInputElement>) => void;
|
|
7
|
+
defaultCountry?: CountryCode;
|
|
8
|
+
maxLength?: number;
|
|
9
|
+
placeholder: string;
|
|
10
|
+
disabled?: boolean;
|
|
11
|
+
}
|
|
12
|
+
interface PhoneNumberRef extends HTMLInputElement {
|
|
13
|
+
childType: string;
|
|
14
|
+
}
|
|
15
|
+
declare const PhoneNumber: React.ForwardRefExoticComponent<PhoneNumberProps & React.RefAttributes<PhoneNumberRef>>;
|
|
16
|
+
export default PhoneNumber;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import React, { useImperativeHandle, useState, forwardRef, useEffect } from 'react';
|
|
2
|
+
import { parsePhoneNumberFromString, getCountryCallingCode, } from 'libphonenumber-js';
|
|
3
|
+
import BaseInput from '../BaseInput';
|
|
4
|
+
import { useInput } from '../../form/hooks/useInput';
|
|
5
|
+
const PhoneNumber = forwardRef((props, ref) => {
|
|
6
|
+
const { name, onChange, onBlur, defaultCountry = 'AM', maxLength = 14, placeholder, disabled } = props;
|
|
7
|
+
const { field } = useInput({
|
|
8
|
+
type: 'phone-number',
|
|
9
|
+
name,
|
|
10
|
+
onChange,
|
|
11
|
+
onBlur,
|
|
12
|
+
});
|
|
13
|
+
const defaultPrefix = `+${getCountryCallingCode(defaultCountry)}`;
|
|
14
|
+
const [inputValue, setInputValue] = useState(field.value || defaultPrefix);
|
|
15
|
+
const [selectedCountry, setSelectedCountry] = useState(defaultCountry);
|
|
16
|
+
// ✅ Attach childType to the input element
|
|
17
|
+
useImperativeHandle(ref, () => ({
|
|
18
|
+
// @ts-ignore
|
|
19
|
+
...field.ref, // Preserve default input methods
|
|
20
|
+
childType: 'phone', // Custom property
|
|
21
|
+
}));
|
|
22
|
+
// ✅ Ensure the prefix is set when the component mounts
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
if (!field.value || !field.value.startsWith('+')) {
|
|
25
|
+
let val = defaultPrefix;
|
|
26
|
+
if (field.value) {
|
|
27
|
+
val = parsePhoneNumberFromString(field.value, selectedCountry).formatInternational();
|
|
28
|
+
}
|
|
29
|
+
setInputValue(val);
|
|
30
|
+
field.onChange(val);
|
|
31
|
+
}
|
|
32
|
+
}, []);
|
|
33
|
+
const handleChange = (e) => {
|
|
34
|
+
let value = e.target.value;
|
|
35
|
+
// ✅ Remove all characters except numbers and "+"
|
|
36
|
+
value = value.replace(/[^\d+]/g, '');
|
|
37
|
+
// ✅ Ensure "+" is only at the start
|
|
38
|
+
if (value.includes('+') && value.indexOf('+') !== 0) {
|
|
39
|
+
value = value.replace(/\+/g, '');
|
|
40
|
+
}
|
|
41
|
+
// ✅ Remove any extra prefix occurrences (avoid +374+374 bug)
|
|
42
|
+
const countryCode = getCountryCallingCode(selectedCountry);
|
|
43
|
+
const cleanValue = value.replace(new RegExp(`^\\+?${countryCode}`), '');
|
|
44
|
+
// ✅ Ensure value always starts with "+374"
|
|
45
|
+
value = `+${countryCode}${cleanValue}`;
|
|
46
|
+
// ✅ If user deletes everything, reset only to "+374"
|
|
47
|
+
if (value === '+') {
|
|
48
|
+
value = `+${countryCode}`;
|
|
49
|
+
}
|
|
50
|
+
const phoneNumber = parsePhoneNumberFromString(value, selectedCountry);
|
|
51
|
+
if (phoneNumber) {
|
|
52
|
+
value = phoneNumber.formatInternational();
|
|
53
|
+
}
|
|
54
|
+
setInputValue(value);
|
|
55
|
+
field.onChange(phoneNumber ? phoneNumber.number : value);
|
|
56
|
+
};
|
|
57
|
+
const handleKeyDown = (e) => {
|
|
58
|
+
const countryCode = getCountryCallingCode(selectedCountry);
|
|
59
|
+
const prefix = `+${countryCode}`;
|
|
60
|
+
// ✅ Prevent backspace/delete from removing the prefix
|
|
61
|
+
if ((e.key === 'Backspace' || e.key === 'Delete') && inputValue === prefix) {
|
|
62
|
+
e.preventDefault();
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
// const handleCountryChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
|
|
66
|
+
// setSelectedCountry(e.target.value);
|
|
67
|
+
// };
|
|
68
|
+
return (React.createElement(BaseInput, { ...props, value: field.value, disabled: disabled },
|
|
69
|
+
React.createElement("div", { className: "phone-number-container" },
|
|
70
|
+
React.createElement("input", { type: "tel", ...field, value: inputValue, className: "phone-number-input", disabled: disabled, onKeyDown: handleKeyDown, onChange: handleChange, onBlur: field.onBlur, maxLength: maxLength, placeholder: placeholder }))));
|
|
71
|
+
});
|
|
72
|
+
export default PhoneNumber;
|
package/dist/input/index.d.ts
CHANGED
|
@@ -2,10 +2,12 @@ import Input from './Input';
|
|
|
2
2
|
import TextArea from './TextArea';
|
|
3
3
|
import Password from './Password';
|
|
4
4
|
import Number from './Number';
|
|
5
|
+
import PhoneNumber from './PhoneNumber';
|
|
5
6
|
type InputComponent = typeof Input & {
|
|
6
7
|
TextArea: typeof TextArea;
|
|
7
8
|
Password: typeof Password;
|
|
8
9
|
Number: typeof Number;
|
|
10
|
+
PhoneNumber: typeof PhoneNumber;
|
|
9
11
|
};
|
|
10
12
|
declare const EnhancedInput: InputComponent;
|
|
11
13
|
export default EnhancedInput;
|
package/dist/input/index.js
CHANGED
|
@@ -2,9 +2,11 @@ import Input from './Input';
|
|
|
2
2
|
import TextArea from './TextArea';
|
|
3
3
|
import Password from './Password';
|
|
4
4
|
import Number from './Number';
|
|
5
|
+
import PhoneNumber from './PhoneNumber';
|
|
5
6
|
// Assign the sub-components to the Input component
|
|
6
7
|
const EnhancedInput = Input;
|
|
7
8
|
EnhancedInput.TextArea = TextArea;
|
|
8
9
|
EnhancedInput.Password = Password;
|
|
9
10
|
EnhancedInput.Number = Number;
|
|
11
|
+
EnhancedInput.PhoneNumber = PhoneNumber;
|
|
10
12
|
export default EnhancedInput;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@weareconceptstudio/form",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.9",
|
|
4
4
|
"description": "Concept Studio Form",
|
|
5
5
|
"author": "Concept Studio",
|
|
6
6
|
"license": "ISC",
|
|
@@ -21,8 +21,8 @@
|
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
23
|
"@weareconceptstudio/core": "*",
|
|
24
|
-
"
|
|
25
|
-
"react-
|
|
24
|
+
"libphonenumber-js": "1.11.20",
|
|
25
|
+
"react-hook-form": "7.54.2",
|
|
26
26
|
"react-select": "5.8.0"
|
|
27
27
|
},
|
|
28
28
|
"devDependencies": {
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import React, { forwardRef, useImperativeHandle, useEffect } from 'react';
|
|
2
|
-
import classNames from 'classnames';
|
|
3
|
-
import PhoneInput from 'react-phone-number-input/input';
|
|
4
|
-
import { parsePhoneNumber } from 'react-phone-number-input';
|
|
5
|
-
import useFormInstance from '../form/hooks/useFormInstance';
|
|
6
|
-
import { Controller } from 'react-hook-form';
|
|
7
|
-
const PhoneNumber = forwardRef(({ name, className, autoComplete = 'new-password', country = 'AM', maxLength = 14, options = {} }, ref) => {
|
|
8
|
-
useImperativeHandle(ref, () => ({ childType: 'phone' }));
|
|
9
|
-
const { control, setValue, getValues, trigger } = useFormInstance();
|
|
10
|
-
const classString = classNames('phone-number-input', {
|
|
11
|
-
[className]: className,
|
|
12
|
-
});
|
|
13
|
-
useEffect(() => {
|
|
14
|
-
let defaultValue = getValues(name);
|
|
15
|
-
try {
|
|
16
|
-
if (defaultValue && !defaultValue.startsWith('+')) {
|
|
17
|
-
const parsed = parsePhoneNumber(defaultValue, country);
|
|
18
|
-
if (parsed)
|
|
19
|
-
defaultValue = parsed.number;
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
catch (e) { }
|
|
23
|
-
setValue(name, defaultValue);
|
|
24
|
-
}, [name, getValues, setValue, country]);
|
|
25
|
-
return (React.createElement(Controller, { name: name, control: control, render: ({ field: { onChange, value, onBlur } }) => {
|
|
26
|
-
return (React.createElement(PhoneInput, { name: name, international: true, value: value, country: country, maxLength: maxLength, withCountryCallingCode: true, className: classString, autoComplete: autoComplete, onChange: (value) => {
|
|
27
|
-
onChange({ value: value || '' });
|
|
28
|
-
setValue(name, value || '');
|
|
29
|
-
control._options.mode == 'onChange' && trigger(name);
|
|
30
|
-
}, onBlur: () => {
|
|
31
|
-
onBlur();
|
|
32
|
-
control._options.mode == 'onBlur' && trigger(name);
|
|
33
|
-
}, ...options }));
|
|
34
|
-
} }));
|
|
35
|
-
});
|
|
36
|
-
export default PhoneNumber;
|