responsive-system 1.4.5 → 1.4.7
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/responsive-system.cjs +3 -3
- package/dist/responsive-system.cjs.map +1 -1
- package/dist/responsive-system.mjs +121 -117
- package/dist/responsive-system.mjs.map +1 -1
- package/package.json +1 -1
- package/scripts/postinstall.js +786 -786
- package/src/providers/ResponsiveLayoutProvider.tsx +7 -2
package/scripts/postinstall.js
CHANGED
|
@@ -1,786 +1,786 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Script postinstall para automatizar la configuración inicial completa
|
|
5
|
-
* - Instala React, TypeScript, Tailwind automáticamente
|
|
6
|
-
* - Inicializa proyecto Vite si está vacío
|
|
7
|
-
* - Pregunta qué layout quiere (interactivo)
|
|
8
|
-
* - Copia solo los componentes necesarios
|
|
9
|
-
* - Copia el hook useResponsive como archivo local
|
|
10
|
-
* - Crea página de ejemplo en pages/
|
|
11
|
-
* - Configura App.tsx con el layout seleccionado
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
import fs from 'fs'
|
|
15
|
-
import path from 'path'
|
|
16
|
-
import { execSync } from 'child_process'
|
|
17
|
-
import { fileURLToPath } from 'url'
|
|
18
|
-
import readline from 'readline'
|
|
19
|
-
|
|
20
|
-
const __filename = fileURLToPath(import.meta.url)
|
|
21
|
-
const __dirname = path.dirname(__filename)
|
|
22
|
-
|
|
23
|
-
const projectRoot = process.cwd()
|
|
24
|
-
const packageJsonPath = path.join(projectRoot, 'package.json')
|
|
25
|
-
|
|
26
|
-
// Detectar si se ejecuta como postinstall o manualmente
|
|
27
|
-
const isPostinstall = process.env.npm_lifecycle_event === 'postinstall'
|
|
28
|
-
const isManual = process.argv[1].includes('postinstall.js') && !isPostinstall
|
|
29
|
-
|
|
30
|
-
console.log('')
|
|
31
|
-
console.log('📦 responsive-system: Iniciando configuración...')
|
|
32
|
-
console.log(` Directorio: ${projectRoot}`)
|
|
33
|
-
console.log('')
|
|
34
|
-
|
|
35
|
-
// Verificar si package.json existe
|
|
36
|
-
if (!fs.existsSync(packageJsonPath)) {
|
|
37
|
-
console.log('⚠️ No se encontró package.json, saltando configuración')
|
|
38
|
-
process.exit(0)
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
let packageJson
|
|
42
|
-
try {
|
|
43
|
-
packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'))
|
|
44
|
-
} catch (error) {
|
|
45
|
-
console.error('❌ Error al leer package.json:', error.message)
|
|
46
|
-
process.exit(1)
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// Verificar si el proyecto está vacío (solo tiene responsive-system)
|
|
50
|
-
const isProjectEmpty = !packageJson.dependencies ||
|
|
51
|
-
Object.keys(packageJson.dependencies).length === 0 ||
|
|
52
|
-
(Object.keys(packageJson.dependencies).length === 1 && packageJson.dependencies['responsive-system'])
|
|
53
|
-
|
|
54
|
-
// Verificar qué está instalado - SOLO en package.json (no en node_modules para evitar conflictos)
|
|
55
|
-
const hasReactInPackageJson = (packageJson.dependencies && packageJson.dependencies.react) ||
|
|
56
|
-
(packageJson.devDependencies && packageJson.devDependencies.react)
|
|
57
|
-
const hasVite = packageJson.devDependencies && packageJson.devDependencies.vite
|
|
58
|
-
const tailwindInDevDeps = packageJson.devDependencies && packageJson.devDependencies.tailwindcss
|
|
59
|
-
const typescriptInDevDeps = packageJson.devDependencies && packageJson.devDependencies.typescript
|
|
60
|
-
|
|
61
|
-
let needsUpdate = false
|
|
62
|
-
|
|
63
|
-
// Función para preguntar al usuario qué layout quiere
|
|
64
|
-
async function askLayout() {
|
|
65
|
-
if (isPostinstall && !isManual) {
|
|
66
|
-
console.log(' ℹ️ Usando layout "default" por defecto')
|
|
67
|
-
console.log(' 💡 Ejecuta "npx responsive-system-setup" para cambiar el layout')
|
|
68
|
-
return 'default'
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
// Si es ejecución manual, preguntar interactivamente
|
|
72
|
-
const rl = readline.createInterface({
|
|
73
|
-
input: process.stdin,
|
|
74
|
-
output: process.stdout
|
|
75
|
-
})
|
|
76
|
-
|
|
77
|
-
return new Promise((resolve) => {
|
|
78
|
-
console.log('')
|
|
79
|
-
console.log('🎨 Selecciona el layout que quieres usar:')
|
|
80
|
-
console.log(' 1. default - Navigation + Footer')
|
|
81
|
-
console.log(' 2. sidebar - Sidebar lateral')
|
|
82
|
-
console.log(' 3. dashboard - Sidebar + Footer')
|
|
83
|
-
console.log(' 4. minimal - Sin componentes (solo contenido)')
|
|
84
|
-
console.log('')
|
|
85
|
-
|
|
86
|
-
rl.question(' Ingresa el número (1-4) o el nombre del layout: ', (answer) => {
|
|
87
|
-
rl.close()
|
|
88
|
-
|
|
89
|
-
const normalized = answer.trim().toLowerCase()
|
|
90
|
-
|
|
91
|
-
if (normalized === '1' || normalized === 'default') {
|
|
92
|
-
resolve('default')
|
|
93
|
-
} else if (normalized === '2' || normalized === 'sidebar') {
|
|
94
|
-
resolve('sidebar')
|
|
95
|
-
} else if (normalized === '3' || normalized === 'dashboard') {
|
|
96
|
-
resolve('dashboard')
|
|
97
|
-
} else if (normalized === '4' || normalized === 'minimal') {
|
|
98
|
-
resolve('minimal')
|
|
99
|
-
} else {
|
|
100
|
-
console.log(' ⚠️ Opción inválida, usando "default"')
|
|
101
|
-
resolve('default')
|
|
102
|
-
}
|
|
103
|
-
})
|
|
104
|
-
})
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
// Función para copiar archivo desde el paquete al proyecto
|
|
108
|
-
function copyFileFromPackage(relativePath, targetPath, isComponent = false) {
|
|
109
|
-
const sourcePath = path.join(__dirname, '..', relativePath)
|
|
110
|
-
const destPath = path.join(projectRoot, targetPath)
|
|
111
|
-
|
|
112
|
-
if (!fs.existsSync(sourcePath)) {
|
|
113
|
-
console.error(` ❌ No se encontró: ${relativePath}`)
|
|
114
|
-
return false
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
// Crear directorio destino si no existe
|
|
118
|
-
const destDir = path.dirname(destPath)
|
|
119
|
-
if (!fs.existsSync(destDir)) {
|
|
120
|
-
fs.mkdirSync(destDir, { recursive: true })
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
let content = fs.readFileSync(sourcePath, 'utf8')
|
|
124
|
-
|
|
125
|
-
// Si es un componente, reemplazar importaciones relativas por importaciones del paquete
|
|
126
|
-
if (isComponent) {
|
|
127
|
-
// Reemplazar importaciones de hooks
|
|
128
|
-
content = content.replace(
|
|
129
|
-
/from ['"]\.\.\/\.\.\/hooks['"]/g,
|
|
130
|
-
"from 'responsive-system'"
|
|
131
|
-
)
|
|
132
|
-
// Reemplazar importaciones de context
|
|
133
|
-
content = content.replace(
|
|
134
|
-
/from ['"]\.\.\/\.\.\/context['"]/g,
|
|
135
|
-
"from 'responsive-system'"
|
|
136
|
-
)
|
|
137
|
-
// Reemplazar importaciones de componentes/layout
|
|
138
|
-
content = content.replace(
|
|
139
|
-
/from ['"]\.\.\/components\/layout['"]/g,
|
|
140
|
-
"from 'responsive-system'"
|
|
141
|
-
)
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
fs.writeFileSync(destPath, content)
|
|
145
|
-
return true
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
// Función para generar componentes genéricos según el layout seleccionado
|
|
149
|
-
function generateLayoutComponents(selectedLayout) {
|
|
150
|
-
const componentsDir = path.join(projectRoot, 'src', 'components', 'layout')
|
|
151
|
-
|
|
152
|
-
// Crear directorio si no existe
|
|
153
|
-
if (!fs.existsSync(componentsDir)) {
|
|
154
|
-
fs.mkdirSync(componentsDir, { recursive: true })
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
const componentsToGenerate = {
|
|
158
|
-
default: ['Navigation', 'Footer'],
|
|
159
|
-
sidebar: ['Sidebar'],
|
|
160
|
-
dashboard: ['Sidebar', 'Footer'],
|
|
161
|
-
minimal: []
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
const components = componentsToGenerate[selectedLayout] || []
|
|
165
|
-
|
|
166
|
-
if (components.length === 0) {
|
|
167
|
-
console.log(' ✅ Layout minimal: No se generan componentes')
|
|
168
|
-
return
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
console.log(` 📦 Generando componentes para layout "${selectedLayout}":`)
|
|
172
|
-
|
|
173
|
-
// Generar Navigation genérico
|
|
174
|
-
if (components.includes('Navigation')) {
|
|
175
|
-
const navigationContent = `import { useResponsiveLayout } from 'responsive-system'
|
|
176
|
-
|
|
177
|
-
const Navigation = () => {
|
|
178
|
-
const { isMobile } = useResponsiveLayout()
|
|
179
|
-
|
|
180
|
-
return (
|
|
181
|
-
<nav className="sticky top-0 z-50 bg-gray-900 border-b border-gray-800">
|
|
182
|
-
<div className="px-4 py-4">
|
|
183
|
-
<div className="flex items-center justify-between">
|
|
184
|
-
<div className="flex items-center space-x-3">
|
|
185
|
-
<div className="w-8 h-8 bg-gray-700 rounded-lg flex items-center justify-center">
|
|
186
|
-
<span className="text-white font-bold text-sm">LO</span>
|
|
187
|
-
</div>
|
|
188
|
-
<h1 className="text-white font-semibold text-lg">Tu Aplicación</h1>
|
|
189
|
-
</div>
|
|
190
|
-
|
|
191
|
-
{isMobile && (
|
|
192
|
-
<button className="p-2 text-gray-400 hover:text-white">
|
|
193
|
-
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
194
|
-
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 6h16M4 12h16M4 18h16" />
|
|
195
|
-
</svg>
|
|
196
|
-
</button>
|
|
197
|
-
)}
|
|
198
|
-
</div>
|
|
199
|
-
</div>
|
|
200
|
-
</nav>
|
|
201
|
-
)
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
export default Navigation
|
|
205
|
-
`
|
|
206
|
-
fs.writeFileSync(path.join(componentsDir, 'Navigation.tsx'), navigationContent)
|
|
207
|
-
console.log(' ✅ Navigation.tsx')
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
// Generar Footer genérico
|
|
211
|
-
if (components.includes('Footer')) {
|
|
212
|
-
const footerContent = `const Footer = () => {
|
|
213
|
-
return (
|
|
214
|
-
<footer className="bg-gray-900 border-t border-gray-800">
|
|
215
|
-
<div className="px-4 py-6">
|
|
216
|
-
<div className="max-w-7xl mx-auto text-center">
|
|
217
|
-
<p className="text-gray-400 text-sm">
|
|
218
|
-
© {new Date().getFullYear()} Tu Aplicación. Todos los derechos reservados.
|
|
219
|
-
</p>
|
|
220
|
-
</div>
|
|
221
|
-
</div>
|
|
222
|
-
</footer>
|
|
223
|
-
)
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
export default Footer
|
|
227
|
-
`
|
|
228
|
-
fs.writeFileSync(path.join(componentsDir, 'Footer.tsx'), footerContent)
|
|
229
|
-
console.log(' ✅ Footer.tsx')
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
// Generar Sidebar genérico
|
|
233
|
-
if (components.includes('Sidebar')) {
|
|
234
|
-
const sidebarContent = `import { useResponsiveLayout } from 'responsive-system'
|
|
235
|
-
import { useSidebar } from 'responsive-system'
|
|
236
|
-
|
|
237
|
-
const Sidebar = () => {
|
|
238
|
-
const { isMobile, isTablet } = useResponsiveLayout()
|
|
239
|
-
const { sidebarOpen, setSidebarOpen } = useSidebar()
|
|
240
|
-
|
|
241
|
-
const menuItems = [
|
|
242
|
-
{ id: 'home', label: 'Inicio' },
|
|
243
|
-
{ id: 'about', label: 'Acerca' },
|
|
244
|
-
{ id: 'contact', label: 'Contacto' },
|
|
245
|
-
]
|
|
246
|
-
|
|
247
|
-
return (
|
|
248
|
-
<>
|
|
249
|
-
{/* Hamburger button para móvil */}
|
|
250
|
-
{isMobile && (
|
|
251
|
-
<button
|
|
252
|
-
onClick={() => setSidebarOpen(true)}
|
|
253
|
-
className="fixed top-4 left-4 z-50 p-2 rounded-lg text-gray-300 hover:text-white hover:bg-gray-800 bg-gray-900 border border-gray-700"
|
|
254
|
-
>
|
|
255
|
-
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
256
|
-
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 6h16M4 12h16M4 18h16" />
|
|
257
|
-
</svg>
|
|
258
|
-
</button>
|
|
259
|
-
)}
|
|
260
|
-
|
|
261
|
-
{/* Sidebar desktop */}
|
|
262
|
-
<aside className={\`bg-gray-900 border-r border-gray-800 \${isMobile ? 'hidden' : 'w-64 flex-shrink-0'} \${isTablet ? 'w-56' : 'w-64'}\`}>
|
|
263
|
-
<div className="p-6 flex flex-col h-full">
|
|
264
|
-
<div className="flex items-center space-x-3 mb-8">
|
|
265
|
-
<div className="w-8 h-8 bg-gray-700 rounded-lg flex items-center justify-center">
|
|
266
|
-
<span className="text-white font-bold text-sm">LO</span>
|
|
267
|
-
</div>
|
|
268
|
-
<span className="text-white font-bold text-lg">Tu Aplicación</span>
|
|
269
|
-
</div>
|
|
270
|
-
|
|
271
|
-
<nav className="space-y-2">
|
|
272
|
-
{menuItems.map((item) => (
|
|
273
|
-
<button
|
|
274
|
-
key={item.id}
|
|
275
|
-
className="w-full flex items-center px-4 py-3 rounded-lg transition-all text-left text-gray-300 hover:text-white hover:bg-gray-800"
|
|
276
|
-
>
|
|
277
|
-
<span className="font-medium">{item.label}</span>
|
|
278
|
-
</button>
|
|
279
|
-
))}
|
|
280
|
-
</nav>
|
|
281
|
-
</div>
|
|
282
|
-
</aside>
|
|
283
|
-
|
|
284
|
-
{/* Sidebar móvil desplegable */}
|
|
285
|
-
{isMobile && sidebarOpen && (
|
|
286
|
-
<div className="fixed inset-0 z-40 bg-black/50" onClick={() => setSidebarOpen(false)}>
|
|
287
|
-
<div className="fixed top-0 left-0 w-64 h-full bg-gray-900 border-r border-gray-800">
|
|
288
|
-
<div className="p-6 flex flex-col h-full pt-20">
|
|
289
|
-
<div className="flex items-center space-x-3 mb-8">
|
|
290
|
-
<div className="w-8 h-8 bg-gray-700 rounded-lg flex items-center justify-center">
|
|
291
|
-
<span className="text-white font-bold text-sm">LO</span>
|
|
292
|
-
</div>
|
|
293
|
-
<span className="text-white font-bold text-lg">Tu Aplicación</span>
|
|
294
|
-
</div>
|
|
295
|
-
|
|
296
|
-
<nav className="space-y-2">
|
|
297
|
-
{menuItems.map((item) => (
|
|
298
|
-
<button
|
|
299
|
-
key={item.id}
|
|
300
|
-
onClick={() => setSidebarOpen(false)}
|
|
301
|
-
className="w-full flex items-center px-4 py-3 rounded-lg transition-all text-left text-gray-300 hover:text-white hover:bg-gray-800"
|
|
302
|
-
>
|
|
303
|
-
<span className="font-medium">{item.label}</span>
|
|
304
|
-
</button>
|
|
305
|
-
))}
|
|
306
|
-
</nav>
|
|
307
|
-
</div>
|
|
308
|
-
</div>
|
|
309
|
-
</div>
|
|
310
|
-
)}
|
|
311
|
-
</>
|
|
312
|
-
)
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
export default Sidebar
|
|
316
|
-
`
|
|
317
|
-
fs.writeFileSync(path.join(componentsDir, 'Sidebar.tsx'), sidebarContent)
|
|
318
|
-
console.log(' ✅ Sidebar.tsx')
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
// Crear index.ts para exportar los componentes
|
|
322
|
-
const indexContent = components.map(c => `export { default as ${c} } from './${c}'`).join('\n')
|
|
323
|
-
fs.writeFileSync(path.join(componentsDir, 'index.ts'), indexContent)
|
|
324
|
-
console.log(' ✅ index.ts')
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
// Función para copiar el hook useResponsive y sus dependencias
|
|
328
|
-
function copyUseResponsiveHook() {
|
|
329
|
-
console.log(' 📦 Copiando hook useResponsive...')
|
|
330
|
-
|
|
331
|
-
// Crear directorio hooks
|
|
332
|
-
const hooksDir = path.join(projectRoot, 'src', 'hooks')
|
|
333
|
-
if (!fs.existsSync(hooksDir)) {
|
|
334
|
-
fs.mkdirSync(hooksDir, { recursive: true })
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
// Copiar tipos
|
|
338
|
-
const typesDir = path.join(projectRoot, 'src', 'types')
|
|
339
|
-
if (!fs.existsSync(typesDir)) {
|
|
340
|
-
fs.mkdirSync(typesDir, { recursive: true })
|
|
341
|
-
}
|
|
342
|
-
copyFileFromPackage('src/types/responsive.ts', 'src/types/responsive.ts')
|
|
343
|
-
console.log(' ✅ types/responsive.ts')
|
|
344
|
-
|
|
345
|
-
// Copiar constantes
|
|
346
|
-
const constantsDir = path.join(projectRoot, 'src', 'constants')
|
|
347
|
-
if (!fs.existsSync(constantsDir)) {
|
|
348
|
-
fs.mkdirSync(constantsDir, { recursive: true })
|
|
349
|
-
}
|
|
350
|
-
copyFileFromPackage('src/constants/breakpoints.ts', 'src/constants/breakpoints.ts')
|
|
351
|
-
console.log(' ✅ constants/breakpoints.ts')
|
|
352
|
-
|
|
353
|
-
// Copiar hook useResponsive
|
|
354
|
-
copyFileFromPackage('src/hooks/useResponsive.ts', 'src/hooks/useResponsive.ts')
|
|
355
|
-
console.log(' ✅ hooks/useResponsive.ts')
|
|
356
|
-
|
|
357
|
-
// Crear index.ts para exportar el hook
|
|
358
|
-
const indexContent = `export { useResponsive } from './useResponsive'
|
|
359
|
-
export type { ResponsiveState, Breakpoint, Orientation } from '../types/responsive'
|
|
360
|
-
export { DEFAULT_BREAKPOINTS, getCurrentBreakpoint, getBreakpointIndex, getBreakpointValue } from '../constants/breakpoints'
|
|
361
|
-
`
|
|
362
|
-
fs.writeFileSync(path.join(hooksDir, 'index.ts'), indexContent)
|
|
363
|
-
console.log(' ✅ hooks/index.ts')
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
console.log('📦 Verificando dependencias...')
|
|
367
|
-
|
|
368
|
-
// Agregar React a dependencies SOLO si NO está en package.json
|
|
369
|
-
if (!hasReactInPackageJson) {
|
|
370
|
-
console.log(' ➕ Agregando React a dependencies...')
|
|
371
|
-
if (!packageJson.dependencies) {
|
|
372
|
-
packageJson.dependencies = {}
|
|
373
|
-
}
|
|
374
|
-
packageJson.dependencies['react'] = '^19.1.1'
|
|
375
|
-
packageJson.dependencies['react-dom'] = '^19.1.1'
|
|
376
|
-
needsUpdate = true
|
|
377
|
-
} else {
|
|
378
|
-
console.log(' ✅ React ya está instalado')
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
// Agregar Vite si el proyecto está vacío
|
|
382
|
-
if (isProjectEmpty && !hasVite) {
|
|
383
|
-
console.log(' ➕ Agregando Vite a devDependencies...')
|
|
384
|
-
if (!packageJson.devDependencies) {
|
|
385
|
-
packageJson.devDependencies = {}
|
|
386
|
-
}
|
|
387
|
-
packageJson.devDependencies['vite'] = '^7.1.7'
|
|
388
|
-
packageJson.devDependencies['@vitejs/plugin-react'] = '^5.0.4'
|
|
389
|
-
needsUpdate = true
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
// Agregar Tailwind y sus dependencias a devDependencies
|
|
393
|
-
if (!tailwindInDevDeps) {
|
|
394
|
-
console.log(' ➕ Agregando Tailwind y PostCSS a devDependencies...')
|
|
395
|
-
if (!packageJson.devDependencies) {
|
|
396
|
-
packageJson.devDependencies = {}
|
|
397
|
-
}
|
|
398
|
-
packageJson.devDependencies['tailwindcss'] = '^4.1.14'
|
|
399
|
-
packageJson.devDependencies['@tailwindcss/postcss'] = '^4.1.14'
|
|
400
|
-
packageJson.devDependencies['postcss'] = '^8.5.6'
|
|
401
|
-
packageJson.devDependencies['autoprefixer'] = '^10.4.21'
|
|
402
|
-
needsUpdate = true
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
// Agregar TypeScript y sus tipos a devDependencies
|
|
406
|
-
if (!typescriptInDevDeps) {
|
|
407
|
-
console.log(' ➕ Agregando TypeScript a devDependencies...')
|
|
408
|
-
if (!packageJson.devDependencies) {
|
|
409
|
-
packageJson.devDependencies = {}
|
|
410
|
-
}
|
|
411
|
-
packageJson.devDependencies['typescript'] = '~5.9.3'
|
|
412
|
-
packageJson.devDependencies['@types/react'] = '^19.1.16'
|
|
413
|
-
packageJson.devDependencies['@types/react-dom'] = '^19.1.9'
|
|
414
|
-
needsUpdate = true
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
// Agregar "type": "module" si no existe (para evitar warnings)
|
|
418
|
-
if (!packageJson.type) {
|
|
419
|
-
packageJson.type = 'module'
|
|
420
|
-
needsUpdate = true
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
// Escribir package.json modificado
|
|
424
|
-
if (needsUpdate) {
|
|
425
|
-
console.log('')
|
|
426
|
-
console.log('📝 Actualizando package.json...')
|
|
427
|
-
fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2))
|
|
428
|
-
console.log('✅ package.json actualizado')
|
|
429
|
-
console.log('')
|
|
430
|
-
|
|
431
|
-
// Ahora instalar las dependencias
|
|
432
|
-
console.log('📦 Instalando dependencias...')
|
|
433
|
-
try {
|
|
434
|
-
execSync('npm install', {
|
|
435
|
-
stdio: 'inherit',
|
|
436
|
-
cwd: projectRoot
|
|
437
|
-
})
|
|
438
|
-
console.log('✅ Dependencias instaladas correctamente')
|
|
439
|
-
} catch (error) {
|
|
440
|
-
console.error('❌ Error al instalar dependencias:', error.message)
|
|
441
|
-
console.log('⚠️ Las dependencias están en package.json, ejecuta "npm install" manualmente')
|
|
442
|
-
}
|
|
443
|
-
} else {
|
|
444
|
-
console.log('✅ Todas las dependencias ya están instaladas')
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
// Si el proyecto está vacío, crear estructura base
|
|
448
|
-
if (isProjectEmpty) {
|
|
449
|
-
console.log('')
|
|
450
|
-
console.log('📦 Proyecto vacío detectado, creando estructura base...')
|
|
451
|
-
console.log('')
|
|
452
|
-
|
|
453
|
-
// Preguntar qué layout quiere
|
|
454
|
-
const selectedLayout = await askLayout()
|
|
455
|
-
console.log(` ✅ Layout seleccionado: "${selectedLayout}"`)
|
|
456
|
-
console.log('')
|
|
457
|
-
|
|
458
|
-
// Crear estructura de directorios
|
|
459
|
-
const dirs = ['src', 'src/components', 'src/components/layout', 'src/pages', 'src/hooks', 'src/types', 'src/constants', 'public']
|
|
460
|
-
dirs.forEach(dir => {
|
|
461
|
-
const dirPath = path.join(projectRoot, dir)
|
|
462
|
-
if (!fs.existsSync(dirPath)) {
|
|
463
|
-
fs.mkdirSync(dirPath, { recursive: true })
|
|
464
|
-
}
|
|
465
|
-
})
|
|
466
|
-
|
|
467
|
-
// Generar componentes genéricos según layout seleccionado
|
|
468
|
-
generateLayoutComponents(selectedLayout)
|
|
469
|
-
console.log('')
|
|
470
|
-
|
|
471
|
-
// Copiar hook useResponsive
|
|
472
|
-
copyUseResponsiveHook()
|
|
473
|
-
console.log('')
|
|
474
|
-
|
|
475
|
-
// Crear vite.config.ts
|
|
476
|
-
const viteConfigPath = path.join(projectRoot, 'vite.config.ts')
|
|
477
|
-
if (!fs.existsSync(viteConfigPath)) {
|
|
478
|
-
const viteConfig = `import { defineConfig } from 'vite'
|
|
479
|
-
import react from '@vitejs/plugin-react'
|
|
480
|
-
|
|
481
|
-
export default defineConfig({
|
|
482
|
-
plugins: [react()],
|
|
483
|
-
})
|
|
484
|
-
`
|
|
485
|
-
fs.writeFileSync(viteConfigPath, viteConfig)
|
|
486
|
-
console.log(' ✅ Creado: vite.config.ts')
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
// Crear tailwind.config.js
|
|
490
|
-
const tailwindConfigPath = path.join(projectRoot, 'tailwind.config.js')
|
|
491
|
-
if (!fs.existsSync(tailwindConfigPath)) {
|
|
492
|
-
const tailwindConfig = `import responsiveScalePlugin from 'responsive-system/plugin'
|
|
493
|
-
|
|
494
|
-
export default {
|
|
495
|
-
content: [
|
|
496
|
-
"./index.html",
|
|
497
|
-
"./src/**/*.{js,ts,jsx,tsx}",
|
|
498
|
-
],
|
|
499
|
-
theme: {
|
|
500
|
-
extend: {
|
|
501
|
-
screens: {
|
|
502
|
-
'xs': '475px', 'sm': '640px', 'md': '768px', 'lg': '1024px',
|
|
503
|
-
'xl': '1280px', '2xl': '1536px', '3xl': '1920px', '4xl': '2560px', '5xl': '3840px'
|
|
504
|
-
}
|
|
505
|
-
},
|
|
506
|
-
},
|
|
507
|
-
plugins: [
|
|
508
|
-
responsiveScalePlugin({
|
|
509
|
-
scaleProperties: {
|
|
510
|
-
typography: true,
|
|
511
|
-
spacing: true,
|
|
512
|
-
lineHeight: true,
|
|
513
|
-
letterSpacing: true,
|
|
514
|
-
shadows: true,
|
|
515
|
-
borderWidth: false,
|
|
516
|
-
sizing: false,
|
|
517
|
-
borderRadius: false
|
|
518
|
-
},
|
|
519
|
-
scales: {
|
|
520
|
-
xs: 1.0, sm: 1.0, md: 1.0, lg: 1.0, xl: 1.0,
|
|
521
|
-
'2xl': 1.05, '3xl': 1.15, '4xl': 1.25, '5xl': 1.35
|
|
522
|
-
}
|
|
523
|
-
})
|
|
524
|
-
],
|
|
525
|
-
}
|
|
526
|
-
`
|
|
527
|
-
fs.writeFileSync(tailwindConfigPath, tailwindConfig)
|
|
528
|
-
console.log(' ✅ Creado: tailwind.config.js')
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
// Crear postcss.config.js
|
|
532
|
-
const postcssConfigPath = path.join(projectRoot, 'postcss.config.js')
|
|
533
|
-
if (!fs.existsSync(postcssConfigPath)) {
|
|
534
|
-
const postcssConfig = `export default {
|
|
535
|
-
plugins: {
|
|
536
|
-
'@tailwindcss/postcss': {},
|
|
537
|
-
autoprefixer: {},
|
|
538
|
-
},
|
|
539
|
-
}
|
|
540
|
-
`
|
|
541
|
-
fs.writeFileSync(postcssConfigPath, postcssConfig)
|
|
542
|
-
console.log(' ✅ Creado: postcss.config.js')
|
|
543
|
-
}
|
|
544
|
-
|
|
545
|
-
// Crear tsconfig.json
|
|
546
|
-
const tsconfigPath = path.join(projectRoot, 'tsconfig.json')
|
|
547
|
-
if (!fs.existsSync(tsconfigPath)) {
|
|
548
|
-
const tsconfig = `{
|
|
549
|
-
"compilerOptions": {
|
|
550
|
-
"target": "ES2020",
|
|
551
|
-
"useDefineForClassFields": true,
|
|
552
|
-
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
|
553
|
-
"module": "ESNext",
|
|
554
|
-
"skipLibCheck": true,
|
|
555
|
-
"moduleResolution": "bundler",
|
|
556
|
-
"allowImportingTsExtensions": true,
|
|
557
|
-
"resolveJsonModule": true,
|
|
558
|
-
"isolatedModules": true,
|
|
559
|
-
"noEmit": true,
|
|
560
|
-
"jsx": "react-jsx",
|
|
561
|
-
"strict": true,
|
|
562
|
-
"noUnusedLocals": true,
|
|
563
|
-
"noUnusedParameters": true,
|
|
564
|
-
"noFallthroughCasesInSwitch": true
|
|
565
|
-
},
|
|
566
|
-
"include": ["src"],
|
|
567
|
-
"references": [{ "path": "./tsconfig.node.json" }]
|
|
568
|
-
}
|
|
569
|
-
`
|
|
570
|
-
fs.writeFileSync(tsconfigPath, tsconfig)
|
|
571
|
-
console.log(' ✅ Creado: tsconfig.json')
|
|
572
|
-
}
|
|
573
|
-
|
|
574
|
-
// Crear tsconfig.node.json
|
|
575
|
-
const tsconfigNodePath = path.join(projectRoot, 'tsconfig.node.json')
|
|
576
|
-
if (!fs.existsSync(tsconfigNodePath)) {
|
|
577
|
-
const tsconfigNode = `{
|
|
578
|
-
"compilerOptions": {
|
|
579
|
-
"composite": true,
|
|
580
|
-
"skipLibCheck": true,
|
|
581
|
-
"module": "ESNext",
|
|
582
|
-
"moduleResolution": "bundler",
|
|
583
|
-
"allowSyntheticDefaultImports": true
|
|
584
|
-
},
|
|
585
|
-
"include": ["vite.config.ts"]
|
|
586
|
-
}
|
|
587
|
-
`
|
|
588
|
-
fs.writeFileSync(tsconfigNodePath, tsconfigNode)
|
|
589
|
-
console.log(' ✅ Creado: tsconfig.node.json')
|
|
590
|
-
}
|
|
591
|
-
|
|
592
|
-
// Crear index.html
|
|
593
|
-
const indexHtmlPath = path.join(projectRoot, 'index.html')
|
|
594
|
-
if (!fs.existsSync(indexHtmlPath)) {
|
|
595
|
-
const indexHtml = `<!doctype html>
|
|
596
|
-
<html lang="es">
|
|
597
|
-
<head>
|
|
598
|
-
<meta charset="UTF-8" />
|
|
599
|
-
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
|
600
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
601
|
-
<title>Responsive System Demo</title>
|
|
602
|
-
</head>
|
|
603
|
-
<body>
|
|
604
|
-
<div id="root"></div>
|
|
605
|
-
<script type="module" src="/src/main.tsx"></script>
|
|
606
|
-
</body>
|
|
607
|
-
</html>
|
|
608
|
-
`
|
|
609
|
-
fs.writeFileSync(indexHtmlPath, indexHtml)
|
|
610
|
-
console.log(' ✅ Creado: index.html')
|
|
611
|
-
}
|
|
612
|
-
|
|
613
|
-
// Crear src/main.tsx
|
|
614
|
-
const mainTsxPath = path.join(projectRoot, 'src', 'main.tsx')
|
|
615
|
-
if (!fs.existsSync(mainTsxPath)) {
|
|
616
|
-
const mainTsx = `import React from 'react'
|
|
617
|
-
import ReactDOM from 'react-dom/client'
|
|
618
|
-
import { ResponsiveLayoutProvider, MainLayout } from 'responsive-system'
|
|
619
|
-
import App from './App'
|
|
620
|
-
import './index.css'
|
|
621
|
-
|
|
622
|
-
ReactDOM.createRoot(document.getElementById('root')!).render(
|
|
623
|
-
<React.StrictMode>
|
|
624
|
-
<ResponsiveLayoutProvider defaultLayout="${selectedLayout}">
|
|
625
|
-
<MainLayout>
|
|
626
|
-
<App />
|
|
627
|
-
</MainLayout>
|
|
628
|
-
</ResponsiveLayoutProvider>
|
|
629
|
-
</React.StrictMode>,
|
|
630
|
-
)
|
|
631
|
-
`
|
|
632
|
-
fs.writeFileSync(mainTsxPath, mainTsx)
|
|
633
|
-
console.log(' ✅ Creado: src/main.tsx')
|
|
634
|
-
}
|
|
635
|
-
|
|
636
|
-
// Crear src/index.css
|
|
637
|
-
const indexCssPath = path.join(projectRoot, 'src', 'index.css')
|
|
638
|
-
if (!fs.existsSync(indexCssPath)) {
|
|
639
|
-
const indexCss = `@import "tailwindcss";
|
|
640
|
-
`
|
|
641
|
-
fs.writeFileSync(indexCssPath, indexCss)
|
|
642
|
-
console.log(' ✅ Creado: src/index.css')
|
|
643
|
-
}
|
|
644
|
-
|
|
645
|
-
// Crear src/pages/HomePage.tsx con página de ejemplo simple
|
|
646
|
-
const homePagePath = path.join(projectRoot, 'src', 'pages', 'HomePage.tsx')
|
|
647
|
-
if (!fs.existsSync(homePagePath)) {
|
|
648
|
-
const homePage = `import { useResponsiveLayout } from 'responsive-system'
|
|
649
|
-
import { useResponsive } from '../hooks'
|
|
650
|
-
|
|
651
|
-
function HomePage() {
|
|
652
|
-
const { breakpoint, isMobile, layout } = useResponsiveLayout()
|
|
653
|
-
const responsive = useResponsive()
|
|
654
|
-
|
|
655
|
-
return (
|
|
656
|
-
<div className="
|
|
657
|
-
<div className="max-w-7xl mx-auto space-y-8">
|
|
658
|
-
{/* Hero Section */}
|
|
659
|
-
<div className="bg-white rounded-lg shadow-md p-8 text-center">
|
|
660
|
-
<h1 className="text-4xl font-bold text-gray-900 mb-4">
|
|
661
|
-
Bienvenido a tu Aplicación
|
|
662
|
-
</h1>
|
|
663
|
-
<p className="text-lg text-gray-600 max-w-2xl mx-auto">
|
|
664
|
-
Esta es una página de ejemplo que demuestra el sistema responsive con auto-scaling.
|
|
665
|
-
Todo el contenido se ajusta automáticamente según el tamaño de pantalla.
|
|
666
|
-
</p>
|
|
667
|
-
</div>
|
|
668
|
-
|
|
669
|
-
{/* Info Cards */}
|
|
670
|
-
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
|
|
671
|
-
<div className="bg-blue-50 rounded-lg p-6 border border-blue-200">
|
|
672
|
-
<h3 className="text-sm font-semibold text-blue-900 mb-2">Breakpoint</h3>
|
|
673
|
-
<p className="text-2xl font-bold text-blue-700">{breakpoint.toUpperCase()}</p>
|
|
674
|
-
</div>
|
|
675
|
-
<div className="bg-green-50 rounded-lg p-6 border border-green-200">
|
|
676
|
-
<h3 className="text-sm font-semibold text-green-900 mb-2">Dispositivo</h3>
|
|
677
|
-
<p className="text-2xl font-bold text-green-700">{isMobile ? 'Móvil' : 'Desktop'}</p>
|
|
678
|
-
</div>
|
|
679
|
-
<div className="bg-purple-50 rounded-lg p-6 border border-purple-200">
|
|
680
|
-
<h3 className="text-sm font-semibold text-purple-900 mb-2">Ancho</h3>
|
|
681
|
-
<p className="text-2xl font-bold text-purple-700">{responsive.width}px</p>
|
|
682
|
-
</div>
|
|
683
|
-
<div className="bg-orange-50 rounded-lg p-6 border border-orange-200">
|
|
684
|
-
<h3 className="text-sm font-semibold text-orange-900 mb-2">Alto</h3>
|
|
685
|
-
<p className="text-2xl font-bold text-orange-700">{responsive.height}px</p>
|
|
686
|
-
</div>
|
|
687
|
-
</div>
|
|
688
|
-
|
|
689
|
-
{/* Content Cards */}
|
|
690
|
-
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
|
691
|
-
{[1, 2, 3, 4, 5, 6].map((i) => (
|
|
692
|
-
<div key={i} className="bg-white rounded-lg shadow-md p-6 hover:shadow-lg transition-shadow">
|
|
693
|
-
<div className="w-12 h-12 bg-blue-500 rounded-lg flex items-center justify-center mb-4">
|
|
694
|
-
<span className="text-white font-bold text-xl">{i}</span>
|
|
695
|
-
</div>
|
|
696
|
-
<h3 className="text-xl font-bold text-gray-900 mb-2">Card {i}</h3>
|
|
697
|
-
<p className="text-gray-600">
|
|
698
|
-
Este es un ejemplo de card. El texto, espaciado y sombras se ajustan automáticamente
|
|
699
|
-
según el tamaño de pantalla gracias al sistema de auto-scaling.
|
|
700
|
-
</p>
|
|
701
|
-
</div>
|
|
702
|
-
))}
|
|
703
|
-
</div>
|
|
704
|
-
|
|
705
|
-
{/* Info Section */}
|
|
706
|
-
<div className="bg-white rounded-lg shadow-md p-8">
|
|
707
|
-
<h2 className="text-2xl font-bold text-gray-900 mb-4">Sistema Responsive</h2>
|
|
708
|
-
<div className="space-y-3 text-gray-700">
|
|
709
|
-
<p>
|
|
710
|
-
<strong className="text-gray-900">Auto-scaling activo:</strong> Todo el contenido escala
|
|
711
|
-
automáticamente según el breakpoint actual (texto, espaciado, sombras).
|
|
712
|
-
</p>
|
|
713
|
-
<p>
|
|
714
|
-
<strong className="text-gray-900">Hook useResponsive:</strong> Disponible en{' '}
|
|
715
|
-
<code className="bg-gray-100 px-2 py-1 rounded text-sm">src/hooks/useResponsive.ts</code>{' '}
|
|
716
|
-
para configuración manual cuando lo necesites.
|
|
717
|
-
</p>
|
|
718
|
-
<p>
|
|
719
|
-
<strong className="text-gray-900">Layout actual:</strong> <span className="capitalize">{layout.current}</span>
|
|
720
|
-
</p>
|
|
721
|
-
</div>
|
|
722
|
-
</div>
|
|
723
|
-
</div>
|
|
724
|
-
</div>
|
|
725
|
-
)
|
|
726
|
-
}
|
|
727
|
-
|
|
728
|
-
export default HomePage
|
|
729
|
-
`
|
|
730
|
-
fs.writeFileSync(homePagePath, homePage)
|
|
731
|
-
console.log(' ✅ Creado: src/pages/HomePage.tsx (página de ejemplo simple)')
|
|
732
|
-
}
|
|
733
|
-
|
|
734
|
-
// Crear src/App.tsx que importa la página
|
|
735
|
-
const appTsxPath = path.join(projectRoot, 'src', 'App.tsx')
|
|
736
|
-
if (!fs.existsSync(appTsxPath)) {
|
|
737
|
-
const appTsx = `import HomePage from './pages/HomePage'
|
|
738
|
-
|
|
739
|
-
function App() {
|
|
740
|
-
return <HomePage />
|
|
741
|
-
}
|
|
742
|
-
|
|
743
|
-
export default App
|
|
744
|
-
`
|
|
745
|
-
fs.writeFileSync(appTsxPath, appTsx)
|
|
746
|
-
console.log(' ✅ Creado: src/App.tsx')
|
|
747
|
-
}
|
|
748
|
-
|
|
749
|
-
// Actualizar package.json con scripts
|
|
750
|
-
if (!packageJson.scripts) {
|
|
751
|
-
packageJson.scripts = {}
|
|
752
|
-
}
|
|
753
|
-
if (!packageJson.scripts.dev) {
|
|
754
|
-
packageJson.scripts.dev = 'vite'
|
|
755
|
-
}
|
|
756
|
-
if (!packageJson.scripts.build) {
|
|
757
|
-
packageJson.scripts.build = 'tsc && vite build'
|
|
758
|
-
}
|
|
759
|
-
if (!packageJson.scripts.preview) {
|
|
760
|
-
packageJson.scripts.preview = 'vite preview'
|
|
761
|
-
}
|
|
762
|
-
|
|
763
|
-
fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2))
|
|
764
|
-
console.log(' ✅ Actualizado: package.json con scripts')
|
|
765
|
-
|
|
766
|
-
console.log('')
|
|
767
|
-
console.log('🎉 Proyecto inicializado correctamente!')
|
|
768
|
-
console.log('')
|
|
769
|
-
console.log('Para empezar:')
|
|
770
|
-
console.log(' 1. npm run dev')
|
|
771
|
-
console.log(' 2. Abre http://localhost:5173')
|
|
772
|
-
console.log('')
|
|
773
|
-
console.log(`Layout seleccionado: "${selectedLayout}"`)
|
|
774
|
-
console.log(' - Componentes en src/components/layout/')
|
|
775
|
-
console.log(' - Hook useResponsive en src/hooks/useResponsive.ts')
|
|
776
|
-
console.log(' - Página de ejemplo en src/pages/HomePage.tsx')
|
|
777
|
-
console.log('')
|
|
778
|
-
console.log('💡 Para cambiar el layout: npx responsive-system-setup')
|
|
779
|
-
console.log('')
|
|
780
|
-
} else {
|
|
781
|
-
console.log('✅ Proyecto ya inicializado')
|
|
782
|
-
}
|
|
783
|
-
|
|
784
|
-
console.log('')
|
|
785
|
-
console.log('✅ Configuración completada')
|
|
786
|
-
console.log('')
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Script postinstall para automatizar la configuración inicial completa
|
|
5
|
+
* - Instala React, TypeScript, Tailwind automáticamente
|
|
6
|
+
* - Inicializa proyecto Vite si está vacío
|
|
7
|
+
* - Pregunta qué layout quiere (interactivo)
|
|
8
|
+
* - Copia solo los componentes necesarios
|
|
9
|
+
* - Copia el hook useResponsive como archivo local
|
|
10
|
+
* - Crea página de ejemplo en pages/
|
|
11
|
+
* - Configura App.tsx con el layout seleccionado
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import fs from 'fs'
|
|
15
|
+
import path from 'path'
|
|
16
|
+
import { execSync } from 'child_process'
|
|
17
|
+
import { fileURLToPath } from 'url'
|
|
18
|
+
import readline from 'readline'
|
|
19
|
+
|
|
20
|
+
const __filename = fileURLToPath(import.meta.url)
|
|
21
|
+
const __dirname = path.dirname(__filename)
|
|
22
|
+
|
|
23
|
+
const projectRoot = process.cwd()
|
|
24
|
+
const packageJsonPath = path.join(projectRoot, 'package.json')
|
|
25
|
+
|
|
26
|
+
// Detectar si se ejecuta como postinstall o manualmente
|
|
27
|
+
const isPostinstall = process.env.npm_lifecycle_event === 'postinstall'
|
|
28
|
+
const isManual = process.argv[1].includes('postinstall.js') && !isPostinstall
|
|
29
|
+
|
|
30
|
+
console.log('')
|
|
31
|
+
console.log('📦 responsive-system: Iniciando configuración...')
|
|
32
|
+
console.log(` Directorio: ${projectRoot}`)
|
|
33
|
+
console.log('')
|
|
34
|
+
|
|
35
|
+
// Verificar si package.json existe
|
|
36
|
+
if (!fs.existsSync(packageJsonPath)) {
|
|
37
|
+
console.log('⚠️ No se encontró package.json, saltando configuración')
|
|
38
|
+
process.exit(0)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
let packageJson
|
|
42
|
+
try {
|
|
43
|
+
packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'))
|
|
44
|
+
} catch (error) {
|
|
45
|
+
console.error('❌ Error al leer package.json:', error.message)
|
|
46
|
+
process.exit(1)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Verificar si el proyecto está vacío (solo tiene responsive-system)
|
|
50
|
+
const isProjectEmpty = !packageJson.dependencies ||
|
|
51
|
+
Object.keys(packageJson.dependencies).length === 0 ||
|
|
52
|
+
(Object.keys(packageJson.dependencies).length === 1 && packageJson.dependencies['responsive-system'])
|
|
53
|
+
|
|
54
|
+
// Verificar qué está instalado - SOLO en package.json (no en node_modules para evitar conflictos)
|
|
55
|
+
const hasReactInPackageJson = (packageJson.dependencies && packageJson.dependencies.react) ||
|
|
56
|
+
(packageJson.devDependencies && packageJson.devDependencies.react)
|
|
57
|
+
const hasVite = packageJson.devDependencies && packageJson.devDependencies.vite
|
|
58
|
+
const tailwindInDevDeps = packageJson.devDependencies && packageJson.devDependencies.tailwindcss
|
|
59
|
+
const typescriptInDevDeps = packageJson.devDependencies && packageJson.devDependencies.typescript
|
|
60
|
+
|
|
61
|
+
let needsUpdate = false
|
|
62
|
+
|
|
63
|
+
// Función para preguntar al usuario qué layout quiere
|
|
64
|
+
async function askLayout() {
|
|
65
|
+
if (isPostinstall && !isManual) {
|
|
66
|
+
console.log(' ℹ️ Usando layout "default" por defecto')
|
|
67
|
+
console.log(' 💡 Ejecuta "npx responsive-system-setup" para cambiar el layout')
|
|
68
|
+
return 'default'
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Si es ejecución manual, preguntar interactivamente
|
|
72
|
+
const rl = readline.createInterface({
|
|
73
|
+
input: process.stdin,
|
|
74
|
+
output: process.stdout
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
return new Promise((resolve) => {
|
|
78
|
+
console.log('')
|
|
79
|
+
console.log('🎨 Selecciona el layout que quieres usar:')
|
|
80
|
+
console.log(' 1. default - Navigation + Footer')
|
|
81
|
+
console.log(' 2. sidebar - Sidebar lateral')
|
|
82
|
+
console.log(' 3. dashboard - Sidebar + Footer')
|
|
83
|
+
console.log(' 4. minimal - Sin componentes (solo contenido)')
|
|
84
|
+
console.log('')
|
|
85
|
+
|
|
86
|
+
rl.question(' Ingresa el número (1-4) o el nombre del layout: ', (answer) => {
|
|
87
|
+
rl.close()
|
|
88
|
+
|
|
89
|
+
const normalized = answer.trim().toLowerCase()
|
|
90
|
+
|
|
91
|
+
if (normalized === '1' || normalized === 'default') {
|
|
92
|
+
resolve('default')
|
|
93
|
+
} else if (normalized === '2' || normalized === 'sidebar') {
|
|
94
|
+
resolve('sidebar')
|
|
95
|
+
} else if (normalized === '3' || normalized === 'dashboard') {
|
|
96
|
+
resolve('dashboard')
|
|
97
|
+
} else if (normalized === '4' || normalized === 'minimal') {
|
|
98
|
+
resolve('minimal')
|
|
99
|
+
} else {
|
|
100
|
+
console.log(' ⚠️ Opción inválida, usando "default"')
|
|
101
|
+
resolve('default')
|
|
102
|
+
}
|
|
103
|
+
})
|
|
104
|
+
})
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Función para copiar archivo desde el paquete al proyecto
|
|
108
|
+
function copyFileFromPackage(relativePath, targetPath, isComponent = false) {
|
|
109
|
+
const sourcePath = path.join(__dirname, '..', relativePath)
|
|
110
|
+
const destPath = path.join(projectRoot, targetPath)
|
|
111
|
+
|
|
112
|
+
if (!fs.existsSync(sourcePath)) {
|
|
113
|
+
console.error(` ❌ No se encontró: ${relativePath}`)
|
|
114
|
+
return false
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Crear directorio destino si no existe
|
|
118
|
+
const destDir = path.dirname(destPath)
|
|
119
|
+
if (!fs.existsSync(destDir)) {
|
|
120
|
+
fs.mkdirSync(destDir, { recursive: true })
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
let content = fs.readFileSync(sourcePath, 'utf8')
|
|
124
|
+
|
|
125
|
+
// Si es un componente, reemplazar importaciones relativas por importaciones del paquete
|
|
126
|
+
if (isComponent) {
|
|
127
|
+
// Reemplazar importaciones de hooks
|
|
128
|
+
content = content.replace(
|
|
129
|
+
/from ['"]\.\.\/\.\.\/hooks['"]/g,
|
|
130
|
+
"from 'responsive-system'"
|
|
131
|
+
)
|
|
132
|
+
// Reemplazar importaciones de context
|
|
133
|
+
content = content.replace(
|
|
134
|
+
/from ['"]\.\.\/\.\.\/context['"]/g,
|
|
135
|
+
"from 'responsive-system'"
|
|
136
|
+
)
|
|
137
|
+
// Reemplazar importaciones de componentes/layout
|
|
138
|
+
content = content.replace(
|
|
139
|
+
/from ['"]\.\.\/components\/layout['"]/g,
|
|
140
|
+
"from 'responsive-system'"
|
|
141
|
+
)
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
fs.writeFileSync(destPath, content)
|
|
145
|
+
return true
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Función para generar componentes genéricos según el layout seleccionado
|
|
149
|
+
function generateLayoutComponents(selectedLayout) {
|
|
150
|
+
const componentsDir = path.join(projectRoot, 'src', 'components', 'layout')
|
|
151
|
+
|
|
152
|
+
// Crear directorio si no existe
|
|
153
|
+
if (!fs.existsSync(componentsDir)) {
|
|
154
|
+
fs.mkdirSync(componentsDir, { recursive: true })
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const componentsToGenerate = {
|
|
158
|
+
default: ['Navigation', 'Footer'],
|
|
159
|
+
sidebar: ['Sidebar'],
|
|
160
|
+
dashboard: ['Sidebar', 'Footer'],
|
|
161
|
+
minimal: []
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const components = componentsToGenerate[selectedLayout] || []
|
|
165
|
+
|
|
166
|
+
if (components.length === 0) {
|
|
167
|
+
console.log(' ✅ Layout minimal: No se generan componentes')
|
|
168
|
+
return
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
console.log(` 📦 Generando componentes para layout "${selectedLayout}":`)
|
|
172
|
+
|
|
173
|
+
// Generar Navigation genérico
|
|
174
|
+
if (components.includes('Navigation')) {
|
|
175
|
+
const navigationContent = `import { useResponsiveLayout } from 'responsive-system'
|
|
176
|
+
|
|
177
|
+
const Navigation = () => {
|
|
178
|
+
const { isMobile } = useResponsiveLayout()
|
|
179
|
+
|
|
180
|
+
return (
|
|
181
|
+
<nav className="sticky top-0 z-50 bg-gray-900 border-b border-gray-800">
|
|
182
|
+
<div className="px-4 py-4">
|
|
183
|
+
<div className="flex items-center justify-between">
|
|
184
|
+
<div className="flex items-center space-x-3">
|
|
185
|
+
<div className="w-8 h-8 bg-gray-700 rounded-lg flex items-center justify-center">
|
|
186
|
+
<span className="text-white font-bold text-sm">LO</span>
|
|
187
|
+
</div>
|
|
188
|
+
<h1 className="text-white font-semibold text-lg">Tu Aplicación</h1>
|
|
189
|
+
</div>
|
|
190
|
+
|
|
191
|
+
{isMobile && (
|
|
192
|
+
<button className="p-2 text-gray-400 hover:text-white">
|
|
193
|
+
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
194
|
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 6h16M4 12h16M4 18h16" />
|
|
195
|
+
</svg>
|
|
196
|
+
</button>
|
|
197
|
+
)}
|
|
198
|
+
</div>
|
|
199
|
+
</div>
|
|
200
|
+
</nav>
|
|
201
|
+
)
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export default Navigation
|
|
205
|
+
`
|
|
206
|
+
fs.writeFileSync(path.join(componentsDir, 'Navigation.tsx'), navigationContent)
|
|
207
|
+
console.log(' ✅ Navigation.tsx')
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Generar Footer genérico
|
|
211
|
+
if (components.includes('Footer')) {
|
|
212
|
+
const footerContent = `const Footer = () => {
|
|
213
|
+
return (
|
|
214
|
+
<footer className="bg-gray-900 border-t border-gray-800">
|
|
215
|
+
<div className="px-4 py-6">
|
|
216
|
+
<div className="max-w-7xl mx-auto text-center">
|
|
217
|
+
<p className="text-gray-400 text-sm">
|
|
218
|
+
© {new Date().getFullYear()} Tu Aplicación. Todos los derechos reservados.
|
|
219
|
+
</p>
|
|
220
|
+
</div>
|
|
221
|
+
</div>
|
|
222
|
+
</footer>
|
|
223
|
+
)
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
export default Footer
|
|
227
|
+
`
|
|
228
|
+
fs.writeFileSync(path.join(componentsDir, 'Footer.tsx'), footerContent)
|
|
229
|
+
console.log(' ✅ Footer.tsx')
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Generar Sidebar genérico
|
|
233
|
+
if (components.includes('Sidebar')) {
|
|
234
|
+
const sidebarContent = `import { useResponsiveLayout } from 'responsive-system'
|
|
235
|
+
import { useSidebar } from 'responsive-system'
|
|
236
|
+
|
|
237
|
+
const Sidebar = () => {
|
|
238
|
+
const { isMobile, isTablet } = useResponsiveLayout()
|
|
239
|
+
const { sidebarOpen, setSidebarOpen } = useSidebar()
|
|
240
|
+
|
|
241
|
+
const menuItems = [
|
|
242
|
+
{ id: 'home', label: 'Inicio' },
|
|
243
|
+
{ id: 'about', label: 'Acerca' },
|
|
244
|
+
{ id: 'contact', label: 'Contacto' },
|
|
245
|
+
]
|
|
246
|
+
|
|
247
|
+
return (
|
|
248
|
+
<>
|
|
249
|
+
{/* Hamburger button para móvil */}
|
|
250
|
+
{isMobile && (
|
|
251
|
+
<button
|
|
252
|
+
onClick={() => setSidebarOpen(true)}
|
|
253
|
+
className="fixed top-4 left-4 z-50 p-2 rounded-lg text-gray-300 hover:text-white hover:bg-gray-800 bg-gray-900 border border-gray-700"
|
|
254
|
+
>
|
|
255
|
+
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
256
|
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 6h16M4 12h16M4 18h16" />
|
|
257
|
+
</svg>
|
|
258
|
+
</button>
|
|
259
|
+
)}
|
|
260
|
+
|
|
261
|
+
{/* Sidebar desktop */}
|
|
262
|
+
<aside className={\`bg-gray-900 border-r border-gray-800 \${isMobile ? 'hidden' : 'w-64 flex-shrink-0'} \${isTablet ? 'w-56' : 'w-64'}\`}>
|
|
263
|
+
<div className="p-6 flex flex-col h-full">
|
|
264
|
+
<div className="flex items-center space-x-3 mb-8">
|
|
265
|
+
<div className="w-8 h-8 bg-gray-700 rounded-lg flex items-center justify-center">
|
|
266
|
+
<span className="text-white font-bold text-sm">LO</span>
|
|
267
|
+
</div>
|
|
268
|
+
<span className="text-white font-bold text-lg">Tu Aplicación</span>
|
|
269
|
+
</div>
|
|
270
|
+
|
|
271
|
+
<nav className="space-y-2">
|
|
272
|
+
{menuItems.map((item) => (
|
|
273
|
+
<button
|
|
274
|
+
key={item.id}
|
|
275
|
+
className="w-full flex items-center px-4 py-3 rounded-lg transition-all text-left text-gray-300 hover:text-white hover:bg-gray-800"
|
|
276
|
+
>
|
|
277
|
+
<span className="font-medium">{item.label}</span>
|
|
278
|
+
</button>
|
|
279
|
+
))}
|
|
280
|
+
</nav>
|
|
281
|
+
</div>
|
|
282
|
+
</aside>
|
|
283
|
+
|
|
284
|
+
{/* Sidebar móvil desplegable */}
|
|
285
|
+
{isMobile && sidebarOpen && (
|
|
286
|
+
<div className="fixed inset-0 z-40 bg-black/50" onClick={() => setSidebarOpen(false)}>
|
|
287
|
+
<div className="fixed top-0 left-0 w-64 h-full bg-gray-900 border-r border-gray-800">
|
|
288
|
+
<div className="p-6 flex flex-col h-full pt-20">
|
|
289
|
+
<div className="flex items-center space-x-3 mb-8">
|
|
290
|
+
<div className="w-8 h-8 bg-gray-700 rounded-lg flex items-center justify-center">
|
|
291
|
+
<span className="text-white font-bold text-sm">LO</span>
|
|
292
|
+
</div>
|
|
293
|
+
<span className="text-white font-bold text-lg">Tu Aplicación</span>
|
|
294
|
+
</div>
|
|
295
|
+
|
|
296
|
+
<nav className="space-y-2">
|
|
297
|
+
{menuItems.map((item) => (
|
|
298
|
+
<button
|
|
299
|
+
key={item.id}
|
|
300
|
+
onClick={() => setSidebarOpen(false)}
|
|
301
|
+
className="w-full flex items-center px-4 py-3 rounded-lg transition-all text-left text-gray-300 hover:text-white hover:bg-gray-800"
|
|
302
|
+
>
|
|
303
|
+
<span className="font-medium">{item.label}</span>
|
|
304
|
+
</button>
|
|
305
|
+
))}
|
|
306
|
+
</nav>
|
|
307
|
+
</div>
|
|
308
|
+
</div>
|
|
309
|
+
</div>
|
|
310
|
+
)}
|
|
311
|
+
</>
|
|
312
|
+
)
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
export default Sidebar
|
|
316
|
+
`
|
|
317
|
+
fs.writeFileSync(path.join(componentsDir, 'Sidebar.tsx'), sidebarContent)
|
|
318
|
+
console.log(' ✅ Sidebar.tsx')
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// Crear index.ts para exportar los componentes
|
|
322
|
+
const indexContent = components.map(c => `export { default as ${c} } from './${c}'`).join('\n')
|
|
323
|
+
fs.writeFileSync(path.join(componentsDir, 'index.ts'), indexContent)
|
|
324
|
+
console.log(' ✅ index.ts')
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// Función para copiar el hook useResponsive y sus dependencias
|
|
328
|
+
function copyUseResponsiveHook() {
|
|
329
|
+
console.log(' 📦 Copiando hook useResponsive...')
|
|
330
|
+
|
|
331
|
+
// Crear directorio hooks
|
|
332
|
+
const hooksDir = path.join(projectRoot, 'src', 'hooks')
|
|
333
|
+
if (!fs.existsSync(hooksDir)) {
|
|
334
|
+
fs.mkdirSync(hooksDir, { recursive: true })
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// Copiar tipos
|
|
338
|
+
const typesDir = path.join(projectRoot, 'src', 'types')
|
|
339
|
+
if (!fs.existsSync(typesDir)) {
|
|
340
|
+
fs.mkdirSync(typesDir, { recursive: true })
|
|
341
|
+
}
|
|
342
|
+
copyFileFromPackage('src/types/responsive.ts', 'src/types/responsive.ts')
|
|
343
|
+
console.log(' ✅ types/responsive.ts')
|
|
344
|
+
|
|
345
|
+
// Copiar constantes
|
|
346
|
+
const constantsDir = path.join(projectRoot, 'src', 'constants')
|
|
347
|
+
if (!fs.existsSync(constantsDir)) {
|
|
348
|
+
fs.mkdirSync(constantsDir, { recursive: true })
|
|
349
|
+
}
|
|
350
|
+
copyFileFromPackage('src/constants/breakpoints.ts', 'src/constants/breakpoints.ts')
|
|
351
|
+
console.log(' ✅ constants/breakpoints.ts')
|
|
352
|
+
|
|
353
|
+
// Copiar hook useResponsive
|
|
354
|
+
copyFileFromPackage('src/hooks/useResponsive.ts', 'src/hooks/useResponsive.ts')
|
|
355
|
+
console.log(' ✅ hooks/useResponsive.ts')
|
|
356
|
+
|
|
357
|
+
// Crear index.ts para exportar el hook
|
|
358
|
+
const indexContent = `export { useResponsive } from './useResponsive'
|
|
359
|
+
export type { ResponsiveState, Breakpoint, Orientation } from '../types/responsive'
|
|
360
|
+
export { DEFAULT_BREAKPOINTS, getCurrentBreakpoint, getBreakpointIndex, getBreakpointValue } from '../constants/breakpoints'
|
|
361
|
+
`
|
|
362
|
+
fs.writeFileSync(path.join(hooksDir, 'index.ts'), indexContent)
|
|
363
|
+
console.log(' ✅ hooks/index.ts')
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
console.log('📦 Verificando dependencias...')
|
|
367
|
+
|
|
368
|
+
// Agregar React a dependencies SOLO si NO está en package.json
|
|
369
|
+
if (!hasReactInPackageJson) {
|
|
370
|
+
console.log(' ➕ Agregando React a dependencies...')
|
|
371
|
+
if (!packageJson.dependencies) {
|
|
372
|
+
packageJson.dependencies = {}
|
|
373
|
+
}
|
|
374
|
+
packageJson.dependencies['react'] = '^19.1.1'
|
|
375
|
+
packageJson.dependencies['react-dom'] = '^19.1.1'
|
|
376
|
+
needsUpdate = true
|
|
377
|
+
} else {
|
|
378
|
+
console.log(' ✅ React ya está instalado')
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// Agregar Vite si el proyecto está vacío
|
|
382
|
+
if (isProjectEmpty && !hasVite) {
|
|
383
|
+
console.log(' ➕ Agregando Vite a devDependencies...')
|
|
384
|
+
if (!packageJson.devDependencies) {
|
|
385
|
+
packageJson.devDependencies = {}
|
|
386
|
+
}
|
|
387
|
+
packageJson.devDependencies['vite'] = '^7.1.7'
|
|
388
|
+
packageJson.devDependencies['@vitejs/plugin-react'] = '^5.0.4'
|
|
389
|
+
needsUpdate = true
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// Agregar Tailwind y sus dependencias a devDependencies
|
|
393
|
+
if (!tailwindInDevDeps) {
|
|
394
|
+
console.log(' ➕ Agregando Tailwind y PostCSS a devDependencies...')
|
|
395
|
+
if (!packageJson.devDependencies) {
|
|
396
|
+
packageJson.devDependencies = {}
|
|
397
|
+
}
|
|
398
|
+
packageJson.devDependencies['tailwindcss'] = '^4.1.14'
|
|
399
|
+
packageJson.devDependencies['@tailwindcss/postcss'] = '^4.1.14'
|
|
400
|
+
packageJson.devDependencies['postcss'] = '^8.5.6'
|
|
401
|
+
packageJson.devDependencies['autoprefixer'] = '^10.4.21'
|
|
402
|
+
needsUpdate = true
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
// Agregar TypeScript y sus tipos a devDependencies
|
|
406
|
+
if (!typescriptInDevDeps) {
|
|
407
|
+
console.log(' ➕ Agregando TypeScript a devDependencies...')
|
|
408
|
+
if (!packageJson.devDependencies) {
|
|
409
|
+
packageJson.devDependencies = {}
|
|
410
|
+
}
|
|
411
|
+
packageJson.devDependencies['typescript'] = '~5.9.3'
|
|
412
|
+
packageJson.devDependencies['@types/react'] = '^19.1.16'
|
|
413
|
+
packageJson.devDependencies['@types/react-dom'] = '^19.1.9'
|
|
414
|
+
needsUpdate = true
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// Agregar "type": "module" si no existe (para evitar warnings)
|
|
418
|
+
if (!packageJson.type) {
|
|
419
|
+
packageJson.type = 'module'
|
|
420
|
+
needsUpdate = true
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
// Escribir package.json modificado
|
|
424
|
+
if (needsUpdate) {
|
|
425
|
+
console.log('')
|
|
426
|
+
console.log('📝 Actualizando package.json...')
|
|
427
|
+
fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2))
|
|
428
|
+
console.log('✅ package.json actualizado')
|
|
429
|
+
console.log('')
|
|
430
|
+
|
|
431
|
+
// Ahora instalar las dependencias
|
|
432
|
+
console.log('📦 Instalando dependencias...')
|
|
433
|
+
try {
|
|
434
|
+
execSync('npm install', {
|
|
435
|
+
stdio: 'inherit',
|
|
436
|
+
cwd: projectRoot
|
|
437
|
+
})
|
|
438
|
+
console.log('✅ Dependencias instaladas correctamente')
|
|
439
|
+
} catch (error) {
|
|
440
|
+
console.error('❌ Error al instalar dependencias:', error.message)
|
|
441
|
+
console.log('⚠️ Las dependencias están en package.json, ejecuta "npm install" manualmente')
|
|
442
|
+
}
|
|
443
|
+
} else {
|
|
444
|
+
console.log('✅ Todas las dependencias ya están instaladas')
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
// Si el proyecto está vacío, crear estructura base
|
|
448
|
+
if (isProjectEmpty) {
|
|
449
|
+
console.log('')
|
|
450
|
+
console.log('📦 Proyecto vacío detectado, creando estructura base...')
|
|
451
|
+
console.log('')
|
|
452
|
+
|
|
453
|
+
// Preguntar qué layout quiere
|
|
454
|
+
const selectedLayout = await askLayout()
|
|
455
|
+
console.log(` ✅ Layout seleccionado: "${selectedLayout}"`)
|
|
456
|
+
console.log('')
|
|
457
|
+
|
|
458
|
+
// Crear estructura de directorios
|
|
459
|
+
const dirs = ['src', 'src/components', 'src/components/layout', 'src/pages', 'src/hooks', 'src/types', 'src/constants', 'public']
|
|
460
|
+
dirs.forEach(dir => {
|
|
461
|
+
const dirPath = path.join(projectRoot, dir)
|
|
462
|
+
if (!fs.existsSync(dirPath)) {
|
|
463
|
+
fs.mkdirSync(dirPath, { recursive: true })
|
|
464
|
+
}
|
|
465
|
+
})
|
|
466
|
+
|
|
467
|
+
// Generar componentes genéricos según layout seleccionado
|
|
468
|
+
generateLayoutComponents(selectedLayout)
|
|
469
|
+
console.log('')
|
|
470
|
+
|
|
471
|
+
// Copiar hook useResponsive
|
|
472
|
+
copyUseResponsiveHook()
|
|
473
|
+
console.log('')
|
|
474
|
+
|
|
475
|
+
// Crear vite.config.ts
|
|
476
|
+
const viteConfigPath = path.join(projectRoot, 'vite.config.ts')
|
|
477
|
+
if (!fs.existsSync(viteConfigPath)) {
|
|
478
|
+
const viteConfig = `import { defineConfig } from 'vite'
|
|
479
|
+
import react from '@vitejs/plugin-react'
|
|
480
|
+
|
|
481
|
+
export default defineConfig({
|
|
482
|
+
plugins: [react()],
|
|
483
|
+
})
|
|
484
|
+
`
|
|
485
|
+
fs.writeFileSync(viteConfigPath, viteConfig)
|
|
486
|
+
console.log(' ✅ Creado: vite.config.ts')
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
// Crear tailwind.config.js
|
|
490
|
+
const tailwindConfigPath = path.join(projectRoot, 'tailwind.config.js')
|
|
491
|
+
if (!fs.existsSync(tailwindConfigPath)) {
|
|
492
|
+
const tailwindConfig = `import responsiveScalePlugin from 'responsive-system/plugin'
|
|
493
|
+
|
|
494
|
+
export default {
|
|
495
|
+
content: [
|
|
496
|
+
"./index.html",
|
|
497
|
+
"./src/**/*.{js,ts,jsx,tsx}",
|
|
498
|
+
],
|
|
499
|
+
theme: {
|
|
500
|
+
extend: {
|
|
501
|
+
screens: {
|
|
502
|
+
'xs': '475px', 'sm': '640px', 'md': '768px', 'lg': '1024px',
|
|
503
|
+
'xl': '1280px', '2xl': '1536px', '3xl': '1920px', '4xl': '2560px', '5xl': '3840px'
|
|
504
|
+
}
|
|
505
|
+
},
|
|
506
|
+
},
|
|
507
|
+
plugins: [
|
|
508
|
+
responsiveScalePlugin({
|
|
509
|
+
scaleProperties: {
|
|
510
|
+
typography: true,
|
|
511
|
+
spacing: true,
|
|
512
|
+
lineHeight: true,
|
|
513
|
+
letterSpacing: true,
|
|
514
|
+
shadows: true,
|
|
515
|
+
borderWidth: false,
|
|
516
|
+
sizing: false,
|
|
517
|
+
borderRadius: false
|
|
518
|
+
},
|
|
519
|
+
scales: {
|
|
520
|
+
xs: 1.0, sm: 1.0, md: 1.0, lg: 1.0, xl: 1.0,
|
|
521
|
+
'2xl': 1.05, '3xl': 1.15, '4xl': 1.25, '5xl': 1.35
|
|
522
|
+
}
|
|
523
|
+
})
|
|
524
|
+
],
|
|
525
|
+
}
|
|
526
|
+
`
|
|
527
|
+
fs.writeFileSync(tailwindConfigPath, tailwindConfig)
|
|
528
|
+
console.log(' ✅ Creado: tailwind.config.js')
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
// Crear postcss.config.js
|
|
532
|
+
const postcssConfigPath = path.join(projectRoot, 'postcss.config.js')
|
|
533
|
+
if (!fs.existsSync(postcssConfigPath)) {
|
|
534
|
+
const postcssConfig = `export default {
|
|
535
|
+
plugins: {
|
|
536
|
+
'@tailwindcss/postcss': {},
|
|
537
|
+
autoprefixer: {},
|
|
538
|
+
},
|
|
539
|
+
}
|
|
540
|
+
`
|
|
541
|
+
fs.writeFileSync(postcssConfigPath, postcssConfig)
|
|
542
|
+
console.log(' ✅ Creado: postcss.config.js')
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
// Crear tsconfig.json
|
|
546
|
+
const tsconfigPath = path.join(projectRoot, 'tsconfig.json')
|
|
547
|
+
if (!fs.existsSync(tsconfigPath)) {
|
|
548
|
+
const tsconfig = `{
|
|
549
|
+
"compilerOptions": {
|
|
550
|
+
"target": "ES2020",
|
|
551
|
+
"useDefineForClassFields": true,
|
|
552
|
+
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
|
553
|
+
"module": "ESNext",
|
|
554
|
+
"skipLibCheck": true,
|
|
555
|
+
"moduleResolution": "bundler",
|
|
556
|
+
"allowImportingTsExtensions": true,
|
|
557
|
+
"resolveJsonModule": true,
|
|
558
|
+
"isolatedModules": true,
|
|
559
|
+
"noEmit": true,
|
|
560
|
+
"jsx": "react-jsx",
|
|
561
|
+
"strict": true,
|
|
562
|
+
"noUnusedLocals": true,
|
|
563
|
+
"noUnusedParameters": true,
|
|
564
|
+
"noFallthroughCasesInSwitch": true
|
|
565
|
+
},
|
|
566
|
+
"include": ["src"],
|
|
567
|
+
"references": [{ "path": "./tsconfig.node.json" }]
|
|
568
|
+
}
|
|
569
|
+
`
|
|
570
|
+
fs.writeFileSync(tsconfigPath, tsconfig)
|
|
571
|
+
console.log(' ✅ Creado: tsconfig.json')
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
// Crear tsconfig.node.json
|
|
575
|
+
const tsconfigNodePath = path.join(projectRoot, 'tsconfig.node.json')
|
|
576
|
+
if (!fs.existsSync(tsconfigNodePath)) {
|
|
577
|
+
const tsconfigNode = `{
|
|
578
|
+
"compilerOptions": {
|
|
579
|
+
"composite": true,
|
|
580
|
+
"skipLibCheck": true,
|
|
581
|
+
"module": "ESNext",
|
|
582
|
+
"moduleResolution": "bundler",
|
|
583
|
+
"allowSyntheticDefaultImports": true
|
|
584
|
+
},
|
|
585
|
+
"include": ["vite.config.ts"]
|
|
586
|
+
}
|
|
587
|
+
`
|
|
588
|
+
fs.writeFileSync(tsconfigNodePath, tsconfigNode)
|
|
589
|
+
console.log(' ✅ Creado: tsconfig.node.json')
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
// Crear index.html
|
|
593
|
+
const indexHtmlPath = path.join(projectRoot, 'index.html')
|
|
594
|
+
if (!fs.existsSync(indexHtmlPath)) {
|
|
595
|
+
const indexHtml = `<!doctype html>
|
|
596
|
+
<html lang="es">
|
|
597
|
+
<head>
|
|
598
|
+
<meta charset="UTF-8" />
|
|
599
|
+
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
|
600
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
601
|
+
<title>Responsive System Demo</title>
|
|
602
|
+
</head>
|
|
603
|
+
<body>
|
|
604
|
+
<div id="root"></div>
|
|
605
|
+
<script type="module" src="/src/main.tsx"></script>
|
|
606
|
+
</body>
|
|
607
|
+
</html>
|
|
608
|
+
`
|
|
609
|
+
fs.writeFileSync(indexHtmlPath, indexHtml)
|
|
610
|
+
console.log(' ✅ Creado: index.html')
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
// Crear src/main.tsx
|
|
614
|
+
const mainTsxPath = path.join(projectRoot, 'src', 'main.tsx')
|
|
615
|
+
if (!fs.existsSync(mainTsxPath)) {
|
|
616
|
+
const mainTsx = `import React from 'react'
|
|
617
|
+
import ReactDOM from 'react-dom/client'
|
|
618
|
+
import { ResponsiveLayoutProvider, MainLayout } from 'responsive-system'
|
|
619
|
+
import App from './App'
|
|
620
|
+
import './index.css'
|
|
621
|
+
|
|
622
|
+
ReactDOM.createRoot(document.getElementById('root')!).render(
|
|
623
|
+
<React.StrictMode>
|
|
624
|
+
<ResponsiveLayoutProvider defaultLayout="${selectedLayout}">
|
|
625
|
+
<MainLayout>
|
|
626
|
+
<App />
|
|
627
|
+
</MainLayout>
|
|
628
|
+
</ResponsiveLayoutProvider>
|
|
629
|
+
</React.StrictMode>,
|
|
630
|
+
)
|
|
631
|
+
`
|
|
632
|
+
fs.writeFileSync(mainTsxPath, mainTsx)
|
|
633
|
+
console.log(' ✅ Creado: src/main.tsx')
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
// Crear src/index.css
|
|
637
|
+
const indexCssPath = path.join(projectRoot, 'src', 'index.css')
|
|
638
|
+
if (!fs.existsSync(indexCssPath)) {
|
|
639
|
+
const indexCss = `@import "tailwindcss";
|
|
640
|
+
`
|
|
641
|
+
fs.writeFileSync(indexCssPath, indexCss)
|
|
642
|
+
console.log(' ✅ Creado: src/index.css')
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
// Crear src/pages/HomePage.tsx con página de ejemplo simple
|
|
646
|
+
const homePagePath = path.join(projectRoot, 'src', 'pages', 'HomePage.tsx')
|
|
647
|
+
if (!fs.existsSync(homePagePath)) {
|
|
648
|
+
const homePage = `import { useResponsiveLayout } from 'responsive-system'
|
|
649
|
+
import { useResponsive } from '../hooks'
|
|
650
|
+
|
|
651
|
+
function HomePage() {
|
|
652
|
+
const { breakpoint, isMobile, layout } = useResponsiveLayout()
|
|
653
|
+
const responsive = useResponsive()
|
|
654
|
+
|
|
655
|
+
return (
|
|
656
|
+
<div className="py-8 px-4">
|
|
657
|
+
<div className="max-w-7xl mx-auto space-y-8">
|
|
658
|
+
{/* Hero Section */}
|
|
659
|
+
<div className="bg-white rounded-lg shadow-md p-8 text-center">
|
|
660
|
+
<h1 className="text-4xl font-bold text-gray-900 mb-4">
|
|
661
|
+
Bienvenido a tu Aplicación
|
|
662
|
+
</h1>
|
|
663
|
+
<p className="text-lg text-gray-600 max-w-2xl mx-auto">
|
|
664
|
+
Esta es una página de ejemplo que demuestra el sistema responsive con auto-scaling.
|
|
665
|
+
Todo el contenido se ajusta automáticamente según el tamaño de pantalla.
|
|
666
|
+
</p>
|
|
667
|
+
</div>
|
|
668
|
+
|
|
669
|
+
{/* Info Cards */}
|
|
670
|
+
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
|
|
671
|
+
<div className="bg-blue-50 rounded-lg p-6 border border-blue-200">
|
|
672
|
+
<h3 className="text-sm font-semibold text-blue-900 mb-2">Breakpoint</h3>
|
|
673
|
+
<p className="text-2xl font-bold text-blue-700">{breakpoint.toUpperCase()}</p>
|
|
674
|
+
</div>
|
|
675
|
+
<div className="bg-green-50 rounded-lg p-6 border border-green-200">
|
|
676
|
+
<h3 className="text-sm font-semibold text-green-900 mb-2">Dispositivo</h3>
|
|
677
|
+
<p className="text-2xl font-bold text-green-700">{isMobile ? 'Móvil' : 'Desktop'}</p>
|
|
678
|
+
</div>
|
|
679
|
+
<div className="bg-purple-50 rounded-lg p-6 border border-purple-200">
|
|
680
|
+
<h3 className="text-sm font-semibold text-purple-900 mb-2">Ancho</h3>
|
|
681
|
+
<p className="text-2xl font-bold text-purple-700">{responsive.width}px</p>
|
|
682
|
+
</div>
|
|
683
|
+
<div className="bg-orange-50 rounded-lg p-6 border border-orange-200">
|
|
684
|
+
<h3 className="text-sm font-semibold text-orange-900 mb-2">Alto</h3>
|
|
685
|
+
<p className="text-2xl font-bold text-orange-700">{responsive.height}px</p>
|
|
686
|
+
</div>
|
|
687
|
+
</div>
|
|
688
|
+
|
|
689
|
+
{/* Content Cards */}
|
|
690
|
+
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
|
691
|
+
{[1, 2, 3, 4, 5, 6].map((i) => (
|
|
692
|
+
<div key={i} className="bg-white rounded-lg shadow-md p-6 hover:shadow-lg transition-shadow">
|
|
693
|
+
<div className="w-12 h-12 bg-blue-500 rounded-lg flex items-center justify-center mb-4">
|
|
694
|
+
<span className="text-white font-bold text-xl">{i}</span>
|
|
695
|
+
</div>
|
|
696
|
+
<h3 className="text-xl font-bold text-gray-900 mb-2">Card {i}</h3>
|
|
697
|
+
<p className="text-gray-600">
|
|
698
|
+
Este es un ejemplo de card. El texto, espaciado y sombras se ajustan automáticamente
|
|
699
|
+
según el tamaño de pantalla gracias al sistema de auto-scaling.
|
|
700
|
+
</p>
|
|
701
|
+
</div>
|
|
702
|
+
))}
|
|
703
|
+
</div>
|
|
704
|
+
|
|
705
|
+
{/* Info Section */}
|
|
706
|
+
<div className="bg-white rounded-lg shadow-md p-8">
|
|
707
|
+
<h2 className="text-2xl font-bold text-gray-900 mb-4">Sistema Responsive</h2>
|
|
708
|
+
<div className="space-y-3 text-gray-700">
|
|
709
|
+
<p>
|
|
710
|
+
<strong className="text-gray-900">Auto-scaling activo:</strong> Todo el contenido escala
|
|
711
|
+
automáticamente según el breakpoint actual (texto, espaciado, sombras).
|
|
712
|
+
</p>
|
|
713
|
+
<p>
|
|
714
|
+
<strong className="text-gray-900">Hook useResponsive:</strong> Disponible en{' '}
|
|
715
|
+
<code className="bg-gray-100 px-2 py-1 rounded text-sm">src/hooks/useResponsive.ts</code>{' '}
|
|
716
|
+
para configuración manual cuando lo necesites.
|
|
717
|
+
</p>
|
|
718
|
+
<p>
|
|
719
|
+
<strong className="text-gray-900">Layout actual:</strong> <span className="capitalize">{layout.current}</span>
|
|
720
|
+
</p>
|
|
721
|
+
</div>
|
|
722
|
+
</div>
|
|
723
|
+
</div>
|
|
724
|
+
</div>
|
|
725
|
+
)
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
export default HomePage
|
|
729
|
+
`
|
|
730
|
+
fs.writeFileSync(homePagePath, homePage)
|
|
731
|
+
console.log(' ✅ Creado: src/pages/HomePage.tsx (página de ejemplo simple)')
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
// Crear src/App.tsx que importa la página
|
|
735
|
+
const appTsxPath = path.join(projectRoot, 'src', 'App.tsx')
|
|
736
|
+
if (!fs.existsSync(appTsxPath)) {
|
|
737
|
+
const appTsx = `import HomePage from './pages/HomePage'
|
|
738
|
+
|
|
739
|
+
function App() {
|
|
740
|
+
return <HomePage />
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
export default App
|
|
744
|
+
`
|
|
745
|
+
fs.writeFileSync(appTsxPath, appTsx)
|
|
746
|
+
console.log(' ✅ Creado: src/App.tsx')
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
// Actualizar package.json con scripts
|
|
750
|
+
if (!packageJson.scripts) {
|
|
751
|
+
packageJson.scripts = {}
|
|
752
|
+
}
|
|
753
|
+
if (!packageJson.scripts.dev) {
|
|
754
|
+
packageJson.scripts.dev = 'vite'
|
|
755
|
+
}
|
|
756
|
+
if (!packageJson.scripts.build) {
|
|
757
|
+
packageJson.scripts.build = 'tsc && vite build'
|
|
758
|
+
}
|
|
759
|
+
if (!packageJson.scripts.preview) {
|
|
760
|
+
packageJson.scripts.preview = 'vite preview'
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2))
|
|
764
|
+
console.log(' ✅ Actualizado: package.json con scripts')
|
|
765
|
+
|
|
766
|
+
console.log('')
|
|
767
|
+
console.log('🎉 Proyecto inicializado correctamente!')
|
|
768
|
+
console.log('')
|
|
769
|
+
console.log('Para empezar:')
|
|
770
|
+
console.log(' 1. npm run dev')
|
|
771
|
+
console.log(' 2. Abre http://localhost:5173')
|
|
772
|
+
console.log('')
|
|
773
|
+
console.log(`Layout seleccionado: "${selectedLayout}"`)
|
|
774
|
+
console.log(' - Componentes en src/components/layout/')
|
|
775
|
+
console.log(' - Hook useResponsive en src/hooks/useResponsive.ts')
|
|
776
|
+
console.log(' - Página de ejemplo en src/pages/HomePage.tsx')
|
|
777
|
+
console.log('')
|
|
778
|
+
console.log('💡 Para cambiar el layout: npx responsive-system-setup')
|
|
779
|
+
console.log('')
|
|
780
|
+
} else {
|
|
781
|
+
console.log('✅ Proyecto ya inicializado')
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
console.log('')
|
|
785
|
+
console.log('✅ Configuración completada')
|
|
786
|
+
console.log('')
|