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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "next-recomponents",
3
- "version": "2.0.10",
3
+ "version": "2.0.12",
4
4
  "description": "description nueva",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -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="z-40">
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
+ }
@@ -1,59 +1,40 @@
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
- }
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
- {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;
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
- <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>
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
+ };
@@ -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 Color =
6
- | "white"
7
- | "primary"
8
- | "secondary"
9
- | "info"
10
- | "danger"
11
- | "warning"
12
- | "success";
13
-
14
- // ─── Color tokens ─────────────────────────────────────────────────────────────
15
-
16
- const COLOR_CONFIG: Record<
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
- function confirm(
138
- message: string,
139
- color: Color = "primary",
140
- ): Promise<boolean> {
141
- return new Promise((resolve) => {
142
- setPopup({
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
- function prompt(
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 c = COLOR_CONFIG[popup.color];
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
- <div
183
- className="fixed inset-0 flex items-center justify-center z-[1000]"
184
- style={{ background: "rgba(15,23,42,0.45)", backdropFilter: "blur(2px)" }}
185
- onClick={(e) => e.target === e.currentTarget && close(false)}
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, modal };
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
+ }