next-recomponents 2.0.10 → 2.0.12
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 +24 -24
- package/dist/index.d.ts +24 -24
- package/dist/index.js +473 -414
- package/dist/index.mjs +445 -386
- package/package.json +1 -1
- package/src/container/index.tsx +1 -1
- package/src/modal/index copy.tsx +59 -0
- package/src/modal/index.tsx +33 -52
- package/src/pop/actions.tsx +30 -0
- package/src/pop/color.tsx +69 -0
- package/src/pop/icon.tsx +15 -0
- package/src/pop/index.tsx +90 -255
- package/src/pop/input.tsx +27 -0
- package/src/pop/overlay.tsx +88 -0
- package/src/pop/types.ts +56 -0
package/package.json
CHANGED
package/src/container/index.tsx
CHANGED
|
@@ -42,7 +42,7 @@ export default function Container({
|
|
|
42
42
|
return (
|
|
43
43
|
<div className="flex flex-col h-screen">
|
|
44
44
|
{/* Header */}
|
|
45
|
-
<header className="
|
|
45
|
+
<header className="">
|
|
46
46
|
<div className="bg-blue-600 text-white p-4 flex justify-between items-center shadow-md">
|
|
47
47
|
<button
|
|
48
48
|
onClick={() => {
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import React, { useRef, useState } from "react";
|
|
2
|
+
import CloseIcon from "./close";
|
|
3
|
+
import { Dialog } from "@mui/material";
|
|
4
|
+
|
|
5
|
+
interface Props extends React.DetailedHTMLProps<
|
|
6
|
+
React.DialogHTMLAttributes<HTMLDialogElement>,
|
|
7
|
+
HTMLDialogElement
|
|
8
|
+
> {
|
|
9
|
+
button: React.ReactElement<{ onClick: (e: React.MouseEvent) => void }>;
|
|
10
|
+
children: React.ReactElement<{ hide: () => void }>;
|
|
11
|
+
}
|
|
12
|
+
export default function Modal({ button, children, ref, title = "" }: Props) {
|
|
13
|
+
const [open, setOpen] = useState(false);
|
|
14
|
+
function show() {
|
|
15
|
+
setOpen(true);
|
|
16
|
+
}
|
|
17
|
+
function hide() {
|
|
18
|
+
setOpen(false);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<>
|
|
23
|
+
{React.Children.map(button, (child) => {
|
|
24
|
+
if (React.isValidElement(child)) {
|
|
25
|
+
const { type, props } = child;
|
|
26
|
+
return React.createElement(type, {
|
|
27
|
+
...props,
|
|
28
|
+
onClick: (e: any) => {
|
|
29
|
+
show();
|
|
30
|
+
child.props?.onClick?.(e);
|
|
31
|
+
},
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
return child;
|
|
35
|
+
})}
|
|
36
|
+
<Dialog open={open} onClose={hide} fullWidth maxWidth="xl">
|
|
37
|
+
<div className="m-auto p-5">
|
|
38
|
+
{/* Botón de cerrar en la esquina superior derecha */}
|
|
39
|
+
<button
|
|
40
|
+
onClick={hide}
|
|
41
|
+
className="absolute top-0 right-0 text-red-500 "
|
|
42
|
+
>
|
|
43
|
+
<CloseIcon />
|
|
44
|
+
</button>
|
|
45
|
+
<div className="font-bold text-xl">{title}</div>
|
|
46
|
+
<div className="flex flex-col gap-3 pt-6">
|
|
47
|
+
{React.Children.map(children, (child) => {
|
|
48
|
+
if (React.isValidElement(child)) {
|
|
49
|
+
const { type, props } = child;
|
|
50
|
+
return React.createElement(type, { ...props, hide });
|
|
51
|
+
}
|
|
52
|
+
return child;
|
|
53
|
+
})}
|
|
54
|
+
</div>
|
|
55
|
+
</div>
|
|
56
|
+
</Dialog>
|
|
57
|
+
</>
|
|
58
|
+
);
|
|
59
|
+
}
|
package/src/modal/index.tsx
CHANGED
|
@@ -1,59 +1,40 @@
|
|
|
1
|
-
import React, {
|
|
2
|
-
import
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
1
|
+
import React, { ReactElement, ReactNode, cloneElement } from "react";
|
|
2
|
+
import usePopup from "../pop";
|
|
3
|
+
export type PopupColor =
|
|
4
|
+
| "white"
|
|
5
|
+
| "primary"
|
|
6
|
+
| "secondary"
|
|
7
|
+
| "info"
|
|
8
|
+
| "danger"
|
|
9
|
+
| "warning"
|
|
10
|
+
| "success";
|
|
11
|
+
export default function Modal({
|
|
12
|
+
button,
|
|
13
|
+
children,
|
|
14
|
+
color = "primary",
|
|
15
|
+
}: {
|
|
16
|
+
button: ReactElement<React.ComponentPropsWithRef<"button">>;
|
|
17
|
+
children: ReactElement<
|
|
18
|
+
React.ComponentPropsWithRef<"div"> & { hide: () => void }
|
|
19
|
+
>;
|
|
20
|
+
color?: PopupColor;
|
|
21
|
+
}) {
|
|
22
|
+
const pop = usePopup();
|
|
20
23
|
|
|
21
24
|
return (
|
|
22
25
|
<>
|
|
23
|
-
{
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
});
|
|
33
|
-
}
|
|
34
|
-
return child;
|
|
26
|
+
{cloneElement(button, {
|
|
27
|
+
onClick: async (e: React.MouseEvent<HTMLButtonElement>) => {
|
|
28
|
+
await pop.modal(
|
|
29
|
+
React.cloneElement(children, { hide: () => pop.close(false) }),
|
|
30
|
+
color,
|
|
31
|
+
false,
|
|
32
|
+
true,
|
|
33
|
+
);
|
|
34
|
+
},
|
|
35
35
|
})}
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
{/* Botón de cerrar en la esquina superior derecha */}
|
|
39
|
-
<button
|
|
40
|
-
onClick={hide}
|
|
41
|
-
className="absolute top-0 right-0 text-red-500 "
|
|
42
|
-
>
|
|
43
|
-
<CloseIcon />
|
|
44
|
-
</button>
|
|
45
|
-
<div className="font-bold text-xl">{title}</div>
|
|
46
|
-
<div className="flex flex-col gap-3 pt-6">
|
|
47
|
-
{React.Children.map(children, (child) => {
|
|
48
|
-
if (React.isValidElement(child)) {
|
|
49
|
-
const { type, props } = child;
|
|
50
|
-
return React.createElement(type, { ...props, hide });
|
|
51
|
-
}
|
|
52
|
-
return child;
|
|
53
|
-
})}
|
|
54
|
-
</div>
|
|
55
|
-
</div>
|
|
56
|
-
</Dialog>
|
|
36
|
+
|
|
37
|
+
{pop.PopupComponent}
|
|
57
38
|
</>
|
|
58
39
|
);
|
|
59
40
|
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { PopupActionsProps } from "./types";
|
|
2
|
+
|
|
3
|
+
export function PopupActions({
|
|
4
|
+
type,
|
|
5
|
+
confirm,
|
|
6
|
+
focusRing,
|
|
7
|
+
onConfirm,
|
|
8
|
+
onCancel,
|
|
9
|
+
}: PopupActionsProps) {
|
|
10
|
+
const showCancel = type === "confirm" || type === "prompt";
|
|
11
|
+
|
|
12
|
+
return (
|
|
13
|
+
<div className="flex gap-2 px-6 py-4 justify-end">
|
|
14
|
+
{showCancel && (
|
|
15
|
+
<button
|
|
16
|
+
onClick={onCancel}
|
|
17
|
+
className="px-4 py-2 rounded-lg text-sm font-medium bg-white border border-gray-200 text-gray-600 hover:bg-gray-50 transition"
|
|
18
|
+
>
|
|
19
|
+
Cancelar
|
|
20
|
+
</button>
|
|
21
|
+
)}
|
|
22
|
+
<button
|
|
23
|
+
onClick={onConfirm}
|
|
24
|
+
className={`px-5 py-2 rounded-lg text-sm font-semibold text-white ${confirm} transition focus:outline-none focus:ring-2 ${focusRing}`}
|
|
25
|
+
>
|
|
26
|
+
Aceptar
|
|
27
|
+
</button>
|
|
28
|
+
</div>
|
|
29
|
+
);
|
|
30
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
// ─── Color tokens ─────────────────────────────────────────────────────────────
|
|
2
|
+
|
|
3
|
+
import { ColorTokens, PopupColor } from "./types";
|
|
4
|
+
|
|
5
|
+
export const COLOR_CONFIG: Record<PopupColor, ColorTokens> = {
|
|
6
|
+
primary: {
|
|
7
|
+
bg: "from-blue-50 to-indigo-50",
|
|
8
|
+
iconBg: "bg-blue-100",
|
|
9
|
+
iconText: "text-blue-600",
|
|
10
|
+
border: "border-blue-200",
|
|
11
|
+
confirm: "bg-blue-600 hover:bg-blue-700",
|
|
12
|
+
focusRing: "focus:ring-blue-300",
|
|
13
|
+
label: "ℹ",
|
|
14
|
+
},
|
|
15
|
+
info: {
|
|
16
|
+
bg: "from-sky-50 to-cyan-50",
|
|
17
|
+
iconBg: "bg-sky-100",
|
|
18
|
+
iconText: "text-sky-600",
|
|
19
|
+
border: "border-sky-200",
|
|
20
|
+
confirm: "bg-sky-600 hover:bg-sky-700",
|
|
21
|
+
focusRing: "focus:ring-sky-300",
|
|
22
|
+
label: "ℹ",
|
|
23
|
+
},
|
|
24
|
+
success: {
|
|
25
|
+
bg: "from-emerald-50 to-green-50",
|
|
26
|
+
iconBg: "bg-emerald-100",
|
|
27
|
+
iconText: "text-emerald-600",
|
|
28
|
+
border: "border-emerald-200",
|
|
29
|
+
confirm: "bg-emerald-600 hover:bg-emerald-700",
|
|
30
|
+
focusRing: "focus:ring-emerald-300",
|
|
31
|
+
label: "✓",
|
|
32
|
+
},
|
|
33
|
+
warning: {
|
|
34
|
+
bg: "from-amber-50 to-yellow-50",
|
|
35
|
+
iconBg: "bg-amber-100",
|
|
36
|
+
iconText: "text-amber-600",
|
|
37
|
+
border: "border-amber-200",
|
|
38
|
+
confirm: "bg-amber-500 hover:bg-amber-600",
|
|
39
|
+
focusRing: "focus:ring-amber-300",
|
|
40
|
+
label: "⚠",
|
|
41
|
+
},
|
|
42
|
+
danger: {
|
|
43
|
+
bg: "from-red-50 to-rose-50",
|
|
44
|
+
iconBg: "bg-red-100",
|
|
45
|
+
iconText: "text-red-600",
|
|
46
|
+
border: "border-red-200",
|
|
47
|
+
confirm: "bg-red-600 hover:bg-red-700",
|
|
48
|
+
focusRing: "focus:ring-red-300",
|
|
49
|
+
label: "✕",
|
|
50
|
+
},
|
|
51
|
+
secondary: {
|
|
52
|
+
bg: "from-slate-50 to-gray-50",
|
|
53
|
+
iconBg: "bg-slate-100",
|
|
54
|
+
iconText: "text-slate-600",
|
|
55
|
+
border: "border-slate-200",
|
|
56
|
+
confirm: "bg-slate-700 hover:bg-slate-800",
|
|
57
|
+
focusRing: "focus:ring-slate-300",
|
|
58
|
+
label: "◎",
|
|
59
|
+
},
|
|
60
|
+
white: {
|
|
61
|
+
bg: "from-gray-50 to-white",
|
|
62
|
+
iconBg: "bg-gray-100",
|
|
63
|
+
iconText: "text-gray-500",
|
|
64
|
+
border: "border-gray-200",
|
|
65
|
+
confirm: "bg-gray-700 hover:bg-gray-800",
|
|
66
|
+
focusRing: "focus:ring-gray-300",
|
|
67
|
+
label: "◎",
|
|
68
|
+
},
|
|
69
|
+
};
|
package/src/pop/icon.tsx
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export interface PopupIconProps {
|
|
2
|
+
label: string;
|
|
3
|
+
iconBg: string;
|
|
4
|
+
iconText: string;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function PopupIcon({ label, iconBg, iconText }: PopupIconProps) {
|
|
8
|
+
return (
|
|
9
|
+
<div
|
|
10
|
+
className={`w-12 h-12 rounded-full ${iconBg} flex items-center justify-center`}
|
|
11
|
+
>
|
|
12
|
+
<span className={`text-xl font-bold ${iconText}`}>{label}</span>
|
|
13
|
+
</div>
|
|
14
|
+
);
|
|
15
|
+
}
|
package/src/pop/index.tsx
CHANGED
|
@@ -1,272 +1,107 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { React } from "next/dist/server/route-modules/app-page/vendored/rsc/entrypoints";
|
|
3
|
-
import { useState } from "react";
|
|
4
2
|
|
|
5
|
-
type
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
Color,
|
|
18
|
-
{
|
|
19
|
-
bg: string;
|
|
20
|
-
iconBg: string;
|
|
21
|
-
iconText: string;
|
|
22
|
-
border: string;
|
|
23
|
-
confirm: string;
|
|
24
|
-
label: string;
|
|
25
|
-
}
|
|
26
|
-
> = {
|
|
27
|
-
primary: {
|
|
28
|
-
bg: "from-blue-50 to-indigo-50",
|
|
29
|
-
iconBg: "bg-blue-100",
|
|
30
|
-
iconText: "text-blue-600",
|
|
31
|
-
border: "border-blue-200",
|
|
32
|
-
confirm: "bg-blue-600 hover:bg-blue-700 focus:ring-blue-300",
|
|
33
|
-
label: "ℹ",
|
|
34
|
-
},
|
|
35
|
-
info: {
|
|
36
|
-
bg: "from-sky-50 to-cyan-50",
|
|
37
|
-
iconBg: "bg-sky-100",
|
|
38
|
-
iconText: "text-sky-600",
|
|
39
|
-
border: "border-sky-200",
|
|
40
|
-
confirm: "bg-sky-600 hover:bg-sky-700 focus:ring-sky-300",
|
|
41
|
-
label: "ℹ",
|
|
42
|
-
},
|
|
43
|
-
success: {
|
|
44
|
-
bg: "from-emerald-50 to-green-50",
|
|
45
|
-
iconBg: "bg-emerald-100",
|
|
46
|
-
iconText: "text-emerald-600",
|
|
47
|
-
border: "border-emerald-200",
|
|
48
|
-
confirm: "bg-emerald-600 hover:bg-emerald-700 focus:ring-emerald-300",
|
|
49
|
-
label: "✓",
|
|
50
|
-
},
|
|
51
|
-
warning: {
|
|
52
|
-
bg: "from-amber-50 to-yellow-50",
|
|
53
|
-
iconBg: "bg-amber-100",
|
|
54
|
-
iconText: "text-amber-600",
|
|
55
|
-
border: "border-amber-200",
|
|
56
|
-
confirm: "bg-amber-500 hover:bg-amber-600 focus:ring-amber-300",
|
|
57
|
-
label: "⚠",
|
|
58
|
-
},
|
|
59
|
-
danger: {
|
|
60
|
-
bg: "from-red-50 to-rose-50",
|
|
61
|
-
iconBg: "bg-red-100",
|
|
62
|
-
iconText: "text-red-600",
|
|
63
|
-
border: "border-red-200",
|
|
64
|
-
confirm: "bg-red-600 hover:bg-red-700 focus:ring-red-300",
|
|
65
|
-
label: "✕",
|
|
66
|
-
},
|
|
67
|
-
secondary: {
|
|
68
|
-
bg: "from-slate-50 to-gray-50",
|
|
69
|
-
iconBg: "bg-slate-100",
|
|
70
|
-
iconText: "text-slate-600",
|
|
71
|
-
border: "border-slate-200",
|
|
72
|
-
confirm: "bg-slate-700 hover:bg-slate-800 focus:ring-slate-300",
|
|
73
|
-
label: "◎",
|
|
74
|
-
},
|
|
75
|
-
white: {
|
|
76
|
-
bg: "from-gray-50 to-white",
|
|
77
|
-
iconBg: "bg-gray-100",
|
|
78
|
-
iconText: "text-gray-500",
|
|
79
|
-
border: "border-gray-200",
|
|
80
|
-
confirm: "bg-gray-700 hover:bg-gray-800 focus:ring-gray-300",
|
|
81
|
-
label: "◎",
|
|
82
|
-
},
|
|
3
|
+
import { useState, useCallback, type ReactNode } from "react";
|
|
4
|
+
import { PopupColor, PopupState } from "./types";
|
|
5
|
+
import { PopupOverlay } from "./overlay";
|
|
6
|
+
|
|
7
|
+
const INITIAL_STATE: PopupState = {
|
|
8
|
+
type: "alert",
|
|
9
|
+
message: "",
|
|
10
|
+
visible: false,
|
|
11
|
+
inputValue: "",
|
|
12
|
+
color: "primary",
|
|
13
|
+
icons: true,
|
|
14
|
+
full: false,
|
|
83
15
|
};
|
|
84
16
|
|
|
85
|
-
// ─── Types ────────────────────────────────────────────────────────────────────
|
|
86
|
-
|
|
87
|
-
interface PopupState {
|
|
88
|
-
type: "alert" | "modal" | "confirm" | "prompt";
|
|
89
|
-
message: string | React.ReactNode;
|
|
90
|
-
visible: boolean;
|
|
91
|
-
inputValue: string;
|
|
92
|
-
onConfirm?: (value?: string) => void;
|
|
93
|
-
onCancel?: () => void;
|
|
94
|
-
color: Color;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
// ─── Hook ─────────────────────────────────────────────────────────────────────
|
|
98
|
-
|
|
99
17
|
export default function usePopup() {
|
|
100
|
-
const [popup, setPopup] = useState<PopupState>(
|
|
101
|
-
type: "alert",
|
|
102
|
-
message: "",
|
|
103
|
-
visible: false,
|
|
104
|
-
inputValue: "",
|
|
105
|
-
color: "primary",
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
function alert(message: string, color: Color = "primary"): Promise<void> {
|
|
109
|
-
return new Promise((resolve) => {
|
|
110
|
-
setPopup({
|
|
111
|
-
type: "alert",
|
|
112
|
-
message,
|
|
113
|
-
visible: true,
|
|
114
|
-
inputValue: "",
|
|
115
|
-
onConfirm: () => resolve(),
|
|
116
|
-
color,
|
|
117
|
-
});
|
|
118
|
-
});
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
function modal(
|
|
122
|
-
message: React.ReactNode,
|
|
123
|
-
color: Color = "primary",
|
|
124
|
-
): Promise<void> {
|
|
125
|
-
return new Promise((resolve) => {
|
|
126
|
-
setPopup({
|
|
127
|
-
type: "modal",
|
|
128
|
-
message,
|
|
129
|
-
visible: true,
|
|
130
|
-
inputValue: "",
|
|
131
|
-
onConfirm: () => resolve(),
|
|
132
|
-
color,
|
|
133
|
-
});
|
|
134
|
-
});
|
|
135
|
-
}
|
|
18
|
+
const [popup, setPopup] = useState<PopupState>(INITIAL_STATE);
|
|
136
19
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
type: "confirm",
|
|
144
|
-
message,
|
|
145
|
-
visible: true,
|
|
146
|
-
inputValue: "",
|
|
147
|
-
onConfirm: () => resolve(true),
|
|
148
|
-
onCancel: () => resolve(false),
|
|
149
|
-
color,
|
|
150
|
-
});
|
|
151
|
-
});
|
|
152
|
-
}
|
|
20
|
+
const open = useCallback(
|
|
21
|
+
(partial: Omit<PopupState, "visible" | "inputValue">) => {
|
|
22
|
+
setPopup({ ...partial, visible: true, inputValue: "" });
|
|
23
|
+
},
|
|
24
|
+
[],
|
|
25
|
+
);
|
|
153
26
|
|
|
154
|
-
|
|
155
|
-
message: string,
|
|
156
|
-
color: Color = "primary",
|
|
157
|
-
): Promise<string | null> {
|
|
158
|
-
return new Promise((resolve) => {
|
|
159
|
-
setPopup({
|
|
160
|
-
type: "prompt",
|
|
161
|
-
message,
|
|
162
|
-
visible: true,
|
|
163
|
-
inputValue: "",
|
|
164
|
-
onConfirm: (value) => resolve(value ?? ""),
|
|
165
|
-
onCancel: () => resolve(null),
|
|
166
|
-
color,
|
|
167
|
-
});
|
|
168
|
-
});
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
function close(confirmed: boolean, value?: string) {
|
|
27
|
+
const close = useCallback((confirmed: boolean, value?: string) => {
|
|
172
28
|
setPopup((prev) => {
|
|
173
29
|
if (confirmed) prev.onConfirm?.(value);
|
|
174
30
|
else prev.onCancel?.();
|
|
175
31
|
return { ...prev, visible: false, inputValue: "" };
|
|
176
32
|
});
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
const
|
|
33
|
+
}, []);
|
|
34
|
+
|
|
35
|
+
const alert = useCallback(
|
|
36
|
+
(message: string, color: PopupColor = "primary"): Promise<void> =>
|
|
37
|
+
new Promise((resolve) =>
|
|
38
|
+
open({
|
|
39
|
+
type: "alert",
|
|
40
|
+
message,
|
|
41
|
+
color,
|
|
42
|
+
onConfirm: () => resolve(),
|
|
43
|
+
onCancel: () => resolve(),
|
|
44
|
+
}),
|
|
45
|
+
),
|
|
46
|
+
[open],
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
const modal = useCallback(
|
|
50
|
+
(
|
|
51
|
+
message: ReactNode,
|
|
52
|
+
color: PopupColor = "primary",
|
|
53
|
+
icons: boolean = true,
|
|
54
|
+
full: boolean = false,
|
|
55
|
+
): Promise<void> =>
|
|
56
|
+
new Promise((resolve) =>
|
|
57
|
+
open({
|
|
58
|
+
type: "modal",
|
|
59
|
+
message,
|
|
60
|
+
color,
|
|
61
|
+
onConfirm: () => resolve(),
|
|
62
|
+
onCancel: () => resolve(),
|
|
63
|
+
icons,
|
|
64
|
+
full,
|
|
65
|
+
}),
|
|
66
|
+
),
|
|
67
|
+
[open],
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
const confirm = useCallback(
|
|
71
|
+
(message: string, color: PopupColor = "primary"): Promise<boolean> =>
|
|
72
|
+
new Promise((resolve) =>
|
|
73
|
+
open({
|
|
74
|
+
type: "confirm",
|
|
75
|
+
message,
|
|
76
|
+
color,
|
|
77
|
+
onConfirm: () => resolve(true),
|
|
78
|
+
onCancel: () => resolve(false),
|
|
79
|
+
}),
|
|
80
|
+
),
|
|
81
|
+
[open],
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
const prompt = useCallback(
|
|
85
|
+
(message: string, color: PopupColor = "primary"): Promise<string | null> =>
|
|
86
|
+
new Promise((resolve) =>
|
|
87
|
+
open({
|
|
88
|
+
type: "prompt",
|
|
89
|
+
message,
|
|
90
|
+
color,
|
|
91
|
+
onConfirm: (value) => resolve(value ?? ""),
|
|
92
|
+
onCancel: () => resolve(null),
|
|
93
|
+
}),
|
|
94
|
+
),
|
|
95
|
+
[open],
|
|
96
|
+
);
|
|
180
97
|
|
|
181
98
|
const PopupComponent = popup.visible ? (
|
|
182
|
-
<
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
<div
|
|
188
|
-
className={`
|
|
189
|
-
bg-gradient-to-br ${c.bg} border ${c.border}
|
|
190
|
-
rounded-2xl shadow-2xl w-full max-w-sm mx-4
|
|
191
|
-
animate-[fadeInScale_0.18s_ease-out]
|
|
192
|
-
`}
|
|
193
|
-
style={{ animation: "fadeInScale 0.18s ease-out" }}
|
|
194
|
-
>
|
|
195
|
-
<style>{`
|
|
196
|
-
@keyframes fadeInScale {
|
|
197
|
-
from { opacity: 0; transform: scale(0.93) translateY(8px); }
|
|
198
|
-
to { opacity: 1; transform: scale(1) translateY(0); }
|
|
199
|
-
}
|
|
200
|
-
`}</style>
|
|
201
|
-
|
|
202
|
-
{/* Icon + Message */}
|
|
203
|
-
<div className="flex flex-col items-center gap-3 px-8 pt-8 pb-5 text-center">
|
|
204
|
-
<div
|
|
205
|
-
className={`w-12 h-12 rounded-full ${c.iconBg} flex items-center justify-center`}
|
|
206
|
-
>
|
|
207
|
-
<span className={`text-xl font-bold ${c.iconText}`}>{c.label}</span>
|
|
208
|
-
</div>
|
|
209
|
-
<p className="text-gray-800 text-[15px] font-medium leading-snug">
|
|
210
|
-
{popup.message}
|
|
211
|
-
</p>
|
|
212
|
-
</div>
|
|
213
|
-
|
|
214
|
-
{/* Prompt input */}
|
|
215
|
-
{popup.type === "prompt" && (
|
|
216
|
-
<div className="px-8 pb-2">
|
|
217
|
-
<input
|
|
218
|
-
autoFocus
|
|
219
|
-
type="text"
|
|
220
|
-
value={popup.inputValue}
|
|
221
|
-
onChange={(e) =>
|
|
222
|
-
setPopup((prev) => ({ ...prev, inputValue: e.target.value }))
|
|
223
|
-
}
|
|
224
|
-
onKeyDown={(e) =>
|
|
225
|
-
e.key === "Enter" && close(true, popup.inputValue)
|
|
226
|
-
}
|
|
227
|
-
className={`
|
|
228
|
-
w-full px-3 py-2 rounded-lg border ${c.border} bg-white
|
|
229
|
-
text-sm text-gray-800 outline-none
|
|
230
|
-
focus:ring-2 ${c.confirm.includes("blue") ? "focus:ring-blue-200" : "focus:ring-gray-200"}
|
|
231
|
-
transition
|
|
232
|
-
`}
|
|
233
|
-
placeholder="Escribe aquí..."
|
|
234
|
-
/>
|
|
235
|
-
</div>
|
|
236
|
-
)}
|
|
237
|
-
|
|
238
|
-
{/* Divider */}
|
|
239
|
-
<div className={`border-t ${c.border} mx-0 mt-4`} />
|
|
240
|
-
|
|
241
|
-
{/* Actions */}
|
|
242
|
-
{popup.type != "modal" && (
|
|
243
|
-
<div className="flex gap-2 px-6 py-4 justify-end">
|
|
244
|
-
{(popup.type === "confirm" || popup.type === "prompt") && (
|
|
245
|
-
<button
|
|
246
|
-
onClick={() => close(false)}
|
|
247
|
-
className="
|
|
248
|
-
px-4 py-2 rounded-lg text-sm font-medium
|
|
249
|
-
bg-white border border-gray-200 text-gray-600
|
|
250
|
-
hover:bg-gray-50 transition
|
|
251
|
-
"
|
|
252
|
-
>
|
|
253
|
-
Cancelar
|
|
254
|
-
</button>
|
|
255
|
-
)}
|
|
256
|
-
<button
|
|
257
|
-
onClick={() => close(true, popup.inputValue)}
|
|
258
|
-
className={`
|
|
259
|
-
px-5 py-2 rounded-lg text-sm font-semibold text-white
|
|
260
|
-
${c.confirm} transition focus:outline-none focus:ring-2
|
|
261
|
-
`}
|
|
262
|
-
>
|
|
263
|
-
Aceptar
|
|
264
|
-
</button>
|
|
265
|
-
</div>
|
|
266
|
-
)}
|
|
267
|
-
</div>
|
|
268
|
-
</div>
|
|
99
|
+
<PopupOverlay
|
|
100
|
+
popup={popup}
|
|
101
|
+
onClose={close}
|
|
102
|
+
onInputChange={(v) => setPopup((prev) => ({ ...prev, inputValue: v }))}
|
|
103
|
+
/>
|
|
269
104
|
) : null;
|
|
270
105
|
|
|
271
|
-
return { alert, confirm, prompt, PopupComponent,
|
|
106
|
+
return { alert, confirm, prompt, modal, PopupComponent, close };
|
|
272
107
|
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { PromptInputProps } from "./types";
|
|
2
|
+
|
|
3
|
+
export function PromptInput({
|
|
4
|
+
value,
|
|
5
|
+
border,
|
|
6
|
+
focusRing,
|
|
7
|
+
onChange,
|
|
8
|
+
onEnter,
|
|
9
|
+
}: PromptInputProps) {
|
|
10
|
+
return (
|
|
11
|
+
<div className="px-8 pb-2">
|
|
12
|
+
<input
|
|
13
|
+
autoFocus
|
|
14
|
+
type="text"
|
|
15
|
+
value={value}
|
|
16
|
+
onChange={(e) => onChange(e.target.value)}
|
|
17
|
+
onKeyDown={(e) => e.key === "Enter" && onEnter()}
|
|
18
|
+
className={`
|
|
19
|
+
w-full px-3 py-2 rounded-lg border ${border} bg-white
|
|
20
|
+
text-sm text-gray-800 outline-none
|
|
21
|
+
focus:ring-2 ${focusRing} transition
|
|
22
|
+
`}
|
|
23
|
+
placeholder="Escribe aquí..."
|
|
24
|
+
/>
|
|
25
|
+
</div>
|
|
26
|
+
);
|
|
27
|
+
}
|