flysoft-react-ui 0.1.8 → 0.1.10
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/README.md +152 -1
- package/dist/App.d.ts.map +1 -1
- package/dist/App.js +12 -2
- package/dist/components/form-controls/Button.d.ts +1 -1
- package/dist/components/form-controls/Button.d.ts.map +1 -1
- package/dist/components/form-controls/Button.js +4 -4
- package/dist/components/layout/AppLayout.d.ts +9 -0
- package/dist/components/layout/AppLayout.d.ts.map +1 -0
- package/dist/components/layout/AppLayout.js +89 -0
- package/dist/components/layout/index.d.ts +2 -0
- package/dist/components/layout/index.d.ts.map +1 -1
- package/dist/components/layout/index.js +1 -0
- package/dist/contexts/ThemeContext.d.ts.map +1 -1
- package/dist/contexts/ThemeContext.js +7 -0
- package/dist/hooks/index.d.ts +5 -0
- package/dist/hooks/index.d.ts.map +1 -0
- package/dist/hooks/index.js +3 -0
- package/dist/hooks/useBreakpoint.d.ts +14 -0
- package/dist/hooks/useBreakpoint.d.ts.map +1 -0
- package/dist/hooks/useBreakpoint.js +59 -0
- package/dist/hooks/useElementScroll.d.ts +5 -0
- package/dist/hooks/useElementScroll.d.ts.map +1 -0
- package/dist/hooks/useElementScroll.js +28 -0
- package/dist/hooks/useGlobalThemeStyles.d.ts +6 -0
- package/dist/hooks/useGlobalThemeStyles.d.ts.map +1 -0
- package/dist/hooks/useGlobalThemeStyles.js +40 -0
- package/dist/index.css +1 -1
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -1
- package/dist/templates/forms/ContactForm.d.ts +27 -0
- package/dist/templates/forms/ContactForm.d.ts.map +1 -0
- package/dist/templates/forms/ContactForm.js +58 -0
- package/dist/templates/forms/LoginForm.d.ts +24 -0
- package/dist/templates/forms/LoginForm.d.ts.map +1 -0
- package/dist/templates/forms/LoginForm.js +36 -0
- package/dist/templates/forms/RegistrationForm.d.ts +27 -0
- package/dist/templates/forms/RegistrationForm.d.ts.map +1 -0
- package/dist/templates/forms/RegistrationForm.js +54 -0
- package/dist/templates/layouts/DashboardLayout.d.ts +38 -0
- package/dist/templates/layouts/DashboardLayout.d.ts.map +1 -0
- package/dist/templates/layouts/DashboardLayout.js +26 -0
- package/dist/templates/layouts/SidebarLayout.d.ts +48 -0
- package/dist/templates/layouts/SidebarLayout.d.ts.map +1 -0
- package/dist/templates/layouts/SidebarLayout.js +27 -0
- package/dist/templates/patterns/FormPattern.d.ts +54 -0
- package/dist/templates/patterns/FormPattern.d.ts.map +1 -0
- package/dist/templates/patterns/FormPattern.js +67 -0
- package/package.json +20 -4
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import React, { useState } from "react";
|
|
3
|
+
import { Button, Input, Card } from "../../index";
|
|
4
|
+
export const LoginForm = ({ onSubmit, loading = false, error, className = "", }) => {
|
|
5
|
+
const [formData, setFormData] = useState({
|
|
6
|
+
email: "",
|
|
7
|
+
password: "",
|
|
8
|
+
});
|
|
9
|
+
const [errors, setErrors] = useState({});
|
|
10
|
+
const handleSubmit = (e) => {
|
|
11
|
+
e.preventDefault();
|
|
12
|
+
// Validación básica
|
|
13
|
+
const newErrors = {};
|
|
14
|
+
if (!formData.email) {
|
|
15
|
+
newErrors.email = "El email es requerido";
|
|
16
|
+
}
|
|
17
|
+
else if (!/\S+@\S+\.\S+/.test(formData.email)) {
|
|
18
|
+
newErrors.email = "El email no es válido";
|
|
19
|
+
}
|
|
20
|
+
if (!formData.password) {
|
|
21
|
+
newErrors.password = "La contraseña es requerida";
|
|
22
|
+
}
|
|
23
|
+
setErrors(newErrors);
|
|
24
|
+
if (Object.keys(newErrors).length === 0) {
|
|
25
|
+
onSubmit?.(formData);
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
const handleChange = (field) => (e) => {
|
|
29
|
+
setFormData((prev) => ({ ...prev, [field]: e.target.value }));
|
|
30
|
+
// Limpiar error cuando el usuario empiece a escribir
|
|
31
|
+
if (errors[field]) {
|
|
32
|
+
setErrors((prev) => ({ ...prev, [field]: undefined }));
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
return (_jsxs(Card, { title: "Iniciar Sesi\u00F3n", subtitle: "Ingresa tus credenciales para acceder", className: className, children: [_jsxs("form", { onSubmit: handleSubmit, className: "space-y-4", children: [error && (_jsx("div", { className: "p-3 bg-red-50 border border-red-200 rounded-lg", children: _jsxs("p", { className: "text-sm text-red-600", children: [_jsx("i", { className: "fa fa-exclamation-triangle mr-2" }), error] }) })), _jsx(Input, { label: "Email", type: "email", placeholder: "tu@email.com", icon: "fa-envelope", value: formData.email, onChange: handleChange("email"), error: errors.email, disabled: loading }), _jsx(Input, { label: "Contrase\u00F1a", type: "password", placeholder: "Tu contrase\u00F1a", icon: "fa-lock", value: formData.password, onChange: handleChange("password"), error: errors.password, disabled: loading }), _jsx(Button, { type: "submit", variant: "primary", size: "lg", icon: "fa-sign-in-alt", loading: loading, className: "w-full", children: "Iniciar Sesi\u00F3n" })] }), _jsx("div", { className: "mt-4 text-center", children: _jsxs("p", { className: "text-sm text-gray-600", children: ["\u00BFNo tienes cuenta?", " ", _jsx("a", { href: "#", className: "text-blue-600 hover:text-blue-800 font-medium", children: "Reg\u00EDstrate aqu\u00ED" })] }) })] }));
|
|
36
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
/**
|
|
3
|
+
* Template de Formulario de Registro
|
|
4
|
+
*
|
|
5
|
+
* Ejemplo de uso:
|
|
6
|
+
* ```tsx
|
|
7
|
+
* import { RegistrationForm } from "flysoft-react-ui/templates/forms/RegistrationForm";
|
|
8
|
+
*
|
|
9
|
+
* function App() {
|
|
10
|
+
* return <RegistrationForm onSubmit={handleRegistration} />;
|
|
11
|
+
* }
|
|
12
|
+
* ```
|
|
13
|
+
*/
|
|
14
|
+
export interface RegistrationFormProps {
|
|
15
|
+
onSubmit?: (data: {
|
|
16
|
+
firstName: string;
|
|
17
|
+
lastName: string;
|
|
18
|
+
email: string;
|
|
19
|
+
password: string;
|
|
20
|
+
confirmPassword: string;
|
|
21
|
+
}) => void;
|
|
22
|
+
loading?: boolean;
|
|
23
|
+
error?: string;
|
|
24
|
+
className?: string;
|
|
25
|
+
}
|
|
26
|
+
export declare const RegistrationForm: React.FC<RegistrationFormProps>;
|
|
27
|
+
//# sourceMappingURL=RegistrationForm.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RegistrationForm.d.ts","sourceRoot":"","sources":["../../../src/templates/forms/RegistrationForm.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAmB,MAAM,OAAO,CAAC;AAGxC;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE;QAChB,SAAS,EAAE,MAAM,CAAC;QAClB,QAAQ,EAAE,MAAM,CAAC;QACjB,KAAK,EAAE,MAAM,CAAC;QACd,QAAQ,EAAE,MAAM,CAAC;QACjB,eAAe,EAAE,MAAM,CAAC;KACzB,KAAK,IAAI,CAAC;IACX,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,eAAO,MAAM,gBAAgB,EAAE,KAAK,CAAC,EAAE,CAAC,qBAAqB,CAmK5D,CAAC"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import React, { useState } from "react";
|
|
3
|
+
import { Button, Input, Card } from "../../index";
|
|
4
|
+
export const RegistrationForm = ({ onSubmit, loading = false, error, className = "", }) => {
|
|
5
|
+
const [formData, setFormData] = useState({
|
|
6
|
+
firstName: "",
|
|
7
|
+
lastName: "",
|
|
8
|
+
email: "",
|
|
9
|
+
password: "",
|
|
10
|
+
confirmPassword: "",
|
|
11
|
+
});
|
|
12
|
+
const [errors, setErrors] = useState({});
|
|
13
|
+
const handleSubmit = (e) => {
|
|
14
|
+
e.preventDefault();
|
|
15
|
+
// Validación
|
|
16
|
+
const newErrors = {};
|
|
17
|
+
if (!formData.firstName.trim()) {
|
|
18
|
+
newErrors.firstName = "El nombre es requerido";
|
|
19
|
+
}
|
|
20
|
+
if (!formData.lastName.trim()) {
|
|
21
|
+
newErrors.lastName = "El apellido es requerido";
|
|
22
|
+
}
|
|
23
|
+
if (!formData.email) {
|
|
24
|
+
newErrors.email = "El email es requerido";
|
|
25
|
+
}
|
|
26
|
+
else if (!/\S+@\S+\.\S+/.test(formData.email)) {
|
|
27
|
+
newErrors.email = "El email no es válido";
|
|
28
|
+
}
|
|
29
|
+
if (!formData.password) {
|
|
30
|
+
newErrors.password = "La contraseña es requerida";
|
|
31
|
+
}
|
|
32
|
+
else if (formData.password.length < 6) {
|
|
33
|
+
newErrors.password = "La contraseña debe tener al menos 6 caracteres";
|
|
34
|
+
}
|
|
35
|
+
if (!formData.confirmPassword) {
|
|
36
|
+
newErrors.confirmPassword = "Confirma tu contraseña";
|
|
37
|
+
}
|
|
38
|
+
else if (formData.password !== formData.confirmPassword) {
|
|
39
|
+
newErrors.confirmPassword = "Las contraseñas no coinciden";
|
|
40
|
+
}
|
|
41
|
+
setErrors(newErrors);
|
|
42
|
+
if (Object.keys(newErrors).length === 0) {
|
|
43
|
+
onSubmit?.(formData);
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
const handleChange = (field) => (e) => {
|
|
47
|
+
setFormData((prev) => ({ ...prev, [field]: e.target.value }));
|
|
48
|
+
// Limpiar error cuando el usuario empiece a escribir
|
|
49
|
+
if (errors[field]) {
|
|
50
|
+
setErrors((prev) => ({ ...prev, [field]: undefined }));
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
return (_jsxs(Card, { title: "Crear Cuenta", subtitle: "Completa los datos para registrarte", className: className, children: [_jsxs("form", { onSubmit: handleSubmit, className: "space-y-4", children: [error && (_jsx("div", { className: "p-3 bg-red-50 border border-red-200 rounded-lg", children: _jsxs("p", { className: "text-sm text-red-600", children: [_jsx("i", { className: "fa fa-exclamation-triangle mr-2" }), error] }) })), _jsxs("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-4", children: [_jsx(Input, { label: "Nombre", placeholder: "Tu nombre", icon: "fa-user", value: formData.firstName, onChange: handleChange("firstName"), error: errors.firstName, disabled: loading }), _jsx(Input, { label: "Apellido", placeholder: "Tu apellido", icon: "fa-user", value: formData.lastName, onChange: handleChange("lastName"), error: errors.lastName, disabled: loading })] }), _jsx(Input, { label: "Email", type: "email", placeholder: "tu@email.com", icon: "fa-envelope", value: formData.email, onChange: handleChange("email"), error: errors.email, disabled: loading }), _jsx(Input, { label: "Contrase\u00F1a", type: "password", placeholder: "M\u00EDnimo 6 caracteres", icon: "fa-lock", value: formData.password, onChange: handleChange("password"), error: errors.password, disabled: loading }), _jsx(Input, { label: "Confirmar Contrase\u00F1a", type: "password", placeholder: "Repite tu contrase\u00F1a", icon: "fa-lock", value: formData.confirmPassword, onChange: handleChange("confirmPassword"), error: errors.confirmPassword, disabled: loading }), _jsx(Button, { type: "submit", variant: "primary", size: "lg", icon: "fa-user-plus", loading: loading, className: "w-full", children: "Crear Cuenta" })] }), _jsx("div", { className: "mt-4 text-center", children: _jsxs("p", { className: "text-sm text-gray-600", children: ["\u00BFYa tienes cuenta?", " ", _jsx("a", { href: "#", className: "text-blue-600 hover:text-blue-800 font-medium", children: "Inicia sesi\u00F3n aqu\u00ED" })] }) })] }));
|
|
54
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
/**
|
|
3
|
+
* Template de Layout de Dashboard
|
|
4
|
+
*
|
|
5
|
+
* Ejemplo de uso:
|
|
6
|
+
* ```tsx
|
|
7
|
+
* import { DashboardLayout } from "flysoft-react-ui/templates/layouts/DashboardLayout";
|
|
8
|
+
*
|
|
9
|
+
* function App() {
|
|
10
|
+
* return (
|
|
11
|
+
* <DashboardLayout
|
|
12
|
+
* title="Mi Dashboard"
|
|
13
|
+
* stats={stats}
|
|
14
|
+
* actions={<Button>Nueva Acción</Button>}
|
|
15
|
+
* >
|
|
16
|
+
* <div>Contenido del dashboard</div>
|
|
17
|
+
* </DashboardLayout>
|
|
18
|
+
* );
|
|
19
|
+
* }
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
export interface DashboardStat {
|
|
23
|
+
title: string;
|
|
24
|
+
value: string | number;
|
|
25
|
+
change?: string;
|
|
26
|
+
changeType?: "positive" | "negative" | "neutral";
|
|
27
|
+
icon?: string;
|
|
28
|
+
}
|
|
29
|
+
export interface DashboardLayoutProps {
|
|
30
|
+
title: string;
|
|
31
|
+
subtitle?: string;
|
|
32
|
+
stats?: DashboardStat[];
|
|
33
|
+
actions?: React.ReactNode;
|
|
34
|
+
children: React.ReactNode;
|
|
35
|
+
className?: string;
|
|
36
|
+
}
|
|
37
|
+
export declare const DashboardLayout: React.FC<DashboardLayoutProps>;
|
|
38
|
+
//# sourceMappingURL=DashboardLayout.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DashboardLayout.d.ts","sourceRoot":"","sources":["../../../src/templates/layouts/DashboardLayout.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAG1B;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC;IACvB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,UAAU,GAAG,UAAU,GAAG,SAAS,CAAC;IACjD,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,oBAAoB;IACnC,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,aAAa,EAAE,CAAC;IACxB,OAAO,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,eAAO,MAAM,eAAe,EAAE,KAAK,CAAC,EAAE,CAAC,oBAAoB,CAoG1D,CAAC"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import React from "react";
|
|
3
|
+
import { Card } from "../../index";
|
|
4
|
+
export const DashboardLayout = ({ title, subtitle, stats = [], actions, children, className = "", }) => {
|
|
5
|
+
const getChangeColor = (changeType) => {
|
|
6
|
+
switch (changeType) {
|
|
7
|
+
case "positive":
|
|
8
|
+
return "text-green-600";
|
|
9
|
+
case "negative":
|
|
10
|
+
return "text-red-600";
|
|
11
|
+
default:
|
|
12
|
+
return "text-gray-600";
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
const getChangeIcon = (changeType) => {
|
|
16
|
+
switch (changeType) {
|
|
17
|
+
case "positive":
|
|
18
|
+
return "fa-arrow-up";
|
|
19
|
+
case "negative":
|
|
20
|
+
return "fa-arrow-down";
|
|
21
|
+
default:
|
|
22
|
+
return "fa-minus";
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
return (_jsxs("div", { className: `min-h-screen bg-gray-50 ${className}`, children: [_jsx("div", { className: "bg-white shadow-sm border-b", children: _jsx("div", { className: "max-w-7xl mx-auto px-4 sm:px-6 lg:px-8", children: _jsxs("div", { className: "flex justify-between items-center py-6", children: [_jsxs("div", { children: [_jsx("h1", { className: "text-2xl font-bold text-gray-900", children: title }), subtitle && (_jsx("p", { className: "mt-1 text-sm text-gray-600", children: subtitle }))] }), actions && (_jsx("div", { className: "flex items-center space-x-3", children: actions }))] }) }) }), stats.length > 0 && (_jsx("div", { className: "max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-6", children: _jsx("div", { className: "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6", children: stats.map((stat, index) => (_jsx(Card, { variant: "elevated", className: "p-6", children: _jsxs("div", { className: "flex items-center", children: [_jsx("div", { className: "flex-shrink-0", children: stat.icon && (_jsx("div", { className: "w-8 h-8 bg-blue-100 rounded-lg flex items-center justify-center", children: _jsx("i", { className: `fa ${stat.icon} text-blue-600` }) })) }), _jsxs("div", { className: "ml-4 flex-1", children: [_jsx("p", { className: "text-sm font-medium text-gray-600", children: stat.title }), _jsx("p", { className: "text-2xl font-semibold text-gray-900", children: stat.value }), stat.change && (_jsxs("div", { className: "flex items-center mt-1", children: [_jsx("i", { className: `fa ${getChangeIcon(stat.changeType)} text-xs mr-1 ${getChangeColor(stat.changeType)}` }), _jsx("span", { className: `text-sm ${getChangeColor(stat.changeType)}`, children: stat.change })] }))] })] }) }, index))) }) })), _jsx("div", { className: "max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-6", children: children })] }));
|
|
26
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
/**
|
|
3
|
+
* Template de Layout con Sidebar
|
|
4
|
+
*
|
|
5
|
+
* Ejemplo de uso:
|
|
6
|
+
* ```tsx
|
|
7
|
+
* import { SidebarLayout } from "flysoft-react-ui/templates/layouts/SidebarLayout";
|
|
8
|
+
*
|
|
9
|
+
* function App() {
|
|
10
|
+
* const menuItems = [
|
|
11
|
+
* { label: "Dashboard", icon: "fa-home", href: "/" },
|
|
12
|
+
* { label: "Usuarios", icon: "fa-users", href: "/users" },
|
|
13
|
+
* ];
|
|
14
|
+
*
|
|
15
|
+
* return (
|
|
16
|
+
* <SidebarLayout
|
|
17
|
+
* title="Mi App"
|
|
18
|
+
* menuItems={menuItems}
|
|
19
|
+
* user={{ name: "Juan Pérez", avatar: "fa-user" }}
|
|
20
|
+
* >
|
|
21
|
+
* <div>Contenido principal</div>
|
|
22
|
+
* </SidebarLayout>
|
|
23
|
+
* );
|
|
24
|
+
* }
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
export interface MenuItem {
|
|
28
|
+
label: string;
|
|
29
|
+
icon: string;
|
|
30
|
+
href: string;
|
|
31
|
+
badge?: string | number;
|
|
32
|
+
children?: MenuItem[];
|
|
33
|
+
}
|
|
34
|
+
export interface User {
|
|
35
|
+
name: string;
|
|
36
|
+
email?: string;
|
|
37
|
+
avatar?: string;
|
|
38
|
+
}
|
|
39
|
+
export interface SidebarLayoutProps {
|
|
40
|
+
title: string;
|
|
41
|
+
menuItems: MenuItem[];
|
|
42
|
+
user: User;
|
|
43
|
+
children: React.ReactNode;
|
|
44
|
+
className?: string;
|
|
45
|
+
onLogout?: () => void;
|
|
46
|
+
}
|
|
47
|
+
export declare const SidebarLayout: React.FC<SidebarLayoutProps>;
|
|
48
|
+
//# sourceMappingURL=SidebarLayout.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SidebarLayout.d.ts","sourceRoot":"","sources":["../../../src/templates/layouts/SidebarLayout.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAmB,MAAM,OAAO,CAAC;AAGxC;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,MAAM,WAAW,QAAQ;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACxB,QAAQ,CAAC,EAAE,QAAQ,EAAE,CAAC;CACvB;AAED,MAAM,WAAW,IAAI;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,QAAQ,EAAE,CAAC;IACtB,IAAI,EAAE,IAAI,CAAC;IACX,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;CACvB;AAED,eAAO,MAAM,aAAa,EAAE,KAAK,CAAC,EAAE,CAAC,kBAAkB,CAwItD,CAAC"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import React, { useState } from "react";
|
|
3
|
+
import { Button, Badge } from "../../index";
|
|
4
|
+
export const SidebarLayout = ({ title, menuItems, user, children, className = "", onLogout, }) => {
|
|
5
|
+
const [sidebarOpen, setSidebarOpen] = useState(false);
|
|
6
|
+
const [activeItem, setActiveItem] = useState(menuItems[0]?.href || "");
|
|
7
|
+
const toggleSidebar = () => {
|
|
8
|
+
setSidebarOpen(!sidebarOpen);
|
|
9
|
+
};
|
|
10
|
+
const handleMenuClick = (href) => {
|
|
11
|
+
setActiveItem(href);
|
|
12
|
+
setSidebarOpen(false); // Cerrar sidebar en móvil
|
|
13
|
+
};
|
|
14
|
+
return (_jsxs("div", { className: `min-h-screen bg-gray-50 ${className}`, children: [sidebarOpen && (_jsx("div", { className: "fixed inset-0 z-40 bg-gray-600 bg-opacity-75 lg:hidden", onClick: () => setSidebarOpen(false) })), _jsxs("div", { className: `
|
|
15
|
+
fixed inset-y-0 left-0 z-50 w-64 bg-white shadow-lg transform transition-transform duration-300 ease-in-out
|
|
16
|
+
${sidebarOpen ? "translate-x-0" : "-translate-x-full"}
|
|
17
|
+
lg:translate-x-0 lg:static lg:inset-0
|
|
18
|
+
`, children: [_jsxs("div", { className: "flex items-center justify-between h-16 px-6 border-b", children: [_jsx("h1", { className: "text-xl font-bold text-gray-900", children: title }), _jsx("button", { onClick: toggleSidebar, className: "lg:hidden p-2 rounded-md text-gray-400 hover:text-gray-600", children: _jsx("i", { className: "fa fa-times" }) })] }), _jsx("nav", { className: "mt-6 px-3", children: _jsx("div", { className: "space-y-1", children: menuItems.map((item) => (_jsxs("a", { href: item.href, onClick: (e) => {
|
|
19
|
+
e.preventDefault();
|
|
20
|
+
handleMenuClick(item.href);
|
|
21
|
+
}, className: `
|
|
22
|
+
group flex items-center px-3 py-2 text-sm font-medium rounded-md transition-colors
|
|
23
|
+
${activeItem === item.href
|
|
24
|
+
? "bg-blue-100 text-blue-700"
|
|
25
|
+
: "text-gray-600 hover:bg-gray-50 hover:text-gray-900"}
|
|
26
|
+
`, children: [_jsx("i", { className: `fa ${item.icon} mr-3 flex-shrink-0` }), _jsx("span", { className: "flex-1", children: item.label }), item.badge && (_jsx(Badge, { variant: "primary", size: "sm", className: "ml-2", children: item.badge }))] }, item.href))) }) }), _jsx("div", { className: "absolute bottom-0 left-0 right-0 p-4 border-t", children: _jsxs("div", { className: "flex items-center", children: [_jsx("div", { className: "flex-shrink-0", children: _jsx("div", { className: "w-8 h-8 bg-gray-300 rounded-full flex items-center justify-center", children: _jsx("i", { className: `fa ${user.avatar || "fa-user"} text-gray-600` }) }) }), _jsxs("div", { className: "ml-3 flex-1", children: [_jsx("p", { className: "text-sm font-medium text-gray-900", children: user.name }), user.email && (_jsx("p", { className: "text-xs text-gray-500", children: user.email }))] }), onLogout && (_jsx("button", { onClick: onLogout, className: "ml-2 p-1 text-gray-400 hover:text-gray-600", title: "Cerrar sesi\u00F3n", children: _jsx("i", { className: "fa fa-sign-out-alt" }) }))] }) })] }), _jsxs("div", { className: "lg:pl-64", children: [_jsx("div", { className: "sticky top-0 z-10 bg-white shadow-sm border-b", children: _jsxs("div", { className: "flex items-center justify-between h-16 px-4 sm:px-6 lg:px-8", children: [_jsx("button", { onClick: toggleSidebar, className: "lg:hidden p-2 rounded-md text-gray-400 hover:text-gray-600", children: _jsx("i", { className: "fa fa-bars" }) }), _jsxs("div", { className: "flex items-center space-x-4", children: [_jsx(Button, { variant: "ghost", size: "sm", icon: "fa-bell", children: "Notificaciones" }), _jsx(Button, { variant: "ghost", size: "sm", icon: "fa-cog", children: "Configuraci\u00F3n" })] })] }) }), _jsx("main", { className: "p-4 sm:p-6 lg:p-8", children: children })] })] }));
|
|
27
|
+
};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
/**
|
|
3
|
+
* Template de Patrón de Formulario Reutilizable
|
|
4
|
+
*
|
|
5
|
+
* Este patrón proporciona una estructura base para cualquier formulario
|
|
6
|
+
* con validación, estados de carga y manejo de errores.
|
|
7
|
+
*
|
|
8
|
+
* Ejemplo de uso:
|
|
9
|
+
* ```tsx
|
|
10
|
+
* import { FormPattern } from "flysoft-react-ui/templates/patterns/FormPattern";
|
|
11
|
+
*
|
|
12
|
+
* function MyForm() {
|
|
13
|
+
* const fields = [
|
|
14
|
+
* { name: "name", label: "Nombre", type: "text", icon: "fa-user", required: true },
|
|
15
|
+
* { name: "email", label: "Email", type: "email", icon: "fa-envelope", required: true },
|
|
16
|
+
* ];
|
|
17
|
+
*
|
|
18
|
+
* return (
|
|
19
|
+
* <FormPattern
|
|
20
|
+
* title="Mi Formulario"
|
|
21
|
+
* fields={fields}
|
|
22
|
+
* onSubmit={handleSubmit}
|
|
23
|
+
* submitText="Guardar"
|
|
24
|
+
* />
|
|
25
|
+
* );
|
|
26
|
+
* }
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
export interface FormField {
|
|
30
|
+
name: string;
|
|
31
|
+
label: string;
|
|
32
|
+
type?: string;
|
|
33
|
+
placeholder?: string;
|
|
34
|
+
icon?: string;
|
|
35
|
+
required?: boolean;
|
|
36
|
+
validation?: (value: string) => string | undefined;
|
|
37
|
+
multiline?: boolean;
|
|
38
|
+
rows?: number;
|
|
39
|
+
}
|
|
40
|
+
export interface FormPatternProps {
|
|
41
|
+
title: string;
|
|
42
|
+
subtitle?: string;
|
|
43
|
+
fields: FormField[];
|
|
44
|
+
onSubmit: (data: Record<string, string>) => void;
|
|
45
|
+
submitText?: string;
|
|
46
|
+
submitIcon?: string;
|
|
47
|
+
loading?: boolean;
|
|
48
|
+
error?: string;
|
|
49
|
+
success?: boolean;
|
|
50
|
+
className?: string;
|
|
51
|
+
gridCols?: 1 | 2;
|
|
52
|
+
}
|
|
53
|
+
export declare const FormPattern: React.FC<FormPatternProps>;
|
|
54
|
+
//# sourceMappingURL=FormPattern.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FormPattern.d.ts","sourceRoot":"","sources":["../../../src/templates/patterns/FormPattern.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAmB,MAAM,OAAO,CAAC;AAGxC;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAC;IACnD,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,SAAS,EAAE,CAAC;IACpB,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KAAK,IAAI,CAAC;IACjD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC;CAClB;AAED,eAAO,MAAM,WAAW,EAAE,KAAK,CAAC,EAAE,CAAC,gBAAgB,CAkLlD,CAAC"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import React, { useState } from "react";
|
|
3
|
+
import { Button, Input, Card } from "../../index";
|
|
4
|
+
export const FormPattern = ({ title, subtitle, fields, onSubmit, submitText = "Enviar", submitIcon = "fa-paper-plane", loading = false, error, success = false, className = "", gridCols = 1, }) => {
|
|
5
|
+
const [formData, setFormData] = useState(fields.reduce((acc, field) => ({ ...acc, [field.name]: "" }), {}));
|
|
6
|
+
const [errors, setErrors] = useState({});
|
|
7
|
+
const handleSubmit = (e) => {
|
|
8
|
+
e.preventDefault();
|
|
9
|
+
// Validación
|
|
10
|
+
const newErrors = {};
|
|
11
|
+
fields.forEach((field) => {
|
|
12
|
+
const value = formData[field.name] || "";
|
|
13
|
+
if (field.required && !value.trim()) {
|
|
14
|
+
newErrors[field.name] = `${field.label} es requerido`;
|
|
15
|
+
}
|
|
16
|
+
else if (field.validation) {
|
|
17
|
+
const validationError = field.validation(value);
|
|
18
|
+
if (validationError) {
|
|
19
|
+
newErrors[field.name] = validationError;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
setErrors(newErrors);
|
|
24
|
+
if (Object.keys(newErrors).length === 0) {
|
|
25
|
+
onSubmit(formData);
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
const handleChange = (fieldName) => (e) => {
|
|
29
|
+
setFormData((prev) => ({ ...prev, [fieldName]: e.target.value }));
|
|
30
|
+
// Limpiar error cuando el usuario empiece a escribir
|
|
31
|
+
if (errors[fieldName]) {
|
|
32
|
+
setErrors((prev) => {
|
|
33
|
+
const newErrors = { ...prev };
|
|
34
|
+
delete newErrors[fieldName];
|
|
35
|
+
return newErrors;
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
const renderField = (field) => {
|
|
40
|
+
const commonProps = {
|
|
41
|
+
label: field.label,
|
|
42
|
+
placeholder: field.placeholder,
|
|
43
|
+
icon: field.icon,
|
|
44
|
+
value: formData[field.name] || "",
|
|
45
|
+
onChange: handleChange(field.name),
|
|
46
|
+
error: errors[field.name],
|
|
47
|
+
disabled: loading,
|
|
48
|
+
};
|
|
49
|
+
if (field.multiline) {
|
|
50
|
+
return (_jsxs("div", { className: "w-full", children: [_jsxs("label", { className: "block text-sm font-medium text-[var(--color-text-primary)] mb-1 font-[var(--font-default)]", children: [field.label, field.required && _jsx("span", { className: "text-red-500 ml-1", children: "*" })] }), _jsxs("div", { className: "relative", children: [field.icon && (_jsx("i", { className: `fa ${field.icon} text-[var(--color-text-muted)] absolute top-3 left-3 w-5 h-5` })), _jsx("textarea", { placeholder: field.placeholder, className: `
|
|
51
|
+
w-full border rounded-lg transition-colors focus:outline-none focus:ring-2
|
|
52
|
+
disabled:opacity-50 disabled:cursor-not-allowed
|
|
53
|
+
font-[var(--font-default)] text-[var(--color-text-primary)]
|
|
54
|
+
bg-[var(--color-bg-default)] px-4 py-3 text-base
|
|
55
|
+
${field.icon ? "pl-10" : "pl-4"}
|
|
56
|
+
${errors[field.name]
|
|
57
|
+
? "border-[var(--color-border-error)] focus:border-[var(--color-border-error)] focus:ring-[var(--color-border-error)]"
|
|
58
|
+
: "border-[var(--color-border-default)] focus:border-[var(--color-border-focus)] focus:ring-[var(--color-border-focus)]"}
|
|
59
|
+
`, rows: field.rows || 4, value: formData[field.name] || "", onChange: handleChange(field.name), disabled: loading })] }), errors[field.name] && (_jsx("p", { className: "mt-1 text-sm text-[var(--color-danger)] font-[var(--font-default)]", children: errors[field.name] }))] }, field.name));
|
|
60
|
+
}
|
|
61
|
+
return (_jsx(Input, { type: field.type || "text", ...commonProps }, field.name));
|
|
62
|
+
};
|
|
63
|
+
if (success) {
|
|
64
|
+
return (_jsx(Card, { title: "\u00A1\u00C9xito!", subtitle: "La operaci\u00F3n se complet\u00F3 correctamente", className: className, children: _jsxs("div", { className: "text-center py-8", children: [_jsx("div", { className: "w-16 h-16 bg-green-100 rounded-full flex items-center justify-center mx-auto mb-4", children: _jsx("i", { className: "fa fa-check text-green-600 text-2xl" }) }), _jsx("h3", { className: "text-lg font-semibold text-gray-900 mb-2", children: "\u00A1Operaci\u00F3n exitosa!" }), _jsx("p", { className: "text-gray-600 mb-4", children: "Los datos se han procesado correctamente." }), _jsx(Button, { variant: "outline", onClick: () => window.location.reload(), icon: "fa-refresh", children: "Continuar" })] }) }));
|
|
65
|
+
}
|
|
66
|
+
return (_jsx(Card, { title: title, subtitle: subtitle, className: className, children: _jsxs("form", { onSubmit: handleSubmit, className: "space-y-4", children: [error && (_jsx("div", { className: "p-3 bg-red-50 border border-red-200 rounded-lg", children: _jsxs("p", { className: "text-sm text-red-600", children: [_jsx("i", { className: "fa fa-exclamation-triangle mr-2" }), error] }) })), _jsx("div", { className: `grid grid-cols-1 ${gridCols === 2 ? "md:grid-cols-2" : ""} gap-4`, children: fields.map(renderField) }), _jsx(Button, { type: "submit", variant: "primary", size: "lg", icon: submitIcon, loading: loading, className: "w-full", children: submitText })] }) }));
|
|
67
|
+
};
|
package/package.json
CHANGED
|
@@ -1,15 +1,27 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "flysoft-react-ui",
|
|
3
3
|
"private": false,
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.10",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"description": "A modern React UI component library with Tailwind CSS",
|
|
6
|
+
"description": "A modern React UI component library with Tailwind CSS, TypeScript, and FontAwesome 5. Includes forms, layouts, themes, and templates for rapid development.",
|
|
7
7
|
"keywords": [
|
|
8
8
|
"react",
|
|
9
9
|
"ui",
|
|
10
10
|
"components",
|
|
11
11
|
"tailwind",
|
|
12
|
-
"typescript"
|
|
12
|
+
"typescript",
|
|
13
|
+
"forms",
|
|
14
|
+
"login",
|
|
15
|
+
"registration",
|
|
16
|
+
"contact-forms",
|
|
17
|
+
"dashboard",
|
|
18
|
+
"layout",
|
|
19
|
+
"themes",
|
|
20
|
+
"fontawesome",
|
|
21
|
+
"accessibility",
|
|
22
|
+
"responsive",
|
|
23
|
+
"modern",
|
|
24
|
+
"design-system"
|
|
13
25
|
],
|
|
14
26
|
"author": "Flysoft",
|
|
15
27
|
"license": "MIT",
|
|
@@ -40,7 +52,11 @@
|
|
|
40
52
|
"prepublishOnly": "npm run build",
|
|
41
53
|
"publish:patch": "npm version patch && npm publish",
|
|
42
54
|
"publish:minor": "npm version minor && npm publish",
|
|
43
|
-
"publish:major": "npm version major && npm publish"
|
|
55
|
+
"publish:major": "npm version major && npm publish",
|
|
56
|
+
"update-docs": "node scripts/update-docs.js",
|
|
57
|
+
"validate-docs": "node scripts/validate-docs.js",
|
|
58
|
+
"docs:update": "npm run update-docs",
|
|
59
|
+
"docs:validate": "npm run validate-docs"
|
|
44
60
|
},
|
|
45
61
|
"peerDependencies": {
|
|
46
62
|
"react": "^18.0.0 || ^19.0.0",
|