next-recomponents 2.0.27 → 2.0.29
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/index.d.mts +29 -3
- package/dist/index.d.ts +29 -3
- package/dist/index.js +295 -112
- package/dist/index.mjs +253 -70
- package/package.json +1 -1
- package/src/input/index.tsx +66 -12
- package/src/table/index.tsx +171 -19
package/src/input/index.tsx
CHANGED
|
@@ -1,14 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { React } from "next/dist/server/route-modules/app-page/vendored/rsc/entrypoints";
|
|
2
|
+
import { InputHTMLAttributes, useState } from "react";
|
|
2
3
|
|
|
3
|
-
interface InputProps
|
|
4
|
-
extends DetailedHTMLProps<
|
|
5
|
-
InputHTMLAttributes<HTMLInputElement>,
|
|
6
|
-
HTMLInputElement
|
|
7
|
-
> {
|
|
4
|
+
interface InputProps extends InputHTMLAttributes<HTMLInputElement> {
|
|
8
5
|
label: React.ReactNode;
|
|
9
6
|
regex?: RegExp;
|
|
10
7
|
invalidMessage?: React.ReactNode;
|
|
11
8
|
}
|
|
9
|
+
|
|
12
10
|
export default function Input({
|
|
13
11
|
label,
|
|
14
12
|
className,
|
|
@@ -18,26 +16,82 @@ export default function Input({
|
|
|
18
16
|
}: InputProps) {
|
|
19
17
|
const value = `${props?.value || ""}`;
|
|
20
18
|
const isValid = !regex ? true : regex.test(value);
|
|
19
|
+
const isPassword = props.type === "password";
|
|
20
|
+
const [showPassword, setShowPassword] = useState(false);
|
|
21
21
|
|
|
22
22
|
return (
|
|
23
23
|
<div className="w-full">
|
|
24
24
|
<label className="flex flex-col gap-1">
|
|
25
|
-
<div className="font-bold
|
|
25
|
+
<div className="font-bold">
|
|
26
26
|
{label} {props?.required && <span className="text-red-500">*</span>}
|
|
27
27
|
</div>
|
|
28
|
-
<div>
|
|
28
|
+
<div className="relative">
|
|
29
29
|
<input
|
|
30
30
|
{...props}
|
|
31
|
+
type={isPassword && showPassword ? "text" : props.type}
|
|
31
32
|
className={[
|
|
32
33
|
"p-2 w-full rounded border shadow",
|
|
33
|
-
|
|
34
|
-
value
|
|
34
|
+
isPassword && "pr-10",
|
|
35
|
+
value !== "" && !isValid && "bg-red-200 text-black",
|
|
36
|
+
value !== "" && isValid && "bg-green-200 text-black",
|
|
35
37
|
className,
|
|
36
|
-
]
|
|
38
|
+
]
|
|
39
|
+
.filter(Boolean)
|
|
40
|
+
.join(" ")}
|
|
37
41
|
/>
|
|
42
|
+
{isPassword && (
|
|
43
|
+
<button
|
|
44
|
+
type="button"
|
|
45
|
+
onClick={() => setShowPassword((prev) => !prev)}
|
|
46
|
+
className="absolute right-2 top-1/2 -translate-y-1/2 text-gray-500 hover:text-gray-800 focus:outline-none"
|
|
47
|
+
aria-label={
|
|
48
|
+
showPassword ? "Ocultar contraseña" : "Mostrar contraseña"
|
|
49
|
+
}
|
|
50
|
+
>
|
|
51
|
+
{showPassword ? (
|
|
52
|
+
// Ojo cerrado (ocultar)
|
|
53
|
+
<svg
|
|
54
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
55
|
+
className="h-5 w-5"
|
|
56
|
+
fill="none"
|
|
57
|
+
viewBox="0 0 24 24"
|
|
58
|
+
stroke="currentColor"
|
|
59
|
+
>
|
|
60
|
+
<path
|
|
61
|
+
strokeLinecap="round"
|
|
62
|
+
strokeLinejoin="round"
|
|
63
|
+
strokeWidth={2}
|
|
64
|
+
d="M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.88 9.88l-3.29-3.29m7.532 7.532l3.29 3.29M3 3l3.59 3.59m0 0A9.953 9.953 0 0112 5c4.478 0 8.268 2.943 9.543 7a10.025 10.025 0 01-4.132 5.411m0 0L21 21"
|
|
65
|
+
/>
|
|
66
|
+
</svg>
|
|
67
|
+
) : (
|
|
68
|
+
// Ojo abierto (mostrar)
|
|
69
|
+
<svg
|
|
70
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
71
|
+
className="h-5 w-5"
|
|
72
|
+
fill="none"
|
|
73
|
+
viewBox="0 0 24 24"
|
|
74
|
+
stroke="currentColor"
|
|
75
|
+
>
|
|
76
|
+
<path
|
|
77
|
+
strokeLinecap="round"
|
|
78
|
+
strokeLinejoin="round"
|
|
79
|
+
strokeWidth={2}
|
|
80
|
+
d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"
|
|
81
|
+
/>
|
|
82
|
+
<path
|
|
83
|
+
strokeLinecap="round"
|
|
84
|
+
strokeLinejoin="round"
|
|
85
|
+
strokeWidth={2}
|
|
86
|
+
d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"
|
|
87
|
+
/>
|
|
88
|
+
</svg>
|
|
89
|
+
)}
|
|
90
|
+
</button>
|
|
91
|
+
)}
|
|
38
92
|
</div>
|
|
39
93
|
</label>
|
|
40
|
-
{!isValid && value
|
|
94
|
+
{!isValid && value !== "" && (
|
|
41
95
|
<div className="text-red-800 invalid">{invalidMessage}</div>
|
|
42
96
|
)}
|
|
43
97
|
</div>
|
package/src/table/index.tsx
CHANGED
|
@@ -22,8 +22,12 @@ interface FooterType {
|
|
|
22
22
|
[key: string]: FooterAggregation;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
type GridDensity = "compact" | "standard" | "comfortable";
|
|
26
|
+
|
|
25
27
|
interface TableProps {
|
|
26
28
|
data: any;
|
|
29
|
+
/** Muestra un input de búsqueda general que filtra filas por palabras clave */
|
|
30
|
+
searchable?: boolean;
|
|
27
31
|
flex?: number;
|
|
28
32
|
editableFields?: string[];
|
|
29
33
|
onSelect?: (data: GridValidRowModel[]) => void;
|
|
@@ -44,6 +48,28 @@ interface TableProps {
|
|
|
44
48
|
className?: string;
|
|
45
49
|
fontSize?: string;
|
|
46
50
|
colSize?: Record<string, number>;
|
|
51
|
+
/**
|
|
52
|
+
* Alto fijo de fila en px.
|
|
53
|
+
* Si se omite usa el default de MUI según `density`
|
|
54
|
+
* (compact ≈ 36, standard ≈ 52, comfortable ≈ 68).
|
|
55
|
+
* Se ignora automáticamente cuando `wrapText` es true.
|
|
56
|
+
*/
|
|
57
|
+
rowHeight?: number;
|
|
58
|
+
/**
|
|
59
|
+
* Cuando es true el texto largo hace wrap en lugar de truncarse,
|
|
60
|
+
* y el alto de cada fila crece para mostrar todo el contenido.
|
|
61
|
+
* Internamente activa `getRowHeight={() => "auto"}` en el DataGrid.
|
|
62
|
+
*/
|
|
63
|
+
wrapText?: boolean;
|
|
64
|
+
/**
|
|
65
|
+
* Densidad visual de la tabla.
|
|
66
|
+
* Afecta el padding de celdas y encabezados independientemente
|
|
67
|
+
* del tamaño de fuente.
|
|
68
|
+
* - "compact" → padding mínimo, tabla muy densa
|
|
69
|
+
* - "standard" → (default) comportamiento por defecto de MUI
|
|
70
|
+
* - "comfortable" → padding generoso, fácil de leer
|
|
71
|
+
*/
|
|
72
|
+
density?: GridDensity;
|
|
47
73
|
[key: string]: any;
|
|
48
74
|
}
|
|
49
75
|
|
|
@@ -256,6 +282,7 @@ function useColumns(
|
|
|
256
282
|
buttons?: Record<string, any>;
|
|
257
283
|
hideColumns: string[];
|
|
258
284
|
modal?: React.ReactNode;
|
|
285
|
+
wrapText?: boolean;
|
|
259
286
|
handleRowUpdate: (row: GridRowModel) => GridRowModel;
|
|
260
287
|
onModalOpen: (row: GridValidRowModel) => void;
|
|
261
288
|
},
|
|
@@ -267,9 +294,11 @@ function useColumns(
|
|
|
267
294
|
buttons,
|
|
268
295
|
hideColumns,
|
|
269
296
|
modal,
|
|
297
|
+
wrapText,
|
|
270
298
|
handleRowUpdate,
|
|
271
299
|
onModalOpen,
|
|
272
300
|
} = options;
|
|
301
|
+
|
|
273
302
|
return useMemo(() => {
|
|
274
303
|
if (!rows.length) return [];
|
|
275
304
|
|
|
@@ -309,7 +338,6 @@ function useColumns(
|
|
|
309
338
|
|
|
310
339
|
if (isNumber) {
|
|
311
340
|
if (isNaN(value)) return value;
|
|
312
|
-
|
|
313
341
|
return value;
|
|
314
342
|
}
|
|
315
343
|
|
|
@@ -319,6 +347,20 @@ function useColumns(
|
|
|
319
347
|
width: key == "id" ? 80 : (colSize?.[key] ?? undefined),
|
|
320
348
|
editable: editableFields?.includes(key) ?? false,
|
|
321
349
|
type: typeof rows[0][key] === "number" ? "number" : "string",
|
|
350
|
+
// When wrapText is enabled, allow cells to grow vertically
|
|
351
|
+
...(wrapText && {
|
|
352
|
+
renderHeader: (params: any) => (
|
|
353
|
+
<span
|
|
354
|
+
style={{
|
|
355
|
+
whiteSpace: "normal",
|
|
356
|
+
lineHeight: 1.3,
|
|
357
|
+
wordBreak: "break-word",
|
|
358
|
+
}}
|
|
359
|
+
>
|
|
360
|
+
{params.colDef.headerName}
|
|
361
|
+
</span>
|
|
362
|
+
),
|
|
363
|
+
}),
|
|
322
364
|
renderCell: buttons?.[key]
|
|
323
365
|
? (params: any) => {
|
|
324
366
|
const children =
|
|
@@ -338,14 +380,28 @@ function useColumns(
|
|
|
338
380
|
},
|
|
339
381
|
});
|
|
340
382
|
}
|
|
341
|
-
:
|
|
383
|
+
: wrapText
|
|
384
|
+
? (params: any) => (
|
|
385
|
+
// Plain cell with wrap — no custom button
|
|
386
|
+
<span
|
|
387
|
+
style={{
|
|
388
|
+
whiteSpace: "normal",
|
|
389
|
+
wordBreak: "break-word",
|
|
390
|
+
lineHeight: 1.4,
|
|
391
|
+
padding: "6px 0",
|
|
392
|
+
display: "block",
|
|
393
|
+
}}
|
|
394
|
+
>
|
|
395
|
+
{params.formattedValue ?? params.value}
|
|
396
|
+
</span>
|
|
397
|
+
)
|
|
398
|
+
: null,
|
|
342
399
|
}));
|
|
343
400
|
|
|
344
401
|
if (modal) {
|
|
345
402
|
cols.unshift({
|
|
346
403
|
field: "Modal",
|
|
347
404
|
headerName: "Modal",
|
|
348
|
-
// flex,
|
|
349
405
|
editable: false,
|
|
350
406
|
type: "string",
|
|
351
407
|
width: 10,
|
|
@@ -363,10 +419,63 @@ function useColumns(
|
|
|
363
419
|
}
|
|
364
420
|
|
|
365
421
|
return cols;
|
|
366
|
-
}, [rows]);
|
|
422
|
+
}, [rows, wrapText]);
|
|
367
423
|
}
|
|
368
424
|
|
|
369
|
-
// ───
|
|
425
|
+
// ─── SearchBar ────────────────────────────────────────────────────────────────
|
|
426
|
+
|
|
427
|
+
function SearchBar({
|
|
428
|
+
value,
|
|
429
|
+
onChange,
|
|
430
|
+
}: {
|
|
431
|
+
value: string;
|
|
432
|
+
onChange: (v: string) => void;
|
|
433
|
+
}) {
|
|
434
|
+
return (
|
|
435
|
+
<div className="flex items-center gap-2 px-2 pb-1">
|
|
436
|
+
<svg
|
|
437
|
+
className="text-gray-400 shrink-0"
|
|
438
|
+
width="14"
|
|
439
|
+
height="14"
|
|
440
|
+
viewBox="0 0 20 20"
|
|
441
|
+
fill="currentColor"
|
|
442
|
+
>
|
|
443
|
+
<path
|
|
444
|
+
fillRule="evenodd"
|
|
445
|
+
d="M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z"
|
|
446
|
+
clipRule="evenodd"
|
|
447
|
+
/>
|
|
448
|
+
</svg>
|
|
449
|
+
<input
|
|
450
|
+
type="text"
|
|
451
|
+
value={value}
|
|
452
|
+
onChange={(e) => onChange(e.target.value)}
|
|
453
|
+
placeholder="Buscar…"
|
|
454
|
+
className="w-full max-w-xs text-xs border border-gray-300 rounded px-2 py-1 outline-none focus:border-blue-400 focus:ring-1 focus:ring-blue-200 transition"
|
|
455
|
+
/>
|
|
456
|
+
{value && (
|
|
457
|
+
<button
|
|
458
|
+
onClick={() => onChange("")}
|
|
459
|
+
className="text-gray-400 hover:text-gray-600 text-sm leading-none"
|
|
460
|
+
title="Limpiar búsqueda"
|
|
461
|
+
>
|
|
462
|
+
×
|
|
463
|
+
</button>
|
|
464
|
+
)}
|
|
465
|
+
</div>
|
|
466
|
+
);
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
/** Devuelve true si la fila contiene TODAS las palabras del query */
|
|
470
|
+
function rowMatchesSearch(row: GridValidRowModel, query: string): boolean {
|
|
471
|
+
if (!query.trim()) return true;
|
|
472
|
+
const words = query.trim().toLowerCase().split(/\s+/);
|
|
473
|
+
const rowText = Object.values(row)
|
|
474
|
+
.filter((v) => v != null && v !== "")
|
|
475
|
+
.join(" ")
|
|
476
|
+
.toLowerCase();
|
|
477
|
+
return words.every((word) => rowText.includes(word));
|
|
478
|
+
}
|
|
370
479
|
|
|
371
480
|
function IHTable({
|
|
372
481
|
data,
|
|
@@ -387,6 +496,10 @@ function IHTable({
|
|
|
387
496
|
fontSize = "1rem",
|
|
388
497
|
className,
|
|
389
498
|
colSize,
|
|
499
|
+
rowHeight,
|
|
500
|
+
wrapText = false,
|
|
501
|
+
density = "standard",
|
|
502
|
+
searchable = false,
|
|
390
503
|
}: TableProps) {
|
|
391
504
|
if (modal && onSelect)
|
|
392
505
|
throw new Error("Solo se puede usar modal o onSelect por separado");
|
|
@@ -398,6 +511,7 @@ function IHTable({
|
|
|
398
511
|
ids: new Set(),
|
|
399
512
|
});
|
|
400
513
|
const [modalRow, setModalRow] = useState<GridValidRowModel | undefined>();
|
|
514
|
+
const [searchQuery, setSearchQuery] = useState("");
|
|
401
515
|
|
|
402
516
|
// Inject Modal button key if modal is provided
|
|
403
517
|
const resolvedButtons = modal ? { ...buttons, Modal: "" } : buttons;
|
|
@@ -437,8 +551,15 @@ function IHTable({
|
|
|
437
551
|
return [];
|
|
438
552
|
}, [selectedRows, rows]);
|
|
439
553
|
|
|
554
|
+
// Rows after applying the search filter (only when searchable is enabled)
|
|
555
|
+
const displayRows = useMemo(
|
|
556
|
+
() =>
|
|
557
|
+
searchable ? rows.filter((r) => rowMatchesSearch(r, searchQuery)) : rows,
|
|
558
|
+
[rows, searchQuery, searchable],
|
|
559
|
+
);
|
|
560
|
+
|
|
440
561
|
const columns = useColumns(
|
|
441
|
-
|
|
562
|
+
displayRows,
|
|
442
563
|
currentCoin,
|
|
443
564
|
{
|
|
444
565
|
flex,
|
|
@@ -446,6 +567,7 @@ function IHTable({
|
|
|
446
567
|
buttons: resolvedButtons,
|
|
447
568
|
hideColumns,
|
|
448
569
|
modal,
|
|
570
|
+
wrapText,
|
|
449
571
|
handleRowUpdate,
|
|
450
572
|
onModalOpen: handleModalOpen,
|
|
451
573
|
},
|
|
@@ -454,22 +576,34 @@ function IHTable({
|
|
|
454
576
|
|
|
455
577
|
if (!rows.length) return null;
|
|
456
578
|
|
|
579
|
+
// When wrapText is active we must use getRowHeight; rowHeight prop is ignored.
|
|
580
|
+
const rowHeightProps = wrapText
|
|
581
|
+
? { getRowHeight: () => "auto" as const }
|
|
582
|
+
: rowHeight != null
|
|
583
|
+
? { rowHeight }
|
|
584
|
+
: {};
|
|
585
|
+
|
|
457
586
|
return (
|
|
458
587
|
<div className="m-2">
|
|
459
588
|
{header && <div className="font-bold p-2 ">{header}</div>}
|
|
460
|
-
<
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
589
|
+
<div className="flex justify-between">
|
|
590
|
+
<Toolbar
|
|
591
|
+
exportName={exportName}
|
|
592
|
+
onSave={onSave}
|
|
593
|
+
onSelect={onSelect}
|
|
594
|
+
rows={rows}
|
|
595
|
+
filteredRows={filteredRows}
|
|
596
|
+
/>
|
|
597
|
+
{searchable && (
|
|
598
|
+
<SearchBar value={searchQuery} onChange={setSearchQuery} />
|
|
599
|
+
)}
|
|
600
|
+
</div>
|
|
467
601
|
|
|
468
602
|
<Box
|
|
469
603
|
sx={{
|
|
470
604
|
display: "flex",
|
|
471
605
|
flexDirection: "column",
|
|
472
|
-
height: HEIGHT_MAP[
|
|
606
|
+
height: HEIGHT_MAP[displayRows.length] ?? height,
|
|
473
607
|
width,
|
|
474
608
|
zIndex: 999999999,
|
|
475
609
|
}}
|
|
@@ -485,24 +619,42 @@ function IHTable({
|
|
|
485
619
|
|
|
486
620
|
<DataGrid
|
|
487
621
|
className={className}
|
|
488
|
-
rows={
|
|
622
|
+
rows={displayRows}
|
|
489
623
|
columns={columns as any}
|
|
624
|
+
density={density}
|
|
490
625
|
checkboxSelection={Boolean(onSelect)}
|
|
491
626
|
rowSelectionModel={selectedRows}
|
|
492
627
|
onRowSelectionModelChange={!modal ? setSelectedRows : undefined}
|
|
628
|
+
{...rowHeightProps}
|
|
493
629
|
sx={{
|
|
494
|
-
fontSize,
|
|
630
|
+
fontSize,
|
|
495
631
|
"& .MuiDataGrid-cell": {
|
|
496
632
|
fontSize,
|
|
633
|
+
// Allow cells to wrap text when wrapText is enabled
|
|
634
|
+
...(wrapText && {
|
|
635
|
+
alignItems: "flex-start",
|
|
636
|
+
paddingTop: "8px",
|
|
637
|
+
paddingBottom: "8px",
|
|
638
|
+
whiteSpace: "normal",
|
|
639
|
+
wordBreak: "break-word",
|
|
640
|
+
}),
|
|
497
641
|
},
|
|
498
642
|
"& .MuiDataGrid-columnHeader": {
|
|
499
643
|
fontSize,
|
|
644
|
+
...(wrapText && {
|
|
645
|
+
whiteSpace: "normal",
|
|
646
|
+
"& .MuiDataGrid-columnHeaderTitle": {
|
|
647
|
+
whiteSpace: "normal",
|
|
648
|
+
lineHeight: 1.3,
|
|
649
|
+
wordBreak: "break-word",
|
|
650
|
+
},
|
|
651
|
+
}),
|
|
500
652
|
},
|
|
501
653
|
"& .MuiDataGrid-cell--editable": {
|
|
502
654
|
backgroundColor: "#c6d8f0",
|
|
503
655
|
fontWeight: 500,
|
|
504
656
|
},
|
|
505
|
-
...(
|
|
657
|
+
...(displayRows.length <= Object.keys(HEIGHT_MAP).length && {
|
|
506
658
|
"& .MuiDataGrid-filler": {
|
|
507
659
|
display: "none",
|
|
508
660
|
},
|
|
@@ -511,10 +663,10 @@ function IHTable({
|
|
|
511
663
|
editMode="row"
|
|
512
664
|
processRowUpdate={handleRowUpdate}
|
|
513
665
|
pageSizeOptions={[5, 10]}
|
|
514
|
-
hideFooter={
|
|
666
|
+
hideFooter={displayRows.length <= Object.keys(HEIGHT_MAP).length}
|
|
515
667
|
/>
|
|
516
668
|
</Box>
|
|
517
|
-
<CustomFooter footer={footer} rows={
|
|
669
|
+
<CustomFooter footer={footer} rows={displayRows} />
|
|
518
670
|
</div>
|
|
519
671
|
);
|
|
520
672
|
}
|