@zauru-sdk/components 2.0.119 → 2.0.121
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/CHANGELOG.md +16 -0
- package/dist/DynamicTable/GenericDynamicTable.d.ts +48 -4
- package/dist/Form/DynamicBaculoForm/index.d.ts +10 -1
- package/dist/Form/FileUpload/index.d.ts +0 -1
- package/dist/Modal/ItemModal.d.ts +16 -5
- package/dist/NavBar/NavBar.types.d.ts +2 -1
- package/dist/NavBar/NavBar.utils.d.ts +7 -0
- package/dist/esm/Buttons/Button.js +11 -7
- package/dist/esm/DynamicTable/GenericDynamicTable.js +78 -19
- package/dist/esm/Form/DynamicBaculoForm/index.js +29 -5
- package/dist/esm/Form/FileUpload/index.js +59 -52
- package/dist/esm/Form/ReactZodForm/index.js +11 -3
- package/dist/esm/Modal/ItemModal.js +93 -22
- package/dist/esm/NavBar/NavBar.js +14 -4
- package/dist/esm/NavBar/NavBar.utils.js +7 -0
- package/package.json +3 -3
- package/src/Buttons/Button.tsx +6 -1
- package/src/DynamicTable/GenericDynamicTable.tsx +140 -39
- package/src/Form/DynamicBaculoForm/index.tsx +32 -6
- package/src/Form/FileUpload/index.tsx +100 -77
- package/src/Form/ReactZodForm/index.tsx +16 -3
- package/src/Modal/ItemModal.tsx +351 -96
- package/src/NavBar/NavBar.tsx +53 -48
- package/src/NavBar/NavBar.types.ts +9 -1
- package/src/NavBar/NavBar.utils.ts +7 -0
|
@@ -10,8 +10,7 @@ type Props = {
|
|
|
10
10
|
helpText?: string;
|
|
11
11
|
hint?: string;
|
|
12
12
|
onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void;
|
|
13
|
-
|
|
14
|
-
readOnly?: boolean;
|
|
13
|
+
readOnly?: boolean; // <-- Usamos readOnly en lugar de disabled
|
|
15
14
|
fileTypes?: string[];
|
|
16
15
|
showAvailableTypes?: boolean;
|
|
17
16
|
className?: string;
|
|
@@ -28,12 +27,11 @@ export const FileUploadField = (props: Props) => {
|
|
|
28
27
|
helpText,
|
|
29
28
|
hint,
|
|
30
29
|
onChange,
|
|
31
|
-
disabled = false,
|
|
32
30
|
readOnly = false,
|
|
33
31
|
fileTypes = [],
|
|
34
32
|
showAvailableTypes = false,
|
|
35
33
|
className,
|
|
36
|
-
defaultValue
|
|
34
|
+
defaultValue,
|
|
37
35
|
download = false,
|
|
38
36
|
required = false,
|
|
39
37
|
} = props;
|
|
@@ -41,113 +39,126 @@ export const FileUploadField = (props: Props) => {
|
|
|
41
39
|
const {
|
|
42
40
|
register: tempRegister,
|
|
43
41
|
formState: { errors },
|
|
44
|
-
} = useFormContext() || { formState: {} };
|
|
45
|
-
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
: undefined; // Solo usar register si está disponible
|
|
42
|
+
} = useFormContext() || { formState: {} };
|
|
43
|
+
|
|
44
|
+
const error = errors ? errors[name] : undefined;
|
|
45
|
+
const register = tempRegister ? tempRegister(name, { required }) : undefined;
|
|
49
46
|
|
|
50
47
|
const [showTooltip, setShowTooltip] = useState<boolean>(false);
|
|
51
48
|
|
|
52
|
-
|
|
49
|
+
// Para mostrar en el hint los tipos de archivo permitidos (opcional)
|
|
50
|
+
let hintMessage = hint;
|
|
51
|
+
if (showAvailableTypes && fileTypes.length > 0) {
|
|
52
|
+
hintMessage = `${hint || ""} Archivos permitidos: ${fileTypes.join(", ")}`;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Clases de estilo basadas en si hay error (color rojo) o no (gris),
|
|
56
|
+
// pero ahora ignoramos el "disabled" y nos centramos en "readOnly".
|
|
57
|
+
const color = error ? "red" : "gray";
|
|
58
|
+
const isReadOnly = readOnly;
|
|
59
|
+
// En modo readOnly, puedes poner un fondo distinto, o dejarlo en blanco
|
|
60
|
+
const bgColor = isReadOnly ? "bg-gray-100" : `bg-${color}-50`;
|
|
61
|
+
const textColor = isReadOnly ? "text-gray-700" : `text-${color}-900`;
|
|
62
|
+
const borderColor = error ? "border-red-500" : `border-${color}-500`;
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* onChange normal del input.
|
|
66
|
+
* Sólo se llama cuando readOnly es false (porque si es true ni renderizamos el input).
|
|
67
|
+
*/
|
|
68
|
+
const handleInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
|
69
|
+
onChange && onChange(event);
|
|
70
|
+
// Si usas register, la parte interna de react-hook-form también se encargará
|
|
71
|
+
// del cambio, no necesitas llamarlo manualmente.
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Para el "preview" cuando `defaultValue` es string:
|
|
76
|
+
* - Si `download` es true, mostramos un icono de descarga.
|
|
77
|
+
* - Si `download` es false, mostramos la imagen en miniatura.
|
|
78
|
+
* El click abre la URL en nueva ventana.
|
|
79
|
+
*/
|
|
80
|
+
function renderPreview(defaultValue: string) {
|
|
53
81
|
if (download) {
|
|
82
|
+
// Botón de descarga
|
|
54
83
|
return (
|
|
55
84
|
<div
|
|
56
85
|
role="button"
|
|
57
86
|
tabIndex={0}
|
|
58
|
-
onClick={() =>
|
|
59
|
-
window.open(defaultValue, "_blank");
|
|
60
|
-
}}
|
|
87
|
+
onClick={() => window.open(defaultValue, "_blank")}
|
|
61
88
|
onKeyDown={(event) => {
|
|
62
|
-
// Permite que el evento se active con la tecla Enter
|
|
63
89
|
if (event.key === "Enter") {
|
|
64
90
|
window.open(defaultValue, "_blank");
|
|
65
91
|
}
|
|
66
92
|
}}
|
|
93
|
+
className="inline-flex items-center cursor-pointer"
|
|
67
94
|
>
|
|
68
|
-
{title && (
|
|
69
|
-
<label
|
|
70
|
-
htmlFor={name}
|
|
71
|
-
className="block mb-1 text-sm font-medium text-gray-700"
|
|
72
|
-
>
|
|
73
|
-
{title}
|
|
74
|
-
</label>
|
|
75
|
-
)}{" "}
|
|
76
95
|
<DownloadIconSVG />
|
|
96
|
+
<span className="ml-1 text-blue-600 underline">
|
|
97
|
+
Descargar archivo
|
|
98
|
+
</span>
|
|
77
99
|
</div>
|
|
78
100
|
);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
{title && (
|
|
83
|
-
<label
|
|
84
|
-
htmlFor={name}
|
|
85
|
-
className={`block mb-1 text-sm font-medium text-gray-700`}
|
|
86
|
-
>
|
|
87
|
-
{title}
|
|
88
|
-
{required && <span className="text-red-500">*</span>}
|
|
89
|
-
</label>
|
|
90
|
-
)}{" "}
|
|
101
|
+
} else {
|
|
102
|
+
// Vista previa como imagen
|
|
103
|
+
return (
|
|
91
104
|
<div
|
|
92
105
|
role="button"
|
|
93
106
|
tabIndex={0}
|
|
94
|
-
onClick={() =>
|
|
95
|
-
if (register) {
|
|
96
|
-
register.onChange({
|
|
97
|
-
target: {
|
|
98
|
-
value: defaultValue,
|
|
99
|
-
},
|
|
100
|
-
});
|
|
101
|
-
}
|
|
102
|
-
window.open(defaultValue, "_blank");
|
|
103
|
-
}}
|
|
107
|
+
onClick={() => window.open(defaultValue, "_blank")}
|
|
104
108
|
onKeyDown={(event) => {
|
|
105
|
-
// Permite que el evento se active con la tecla Enter
|
|
106
109
|
if (event.key === "Enter") {
|
|
107
|
-
if (register) {
|
|
108
|
-
register.onChange({
|
|
109
|
-
target: {
|
|
110
|
-
value: defaultValue,
|
|
111
|
-
},
|
|
112
|
-
});
|
|
113
|
-
}
|
|
114
110
|
window.open(defaultValue, "_blank");
|
|
115
111
|
}
|
|
116
112
|
}}
|
|
113
|
+
className="inline-block cursor-pointer"
|
|
117
114
|
>
|
|
118
115
|
<img
|
|
119
116
|
src={defaultValue}
|
|
120
117
|
alt={name}
|
|
121
|
-
className=
|
|
118
|
+
className="h-48 w-48 inline mr-1 pb-1"
|
|
122
119
|
style={{
|
|
123
|
-
|
|
124
|
-
strokeWidth: 2,
|
|
125
|
-
strokeLinecap: "round",
|
|
126
|
-
strokeLinejoin: "round",
|
|
127
|
-
fill: "none",
|
|
120
|
+
objectFit: "contain",
|
|
128
121
|
backgroundColor: "transparent",
|
|
129
122
|
}}
|
|
130
123
|
/>
|
|
131
124
|
</div>
|
|
132
|
-
|
|
133
|
-
|
|
125
|
+
);
|
|
126
|
+
}
|
|
134
127
|
}
|
|
135
128
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
129
|
+
/**
|
|
130
|
+
* 1) Si readOnly = true:
|
|
131
|
+
* - Si defaultValue es string -> Sólo mostramos el preview (descarga/imagen).
|
|
132
|
+
* - Si no hay defaultValue (o es File) -> mostramos "nada" o un texto de "Sin archivo".
|
|
133
|
+
*/
|
|
134
|
+
if (readOnly) {
|
|
135
|
+
return (
|
|
136
|
+
<div className={`col-span-6 sm:col-span-3 ${className}`}>
|
|
137
|
+
{title && (
|
|
138
|
+
<label
|
|
139
|
+
htmlFor={name}
|
|
140
|
+
className={`block mb-1 text-sm font-medium text-gray-700`}
|
|
141
|
+
>
|
|
142
|
+
{title}
|
|
143
|
+
</label>
|
|
144
|
+
)}
|
|
139
145
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
146
|
+
{typeof defaultValue === "string" && defaultValue ? (
|
|
147
|
+
renderPreview(defaultValue)
|
|
148
|
+
) : (
|
|
149
|
+
<div className="text-sm italic text-gray-400">
|
|
150
|
+
No hay archivo disponible
|
|
151
|
+
</div>
|
|
152
|
+
)}
|
|
153
|
+
</div>
|
|
154
|
+
);
|
|
143
155
|
}
|
|
144
156
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
157
|
+
/**
|
|
158
|
+
* 2) readOnly = false:
|
|
159
|
+
* - Si hay defaultValue y es string, mostramos la vista previa + el input
|
|
160
|
+
* - Si no hay defaultValue (o no es string) mostramos solo el input
|
|
161
|
+
*/
|
|
151
162
|
return (
|
|
152
163
|
<div className={`col-span-6 sm:col-span-3 ${className}`}>
|
|
153
164
|
{title && (
|
|
@@ -156,19 +167,28 @@ export const FileUploadField = (props: Props) => {
|
|
|
156
167
|
className={`block mb-1 text-sm font-medium text-${color}-700`}
|
|
157
168
|
>
|
|
158
169
|
{title}
|
|
170
|
+
{required && <span className="text-red-500 ml-1">*</span>}
|
|
159
171
|
</label>
|
|
160
172
|
)}
|
|
173
|
+
|
|
174
|
+
{/* Mostrar la vista previa si defaultValue es string */}
|
|
175
|
+
{typeof defaultValue === "string" && defaultValue && (
|
|
176
|
+
<div className="mb-2">{renderPreview(defaultValue)}</div>
|
|
177
|
+
)}
|
|
178
|
+
|
|
179
|
+
{/* Input para cambiar/cargar archivo */}
|
|
161
180
|
<div className="flex relative items-center">
|
|
162
181
|
<input
|
|
163
182
|
type="file"
|
|
164
|
-
name={name}
|
|
165
183
|
id={id ?? name}
|
|
166
|
-
disabled={disabled}
|
|
167
|
-
readOnly={readOnly}
|
|
168
184
|
accept={fileTypes.map((ft) => `.${ft}`).join(", ")}
|
|
169
|
-
onChange={handleInputChange}
|
|
170
185
|
className={`block w-full rounded-md ${bgColor} ${borderColor} ${textColor} shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm`}
|
|
186
|
+
{...(register ?? {})}
|
|
187
|
+
name={name}
|
|
188
|
+
onChange={handleInputChange}
|
|
171
189
|
/>
|
|
190
|
+
|
|
191
|
+
{/* Botón de ayuda con tooltip */}
|
|
172
192
|
{helpText && (
|
|
173
193
|
<div className="flex items-center relative ml-3">
|
|
174
194
|
<div
|
|
@@ -186,11 +206,14 @@ export const FileUploadField = (props: Props) => {
|
|
|
186
206
|
</div>
|
|
187
207
|
)}
|
|
188
208
|
</div>
|
|
209
|
+
|
|
210
|
+
{/* Mensaje de error */}
|
|
189
211
|
{error && (
|
|
190
|
-
<p className={`mt-2 text-sm text
|
|
212
|
+
<p className={`mt-2 text-sm text-red-600`}>
|
|
191
213
|
<span className="font-medium">Oops!</span> {error.message?.toString()}
|
|
192
214
|
</p>
|
|
193
215
|
)}
|
|
216
|
+
{/* Hint (si no hay error) */}
|
|
194
217
|
{!error && hintMessage && (
|
|
195
218
|
<p className={`mt-2 italic text-sm text-${color}-500`}>{hintMessage}</p>
|
|
196
219
|
)}
|
|
@@ -44,10 +44,23 @@ export const ReactZodForm = (props: Props) => {
|
|
|
44
44
|
const handleSubmit: SubmitHandler<FieldValues> = (data, event) => {
|
|
45
45
|
if (onSubmit) {
|
|
46
46
|
onSubmit(data, event);
|
|
47
|
-
|
|
48
|
-
// If no onSubmit is provided, use Remix's submit function
|
|
49
|
-
submit(event?.target as HTMLFormElement, { method });
|
|
47
|
+
return;
|
|
50
48
|
}
|
|
49
|
+
|
|
50
|
+
const form = event?.target as HTMLFormElement;
|
|
51
|
+
const nativeEvent = event?.nativeEvent as SubmitEvent;
|
|
52
|
+
|
|
53
|
+
// Armamos el FormData a partir del propio <form>
|
|
54
|
+
const formData = new FormData(form);
|
|
55
|
+
|
|
56
|
+
// Detectamos el botón que disparó el submit (submitter),
|
|
57
|
+
// en caso de que tenga un name/value, lo agregamos al formData
|
|
58
|
+
const submitter = nativeEvent.submitter;
|
|
59
|
+
if (submitter instanceof HTMLButtonElement && submitter.name) {
|
|
60
|
+
formData.append(submitter.name, submitter.value);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
submit(formData, { method, encType });
|
|
51
64
|
};
|
|
52
65
|
|
|
53
66
|
return (
|