responsive-system 1.1.4 → 1.2.0
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 +5 -1
- package/scripts/postinstall.js +8 -1
- package/src/App.css +42 -0
- package/src/App.tsx +29 -0
- package/src/assets/react.svg +1 -0
- package/src/components/LayoutSwitcher.tsx +62 -0
- package/src/components/ResponsiveDemo.tsx +282 -0
- package/src/components/layout/Footer.tsx +90 -0
- package/src/components/layout/Header.tsx +105 -0
- package/src/components/layout/Navigation.tsx +96 -0
- package/src/components/layout/Sidebar.tsx +108 -0
- package/src/components/layout/index.ts +4 -0
- package/src/config/layout.ts +61 -0
- package/src/constants/breakpoints.ts +48 -0
- package/src/context/NavigationContext.tsx +32 -0
- package/src/context/ResponsiveLayoutContext.tsx +37 -0
- package/src/context/SidebarContext.tsx +26 -0
- package/src/context/index.ts +4 -0
- package/src/hooks/index.ts +4 -0
- package/src/hooks/useLayout.ts +27 -0
- package/src/hooks/useResponsive.ts +189 -0
- package/src/hooks/useResponsiveLayout.ts +51 -0
- package/src/index.css +1 -0
- package/src/index.ts +100 -0
- package/src/layouts/DashboardLayout.tsx +76 -0
- package/src/layouts/DefaultLayout.tsx +30 -0
- package/src/layouts/MainLayout.tsx +38 -0
- package/src/layouts/MinimalLayout.tsx +20 -0
- package/src/layouts/SidebarLayout.tsx +36 -0
- package/src/layouts/index.ts +5 -0
- package/src/main.tsx +10 -0
- package/src/pages/ResponsiveTestPage.tsx +400 -0
- package/src/providers/ResponsiveLayoutProvider.tsx +92 -0
- package/src/providers/ResponsiveProvider.tsx +18 -0
- package/src/providers/index.ts +3 -0
- package/src/types/responsive.ts +64 -0
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { useResponsiveLayout } from '../../hooks'
|
|
2
|
+
import { useNavigation, useSidebar } from '../../context'
|
|
3
|
+
|
|
4
|
+
const Header = () => {
|
|
5
|
+
const { isMobile } = useResponsiveLayout()
|
|
6
|
+
const { currentPage, setCurrentPage } = useNavigation()
|
|
7
|
+
const { sidebarOpen, setSidebarOpen } = useSidebar()
|
|
8
|
+
|
|
9
|
+
const menuItems = [
|
|
10
|
+
{ id: 'test', label: 'Suite de Test' },
|
|
11
|
+
{ id: 'demo', label: 'Demo' },
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
return (
|
|
15
|
+
<div className="sticky top-0 z-50">
|
|
16
|
+
<header className="bg-gradient-to-r from-gray-900 via-black to-gray-900 border-b border-cyan-500/20 shadow-2xl relative">
|
|
17
|
+
<div className="w-full">
|
|
18
|
+
<div className="px-4 py-4">
|
|
19
|
+
<div className="flex items-center justify-between">
|
|
20
|
+
<div className="flex items-center space-x-2">
|
|
21
|
+
{/* Hamburger button para móvil - A LA IZQUIERDA */}
|
|
22
|
+
{isMobile && (
|
|
23
|
+
<button
|
|
24
|
+
onClick={() => setSidebarOpen(true)}
|
|
25
|
+
className="p-2 rounded-lg text-gray-300 hover:text-cyan-400 hover:bg-cyan-500/10 transition-colors"
|
|
26
|
+
>
|
|
27
|
+
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
28
|
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 6h16M4 12h16M4 18h16" />
|
|
29
|
+
</svg>
|
|
30
|
+
</button>
|
|
31
|
+
)}
|
|
32
|
+
|
|
33
|
+
<div className="flex items-center space-x-2">
|
|
34
|
+
<div className="w-1.5 h-1.5 bg-cyan-400 rounded-full shadow-lg shadow-cyan-400/50 animate-pulse"></div>
|
|
35
|
+
<h3 className="text-base font-black text-white tracking-tight">
|
|
36
|
+
Sistema Responsivo
|
|
37
|
+
</h3>
|
|
38
|
+
</div>
|
|
39
|
+
<div className="px-2 py-0.5 text-cyan-400 font-mono bg-black/50 border border-cyan-500/30 rounded text-xs font-bold tracking-widest">
|
|
40
|
+
DASHBOARD
|
|
41
|
+
</div>
|
|
42
|
+
</div>
|
|
43
|
+
|
|
44
|
+
<div className="flex items-center space-x-2">
|
|
45
|
+
{/* Botones visibles solo en desktop */}
|
|
46
|
+
{!isMobile && menuItems.map((page) => (
|
|
47
|
+
<button
|
|
48
|
+
key={page.id}
|
|
49
|
+
onClick={() => setCurrentPage(page.id as 'demo' | 'test')}
|
|
50
|
+
className={`px-3 py-1.5 rounded-lg transition-all font-bold text-xs tracking-wide border ${
|
|
51
|
+
currentPage === page.id
|
|
52
|
+
? 'bg-cyan-500/20 text-cyan-400 border-cyan-500/50'
|
|
53
|
+
: 'bg-black/50 text-gray-400 hover:text-gray-300 border-gray-700 hover:border-gray-600'
|
|
54
|
+
}`}
|
|
55
|
+
>
|
|
56
|
+
{page.label}
|
|
57
|
+
</button>
|
|
58
|
+
))}
|
|
59
|
+
</div>
|
|
60
|
+
</div>
|
|
61
|
+
</div>
|
|
62
|
+
</div>
|
|
63
|
+
</header>
|
|
64
|
+
|
|
65
|
+
{/* Sidebar móvil desplegable */}
|
|
66
|
+
{isMobile && sidebarOpen && (
|
|
67
|
+
<div className="fixed inset-0 z-40 bg-black/50" onClick={() => setSidebarOpen(false)}>
|
|
68
|
+
<div className="fixed top-0 left-0 w-64 h-full bg-gradient-to-b from-gray-900 to-black border-r border-cyan-500/20">
|
|
69
|
+
<div className="p-6 flex flex-col h-full pt-20">
|
|
70
|
+
{/* Logo */}
|
|
71
|
+
<div className="flex items-center space-x-3 mb-8">
|
|
72
|
+
<div className="w-8 h-8 bg-cyan-500 rounded-lg flex items-center justify-center">
|
|
73
|
+
<span className="text-white font-bold text-sm">RS</span>
|
|
74
|
+
</div>
|
|
75
|
+
<span className="text-white font-bold text-lg">Sistema Responsivo</span>
|
|
76
|
+
</div>
|
|
77
|
+
|
|
78
|
+
{/* Navigation */}
|
|
79
|
+
<nav className="space-y-2">
|
|
80
|
+
{menuItems.map((item) => (
|
|
81
|
+
<button
|
|
82
|
+
key={item.id}
|
|
83
|
+
onClick={() => {
|
|
84
|
+
setCurrentPage(item.id as 'demo' | 'test')
|
|
85
|
+
setSidebarOpen(false)
|
|
86
|
+
}}
|
|
87
|
+
className={`w-full flex items-center px-4 py-3 rounded-lg transition-all group text-left ${
|
|
88
|
+
currentPage === item.id
|
|
89
|
+
? 'bg-cyan-500/20 text-cyan-400 border border-cyan-500/50'
|
|
90
|
+
: 'text-gray-300 hover:text-cyan-400 hover:bg-cyan-500/10'
|
|
91
|
+
}`}
|
|
92
|
+
>
|
|
93
|
+
<span className="font-medium">{item.label}</span>
|
|
94
|
+
</button>
|
|
95
|
+
))}
|
|
96
|
+
</nav>
|
|
97
|
+
</div>
|
|
98
|
+
</div>
|
|
99
|
+
</div>
|
|
100
|
+
)}
|
|
101
|
+
</div>
|
|
102
|
+
)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export default Header
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { useState } from 'react'
|
|
2
|
+
import { useResponsiveLayout } from '../../hooks'
|
|
3
|
+
import { useNavigation } from '../../context'
|
|
4
|
+
|
|
5
|
+
const Navigation = () => {
|
|
6
|
+
const { isMobile, breakpoint } = useResponsiveLayout()
|
|
7
|
+
const { currentPage, setCurrentPage } = useNavigation()
|
|
8
|
+
const [sidebarOpen, setSidebarOpen] = useState(false)
|
|
9
|
+
|
|
10
|
+
const pages = [
|
|
11
|
+
{ id: 'demo', name: 'Demo' },
|
|
12
|
+
{ id: 'test', name: 'Suite de Test' }
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
return (
|
|
16
|
+
<div className="sticky top-0 z-50">
|
|
17
|
+
<nav className="bg-gradient-to-r from-gray-900 via-black to-gray-900 border-b border-cyan-500/20 shadow-2xl relative">
|
|
18
|
+
<div className="w-full">
|
|
19
|
+
<div className="px-4 py-4">
|
|
20
|
+
<div className="flex items-center justify-between">
|
|
21
|
+
<div className="flex items-center space-x-2">
|
|
22
|
+
<div className="flex items-center space-x-2">
|
|
23
|
+
<div className="w-1.5 h-1.5 bg-cyan-400 rounded-full shadow-lg shadow-cyan-400/50 animate-pulse"></div>
|
|
24
|
+
<h3 className="text-base font-black text-white tracking-tight">
|
|
25
|
+
Sistema Responsivo
|
|
26
|
+
</h3>
|
|
27
|
+
</div>
|
|
28
|
+
<div className="px-2 py-0.5 text-cyan-400 font-mono bg-black/50 border border-cyan-500/30 rounded text-xs font-bold tracking-widest">
|
|
29
|
+
{breakpoint.toUpperCase()}
|
|
30
|
+
</div>
|
|
31
|
+
</div>
|
|
32
|
+
|
|
33
|
+
<div className="flex items-center space-x-2">
|
|
34
|
+
{/* Botones visibles solo en desktop */}
|
|
35
|
+
{!isMobile && pages.map(page => (
|
|
36
|
+
<button
|
|
37
|
+
key={page.id}
|
|
38
|
+
onClick={() => setCurrentPage(page.id as 'demo' | 'test')}
|
|
39
|
+
className={`px-3 py-1.5 rounded-lg transition-all font-bold text-xs tracking-wide border ${
|
|
40
|
+
currentPage === page.id
|
|
41
|
+
? 'bg-cyan-500/20 text-cyan-400 border-cyan-500/50'
|
|
42
|
+
: 'bg-black/50 text-gray-400 hover:text-gray-300 border-gray-700 hover:border-gray-600'
|
|
43
|
+
}`}
|
|
44
|
+
>
|
|
45
|
+
{page.name}
|
|
46
|
+
</button>
|
|
47
|
+
))}
|
|
48
|
+
|
|
49
|
+
{/* Menú hamburguesa para móvil - A LA DERECHA */}
|
|
50
|
+
{isMobile && (
|
|
51
|
+
<button
|
|
52
|
+
onClick={() => setSidebarOpen(!sidebarOpen)}
|
|
53
|
+
className="p-2 rounded-lg text-gray-300 hover:text-cyan-400 hover:bg-cyan-500/10 transition-colors"
|
|
54
|
+
>
|
|
55
|
+
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
56
|
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 6h16M4 12h16M4 18h16" />
|
|
57
|
+
</svg>
|
|
58
|
+
</button>
|
|
59
|
+
)}
|
|
60
|
+
</div>
|
|
61
|
+
</div>
|
|
62
|
+
</div>
|
|
63
|
+
</div>
|
|
64
|
+
|
|
65
|
+
{/* Menú móvil desplegable - TRADICIONAL */}
|
|
66
|
+
{isMobile && sidebarOpen && (
|
|
67
|
+
<div className="absolute top-full left-0 right-0 bg-gradient-to-r from-gray-900 via-black to-gray-900 border-b border-cyan-500/20 shadow-2xl z-50">
|
|
68
|
+
<div className="p-4">
|
|
69
|
+
{/* Navigation - VERTICAL como menú tradicional */}
|
|
70
|
+
<nav className="space-y-2">
|
|
71
|
+
{pages.map((page) => (
|
|
72
|
+
<button
|
|
73
|
+
key={page.id}
|
|
74
|
+
onClick={() => {
|
|
75
|
+
setCurrentPage(page.id as 'demo' | 'test')
|
|
76
|
+
setSidebarOpen(false)
|
|
77
|
+
}}
|
|
78
|
+
className={`w-full text-left px-4 py-3 rounded-lg transition-all font-bold text-sm tracking-wide border ${
|
|
79
|
+
currentPage === page.id
|
|
80
|
+
? 'bg-cyan-500/20 text-cyan-400 border-cyan-500/50'
|
|
81
|
+
: 'bg-black/50 text-gray-400 hover:text-gray-300 border-gray-700 hover:border-gray-600'
|
|
82
|
+
}`}
|
|
83
|
+
>
|
|
84
|
+
{page.name}
|
|
85
|
+
</button>
|
|
86
|
+
))}
|
|
87
|
+
</nav>
|
|
88
|
+
</div>
|
|
89
|
+
</div>
|
|
90
|
+
)}
|
|
91
|
+
</nav>
|
|
92
|
+
</div>
|
|
93
|
+
)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export default Navigation
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { useResponsiveLayout } from '../../hooks'
|
|
2
|
+
import { useNavigation, useSidebar } from '../../context'
|
|
3
|
+
|
|
4
|
+
const Sidebar = () => {
|
|
5
|
+
const { isMobile, isTablet, breakpoint } = useResponsiveLayout()
|
|
6
|
+
const { currentPage, setCurrentPage } = useNavigation()
|
|
7
|
+
const { sidebarOpen, setSidebarOpen } = useSidebar()
|
|
8
|
+
|
|
9
|
+
const menuItems = [
|
|
10
|
+
{ id: 'test', label: 'Suite de Test' },
|
|
11
|
+
{ id: 'demo', label: 'Demo' },
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
return (
|
|
15
|
+
<>
|
|
16
|
+
{/* Hamburger button para móvil */}
|
|
17
|
+
{isMobile && (
|
|
18
|
+
<button
|
|
19
|
+
onClick={() => setSidebarOpen(true)}
|
|
20
|
+
className="fixed top-4 left-4 z-50 p-2 rounded-lg text-gray-300 hover:text-cyan-400 hover:bg-cyan-500/10 transition-colors bg-black/50 border border-gray-700"
|
|
21
|
+
>
|
|
22
|
+
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
23
|
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 6h16M4 12h16M4 18h16" />
|
|
24
|
+
</svg>
|
|
25
|
+
</button>
|
|
26
|
+
)}
|
|
27
|
+
|
|
28
|
+
{/* Sidebar desktop */}
|
|
29
|
+
<aside className={`
|
|
30
|
+
bg-gradient-to-b from-gray-900 to-black border-r border-cyan-500/20
|
|
31
|
+
${isMobile ? 'hidden' : 'w-64 flex-shrink-0'}
|
|
32
|
+
${isTablet ? 'w-56' : 'w-64'}
|
|
33
|
+
`}>
|
|
34
|
+
<div className="p-6 flex flex-col h-full">
|
|
35
|
+
{/* Logo y Breakpoint */}
|
|
36
|
+
<div className="flex items-center space-x-3 mb-8">
|
|
37
|
+
<div className="w-8 h-8 bg-cyan-500 rounded-lg flex items-center justify-center">
|
|
38
|
+
<span className="text-white font-bold text-sm">RS</span>
|
|
39
|
+
</div>
|
|
40
|
+
<div>
|
|
41
|
+
<span className="text-white font-bold text-lg">Sistema Responsivo</span>
|
|
42
|
+
<div className="px-2 py-0.5 text-cyan-400 font-mono bg-black/50 border border-cyan-500/30 rounded text-xs font-bold tracking-widest mt-1">
|
|
43
|
+
{breakpoint.toUpperCase()}
|
|
44
|
+
</div>
|
|
45
|
+
</div>
|
|
46
|
+
</div>
|
|
47
|
+
|
|
48
|
+
{/* Navigation */}
|
|
49
|
+
<nav className="space-y-2">
|
|
50
|
+
{menuItems.map((item) => (
|
|
51
|
+
<button
|
|
52
|
+
key={item.id}
|
|
53
|
+
onClick={() => setCurrentPage(item.id as 'demo' | 'test')}
|
|
54
|
+
className={`w-full flex items-center px-4 py-3 rounded-lg transition-all group text-left ${
|
|
55
|
+
currentPage === item.id
|
|
56
|
+
? 'bg-cyan-500/20 text-cyan-400 border border-cyan-500/50'
|
|
57
|
+
: 'text-gray-300 hover:text-cyan-400 hover:bg-cyan-500/10'
|
|
58
|
+
}`}
|
|
59
|
+
>
|
|
60
|
+
<span className="font-medium">{item.label}</span>
|
|
61
|
+
</button>
|
|
62
|
+
))}
|
|
63
|
+
</nav>
|
|
64
|
+
|
|
65
|
+
</div>
|
|
66
|
+
</aside>
|
|
67
|
+
|
|
68
|
+
{/* Sidebar móvil desplegable */}
|
|
69
|
+
{isMobile && sidebarOpen && (
|
|
70
|
+
<div className="fixed inset-0 z-40 bg-black/50" onClick={() => setSidebarOpen(false)}>
|
|
71
|
+
<div className="fixed top-0 left-0 w-64 h-full bg-gradient-to-b from-gray-900 to-black border-r border-cyan-500/20">
|
|
72
|
+
<div className="p-6 flex flex-col h-full pt-20">
|
|
73
|
+
{/* Logo */}
|
|
74
|
+
<div className="flex items-center space-x-3 mb-8">
|
|
75
|
+
<div className="w-8 h-8 bg-cyan-500 rounded-lg flex items-center justify-center">
|
|
76
|
+
<span className="text-white font-bold text-sm">RS</span>
|
|
77
|
+
</div>
|
|
78
|
+
<span className="text-white font-bold text-lg">Sistema Responsivo</span>
|
|
79
|
+
</div>
|
|
80
|
+
|
|
81
|
+
{/* Navigation */}
|
|
82
|
+
<nav className="space-y-2">
|
|
83
|
+
{menuItems.map((item) => (
|
|
84
|
+
<button
|
|
85
|
+
key={item.id}
|
|
86
|
+
onClick={() => {
|
|
87
|
+
setCurrentPage(item.id as 'demo' | 'test')
|
|
88
|
+
setSidebarOpen(false)
|
|
89
|
+
}}
|
|
90
|
+
className={`w-full flex items-center px-4 py-3 rounded-lg transition-all group text-left ${
|
|
91
|
+
currentPage === item.id
|
|
92
|
+
? 'bg-cyan-500/20 text-cyan-400 border border-cyan-500/50'
|
|
93
|
+
: 'text-gray-300 hover:text-cyan-400 hover:bg-cyan-500/10'
|
|
94
|
+
}`}
|
|
95
|
+
>
|
|
96
|
+
<span className="font-medium">{item.label}</span>
|
|
97
|
+
</button>
|
|
98
|
+
))}
|
|
99
|
+
</nav>
|
|
100
|
+
</div>
|
|
101
|
+
</div>
|
|
102
|
+
</div>
|
|
103
|
+
)}
|
|
104
|
+
</>
|
|
105
|
+
)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export default Sidebar
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
export interface LayoutConfig {
|
|
2
|
+
name: string
|
|
3
|
+
description: string
|
|
4
|
+
components: string[]
|
|
5
|
+
spacing: string
|
|
6
|
+
responsive: {
|
|
7
|
+
mobile: string
|
|
8
|
+
tablet: string
|
|
9
|
+
desktop: string
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const LAYOUT_CONFIG: Record<string, LayoutConfig> = {
|
|
14
|
+
default: {
|
|
15
|
+
name: 'Default',
|
|
16
|
+
description: 'Navbar arriba, body central, footer abajo',
|
|
17
|
+
components: ['Navigation', 'Footer'],
|
|
18
|
+
spacing: 'p-4 md:p-6 lg:p-8',
|
|
19
|
+
responsive: {
|
|
20
|
+
mobile: 'p-4',
|
|
21
|
+
tablet: 'p-6',
|
|
22
|
+
desktop: 'p-8'
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
sidebar: {
|
|
26
|
+
name: 'Sidebar',
|
|
27
|
+
description: 'Sidebar izquierda, contenido principal',
|
|
28
|
+
components: ['Sidebar'],
|
|
29
|
+
spacing: 'p-4 md:p-6',
|
|
30
|
+
responsive: {
|
|
31
|
+
mobile: 'p-4',
|
|
32
|
+
tablet: 'p-6',
|
|
33
|
+
desktop: 'p-6'
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
dashboard: {
|
|
37
|
+
name: 'Dashboard',
|
|
38
|
+
description: 'Header + Sidebar + Main + Footer',
|
|
39
|
+
components: ['Header', 'Sidebar', 'Footer'],
|
|
40
|
+
spacing: 'p-4 md:p-6 lg:p-8',
|
|
41
|
+
responsive: {
|
|
42
|
+
mobile: 'p-4',
|
|
43
|
+
tablet: 'p-6',
|
|
44
|
+
desktop: 'p-8'
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
minimal: {
|
|
48
|
+
name: 'Minimal',
|
|
49
|
+
description: 'Solo contenido, sin navegación',
|
|
50
|
+
components: [],
|
|
51
|
+
spacing: 'p-4 md:p-6',
|
|
52
|
+
responsive: {
|
|
53
|
+
mobile: 'p-4',
|
|
54
|
+
tablet: 'p-6',
|
|
55
|
+
desktop: 'p-6'
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export const DEFAULT_LAYOUT = 'default'
|
|
61
|
+
export const AVAILABLE_LAYOUTS = Object.keys(LAYOUT_CONFIG)
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { Breakpoint } from '../types/responsive'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Default breakpoint values
|
|
5
|
+
* Deben coincidir con tailwind.config.js
|
|
6
|
+
*/
|
|
7
|
+
export const DEFAULT_BREAKPOINTS: Record<Breakpoint, number> = {
|
|
8
|
+
xs: 475,
|
|
9
|
+
sm: 640,
|
|
10
|
+
md: 768,
|
|
11
|
+
lg: 1024,
|
|
12
|
+
xl: 1280,
|
|
13
|
+
'2xl': 1536,
|
|
14
|
+
'3xl': 1920,
|
|
15
|
+
'4xl': 2560,
|
|
16
|
+
'5xl': 3840
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Get current breakpoint based on window width
|
|
21
|
+
*/
|
|
22
|
+
export const getCurrentBreakpoint = (width: number): Breakpoint => {
|
|
23
|
+
if (width >= DEFAULT_BREAKPOINTS['5xl']) return '5xl'
|
|
24
|
+
if (width >= DEFAULT_BREAKPOINTS['4xl']) return '4xl'
|
|
25
|
+
if (width >= DEFAULT_BREAKPOINTS['3xl']) return '3xl'
|
|
26
|
+
if (width >= DEFAULT_BREAKPOINTS['2xl']) return '2xl'
|
|
27
|
+
if (width >= DEFAULT_BREAKPOINTS.xl) return 'xl'
|
|
28
|
+
if (width >= DEFAULT_BREAKPOINTS.lg) return 'lg'
|
|
29
|
+
if (width >= DEFAULT_BREAKPOINTS.md) return 'md'
|
|
30
|
+
if (width >= DEFAULT_BREAKPOINTS.sm) return 'sm'
|
|
31
|
+
return 'xs'
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Get breakpoint index (for comparisons)
|
|
36
|
+
*/
|
|
37
|
+
export const getBreakpointIndex = (breakpoint: Breakpoint): number => {
|
|
38
|
+
const breakpoints: Breakpoint[] = ['xs', 'sm', 'md', 'lg', 'xl', '2xl', '3xl', '4xl', '5xl']
|
|
39
|
+
return breakpoints.indexOf(breakpoint)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Get breakpoint value in pixels
|
|
44
|
+
*/
|
|
45
|
+
export const getBreakpointValue = (breakpoint: Breakpoint): number => {
|
|
46
|
+
return DEFAULT_BREAKPOINTS[breakpoint]
|
|
47
|
+
}
|
|
48
|
+
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import React, { createContext, useContext, useState } from 'react'
|
|
2
|
+
|
|
3
|
+
interface NavigationContextType {
|
|
4
|
+
currentPage: 'demo' | 'test'
|
|
5
|
+
setCurrentPage: (page: 'demo' | 'test') => void
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const NavigationContext = createContext<NavigationContextType>({
|
|
9
|
+
currentPage: 'test',
|
|
10
|
+
setCurrentPage: () => {}
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
export const useNavigation = () => useContext(NavigationContext)
|
|
14
|
+
|
|
15
|
+
interface NavigationProviderProps {
|
|
16
|
+
children: React.ReactNode
|
|
17
|
+
defaultPage?: 'demo' | 'test'
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export const NavigationProvider: React.FC<NavigationProviderProps> = ({
|
|
21
|
+
children,
|
|
22
|
+
defaultPage = 'test'
|
|
23
|
+
}) => {
|
|
24
|
+
const [currentPage, setCurrentPage] = useState<'demo' | 'test'>(defaultPage)
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
<NavigationContext.Provider value={{ currentPage, setCurrentPage }}>
|
|
28
|
+
{children}
|
|
29
|
+
</NavigationContext.Provider>
|
|
30
|
+
)
|
|
31
|
+
}
|
|
32
|
+
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { createContext, useContext } from 'react'
|
|
2
|
+
import type { ResponsiveState } from '../types/responsive'
|
|
3
|
+
import type { LayoutConfig } from '../config/layout'
|
|
4
|
+
|
|
5
|
+
export interface ResponsiveLayoutState {
|
|
6
|
+
// Estado del sistema responsivo
|
|
7
|
+
responsive: ResponsiveState
|
|
8
|
+
|
|
9
|
+
// Estado del layout
|
|
10
|
+
layout: {
|
|
11
|
+
current: string
|
|
12
|
+
config: LayoutConfig
|
|
13
|
+
setLayout: (layout: string) => void
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// Utilidades de layout
|
|
17
|
+
layoutUtils: {
|
|
18
|
+
getContainerClass: () => string
|
|
19
|
+
getMainClass: () => string
|
|
20
|
+
hasSidebar: () => boolean
|
|
21
|
+
hasHeader: () => boolean
|
|
22
|
+
hasFooter: () => boolean
|
|
23
|
+
hasNavigation: () => boolean
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const ResponsiveLayoutContext = createContext<ResponsiveLayoutState | undefined>(undefined)
|
|
28
|
+
|
|
29
|
+
export const useResponsiveLayoutContext = () => {
|
|
30
|
+
const context = useContext(ResponsiveLayoutContext)
|
|
31
|
+
if (!context) {
|
|
32
|
+
throw new Error('useResponsiveLayoutContext must be used within a ResponsiveLayoutProvider')
|
|
33
|
+
}
|
|
34
|
+
return context
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export { ResponsiveLayoutContext }
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import React, { createContext, useContext, useState } from 'react'
|
|
2
|
+
|
|
3
|
+
interface SidebarContextType {
|
|
4
|
+
sidebarOpen: boolean
|
|
5
|
+
setSidebarOpen: (open: boolean) => void
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const SidebarContext = createContext<SidebarContextType | undefined>(undefined)
|
|
9
|
+
|
|
10
|
+
export const SidebarProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
|
11
|
+
const [sidebarOpen, setSidebarOpen] = useState(false)
|
|
12
|
+
|
|
13
|
+
return (
|
|
14
|
+
<SidebarContext.Provider value={{ sidebarOpen, setSidebarOpen }}>
|
|
15
|
+
{children}
|
|
16
|
+
</SidebarContext.Provider>
|
|
17
|
+
)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export const useSidebar = () => {
|
|
21
|
+
const context = useContext(SidebarContext)
|
|
22
|
+
if (!context) {
|
|
23
|
+
throw new Error('useSidebar must be used within a SidebarProvider')
|
|
24
|
+
}
|
|
25
|
+
return context
|
|
26
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { useResponsiveLayout } from './index'
|
|
2
|
+
|
|
3
|
+
export const useLayout = () => {
|
|
4
|
+
const responsiveLayout = useResponsiveLayout()
|
|
5
|
+
|
|
6
|
+
return {
|
|
7
|
+
// Layout actual
|
|
8
|
+
current: responsiveLayout.layout.current,
|
|
9
|
+
config: responsiveLayout.layout.config,
|
|
10
|
+
setLayout: responsiveLayout.layout.setLayout,
|
|
11
|
+
|
|
12
|
+
// Utilidades
|
|
13
|
+
...responsiveLayout.layoutUtils,
|
|
14
|
+
|
|
15
|
+
// Helpers específicos
|
|
16
|
+
isDefaultLayout: responsiveLayout.isDefaultLayout(),
|
|
17
|
+
isSidebarLayout: responsiveLayout.isSidebarLayout(),
|
|
18
|
+
isDashboardLayout: responsiveLayout.isDashboardLayout(),
|
|
19
|
+
isMinimalLayout: responsiveLayout.isMinimalLayout(),
|
|
20
|
+
|
|
21
|
+
// Grid helpers
|
|
22
|
+
grid: responsiveLayout.grid,
|
|
23
|
+
|
|
24
|
+
// Spacing helpers
|
|
25
|
+
spacing: responsiveLayout.spacing,
|
|
26
|
+
}
|
|
27
|
+
}
|