formik-form-components 2.0.3 → 2.0.4
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/README.md +1 -21
- package/dist/Form/AppAutoCompleter.d.ts +1 -2
- package/dist/Form/AppAutoCompleter.d.ts.map +1 -1
- package/dist/Form/AppCheckBox.d.ts +1 -1
- package/dist/Form/AppCheckBox.d.ts.map +1 -1
- package/dist/Form/AppDateAndTimePicker.d.ts +1 -1
- package/dist/Form/AppDateAndTimePicker.d.ts.map +1 -1
- package/dist/Form/AppFormErrorMessage.d.ts +1 -1
- package/dist/Form/AppFormErrorMessage.d.ts.map +1 -1
- package/dist/Form/AppInputField.d.ts +1 -1
- package/dist/Form/AppInputField.d.ts.map +1 -1
- package/dist/Form/AppMultiSelector.d.ts +1 -1
- package/dist/Form/AppMultiSelector.d.ts.map +1 -1
- package/dist/Form/AppPhoneNoInput.d.ts +1 -1
- package/dist/Form/AppPhoneNoInput.d.ts.map +1 -1
- package/dist/Form/AppRadioGroup.d.ts +1 -1
- package/dist/Form/AppRadioGroup.d.ts.map +1 -1
- package/dist/Form/AppRating.d.ts +1 -1
- package/dist/Form/AppRating.d.ts.map +1 -1
- package/dist/Form/AppSimpleUploadFile.d.ts +1 -1
- package/dist/Form/AppSimpleUploadFile.d.ts.map +1 -1
- package/dist/Form/AppSwitch.d.ts +1 -2
- package/dist/Form/AppSwitch.d.ts.map +1 -1
- package/dist/Form/AppTagsCreator.d.ts +1 -2
- package/dist/Form/AppTagsCreator.d.ts.map +1 -1
- package/dist/Form/AppTextArea.d.ts +1 -1
- package/dist/Form/AppTextArea.d.ts.map +1 -1
- package/dist/Form/AppUploadFile.d.ts +1 -1
- package/dist/Form/AppUploadFile.d.ts.map +1 -1
- package/dist/Form/SubmitButton.d.ts +1 -1
- package/dist/Form/SubmitButton.d.ts.map +1 -1
- package/dist/index.d.ts +17 -0
- package/dist/index.esm.js +2 -1
- package/dist/index.js +2 -1
- package/dist/lib/index.d.ts +17 -0
- package/dist/lib/index.d.ts.map +1 -1
- package/package.json +16 -22
- package/src/App.css +0 -38
- package/src/App.test.tsx +0 -9
- package/src/App.tsx +0 -166
- package/src/Form/AppAutoCompleter.tsx +0 -252
- package/src/Form/AppCheckBox.tsx +0 -101
- package/src/Form/AppDateAndTimePicker.tsx +0 -94
- package/src/Form/AppDatePicker.tsx +0 -69
- package/src/Form/AppFormErrorMessage.tsx +0 -34
- package/src/Form/AppInputField.tsx +0 -80
- package/src/Form/AppMultiSelector.tsx +0 -163
- package/src/Form/AppPhoneNoInput.tsx +0 -106
- package/src/Form/AppRadioGroup.tsx +0 -92
- package/src/Form/AppRating.tsx +0 -98
- package/src/Form/AppSelectInput.tsx +0 -249
- package/src/Form/AppSimpleUploadFile.tsx +0 -154
- package/src/Form/AppSwitch.tsx +0 -84
- package/src/Form/AppTagsCreator.tsx +0 -252
- package/src/Form/AppTextArea.tsx +0 -90
- package/src/Form/AppUploadFile.tsx +0 -167
- package/src/Form/SubmitButton.tsx +0 -122
- package/src/Form/index.tsx +0 -27
- package/src/assets/illustrations/BackgroundIllustration.tsx +0 -42
- package/src/assets/illustrations/UploadIllustration.tsx +0 -659
- package/src/assets/illustrations/index.ts +0 -1
- package/src/file-thumbnail/types.ts +0 -7
- package/src/file-thumbnail/utils.ts +0 -162
- package/src/index.css +0 -9
- package/src/index.tsx +0 -19
- package/src/lib/index.ts +0 -47
- package/src/logo.svg +0 -1
- package/src/react-app-env.d.ts +0 -1
- package/src/reportWebVitals.ts +0 -15
- package/src/setupTests.ts +0 -5
- package/src/styles/PhoneInputCustom.css +0 -238
- package/src/styles/compiled-tailwind.css +0 -1
- package/src/upload/Upload.tsx +0 -162
- package/src/upload/errors/RejectionFiles.tsx +0 -49
- package/src/upload/index.ts +0 -5
- package/src/upload/preview/MultiFilePreview.tsx +0 -297
- package/src/upload/preview/SingleFilePreview.tsx +0 -81
- package/src/upload/types.ts +0 -51
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
import React from "react";
|
|
4
|
-
import { useFormikContext, FormikValues } from "formik";
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Returns current local datetime in `YYYY-MM-DDTHH:mm`
|
|
8
|
-
* format required by <input type="datetime-local" />
|
|
9
|
-
*/
|
|
10
|
-
const getNowForInput = (): string => {
|
|
11
|
-
return new Date(Date.now() - new Date().getTimezoneOffset() * 60000)
|
|
12
|
-
.toISOString()
|
|
13
|
-
.slice(0, 16);
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
interface AppDateTimeInputProps
|
|
17
|
-
extends Omit<
|
|
18
|
-
React.InputHTMLAttributes<HTMLInputElement>,
|
|
19
|
-
"type" | "name" | "value" | "onChange" | "min" | "max"
|
|
20
|
-
> {
|
|
21
|
-
name: string;
|
|
22
|
-
label?: string;
|
|
23
|
-
|
|
24
|
-
/** Disable selecting past date & time */
|
|
25
|
-
disablePast?: boolean;
|
|
26
|
-
|
|
27
|
-
/** Disable selecting future date & time */
|
|
28
|
-
disableFuture?: boolean;
|
|
29
|
-
min?: string;
|
|
30
|
-
max?: string;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
const AppDateTimeInput: React.FC<AppDateTimeInputProps> = ({
|
|
34
|
-
name,
|
|
35
|
-
label,
|
|
36
|
-
disablePast = false,
|
|
37
|
-
disableFuture = false,
|
|
38
|
-
className = "",
|
|
39
|
-
required,
|
|
40
|
-
min,
|
|
41
|
-
max,
|
|
42
|
-
...inputProps
|
|
43
|
-
}) => {
|
|
44
|
-
const { values, errors, touched, setFieldValue, setFieldTouched } =
|
|
45
|
-
useFormikContext<FormikValues>();
|
|
46
|
-
|
|
47
|
-
const value = values[name] ?? "";
|
|
48
|
-
const error = errors[name] as string | undefined;
|
|
49
|
-
const isTouched = touched[name] as boolean | undefined;
|
|
50
|
-
|
|
51
|
-
const now = getNowForInput();
|
|
52
|
-
|
|
53
|
-
// Priority:
|
|
54
|
-
// disablePast > min
|
|
55
|
-
// disableFuture > max
|
|
56
|
-
const computedMin = disablePast ? now : min;
|
|
57
|
-
const computedMax = disableFuture ? now : max;
|
|
58
|
-
|
|
59
|
-
return (
|
|
60
|
-
<div className={`w-full ${className}`}>
|
|
61
|
-
{label && (
|
|
62
|
-
<label className="mb-1 block text-sm font-medium text-gray-700">
|
|
63
|
-
{label}
|
|
64
|
-
{required && <span className="ml-0.5 text-red-500">*</span>}
|
|
65
|
-
</label>
|
|
66
|
-
)}
|
|
67
|
-
|
|
68
|
-
<input
|
|
69
|
-
type="datetime-local"
|
|
70
|
-
name={name}
|
|
71
|
-
value={value}
|
|
72
|
-
min={computedMin}
|
|
73
|
-
max={computedMax}
|
|
74
|
-
required={required}
|
|
75
|
-
onChange={(e) => setFieldValue(name, e.target.value)}
|
|
76
|
-
onBlur={() => setFieldTouched(name, true)}
|
|
77
|
-
className={`w-full rounded-lg border px-3 py-2 text-sm shadow-sm transition
|
|
78
|
-
focus:outline-none focus:ring-2
|
|
79
|
-
${
|
|
80
|
-
isTouched && error
|
|
81
|
-
? "border-red-500 focus:ring-red-500/20"
|
|
82
|
-
: "border-gray-300 focus:border-blue-500 focus:ring-blue-500/20"
|
|
83
|
-
}`}
|
|
84
|
-
{...inputProps}
|
|
85
|
-
/>
|
|
86
|
-
|
|
87
|
-
{isTouched && error && (
|
|
88
|
-
<p className="mt-1 text-xs text-red-500">{error}</p>
|
|
89
|
-
)}
|
|
90
|
-
</div>
|
|
91
|
-
);
|
|
92
|
-
};
|
|
93
|
-
|
|
94
|
-
export default AppDateTimeInput;
|
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
import React from "react";
|
|
4
|
-
import { useFormikContext, FormikValues } from "formik";
|
|
5
|
-
|
|
6
|
-
export interface AppDatePickerProps
|
|
7
|
-
extends Omit<
|
|
8
|
-
React.InputHTMLAttributes<HTMLInputElement>,
|
|
9
|
-
"type" | "value" | "onChange" | "min" | "max"
|
|
10
|
-
> {
|
|
11
|
-
name: string;
|
|
12
|
-
label: string;
|
|
13
|
-
disablePast?: boolean;
|
|
14
|
-
disableFuture?: boolean;
|
|
15
|
-
className?: string;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
const AppDatePicker: React.FC<AppDatePickerProps> = ({
|
|
19
|
-
name,
|
|
20
|
-
label,
|
|
21
|
-
disablePast = false,
|
|
22
|
-
disableFuture = false,
|
|
23
|
-
className = "",
|
|
24
|
-
...rest
|
|
25
|
-
}) => {
|
|
26
|
-
const { values, setFieldValue, touched, errors, setFieldTouched } =
|
|
27
|
-
useFormikContext<FormikValues>();
|
|
28
|
-
|
|
29
|
-
const value = values[name] ?? "";
|
|
30
|
-
const isTouched = touched[name];
|
|
31
|
-
const error = errors[name] as string | undefined;
|
|
32
|
-
const today = new Date().toISOString().slice(0, 10);
|
|
33
|
-
|
|
34
|
-
return (
|
|
35
|
-
<div className={`w-full ${className}`}>
|
|
36
|
-
{label && (
|
|
37
|
-
<label
|
|
38
|
-
htmlFor={name}
|
|
39
|
-
className="block text-sm font-medium mb-1 text-gray-700"
|
|
40
|
-
>
|
|
41
|
-
{label}
|
|
42
|
-
</label>
|
|
43
|
-
)}
|
|
44
|
-
|
|
45
|
-
<input
|
|
46
|
-
id={name}
|
|
47
|
-
name={name}
|
|
48
|
-
type="date"
|
|
49
|
-
value={value}
|
|
50
|
-
min={disablePast ? today : undefined}
|
|
51
|
-
max={disableFuture ? today : undefined}
|
|
52
|
-
onChange={(e) => setFieldValue(name, e.target.value)}
|
|
53
|
-
onBlur={() => setFieldTouched(name, true)}
|
|
54
|
-
className={`w-full rounded-md border px-3 py-2 text-sm
|
|
55
|
-
focus:outline-none focus:ring-2 focus:ring-blue-500
|
|
56
|
-
${error && isTouched ? "border-red-500" : "border-gray-300"}
|
|
57
|
-
${rest.disabled ? "bg-gray-100 cursor-not-allowed" : "bg-white"}
|
|
58
|
-
`}
|
|
59
|
-
{...rest}
|
|
60
|
-
/>
|
|
61
|
-
|
|
62
|
-
{isTouched && error && (
|
|
63
|
-
<p className="mt-1 text-xs text-red-500">{error}</p>
|
|
64
|
-
)}
|
|
65
|
-
</div>
|
|
66
|
-
);
|
|
67
|
-
};
|
|
68
|
-
|
|
69
|
-
export default AppDatePicker;
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
import React from "react";
|
|
2
|
-
import { useFormikContext, FormikValues } from "formik";
|
|
3
|
-
|
|
4
|
-
interface AppFormErrorMessageProps
|
|
5
|
-
extends React.HTMLAttributes<HTMLDivElement> {
|
|
6
|
-
name: string;
|
|
7
|
-
alwaysShow?: boolean;
|
|
8
|
-
className?: string;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
const AppFormErrorMessage: React.FC<AppFormErrorMessageProps> = ({
|
|
12
|
-
name,
|
|
13
|
-
alwaysShow = false,
|
|
14
|
-
className = "",
|
|
15
|
-
...rest
|
|
16
|
-
}) => {
|
|
17
|
-
const { errors, touched } = useFormikContext<FormikValues>();
|
|
18
|
-
|
|
19
|
-
const fieldError = errors?.[name] as string | undefined;
|
|
20
|
-
const isTouched = touched?.[name] as boolean | undefined;
|
|
21
|
-
|
|
22
|
-
const showError = !!fieldError && typeof fieldError === "string";
|
|
23
|
-
|
|
24
|
-
if (!showError) return null;
|
|
25
|
-
if (!alwaysShow && !isTouched) return null;
|
|
26
|
-
|
|
27
|
-
return (
|
|
28
|
-
<div className={`mt-1 ${className}`} {...rest}>
|
|
29
|
-
<span className="text-red-500 text-xs block">{fieldError}</span>
|
|
30
|
-
</div>
|
|
31
|
-
);
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
export default AppFormErrorMessage;
|
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
import React, { useState } from "react";
|
|
4
|
-
import { useFormikContext, FormikValues } from "formik";
|
|
5
|
-
|
|
6
|
-
interface AppInputFieldProps
|
|
7
|
-
extends Omit<React.InputHTMLAttributes<HTMLInputElement>, "name"> {
|
|
8
|
-
name: string;
|
|
9
|
-
label: React.ReactNode;
|
|
10
|
-
className?: string;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
const AppInputField: React.FC<AppInputFieldProps> = ({
|
|
14
|
-
name,
|
|
15
|
-
label,
|
|
16
|
-
className = "",
|
|
17
|
-
...rest
|
|
18
|
-
}) => {
|
|
19
|
-
const { values, setFieldValue, touched, errors, setFieldTouched } =
|
|
20
|
-
useFormikContext<FormikValues>();
|
|
21
|
-
|
|
22
|
-
const fieldValue = values[name] ?? "";
|
|
23
|
-
const isTouched = touched[name];
|
|
24
|
-
const fieldError = errors[name] as string | undefined;
|
|
25
|
-
|
|
26
|
-
const [showPassword, setShowPassword] = useState(false);
|
|
27
|
-
const isPassword = rest.type === "password";
|
|
28
|
-
const inputType = isPassword
|
|
29
|
-
? showPassword
|
|
30
|
-
? "text"
|
|
31
|
-
: "password"
|
|
32
|
-
: rest.type;
|
|
33
|
-
|
|
34
|
-
return (
|
|
35
|
-
<div className={`w-full ${className}`}>
|
|
36
|
-
{label && (
|
|
37
|
-
<label
|
|
38
|
-
htmlFor={name}
|
|
39
|
-
className="block text-sm font-medium mb-1 text-gray-700"
|
|
40
|
-
>
|
|
41
|
-
{label}
|
|
42
|
-
{rest.required && <span className="text-red-500 ml-0.5">*</span>}
|
|
43
|
-
</label>
|
|
44
|
-
)}
|
|
45
|
-
|
|
46
|
-
<div className="relative">
|
|
47
|
-
<input
|
|
48
|
-
id={name}
|
|
49
|
-
name={name}
|
|
50
|
-
type={inputType}
|
|
51
|
-
value={fieldValue}
|
|
52
|
-
onChange={(e) => setFieldValue(name, e.target.value)}
|
|
53
|
-
onBlur={() => setFieldTouched(name, true)}
|
|
54
|
-
className={`w-full rounded-md border px-3 py-2 text-sm
|
|
55
|
-
focus:outline-none focus:ring-2 focus:ring-blue-500
|
|
56
|
-
${fieldError && isTouched ? "border-red-500" : "border-gray-300"}
|
|
57
|
-
${rest.disabled ? "bg-gray-100 cursor-not-allowed" : "bg-white"}
|
|
58
|
-
`}
|
|
59
|
-
{...rest}
|
|
60
|
-
/>
|
|
61
|
-
|
|
62
|
-
{isPassword && (
|
|
63
|
-
<button
|
|
64
|
-
type="button"
|
|
65
|
-
onClick={() => setShowPassword(!showPassword)}
|
|
66
|
-
className="absolute right-2 top-1/2 -translate-y-1/2 text-gray-500 hover:text-gray-700"
|
|
67
|
-
>
|
|
68
|
-
{showPassword ? "🙈" : "👁️"}
|
|
69
|
-
</button>
|
|
70
|
-
)}
|
|
71
|
-
</div>
|
|
72
|
-
|
|
73
|
-
{isTouched && fieldError && (
|
|
74
|
-
<p className="mt-1 text-xs text-red-500">{fieldError}</p>
|
|
75
|
-
)}
|
|
76
|
-
</div>
|
|
77
|
-
);
|
|
78
|
-
};
|
|
79
|
-
|
|
80
|
-
export default AppInputField;
|
|
@@ -1,163 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
import React, { useState, useRef, useEffect } from "react";
|
|
4
|
-
import { useFormikContext, FormikValues } from "formik";
|
|
5
|
-
|
|
6
|
-
export interface AppSelectOption {
|
|
7
|
-
label: string;
|
|
8
|
-
value: string | number;
|
|
9
|
-
disabled?: boolean;
|
|
10
|
-
icon?: React.ReactNode;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
interface AppMultiSelectProps
|
|
14
|
-
extends Omit<React.HTMLAttributes<HTMLDivElement>, "onChange"> {
|
|
15
|
-
name: string;
|
|
16
|
-
label?: string;
|
|
17
|
-
options: AppSelectOption[];
|
|
18
|
-
maxSelections?: number;
|
|
19
|
-
placeholder?: string;
|
|
20
|
-
showSelectedCount?: boolean;
|
|
21
|
-
className?: string;
|
|
22
|
-
onChange?: (value: Array<string | number>) => void;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
const AppMultiSelect: React.FC<AppMultiSelectProps> = ({
|
|
26
|
-
name,
|
|
27
|
-
label,
|
|
28
|
-
options = [],
|
|
29
|
-
maxSelections,
|
|
30
|
-
placeholder = "Select options...",
|
|
31
|
-
showSelectedCount = true,
|
|
32
|
-
className = "",
|
|
33
|
-
onChange: externalOnChange,
|
|
34
|
-
...rest
|
|
35
|
-
}) => {
|
|
36
|
-
const { values, setFieldValue, errors, touched, setFieldTouched } =
|
|
37
|
-
useFormikContext<FormikValues>();
|
|
38
|
-
|
|
39
|
-
const fieldValue = Array.isArray(values[name]) ? values[name] : [];
|
|
40
|
-
const fieldError = errors[name] as string | undefined;
|
|
41
|
-
const isTouched = touched[name] as boolean | undefined;
|
|
42
|
-
|
|
43
|
-
const [isOpen, setIsOpen] = useState(false);
|
|
44
|
-
const [search, setSearch] = useState("");
|
|
45
|
-
const dropdownRef = useRef<HTMLDivElement>(null);
|
|
46
|
-
|
|
47
|
-
const filteredOptions = options.filter(
|
|
48
|
-
(opt) =>
|
|
49
|
-
!search ||
|
|
50
|
-
opt.label.toLowerCase().includes(search.toLowerCase()) ||
|
|
51
|
-
String(opt.value).toLowerCase().includes(search.toLowerCase())
|
|
52
|
-
);
|
|
53
|
-
|
|
54
|
-
const isMaxReached = maxSelections
|
|
55
|
-
? fieldValue.length >= maxSelections
|
|
56
|
-
: false;
|
|
57
|
-
|
|
58
|
-
const toggleOption = (value: string | number) => {
|
|
59
|
-
let newValues: Array<string | number>;
|
|
60
|
-
if (fieldValue.includes(value)) {
|
|
61
|
-
newValues = fieldValue.filter((v: string | number) => v !== value);
|
|
62
|
-
} else {
|
|
63
|
-
if (isMaxReached) return;
|
|
64
|
-
newValues = [...fieldValue, value];
|
|
65
|
-
}
|
|
66
|
-
setFieldValue(name, newValues);
|
|
67
|
-
if (externalOnChange) externalOnChange(newValues);
|
|
68
|
-
};
|
|
69
|
-
|
|
70
|
-
const handleBlur = () => setFieldTouched(name, true);
|
|
71
|
-
|
|
72
|
-
useEffect(() => {
|
|
73
|
-
const handleClickOutside = (event: MouseEvent) => {
|
|
74
|
-
if (
|
|
75
|
-
dropdownRef.current &&
|
|
76
|
-
!dropdownRef.current.contains(event.target as Node)
|
|
77
|
-
) {
|
|
78
|
-
setIsOpen(false);
|
|
79
|
-
handleBlur();
|
|
80
|
-
}
|
|
81
|
-
};
|
|
82
|
-
document.addEventListener("mousedown", handleClickOutside);
|
|
83
|
-
return () => document.removeEventListener("mousedown", handleClickOutside);
|
|
84
|
-
}, []);
|
|
85
|
-
|
|
86
|
-
const selectedLabels = options
|
|
87
|
-
.filter((opt) => fieldValue.includes(opt.value))
|
|
88
|
-
.map((opt) => opt.label)
|
|
89
|
-
.join(", ");
|
|
90
|
-
|
|
91
|
-
return (
|
|
92
|
-
<div className={`relative w-full ${className}`} ref={dropdownRef} {...rest}>
|
|
93
|
-
{label && (
|
|
94
|
-
<label className="block mb-1 text-sm font-medium text-gray-700">
|
|
95
|
-
{label}
|
|
96
|
-
</label>
|
|
97
|
-
)}
|
|
98
|
-
|
|
99
|
-
<div
|
|
100
|
-
className={`border rounded-md px-3 py-2 bg-white flex justify-between items-center cursor-pointer
|
|
101
|
-
hover:ring-1 hover:ring-blue-500
|
|
102
|
-
${fieldError && isTouched ? "border-red-500" : "border-gray-300"}`}
|
|
103
|
-
onClick={() => setIsOpen(!isOpen)}
|
|
104
|
-
>
|
|
105
|
-
<span className="text-sm text-gray-700">
|
|
106
|
-
{fieldValue.length ? selectedLabels : placeholder}
|
|
107
|
-
</span>
|
|
108
|
-
<span className="ml-2">▾</span>
|
|
109
|
-
</div>
|
|
110
|
-
|
|
111
|
-
{isOpen && (
|
|
112
|
-
<div className="absolute z-10 mt-1 w-full bg-white border border-gray-300 rounded-md shadow max-h-60 overflow-y-auto">
|
|
113
|
-
<input
|
|
114
|
-
type="text"
|
|
115
|
-
value={search}
|
|
116
|
-
onChange={(e) => setSearch(e.target.value)}
|
|
117
|
-
placeholder="Search..."
|
|
118
|
-
className="w-full px-3 py-2 border-b border-gray-200 text-sm focus:outline-none"
|
|
119
|
-
/>
|
|
120
|
-
{filteredOptions.map((option) => {
|
|
121
|
-
const checked = fieldValue.includes(option.value);
|
|
122
|
-
const disabledOption =
|
|
123
|
-
option.disabled || (isMaxReached && !checked);
|
|
124
|
-
return (
|
|
125
|
-
<div
|
|
126
|
-
key={option.value}
|
|
127
|
-
className={`flex items-center px-3 py-2 cursor-pointer hover:bg-gray-100
|
|
128
|
-
${disabledOption ? "opacity-50 cursor-not-allowed" : ""}`}
|
|
129
|
-
onClick={() => !disabledOption && toggleOption(option.value)}
|
|
130
|
-
>
|
|
131
|
-
<input
|
|
132
|
-
type="checkbox"
|
|
133
|
-
checked={checked}
|
|
134
|
-
readOnly
|
|
135
|
-
className="mr-2"
|
|
136
|
-
/>
|
|
137
|
-
{option.icon}
|
|
138
|
-
<span className="text-sm text-gray-700">{option.label}</span>
|
|
139
|
-
</div>
|
|
140
|
-
);
|
|
141
|
-
})}
|
|
142
|
-
</div>
|
|
143
|
-
)}
|
|
144
|
-
|
|
145
|
-
{showSelectedCount && (
|
|
146
|
-
<p
|
|
147
|
-
className={`mt-1 text-xs ${
|
|
148
|
-
isMaxReached ? "text-red-500" : "text-gray-500"
|
|
149
|
-
}`}
|
|
150
|
-
>
|
|
151
|
-
{`${fieldValue.length} selected`}
|
|
152
|
-
{maxSelections ? ` (${fieldValue.length} of ${maxSelections})` : ""}
|
|
153
|
-
</p>
|
|
154
|
-
)}
|
|
155
|
-
|
|
156
|
-
{isTouched && fieldError && (
|
|
157
|
-
<p className="mt-1 text-xs text-red-500">{fieldError}</p>
|
|
158
|
-
)}
|
|
159
|
-
</div>
|
|
160
|
-
);
|
|
161
|
-
};
|
|
162
|
-
|
|
163
|
-
export default AppMultiSelect;
|
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
import React, { useState, useEffect, useRef } from "react";
|
|
4
|
-
import PhoneInput from "react-phone-number-input";
|
|
5
|
-
import "react-phone-number-input/style.css";
|
|
6
|
-
import "../styles/PhoneInputCustom.css";
|
|
7
|
-
|
|
8
|
-
interface AppPhoneNoInputProps
|
|
9
|
-
extends Omit<
|
|
10
|
-
React.InputHTMLAttributes<HTMLInputElement>,
|
|
11
|
-
"onChange" | "value"
|
|
12
|
-
> {
|
|
13
|
-
name: string;
|
|
14
|
-
label: string;
|
|
15
|
-
international?: boolean;
|
|
16
|
-
withCountryCallingCode?: boolean;
|
|
17
|
-
className?: string;
|
|
18
|
-
onChange?: (value: string | undefined) => void;
|
|
19
|
-
value?: string;
|
|
20
|
-
error?: string;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
const AppPhoneNoInput: React.FC<AppPhoneNoInputProps> = ({
|
|
24
|
-
name,
|
|
25
|
-
label,
|
|
26
|
-
international = true,
|
|
27
|
-
withCountryCallingCode = true,
|
|
28
|
-
className = "",
|
|
29
|
-
onChange,
|
|
30
|
-
value,
|
|
31
|
-
disabled,
|
|
32
|
-
error,
|
|
33
|
-
placeholder = "Enter phone number", // Default still available here
|
|
34
|
-
required = false, // Default still available here
|
|
35
|
-
...props
|
|
36
|
-
}) => {
|
|
37
|
-
const [phoneValue, setPhoneValue] = useState<string | undefined>(value);
|
|
38
|
-
const [hasFocus, setHasFocus] = useState(false);
|
|
39
|
-
|
|
40
|
-
useEffect(() => {
|
|
41
|
-
setPhoneValue(value);
|
|
42
|
-
}, [value]);
|
|
43
|
-
|
|
44
|
-
const handleChange = (phoneNumber: string | undefined) => {
|
|
45
|
-
setPhoneValue(phoneNumber);
|
|
46
|
-
if (onChange) {
|
|
47
|
-
onChange(phoneNumber);
|
|
48
|
-
}
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
const inputRef = useRef<HTMLDivElement>(null);
|
|
52
|
-
|
|
53
|
-
return (
|
|
54
|
-
<div className={`phone-input-wrapper ${className}`}>
|
|
55
|
-
{label && (
|
|
56
|
-
<label
|
|
57
|
-
htmlFor={`${name}-phone-input`}
|
|
58
|
-
className={`phone-input-label ${
|
|
59
|
-
hasFocus || phoneValue ? "has-value" : ""
|
|
60
|
-
}`}
|
|
61
|
-
>
|
|
62
|
-
{label}
|
|
63
|
-
{required && (
|
|
64
|
-
<span className="required-star" aria-hidden="true">
|
|
65
|
-
*
|
|
66
|
-
</span>
|
|
67
|
-
)}
|
|
68
|
-
</label>
|
|
69
|
-
)}
|
|
70
|
-
|
|
71
|
-
<div
|
|
72
|
-
className={`phone-input-container ${error ? "error" : ""} ${
|
|
73
|
-
hasFocus ? "focus" : ""
|
|
74
|
-
}`}
|
|
75
|
-
ref={inputRef}
|
|
76
|
-
>
|
|
77
|
-
<PhoneInput
|
|
78
|
-
international={international}
|
|
79
|
-
withCountryCallingCode={withCountryCallingCode}
|
|
80
|
-
defaultCountry="PK"
|
|
81
|
-
value={phoneValue}
|
|
82
|
-
onChange={handleChange}
|
|
83
|
-
onFocus={() => setHasFocus(true)}
|
|
84
|
-
onBlur={() => setHasFocus(false)}
|
|
85
|
-
id={`${name}-phone-input`}
|
|
86
|
-
disabled={disabled}
|
|
87
|
-
placeholder={placeholder}
|
|
88
|
-
required={required}
|
|
89
|
-
aria-required={required}
|
|
90
|
-
className="custom-phone-input"
|
|
91
|
-
aria-invalid={!!error}
|
|
92
|
-
aria-describedby={error ? `${name}-error` : undefined}
|
|
93
|
-
{...props}
|
|
94
|
-
/>
|
|
95
|
-
</div>
|
|
96
|
-
|
|
97
|
-
{error && (
|
|
98
|
-
<div id={`${name}-error`} className="error-message" role="alert">
|
|
99
|
-
{error}
|
|
100
|
-
</div>
|
|
101
|
-
)}
|
|
102
|
-
</div>
|
|
103
|
-
);
|
|
104
|
-
};
|
|
105
|
-
|
|
106
|
-
export default AppPhoneNoInput;
|
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
import React from "react";
|
|
4
|
-
import { useFormikContext, FormikValues } from "formik";
|
|
5
|
-
|
|
6
|
-
export interface RadioOption {
|
|
7
|
-
label: string;
|
|
8
|
-
value: string | number;
|
|
9
|
-
disabled?: boolean;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
interface AppRadioGroupProps
|
|
13
|
-
extends Omit<
|
|
14
|
-
React.InputHTMLAttributes<HTMLInputElement>,
|
|
15
|
-
"onChange" | "value" | "name"
|
|
16
|
-
> {
|
|
17
|
-
name: string;
|
|
18
|
-
options: RadioOption[];
|
|
19
|
-
label?: string;
|
|
20
|
-
className?: string;
|
|
21
|
-
row?: boolean;
|
|
22
|
-
onChange?: (value: string | number) => void;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
const AppRadioGroup: React.FC<AppRadioGroupProps> = ({
|
|
26
|
-
name,
|
|
27
|
-
options = [],
|
|
28
|
-
label,
|
|
29
|
-
className = "",
|
|
30
|
-
row = false,
|
|
31
|
-
onChange: externalOnChange,
|
|
32
|
-
...rest
|
|
33
|
-
}) => {
|
|
34
|
-
const { values, setFieldValue, errors, touched, setFieldTouched } =
|
|
35
|
-
useFormikContext<FormikValues>();
|
|
36
|
-
|
|
37
|
-
const fieldValue = values[name] as string | number | undefined;
|
|
38
|
-
const fieldError = errors[name] as string | undefined;
|
|
39
|
-
const isTouched = touched[name] as boolean | undefined;
|
|
40
|
-
|
|
41
|
-
const handleChange = (value: string | number) => {
|
|
42
|
-
setFieldValue(name, value);
|
|
43
|
-
if (externalOnChange) externalOnChange(value);
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
const handleBlur = () => setFieldTouched(name, true);
|
|
47
|
-
|
|
48
|
-
return (
|
|
49
|
-
<div className={`w-full ${className}`}>
|
|
50
|
-
{label && (
|
|
51
|
-
<label className="block text-sm font-medium text-gray-700 mb-1">
|
|
52
|
-
{label}
|
|
53
|
-
{rest.required && <span className="text-red-500 ml-1">*</span>}
|
|
54
|
-
</label>
|
|
55
|
-
)}
|
|
56
|
-
|
|
57
|
-
<div
|
|
58
|
-
className={`flex ${row ? "flex-row space-x-4" : "flex-col space-y-2"}`}
|
|
59
|
-
onBlur={handleBlur}
|
|
60
|
-
>
|
|
61
|
-
{options.map((option) => (
|
|
62
|
-
<label
|
|
63
|
-
key={option.value}
|
|
64
|
-
className={`flex items-center cursor-pointer ${
|
|
65
|
-
option.disabled || rest.disabled
|
|
66
|
-
? "opacity-50 cursor-not-allowed"
|
|
67
|
-
: ""
|
|
68
|
-
}`}
|
|
69
|
-
>
|
|
70
|
-
<input
|
|
71
|
-
type="radio"
|
|
72
|
-
name={name}
|
|
73
|
-
value={option.value}
|
|
74
|
-
checked={fieldValue === option.value}
|
|
75
|
-
disabled={option.disabled || rest.disabled}
|
|
76
|
-
onChange={() => handleChange(option.value)}
|
|
77
|
-
className="form-radio h-4 w-4 text-blue-600 focus:ring-blue-500"
|
|
78
|
-
{...rest}
|
|
79
|
-
/>
|
|
80
|
-
<span className="ml-2 text-gray-700">{option.label}</span>
|
|
81
|
-
</label>
|
|
82
|
-
))}
|
|
83
|
-
</div>
|
|
84
|
-
|
|
85
|
-
{isTouched && fieldError && (
|
|
86
|
-
<p className="mt-1 text-xs text-red-500">{fieldError}</p>
|
|
87
|
-
)}
|
|
88
|
-
</div>
|
|
89
|
-
);
|
|
90
|
-
};
|
|
91
|
-
|
|
92
|
-
export default AppRadioGroup;
|