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.
Files changed (78) hide show
  1. package/README.md +1 -21
  2. package/dist/Form/AppAutoCompleter.d.ts +1 -2
  3. package/dist/Form/AppAutoCompleter.d.ts.map +1 -1
  4. package/dist/Form/AppCheckBox.d.ts +1 -1
  5. package/dist/Form/AppCheckBox.d.ts.map +1 -1
  6. package/dist/Form/AppDateAndTimePicker.d.ts +1 -1
  7. package/dist/Form/AppDateAndTimePicker.d.ts.map +1 -1
  8. package/dist/Form/AppFormErrorMessage.d.ts +1 -1
  9. package/dist/Form/AppFormErrorMessage.d.ts.map +1 -1
  10. package/dist/Form/AppInputField.d.ts +1 -1
  11. package/dist/Form/AppInputField.d.ts.map +1 -1
  12. package/dist/Form/AppMultiSelector.d.ts +1 -1
  13. package/dist/Form/AppMultiSelector.d.ts.map +1 -1
  14. package/dist/Form/AppPhoneNoInput.d.ts +1 -1
  15. package/dist/Form/AppPhoneNoInput.d.ts.map +1 -1
  16. package/dist/Form/AppRadioGroup.d.ts +1 -1
  17. package/dist/Form/AppRadioGroup.d.ts.map +1 -1
  18. package/dist/Form/AppRating.d.ts +1 -1
  19. package/dist/Form/AppRating.d.ts.map +1 -1
  20. package/dist/Form/AppSimpleUploadFile.d.ts +1 -1
  21. package/dist/Form/AppSimpleUploadFile.d.ts.map +1 -1
  22. package/dist/Form/AppSwitch.d.ts +1 -2
  23. package/dist/Form/AppSwitch.d.ts.map +1 -1
  24. package/dist/Form/AppTagsCreator.d.ts +1 -2
  25. package/dist/Form/AppTagsCreator.d.ts.map +1 -1
  26. package/dist/Form/AppTextArea.d.ts +1 -1
  27. package/dist/Form/AppTextArea.d.ts.map +1 -1
  28. package/dist/Form/AppUploadFile.d.ts +1 -1
  29. package/dist/Form/AppUploadFile.d.ts.map +1 -1
  30. package/dist/Form/SubmitButton.d.ts +1 -1
  31. package/dist/Form/SubmitButton.d.ts.map +1 -1
  32. package/dist/index.d.ts +17 -0
  33. package/dist/index.esm.js +2 -1
  34. package/dist/index.js +2 -1
  35. package/dist/lib/index.d.ts +17 -0
  36. package/dist/lib/index.d.ts.map +1 -1
  37. package/package.json +16 -22
  38. package/src/App.css +0 -38
  39. package/src/App.test.tsx +0 -9
  40. package/src/App.tsx +0 -166
  41. package/src/Form/AppAutoCompleter.tsx +0 -252
  42. package/src/Form/AppCheckBox.tsx +0 -101
  43. package/src/Form/AppDateAndTimePicker.tsx +0 -94
  44. package/src/Form/AppDatePicker.tsx +0 -69
  45. package/src/Form/AppFormErrorMessage.tsx +0 -34
  46. package/src/Form/AppInputField.tsx +0 -80
  47. package/src/Form/AppMultiSelector.tsx +0 -163
  48. package/src/Form/AppPhoneNoInput.tsx +0 -106
  49. package/src/Form/AppRadioGroup.tsx +0 -92
  50. package/src/Form/AppRating.tsx +0 -98
  51. package/src/Form/AppSelectInput.tsx +0 -249
  52. package/src/Form/AppSimpleUploadFile.tsx +0 -154
  53. package/src/Form/AppSwitch.tsx +0 -84
  54. package/src/Form/AppTagsCreator.tsx +0 -252
  55. package/src/Form/AppTextArea.tsx +0 -90
  56. package/src/Form/AppUploadFile.tsx +0 -167
  57. package/src/Form/SubmitButton.tsx +0 -122
  58. package/src/Form/index.tsx +0 -27
  59. package/src/assets/illustrations/BackgroundIllustration.tsx +0 -42
  60. package/src/assets/illustrations/UploadIllustration.tsx +0 -659
  61. package/src/assets/illustrations/index.ts +0 -1
  62. package/src/file-thumbnail/types.ts +0 -7
  63. package/src/file-thumbnail/utils.ts +0 -162
  64. package/src/index.css +0 -9
  65. package/src/index.tsx +0 -19
  66. package/src/lib/index.ts +0 -47
  67. package/src/logo.svg +0 -1
  68. package/src/react-app-env.d.ts +0 -1
  69. package/src/reportWebVitals.ts +0 -15
  70. package/src/setupTests.ts +0 -5
  71. package/src/styles/PhoneInputCustom.css +0 -238
  72. package/src/styles/compiled-tailwind.css +0 -1
  73. package/src/upload/Upload.tsx +0 -162
  74. package/src/upload/errors/RejectionFiles.tsx +0 -49
  75. package/src/upload/index.ts +0 -5
  76. package/src/upload/preview/MultiFilePreview.tsx +0 -297
  77. package/src/upload/preview/SingleFilePreview.tsx +0 -81
  78. 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">&#9662;</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;