responsive-system 1.2.8 → 1.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/scripts/postinstall.js +601 -60
package/package.json
CHANGED
package/scripts/postinstall.js
CHANGED
|
@@ -4,14 +4,18 @@
|
|
|
4
4
|
* Script postinstall para automatizar la configuración inicial completa
|
|
5
5
|
* - Instala React, TypeScript, Tailwind automáticamente
|
|
6
6
|
* - Inicializa proyecto Vite si está vacío
|
|
7
|
-
* -
|
|
8
|
-
* -
|
|
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
|
|
9
12
|
*/
|
|
10
13
|
|
|
11
14
|
import fs from 'fs'
|
|
12
15
|
import path from 'path'
|
|
13
16
|
import { execSync } from 'child_process'
|
|
14
17
|
import { fileURLToPath } from 'url'
|
|
18
|
+
import readline from 'readline'
|
|
15
19
|
|
|
16
20
|
const __filename = fileURLToPath(import.meta.url)
|
|
17
21
|
const __dirname = path.dirname(__filename)
|
|
@@ -19,10 +23,17 @@ const __dirname = path.dirname(__filename)
|
|
|
19
23
|
const projectRoot = process.cwd()
|
|
20
24
|
const packageJsonPath = path.join(projectRoot, 'package.json')
|
|
21
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
|
+
|
|
22
30
|
console.log('')
|
|
23
|
-
console.log('📦 responsive-system: Iniciando
|
|
31
|
+
console.log('📦 responsive-system: Iniciando configuración...')
|
|
24
32
|
console.log(` Directorio: ${projectRoot}`)
|
|
25
33
|
console.log('')
|
|
34
|
+
console.log('💡 Si este es tu primer uso, este script configurará todo automáticamente.')
|
|
35
|
+
console.log(' Si ya tienes el proyecto configurado, puedes cancelar (Ctrl+C)')
|
|
36
|
+
console.log('')
|
|
26
37
|
|
|
27
38
|
// Verificar si package.json existe
|
|
28
39
|
if (!fs.existsSync(packageJsonPath)) {
|
|
@@ -46,27 +57,356 @@ const isProjectEmpty = !packageJson.dependencies ||
|
|
|
46
57
|
// Verificar qué está instalado - SOLO en package.json (no en node_modules para evitar conflictos)
|
|
47
58
|
const hasReactInPackageJson = (packageJson.dependencies && packageJson.dependencies.react) ||
|
|
48
59
|
(packageJson.devDependencies && packageJson.devDependencies.react)
|
|
49
|
-
// NO verificar node_modules porque puede estar como dependencia transitiva y causar conflictos
|
|
50
|
-
const hasReact = hasReactInPackageJson
|
|
51
|
-
|
|
52
60
|
const hasVite = packageJson.devDependencies && packageJson.devDependencies.vite
|
|
53
|
-
|
|
54
61
|
const tailwindInDevDeps = packageJson.devDependencies && packageJson.devDependencies.tailwindcss
|
|
55
62
|
const typescriptInDevDeps = packageJson.devDependencies && packageJson.devDependencies.typescript
|
|
56
63
|
|
|
57
64
|
let needsUpdate = false
|
|
58
65
|
|
|
66
|
+
// Función para preguntar al usuario qué layout quiere
|
|
67
|
+
async function askLayout() {
|
|
68
|
+
if (isPostinstall && !isManual) {
|
|
69
|
+
// Si es postinstall automático, usar 'default' por defecto
|
|
70
|
+
console.log(' ℹ️ Usando layout "default" por defecto')
|
|
71
|
+
console.log(' 💡 Ejecuta "npx responsive-system-setup" para cambiar el layout')
|
|
72
|
+
return 'default'
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Si es ejecución manual, preguntar interactivamente
|
|
76
|
+
const rl = readline.createInterface({
|
|
77
|
+
input: process.stdin,
|
|
78
|
+
output: process.stdout
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
return new Promise((resolve) => {
|
|
82
|
+
console.log('')
|
|
83
|
+
console.log('🎨 Selecciona el layout que quieres usar:')
|
|
84
|
+
console.log(' 1. default - Navigation + Footer')
|
|
85
|
+
console.log(' 2. sidebar - Sidebar lateral')
|
|
86
|
+
console.log(' 3. dashboard - Sidebar + Footer')
|
|
87
|
+
console.log(' 4. minimal - Sin componentes (solo contenido)')
|
|
88
|
+
console.log('')
|
|
89
|
+
|
|
90
|
+
rl.question(' Ingresa el número (1-4) o el nombre del layout: ', (answer) => {
|
|
91
|
+
rl.close()
|
|
92
|
+
|
|
93
|
+
const normalized = answer.trim().toLowerCase()
|
|
94
|
+
|
|
95
|
+
if (normalized === '1' || normalized === 'default') {
|
|
96
|
+
resolve('default')
|
|
97
|
+
} else if (normalized === '2' || normalized === 'sidebar') {
|
|
98
|
+
resolve('sidebar')
|
|
99
|
+
} else if (normalized === '3' || normalized === 'dashboard') {
|
|
100
|
+
resolve('dashboard')
|
|
101
|
+
} else if (normalized === '4' || normalized === 'minimal') {
|
|
102
|
+
resolve('minimal')
|
|
103
|
+
} else {
|
|
104
|
+
console.log(' ⚠️ Opción inválida, usando "default"')
|
|
105
|
+
resolve('default')
|
|
106
|
+
}
|
|
107
|
+
})
|
|
108
|
+
})
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Función para copiar archivo desde el paquete al proyecto
|
|
112
|
+
function copyFileFromPackage(relativePath, targetPath, isComponent = false) {
|
|
113
|
+
const sourcePath = path.join(__dirname, '..', relativePath)
|
|
114
|
+
const destPath = path.join(projectRoot, targetPath)
|
|
115
|
+
|
|
116
|
+
if (!fs.existsSync(sourcePath)) {
|
|
117
|
+
console.error(` ❌ No se encontró: ${relativePath}`)
|
|
118
|
+
return false
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Crear directorio destino si no existe
|
|
122
|
+
const destDir = path.dirname(destPath)
|
|
123
|
+
if (!fs.existsSync(destDir)) {
|
|
124
|
+
fs.mkdirSync(destDir, { recursive: true })
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
let content = fs.readFileSync(sourcePath, 'utf8')
|
|
128
|
+
|
|
129
|
+
// Si es un componente, reemplazar importaciones relativas por importaciones del paquete
|
|
130
|
+
if (isComponent) {
|
|
131
|
+
// Reemplazar importaciones de hooks
|
|
132
|
+
content = content.replace(
|
|
133
|
+
/from ['"]\.\.\/\.\.\/hooks['"]/g,
|
|
134
|
+
"from 'responsive-system'"
|
|
135
|
+
)
|
|
136
|
+
// Reemplazar importaciones de context
|
|
137
|
+
content = content.replace(
|
|
138
|
+
/from ['"]\.\.\/\.\.\/context['"]/g,
|
|
139
|
+
"from 'responsive-system'"
|
|
140
|
+
)
|
|
141
|
+
// Reemplazar importaciones de componentes/layout
|
|
142
|
+
content = content.replace(
|
|
143
|
+
/from ['"]\.\.\/components\/layout['"]/g,
|
|
144
|
+
"from 'responsive-system'"
|
|
145
|
+
)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
fs.writeFileSync(destPath, content)
|
|
149
|
+
return true
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Función para generar componentes genéricos según el layout seleccionado
|
|
153
|
+
function generateLayoutComponents(selectedLayout) {
|
|
154
|
+
const componentsDir = path.join(projectRoot, 'src', 'components', 'layout')
|
|
155
|
+
|
|
156
|
+
// Crear directorio si no existe
|
|
157
|
+
if (!fs.existsSync(componentsDir)) {
|
|
158
|
+
fs.mkdirSync(componentsDir, { recursive: true })
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const componentsToGenerate = {
|
|
162
|
+
default: ['Navigation', 'Footer'],
|
|
163
|
+
sidebar: ['Sidebar'],
|
|
164
|
+
dashboard: ['Sidebar', 'Footer'],
|
|
165
|
+
minimal: []
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const components = componentsToGenerate[selectedLayout] || []
|
|
169
|
+
|
|
170
|
+
if (components.length === 0) {
|
|
171
|
+
console.log(' ✅ Layout minimal: No se generan componentes')
|
|
172
|
+
return
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
console.log(` 📦 Generando componentes genéricos para layout "${selectedLayout}":`)
|
|
176
|
+
|
|
177
|
+
// Generar Navigation genérico
|
|
178
|
+
if (components.includes('Navigation')) {
|
|
179
|
+
const navigationContent = `import { useResponsiveLayout } from 'responsive-system'
|
|
180
|
+
|
|
181
|
+
const Navigation = () => {
|
|
182
|
+
const { isMobile, breakpoint } = useResponsiveLayout()
|
|
183
|
+
|
|
184
|
+
return (
|
|
185
|
+
<nav className="sticky top-0 z-50 bg-gray-900 border-b border-gray-800">
|
|
186
|
+
<div className="px-4 py-4">
|
|
187
|
+
<div className="flex items-center justify-between">
|
|
188
|
+
<div className="flex items-center space-x-3">
|
|
189
|
+
<div className="w-8 h-8 bg-blue-500 rounded-lg flex items-center justify-center">
|
|
190
|
+
<span className="text-white font-bold text-sm">LO</span>
|
|
191
|
+
</div>
|
|
192
|
+
<h1 className="text-white font-semibold text-lg">Tu Aplicación</h1>
|
|
193
|
+
</div>
|
|
194
|
+
|
|
195
|
+
{isMobile && (
|
|
196
|
+
<button className="p-2 text-gray-400 hover:text-white">
|
|
197
|
+
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
198
|
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 6h16M4 12h16M4 18h16" />
|
|
199
|
+
</svg>
|
|
200
|
+
</button>
|
|
201
|
+
)}
|
|
202
|
+
</div>
|
|
203
|
+
</div>
|
|
204
|
+
</nav>
|
|
205
|
+
)
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export default Navigation
|
|
209
|
+
`
|
|
210
|
+
fs.writeFileSync(path.join(componentsDir, 'Navigation.tsx'), navigationContent)
|
|
211
|
+
console.log(' ✅ Navigation.tsx (genérico)')
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Generar Footer genérico
|
|
215
|
+
if (components.includes('Footer')) {
|
|
216
|
+
const footerContent = `import { useResponsiveLayout } from 'responsive-system'
|
|
217
|
+
|
|
218
|
+
const Footer = () => {
|
|
219
|
+
const { breakpoint } = useResponsiveLayout()
|
|
220
|
+
|
|
221
|
+
return (
|
|
222
|
+
<footer className="bg-gray-900 border-t border-gray-800">
|
|
223
|
+
<div className="px-4 py-6">
|
|
224
|
+
<div className="max-w-7xl mx-auto">
|
|
225
|
+
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
|
226
|
+
<div>
|
|
227
|
+
<h3 className="text-white font-semibold mb-2">Tu Aplicación</h3>
|
|
228
|
+
<p className="text-gray-400 text-sm">
|
|
229
|
+
Descripción de tu aplicación aquí.
|
|
230
|
+
</p>
|
|
231
|
+
</div>
|
|
232
|
+
|
|
233
|
+
<div>
|
|
234
|
+
<h3 className="text-white font-semibold mb-2">Enlaces</h3>
|
|
235
|
+
<ul className="space-y-1 text-gray-400 text-sm">
|
|
236
|
+
<li><a href="#" className="hover:text-white">Inicio</a></li>
|
|
237
|
+
<li><a href="#" className="hover:text-white">Acerca</a></li>
|
|
238
|
+
<li><a href="#" className="hover:text-white">Contacto</a></li>
|
|
239
|
+
</ul>
|
|
240
|
+
</div>
|
|
241
|
+
|
|
242
|
+
<div>
|
|
243
|
+
<h3 className="text-white font-semibold mb-2">Info</h3>
|
|
244
|
+
<p className="text-gray-400 text-xs">
|
|
245
|
+
Breakpoint: <span className="text-blue-400">{breakpoint.toUpperCase()}</span>
|
|
246
|
+
</p>
|
|
247
|
+
</div>
|
|
248
|
+
</div>
|
|
249
|
+
|
|
250
|
+
<div className="border-t border-gray-800 mt-6 pt-4 text-center">
|
|
251
|
+
<p className="text-gray-500 text-xs">
|
|
252
|
+
© {new Date().getFullYear()} Tu Aplicación. Todos los derechos reservados.
|
|
253
|
+
</p>
|
|
254
|
+
</div>
|
|
255
|
+
</div>
|
|
256
|
+
</div>
|
|
257
|
+
</footer>
|
|
258
|
+
)
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
export default Footer
|
|
262
|
+
`
|
|
263
|
+
fs.writeFileSync(path.join(componentsDir, 'Footer.tsx'), footerContent)
|
|
264
|
+
console.log(' ✅ Footer.tsx (genérico)')
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// Generar Sidebar genérico
|
|
268
|
+
if (components.includes('Sidebar')) {
|
|
269
|
+
const sidebarContent = `import { useResponsiveLayout } from 'responsive-system'
|
|
270
|
+
import { useSidebar } from 'responsive-system'
|
|
271
|
+
|
|
272
|
+
const Sidebar = () => {
|
|
273
|
+
const { isMobile, isTablet } = useResponsiveLayout()
|
|
274
|
+
const { sidebarOpen, setSidebarOpen } = useSidebar()
|
|
275
|
+
|
|
276
|
+
const menuItems = [
|
|
277
|
+
{ id: 'home', label: 'Inicio' },
|
|
278
|
+
{ id: 'about', label: 'Acerca' },
|
|
279
|
+
{ id: 'contact', label: 'Contacto' },
|
|
280
|
+
]
|
|
281
|
+
|
|
282
|
+
return (
|
|
283
|
+
<>
|
|
284
|
+
{/* Hamburger button para móvil */}
|
|
285
|
+
{isMobile && (
|
|
286
|
+
<button
|
|
287
|
+
onClick={() => setSidebarOpen(true)}
|
|
288
|
+
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"
|
|
289
|
+
>
|
|
290
|
+
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
291
|
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 6h16M4 12h16M4 18h16" />
|
|
292
|
+
</svg>
|
|
293
|
+
</button>
|
|
294
|
+
)}
|
|
295
|
+
|
|
296
|
+
{/* Sidebar desktop */}
|
|
297
|
+
<aside className={\`bg-gray-900 border-r border-gray-800 \${isMobile ? 'hidden' : 'w-64 flex-shrink-0'} \${isTablet ? 'w-56' : 'w-64'}\`}>
|
|
298
|
+
<div className="p-6 flex flex-col h-full">
|
|
299
|
+
<div className="flex items-center space-x-3 mb-8">
|
|
300
|
+
<div className="w-8 h-8 bg-blue-500 rounded-lg flex items-center justify-center">
|
|
301
|
+
<span className="text-white font-bold text-sm">LO</span>
|
|
302
|
+
</div>
|
|
303
|
+
<span className="text-white font-bold text-lg">Tu Aplicación</span>
|
|
304
|
+
</div>
|
|
305
|
+
|
|
306
|
+
<nav className="space-y-2">
|
|
307
|
+
{menuItems.map((item) => (
|
|
308
|
+
<button
|
|
309
|
+
key={item.id}
|
|
310
|
+
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"
|
|
311
|
+
>
|
|
312
|
+
<span className="font-medium">{item.label}</span>
|
|
313
|
+
</button>
|
|
314
|
+
))}
|
|
315
|
+
</nav>
|
|
316
|
+
</div>
|
|
317
|
+
</aside>
|
|
318
|
+
|
|
319
|
+
{/* Sidebar móvil desplegable */}
|
|
320
|
+
{isMobile && sidebarOpen && (
|
|
321
|
+
<div className="fixed inset-0 z-40 bg-black/50" onClick={() => setSidebarOpen(false)}>
|
|
322
|
+
<div className="fixed top-0 left-0 w-64 h-full bg-gray-900 border-r border-gray-800">
|
|
323
|
+
<div className="p-6 flex flex-col h-full pt-20">
|
|
324
|
+
<div className="flex items-center space-x-3 mb-8">
|
|
325
|
+
<div className="w-8 h-8 bg-blue-500 rounded-lg flex items-center justify-center">
|
|
326
|
+
<span className="text-white font-bold text-sm">LO</span>
|
|
327
|
+
</div>
|
|
328
|
+
<span className="text-white font-bold text-lg">Tu Aplicación</span>
|
|
329
|
+
</div>
|
|
330
|
+
|
|
331
|
+
<nav className="space-y-2">
|
|
332
|
+
{menuItems.map((item) => (
|
|
333
|
+
<button
|
|
334
|
+
key={item.id}
|
|
335
|
+
onClick={() => setSidebarOpen(false)}
|
|
336
|
+
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"
|
|
337
|
+
>
|
|
338
|
+
<span className="font-medium">{item.label}</span>
|
|
339
|
+
</button>
|
|
340
|
+
))}
|
|
341
|
+
</nav>
|
|
342
|
+
</div>
|
|
343
|
+
</div>
|
|
344
|
+
</div>
|
|
345
|
+
)}
|
|
346
|
+
</>
|
|
347
|
+
)
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
export default Sidebar
|
|
351
|
+
`
|
|
352
|
+
fs.writeFileSync(path.join(componentsDir, 'Sidebar.tsx'), sidebarContent)
|
|
353
|
+
console.log(' ✅ Sidebar.tsx (genérico)')
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// Crear index.ts para exportar los componentes
|
|
357
|
+
const indexContent = components.map(c => `export { default as ${c} } from './${c}'`).join('\n')
|
|
358
|
+
fs.writeFileSync(path.join(componentsDir, 'index.ts'), indexContent)
|
|
359
|
+
console.log(' ✅ index.ts')
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// Función para copiar el hook useResponsive y sus dependencias
|
|
363
|
+
function copyUseResponsiveHook() {
|
|
364
|
+
console.log(' 📦 Copiando hook useResponsive y dependencias...')
|
|
365
|
+
|
|
366
|
+
// Crear directorio hooks
|
|
367
|
+
const hooksDir = path.join(projectRoot, 'src', 'hooks')
|
|
368
|
+
if (!fs.existsSync(hooksDir)) {
|
|
369
|
+
fs.mkdirSync(hooksDir, { recursive: true })
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// Copiar tipos
|
|
373
|
+
const typesDir = path.join(projectRoot, 'src', 'types')
|
|
374
|
+
if (!fs.existsSync(typesDir)) {
|
|
375
|
+
fs.mkdirSync(typesDir, { recursive: true })
|
|
376
|
+
}
|
|
377
|
+
copyFileFromPackage('src/types/responsive.ts', 'src/types/responsive.ts')
|
|
378
|
+
console.log(' ✅ types/responsive.ts')
|
|
379
|
+
|
|
380
|
+
// Copiar constantes
|
|
381
|
+
const constantsDir = path.join(projectRoot, 'src', 'constants')
|
|
382
|
+
if (!fs.existsSync(constantsDir)) {
|
|
383
|
+
fs.mkdirSync(constantsDir, { recursive: true })
|
|
384
|
+
}
|
|
385
|
+
copyFileFromPackage('src/constants/breakpoints.ts', 'src/constants/breakpoints.ts')
|
|
386
|
+
console.log(' ✅ constants/breakpoints.ts')
|
|
387
|
+
|
|
388
|
+
// Copiar hook useResponsive
|
|
389
|
+
copyFileFromPackage('src/hooks/useResponsive.ts', 'src/hooks/useResponsive.ts')
|
|
390
|
+
console.log(' ✅ hooks/useResponsive.ts')
|
|
391
|
+
|
|
392
|
+
// Crear index.ts para exportar el hook
|
|
393
|
+
const indexContent = `export { useResponsive } from './useResponsive'
|
|
394
|
+
export type { ResponsiveState, Breakpoint, Orientation } from '../types/responsive'
|
|
395
|
+
export { DEFAULT_BREAKPOINTS, getCurrentBreakpoint, getBreakpointIndex, getBreakpointValue } from '../constants/breakpoints'
|
|
396
|
+
`
|
|
397
|
+
fs.writeFileSync(path.join(hooksDir, 'index.ts'), indexContent)
|
|
398
|
+
console.log(' ✅ hooks/index.ts')
|
|
399
|
+
}
|
|
400
|
+
|
|
59
401
|
// ESTRATEGIA: Modificar directamente el package.json primero, luego instalar
|
|
60
402
|
console.log('📦 responsive-system: Verificando dependencias...')
|
|
61
403
|
|
|
62
404
|
// Agregar React a dependencies SOLO si NO está en package.json
|
|
63
|
-
// NO verificar node_modules para evitar múltiples copias de React
|
|
64
405
|
if (!hasReactInPackageJson) {
|
|
65
406
|
console.log(' ➕ Agregando React a dependencies...')
|
|
66
407
|
if (!packageJson.dependencies) {
|
|
67
408
|
packageJson.dependencies = {}
|
|
68
409
|
}
|
|
69
|
-
// Usar versión compatible con peerDependencies del paquete
|
|
70
410
|
packageJson.dependencies['react'] = '^19.1.1'
|
|
71
411
|
packageJson.dependencies['react-dom'] = '^19.1.1'
|
|
72
412
|
needsUpdate = true
|
|
@@ -85,9 +425,9 @@ if (isProjectEmpty && !hasVite) {
|
|
|
85
425
|
needsUpdate = true
|
|
86
426
|
}
|
|
87
427
|
|
|
88
|
-
// Agregar
|
|
428
|
+
// Agregar Tailwind y sus dependencias a devDependencies
|
|
89
429
|
if (!tailwindInDevDeps) {
|
|
90
|
-
console.log(' ➕ Agregando Tailwind a devDependencies...')
|
|
430
|
+
console.log(' ➕ Agregando Tailwind y PostCSS a devDependencies...')
|
|
91
431
|
if (!packageJson.devDependencies) {
|
|
92
432
|
packageJson.devDependencies = {}
|
|
93
433
|
}
|
|
@@ -98,6 +438,7 @@ if (!tailwindInDevDeps) {
|
|
|
98
438
|
needsUpdate = true
|
|
99
439
|
}
|
|
100
440
|
|
|
441
|
+
// Agregar TypeScript y sus tipos a devDependencies
|
|
101
442
|
if (!typescriptInDevDeps) {
|
|
102
443
|
console.log(' ➕ Agregando TypeScript a devDependencies...')
|
|
103
444
|
if (!packageJson.devDependencies) {
|
|
@@ -139,21 +480,73 @@ if (needsUpdate) {
|
|
|
139
480
|
console.log('✅ responsive-system: Todas las dependencias ya están en package.json')
|
|
140
481
|
}
|
|
141
482
|
|
|
142
|
-
// Si el proyecto está vacío, crear
|
|
483
|
+
// Si el proyecto está vacío, crear README básico PRIMERO
|
|
143
484
|
if (isProjectEmpty) {
|
|
485
|
+
// Crear README básico INMEDIATAMENTE para que el usuario sepa qué hacer
|
|
486
|
+
const readmePath = path.join(projectRoot, 'README.md')
|
|
487
|
+
if (!fs.existsSync(readmePath)) {
|
|
488
|
+
const basicReadme = `# Tu Aplicación
|
|
489
|
+
|
|
490
|
+
## 🚀 Configuración Inicial
|
|
491
|
+
|
|
492
|
+
Acabas de instalar **responsive-system**. Para completar la configuración, ejecuta:
|
|
493
|
+
|
|
494
|
+
\`\`\`bash
|
|
495
|
+
npx responsive-system-setup
|
|
496
|
+
\`\`\`
|
|
497
|
+
|
|
498
|
+
Este comando:
|
|
499
|
+
- ✅ Instalará React, TypeScript, Tailwind y Vite automáticamente
|
|
500
|
+
- ✅ Te permitirá seleccionar el layout que prefieras
|
|
501
|
+
- ✅ Creará la estructura base del proyecto
|
|
502
|
+
- ✅ Generará componentes genéricos y página de ejemplo
|
|
503
|
+
|
|
504
|
+
## 📦 ¿Qué es responsive-system?
|
|
505
|
+
|
|
506
|
+
Sistema completo de layout responsive con auto-scaling para React + Tailwind CSS.
|
|
507
|
+
|
|
508
|
+
- Auto-scaling automático (texto, espaciado, sombras)
|
|
509
|
+
- Layouts pre-configurados (default, sidebar, dashboard, minimal)
|
|
510
|
+
- Hook \`useResponsive\` para configuración manual
|
|
511
|
+
- Sin media queries necesarias
|
|
512
|
+
|
|
513
|
+
---
|
|
514
|
+
|
|
515
|
+
**Siguiente paso:** Ejecuta \`npx responsive-system-setup\` para comenzar 🚀
|
|
516
|
+
`
|
|
517
|
+
fs.writeFileSync(readmePath, basicReadme)
|
|
518
|
+
console.log(' ✅ README.md básico creado con instrucciones')
|
|
519
|
+
}
|
|
520
|
+
|
|
144
521
|
console.log('')
|
|
145
522
|
console.log('📦 responsive-system: Proyecto vacío detectado, creando estructura base...')
|
|
523
|
+
console.log('')
|
|
524
|
+
console.log('💡 IMPORTANTE: Si este es tu primer uso, este script configurará todo automáticamente.')
|
|
525
|
+
console.log(' Si ya tienes el proyecto configurado, puedes cancelar (Ctrl+C)')
|
|
526
|
+
console.log('')
|
|
527
|
+
|
|
528
|
+
// Preguntar qué layout quiere
|
|
529
|
+
const selectedLayout = await askLayout()
|
|
530
|
+
console.log(` ✅ Layout seleccionado: "${selectedLayout}"`)
|
|
531
|
+
console.log('')
|
|
146
532
|
|
|
147
533
|
// Crear estructura de directorios
|
|
148
|
-
const dirs = ['src', 'src/components', 'src/pages', 'public']
|
|
534
|
+
const dirs = ['src', 'src/components', 'src/components/layout', 'src/pages', 'src/hooks', 'src/types', 'src/constants', 'public']
|
|
149
535
|
dirs.forEach(dir => {
|
|
150
536
|
const dirPath = path.join(projectRoot, dir)
|
|
151
537
|
if (!fs.existsSync(dirPath)) {
|
|
152
538
|
fs.mkdirSync(dirPath, { recursive: true })
|
|
153
|
-
console.log(` ✅ Creado: ${dir}/`)
|
|
154
539
|
}
|
|
155
540
|
})
|
|
156
541
|
|
|
542
|
+
// Generar componentes genéricos según layout seleccionado
|
|
543
|
+
generateLayoutComponents(selectedLayout)
|
|
544
|
+
console.log('')
|
|
545
|
+
|
|
546
|
+
// Copiar hook useResponsive
|
|
547
|
+
copyUseResponsiveHook()
|
|
548
|
+
console.log('')
|
|
549
|
+
|
|
157
550
|
// Crear vite.config.ts
|
|
158
551
|
const viteConfigPath = path.join(projectRoot, 'vite.config.ts')
|
|
159
552
|
if (!fs.existsSync(viteConfigPath)) {
|
|
@@ -303,7 +696,7 @@ import './index.css'
|
|
|
303
696
|
|
|
304
697
|
ReactDOM.createRoot(document.getElementById('root')!).render(
|
|
305
698
|
<React.StrictMode>
|
|
306
|
-
<ResponsiveLayoutProvider defaultLayout="
|
|
699
|
+
<ResponsiveLayoutProvider defaultLayout="${selectedLayout}">
|
|
307
700
|
<MainLayout>
|
|
308
701
|
<App />
|
|
309
702
|
</MainLayout>
|
|
@@ -318,81 +711,114 @@ ReactDOM.createRoot(document.getElementById('root')!).render(
|
|
|
318
711
|
// Crear src/index.css
|
|
319
712
|
const indexCssPath = path.join(projectRoot, 'src', 'index.css')
|
|
320
713
|
if (!fs.existsSync(indexCssPath)) {
|
|
321
|
-
// Tailwind CSS v4 usa @import en lugar de @tailwind
|
|
322
714
|
const indexCss = `@import "tailwindcss";
|
|
323
715
|
`
|
|
324
716
|
fs.writeFileSync(indexCssPath, indexCss)
|
|
325
717
|
console.log(' ✅ Creado: src/index.css')
|
|
326
718
|
}
|
|
327
719
|
|
|
328
|
-
// Crear src/
|
|
329
|
-
const
|
|
330
|
-
if (!fs.existsSync(
|
|
331
|
-
const
|
|
720
|
+
// Crear src/pages/HomePage.tsx con página de ejemplo simple
|
|
721
|
+
const homePagePath = path.join(projectRoot, 'src', 'pages', 'HomePage.tsx')
|
|
722
|
+
if (!fs.existsSync(homePagePath)) {
|
|
723
|
+
const homePage = `import { useResponsiveLayout } from 'responsive-system'
|
|
724
|
+
import { useResponsive } from '../hooks'
|
|
332
725
|
|
|
333
|
-
function
|
|
334
|
-
const { breakpoint, isMobile
|
|
726
|
+
function HomePage() {
|
|
727
|
+
const { breakpoint, isMobile } = useResponsiveLayout()
|
|
728
|
+
const responsive = useResponsive()
|
|
335
729
|
|
|
336
730
|
return (
|
|
337
|
-
<div className="min-h-screen bg-gray-50
|
|
338
|
-
<div className="max-w-7xl mx-auto space-y-
|
|
339
|
-
{/*
|
|
340
|
-
<div className="bg-white rounded-lg shadow-
|
|
341
|
-
<h1 className="text-4xl font-bold mb-4">
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
<p className="text-sm text-gray-600">Layout actual</p>
|
|
349
|
-
<p className="text-2xl font-bold capitalize">{layout.current}</p>
|
|
350
|
-
</div>
|
|
351
|
-
<div className="p-4 bg-purple-50 rounded">
|
|
352
|
-
<p className="text-sm text-gray-600">Dispositivo</p>
|
|
353
|
-
<p className="text-2xl font-bold">{isMobile ? 'Móvil' : 'Desktop'}</p>
|
|
354
|
-
</div>
|
|
355
|
-
</div>
|
|
731
|
+
<div className="min-h-screen bg-gray-50 py-8 px-4">
|
|
732
|
+
<div className="max-w-7xl mx-auto space-y-8">
|
|
733
|
+
{/* Hero Section */}
|
|
734
|
+
<div className="bg-white rounded-lg shadow-md p-8 text-center">
|
|
735
|
+
<h1 className="text-4xl font-bold text-gray-900 mb-4">
|
|
736
|
+
Bienvenido a tu Aplicación
|
|
737
|
+
</h1>
|
|
738
|
+
<p className="text-lg text-gray-600 max-w-2xl mx-auto">
|
|
739
|
+
Esta es una página de ejemplo que demuestra el sistema responsive con auto-scaling.
|
|
740
|
+
Todo el contenido se ajusta automáticamente según el tamaño de pantalla.
|
|
741
|
+
</p>
|
|
356
742
|
</div>
|
|
357
743
|
|
|
358
|
-
{/*
|
|
359
|
-
<div className="
|
|
360
|
-
<
|
|
361
|
-
|
|
744
|
+
{/* Info Cards */}
|
|
745
|
+
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
|
|
746
|
+
<div className="bg-blue-50 rounded-lg p-6 border border-blue-200">
|
|
747
|
+
<h3 className="text-sm font-semibold text-blue-900 mb-2">Breakpoint</h3>
|
|
748
|
+
<p className="text-2xl font-bold text-blue-700">{breakpoint.toUpperCase()}</p>
|
|
749
|
+
</div>
|
|
750
|
+
<div className="bg-green-50 rounded-lg p-6 border border-green-200">
|
|
751
|
+
<h3 className="text-sm font-semibold text-green-900 mb-2">Dispositivo</h3>
|
|
752
|
+
<p className="text-2xl font-bold text-green-700">{isMobile ? 'Móvil' : 'Desktop'}</p>
|
|
753
|
+
</div>
|
|
754
|
+
<div className="bg-purple-50 rounded-lg p-6 border border-purple-200">
|
|
755
|
+
<h3 className="text-sm font-semibold text-purple-900 mb-2">Ancho</h3>
|
|
756
|
+
<p className="text-2xl font-bold text-purple-700">{responsive.width}px</p>
|
|
757
|
+
</div>
|
|
758
|
+
<div className="bg-orange-50 rounded-lg p-6 border border-orange-200">
|
|
759
|
+
<h3 className="text-sm font-semibold text-orange-900 mb-2">Alto</h3>
|
|
760
|
+
<p className="text-2xl font-bold text-orange-700">{responsive.height}px</p>
|
|
761
|
+
</div>
|
|
362
762
|
</div>
|
|
363
763
|
|
|
364
|
-
{/* Cards
|
|
764
|
+
{/* Content Cards */}
|
|
365
765
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
|
366
766
|
{[1, 2, 3, 4, 5, 6].map((i) => (
|
|
367
|
-
<div key={i} className="bg-white rounded-lg shadow-
|
|
368
|
-
<
|
|
767
|
+
<div key={i} className="bg-white rounded-lg shadow-md p-6 hover:shadow-lg transition-shadow">
|
|
768
|
+
<div className="w-12 h-12 bg-blue-500 rounded-lg flex items-center justify-center mb-4">
|
|
769
|
+
<span className="text-white font-bold text-xl">{i}</span>
|
|
770
|
+
</div>
|
|
771
|
+
<h3 className="text-xl font-bold text-gray-900 mb-2">Card {i}</h3>
|
|
369
772
|
<p className="text-gray-600">
|
|
370
|
-
Este es un ejemplo de card
|
|
371
|
-
|
|
773
|
+
Este es un ejemplo de card. El texto, espaciado y sombras se ajustan automáticamente
|
|
774
|
+
según el tamaño de pantalla gracias al sistema de auto-scaling.
|
|
372
775
|
</p>
|
|
373
776
|
</div>
|
|
374
777
|
))}
|
|
375
778
|
</div>
|
|
376
779
|
|
|
377
|
-
{/*
|
|
378
|
-
<div className="bg-white rounded-lg shadow-
|
|
379
|
-
<h2 className="text-2xl font-bold mb-4">
|
|
380
|
-
<
|
|
381
|
-
<
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
780
|
+
{/* Info Section */}
|
|
781
|
+
<div className="bg-white rounded-lg shadow-md p-8">
|
|
782
|
+
<h2 className="text-2xl font-bold text-gray-900 mb-4">Sistema Responsive</h2>
|
|
783
|
+
<div className="space-y-3 text-gray-700">
|
|
784
|
+
<p>
|
|
785
|
+
<strong className="text-gray-900">Auto-scaling activo:</strong> Todo el contenido escala
|
|
786
|
+
automáticamente según el breakpoint actual (texto, espaciado, sombras).
|
|
787
|
+
</p>
|
|
788
|
+
<p>
|
|
789
|
+
<strong className="text-gray-900">Hook useResponsive:</strong> Disponible en{' '}
|
|
790
|
+
<code className="bg-gray-100 px-2 py-1 rounded text-sm">src/hooks/useResponsive.ts</code>{' '}
|
|
791
|
+
para configuración manual cuando lo necesites.
|
|
792
|
+
</p>
|
|
793
|
+
<p>
|
|
794
|
+
<strong className="text-gray-900">Layout actual:</strong> <span className="capitalize">{selectedLayout}</span>
|
|
795
|
+
</p>
|
|
796
|
+
</div>
|
|
386
797
|
</div>
|
|
387
798
|
</div>
|
|
388
799
|
</div>
|
|
389
800
|
)
|
|
390
801
|
}
|
|
391
802
|
|
|
803
|
+
export default HomePage
|
|
804
|
+
`
|
|
805
|
+
fs.writeFileSync(homePagePath, homePage)
|
|
806
|
+
console.log(' ✅ Creado: src/pages/HomePage.tsx (página de ejemplo simple)')
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
// Crear src/App.tsx que importa la página
|
|
810
|
+
const appTsxPath = path.join(projectRoot, 'src', 'App.tsx')
|
|
811
|
+
if (!fs.existsSync(appTsxPath)) {
|
|
812
|
+
const appTsx = `import HomePage from './pages/HomePage'
|
|
813
|
+
|
|
814
|
+
function App() {
|
|
815
|
+
return <HomePage />
|
|
816
|
+
}
|
|
817
|
+
|
|
392
818
|
export default App
|
|
393
819
|
`
|
|
394
820
|
fs.writeFileSync(appTsxPath, appTsx)
|
|
395
|
-
console.log(' ✅ Creado: src/App.tsx
|
|
821
|
+
console.log(' ✅ Creado: src/App.tsx')
|
|
396
822
|
}
|
|
397
823
|
|
|
398
824
|
// Actualizar package.json con scripts
|
|
@@ -412,6 +838,106 @@ export default App
|
|
|
412
838
|
fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2))
|
|
413
839
|
console.log(' ✅ Actualizado: package.json con scripts')
|
|
414
840
|
|
|
841
|
+
// Actualizar README.md con información completa
|
|
842
|
+
// Si ya existe (el básico), lo reemplazamos con el completo
|
|
843
|
+
if (fs.existsSync(readmePath)) {
|
|
844
|
+
const readmeContent = `# Tu Aplicación
|
|
845
|
+
|
|
846
|
+
Proyecto inicializado con **responsive-system**.
|
|
847
|
+
|
|
848
|
+
## 🚀 Inicio Rápido
|
|
849
|
+
|
|
850
|
+
Después de instalar el paquete, ejecuta el script de configuración:
|
|
851
|
+
|
|
852
|
+
\`\`\`bash
|
|
853
|
+
npx responsive-system-setup
|
|
854
|
+
\`\`\`
|
|
855
|
+
|
|
856
|
+
O si prefieres ejecutarlo directamente:
|
|
857
|
+
|
|
858
|
+
\`\`\`bash
|
|
859
|
+
node node_modules/responsive-system/scripts/postinstall.js
|
|
860
|
+
\`\`\`
|
|
861
|
+
|
|
862
|
+
Luego inicia el servidor de desarrollo:
|
|
863
|
+
|
|
864
|
+
\`\`\`bash
|
|
865
|
+
npm run dev
|
|
866
|
+
\`\`\`
|
|
867
|
+
|
|
868
|
+
Abre http://localhost:5173 en tu navegador.
|
|
869
|
+
|
|
870
|
+
## 📁 Estructura del Proyecto
|
|
871
|
+
|
|
872
|
+
- \`src/pages/\` - Páginas de tu aplicación
|
|
873
|
+
- \`src/components/layout/\` - Componentes de layout (Navigation, Footer, Sidebar)
|
|
874
|
+
- \`src/hooks/useResponsive.ts\` - Hook para configuración responsive manual
|
|
875
|
+
- \`src/App.tsx\` - Componente principal
|
|
876
|
+
- \`src/main.tsx\` - Punto de entrada
|
|
877
|
+
|
|
878
|
+
## 🎨 Layout Actual
|
|
879
|
+
|
|
880
|
+
Layout seleccionado: **${selectedLayout}**
|
|
881
|
+
|
|
882
|
+
- **default**: Navigation + Footer
|
|
883
|
+
- **sidebar**: Sidebar lateral
|
|
884
|
+
- **dashboard**: Sidebar + Footer
|
|
885
|
+
- **minimal**: Sin componentes (solo contenido)
|
|
886
|
+
|
|
887
|
+
## 🔧 Personalización
|
|
888
|
+
|
|
889
|
+
### Cambiar el Layout
|
|
890
|
+
|
|
891
|
+
Si quieres cambiar el layout después de la instalación:
|
|
892
|
+
|
|
893
|
+
\`\`\`bash
|
|
894
|
+
npx responsive-system-setup
|
|
895
|
+
\`\`\`
|
|
896
|
+
|
|
897
|
+
### Usar el Hook useResponsive
|
|
898
|
+
|
|
899
|
+
El hook \`useResponsive\` está disponible localmente en \`src/hooks/useResponsive.ts\`:
|
|
900
|
+
|
|
901
|
+
\`\`\`typescript
|
|
902
|
+
import { useResponsive } from '../hooks'
|
|
903
|
+
|
|
904
|
+
function MyComponent() {
|
|
905
|
+
const { breakpoint, isMobile, isDesktop, width, height } = useResponsive()
|
|
906
|
+
|
|
907
|
+
// Tu lógica aquí
|
|
908
|
+
}
|
|
909
|
+
\`\`\`
|
|
910
|
+
|
|
911
|
+
### Personalizar Componentes
|
|
912
|
+
|
|
913
|
+
Los componentes de layout están en \`src/components/layout/\` y puedes modificarlos según tus necesidades.
|
|
914
|
+
|
|
915
|
+
## 📚 Documentación
|
|
916
|
+
|
|
917
|
+
- [responsive-system en npm](https://www.npmjs.com/package/responsive-system)
|
|
918
|
+
- Hook \`useResponsive\`: \`src/hooks/useResponsive.ts\`
|
|
919
|
+
- Tipos TypeScript: \`src/types/responsive.ts\`
|
|
920
|
+
|
|
921
|
+
## ✅ Características
|
|
922
|
+
|
|
923
|
+
- ✅ Auto-scaling automático (texto, espaciado, sombras)
|
|
924
|
+
- ✅ Layout responsive que se adapta a todos los tamaños
|
|
925
|
+
- ✅ Hook \`useResponsive\` para configuración manual
|
|
926
|
+
- ✅ Componentes de layout personalizables
|
|
927
|
+
- ✅ Sin media queries necesarias
|
|
928
|
+
|
|
929
|
+
---
|
|
930
|
+
|
|
931
|
+
**¡Listo para empezar a desarrollar!** 🎉
|
|
932
|
+
`
|
|
933
|
+
fs.writeFileSync(readmePath, readmeContent)
|
|
934
|
+
console.log(' ✅ Actualizado: README.md con instrucciones completas')
|
|
935
|
+
} else {
|
|
936
|
+
// Si no existe, crearlo completo
|
|
937
|
+
fs.writeFileSync(readmePath, readmeContent)
|
|
938
|
+
console.log(' ✅ Creado: README.md con instrucciones completas')
|
|
939
|
+
}
|
|
940
|
+
|
|
415
941
|
console.log('')
|
|
416
942
|
console.log('🎉 responsive-system: Proyecto inicializado correctamente!')
|
|
417
943
|
console.log('')
|
|
@@ -419,8 +945,23 @@ export default App
|
|
|
419
945
|
console.log(' 1. npm run dev')
|
|
420
946
|
console.log(' 2. Abre http://localhost:5173')
|
|
421
947
|
console.log('')
|
|
948
|
+
console.log(`Layout seleccionado: "${selectedLayout}"`)
|
|
949
|
+
console.log(' - Componentes generados en src/components/layout/')
|
|
950
|
+
console.log(' - Hook useResponsive disponible en src/hooks/useResponsive.ts')
|
|
951
|
+
console.log(' - Página de ejemplo en src/pages/HomePage.tsx')
|
|
952
|
+
console.log(' - README.md creado con instrucciones')
|
|
953
|
+
console.log('')
|
|
954
|
+
console.log('💡 Para cambiar el layout después, ejecuta: npx responsive-system-setup')
|
|
955
|
+
console.log('')
|
|
956
|
+
} else {
|
|
957
|
+
console.log('✅ responsive-system: Proyecto ya inicializado')
|
|
422
958
|
}
|
|
423
959
|
|
|
424
960
|
console.log('')
|
|
425
961
|
console.log('✅ responsive-system: postinstall completado')
|
|
426
962
|
console.log('')
|
|
963
|
+
console.log('📝 NOTA: Si instalaste el paquete pero este script no se ejecutó automáticamente,')
|
|
964
|
+
console.log(' ejecuta uno de estos comandos:')
|
|
965
|
+
console.log(' - npx responsive-system-setup')
|
|
966
|
+
console.log(' - node node_modules/responsive-system/scripts/postinstall.js')
|
|
967
|
+
console.log('')
|