responsive-system 1.1.5 → 1.2.1

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.
Files changed (36) hide show
  1. package/package.json +2 -1
  2. package/scripts/postinstall.js +2 -3
  3. package/src/App.css +42 -0
  4. package/src/App.tsx +29 -0
  5. package/src/assets/react.svg +1 -0
  6. package/src/components/LayoutSwitcher.tsx +62 -0
  7. package/src/components/ResponsiveDemo.tsx +282 -0
  8. package/src/components/layout/Footer.tsx +90 -0
  9. package/src/components/layout/Header.tsx +105 -0
  10. package/src/components/layout/Navigation.tsx +96 -0
  11. package/src/components/layout/Sidebar.tsx +108 -0
  12. package/src/components/layout/index.ts +4 -0
  13. package/src/config/layout.ts +61 -0
  14. package/src/constants/breakpoints.ts +48 -0
  15. package/src/context/NavigationContext.tsx +32 -0
  16. package/src/context/ResponsiveLayoutContext.tsx +37 -0
  17. package/src/context/SidebarContext.tsx +26 -0
  18. package/src/context/index.ts +4 -0
  19. package/src/hooks/index.ts +4 -0
  20. package/src/hooks/useLayout.ts +27 -0
  21. package/src/hooks/useResponsive.ts +189 -0
  22. package/src/hooks/useResponsiveLayout.ts +51 -0
  23. package/src/index.css +1 -0
  24. package/src/index.ts +100 -0
  25. package/src/layouts/DashboardLayout.tsx +76 -0
  26. package/src/layouts/DefaultLayout.tsx +30 -0
  27. package/src/layouts/MainLayout.tsx +38 -0
  28. package/src/layouts/MinimalLayout.tsx +20 -0
  29. package/src/layouts/SidebarLayout.tsx +36 -0
  30. package/src/layouts/index.ts +5 -0
  31. package/src/main.tsx +10 -0
  32. package/src/pages/ResponsiveTestPage.tsx +400 -0
  33. package/src/providers/ResponsiveLayoutProvider.tsx +92 -0
  34. package/src/providers/ResponsiveProvider.tsx +18 -0
  35. package/src/providers/index.ts +3 -0
  36. package/src/types/responsive.ts +64 -0
@@ -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,4 @@
1
+ export { default as Header } from './Header'
2
+ export { default as Sidebar } from './Sidebar'
3
+ export { default as Footer } from './Footer'
4
+ export { default as Navigation } from './Navigation'
@@ -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,4 @@
1
+ export { ResponsiveLayoutContext, useResponsiveLayoutContext } from './ResponsiveLayoutContext'
2
+ export { SidebarProvider, useSidebar } from './SidebarContext'
3
+ export { NavigationProvider, useNavigation } from './NavigationContext'
4
+
@@ -0,0 +1,4 @@
1
+ export { useResponsive } from './useResponsive'
2
+ export { useResponsiveLayout } from './useResponsiveLayout'
3
+ export { useLayout } from './useLayout'
4
+
@@ -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
+ }
@@ -0,0 +1,189 @@
1
+ import { useState, useEffect, useCallback, useMemo } from 'react'
2
+ import type {
3
+ ResponsiveState,
4
+ Breakpoint
5
+ } from '../types/responsive'
6
+ import {
7
+ getCurrentBreakpoint,
8
+ getBreakpointIndex
9
+ } from '../constants/breakpoints'
10
+
11
+ /**
12
+ * Debounce utility
13
+ */
14
+ function debounce<T extends (...args: unknown[]) => void>(
15
+ func: T,
16
+ wait: number
17
+ ): (...args: Parameters<T>) => void {
18
+ let timeout: ReturnType<typeof setTimeout> | null = null
19
+ return (...args: Parameters<T>) => {
20
+ if (timeout) clearTimeout(timeout)
21
+ timeout = setTimeout(() => func(...args), wait)
22
+ }
23
+ }
24
+
25
+ /**
26
+ * Get orientation based on dimensions
27
+ */
28
+ function getOrientation(width: number, height: number): 'landscape' | 'portrait' {
29
+ return width >= height ? 'landscape' : 'portrait'
30
+ }
31
+
32
+ /**
33
+ * Hook principal useResponsive
34
+ * Provee información sobre el breakpoint actual y helpers para responsive
35
+ */
36
+ export const useResponsive = (): ResponsiveState => {
37
+ const [dimensions, setDimensions] = useState({
38
+ width: typeof window !== 'undefined' ? window.innerWidth : 1024,
39
+ height: typeof window !== 'undefined' ? window.innerHeight : 768
40
+ })
41
+
42
+ // Función para actualizar dimensiones
43
+ const updateDimensions = useCallback(() => {
44
+ setDimensions({
45
+ width: window.innerWidth,
46
+ height: window.innerHeight
47
+ })
48
+ }, [])
49
+
50
+ // Debounced update para optimizar performance
51
+ const debouncedUpdateDimensions = useMemo(
52
+ () => debounce(updateDimensions, 100),
53
+ [updateDimensions]
54
+ )
55
+
56
+ // Effect para escuchar cambios de tamaño
57
+ useEffect(() => {
58
+ if (typeof window === 'undefined') return
59
+
60
+ window.addEventListener('resize', debouncedUpdateDimensions)
61
+
62
+ return () => {
63
+ window.removeEventListener('resize', debouncedUpdateDimensions)
64
+ }
65
+ }, [debouncedUpdateDimensions])
66
+
67
+ const { width, height } = dimensions
68
+
69
+ // Calcular breakpoint actual
70
+ const breakpoint = useMemo(() => getCurrentBreakpoint(width), [width])
71
+
72
+ // Calcular orientación
73
+ const orientation = useMemo(() => getOrientation(width, height), [width, height])
74
+
75
+ // Helpers booleanos por breakpoint específico
76
+ const isXs = breakpoint === 'xs'
77
+ const isSm = breakpoint === 'sm'
78
+ const isMd = breakpoint === 'md'
79
+ const isLg = breakpoint === 'lg'
80
+ const isXl = breakpoint === 'xl'
81
+ const is2Xl = breakpoint === '2xl'
82
+ const is3Xl = breakpoint === '3xl'
83
+ const is4Xl = breakpoint === '4xl'
84
+ const is5Xl = breakpoint === '5xl'
85
+
86
+ // Helpers booleanos agrupados
87
+ const isMobile = isXs || isSm
88
+ const isTablet = isMd
89
+ const isDesktop = isLg || isXl || is2Xl || is3Xl || is4Xl || is5Xl
90
+ const isSmall = isXs || isSm || isMd
91
+ const isLarge = isLg || isXl || is2Xl || is3Xl || is4Xl || is5Xl
92
+ const isUltraWide = is3Xl || is4Xl || is5Xl
93
+ const is4K = is4Xl || is5Xl
94
+ const is5K = is5Xl
95
+
96
+ // Helpers de orientación
97
+ const isPortrait = orientation === 'portrait'
98
+ const isLandscape = orientation === 'landscape'
99
+
100
+ // Funciones de comparación de breakpoints
101
+ const isBreakpointUp = useCallback((bp: Breakpoint): boolean => {
102
+ return getBreakpointIndex(breakpoint) >= getBreakpointIndex(bp)
103
+ }, [breakpoint])
104
+
105
+ const isBreakpointDown = useCallback((bp: Breakpoint): boolean => {
106
+ return getBreakpointIndex(breakpoint) <= getBreakpointIndex(bp)
107
+ }, [breakpoint])
108
+
109
+ const isBreakpointBetween = useCallback((min: Breakpoint, max: Breakpoint): boolean => {
110
+ const current = getBreakpointIndex(breakpoint)
111
+ return current >= getBreakpointIndex(min) && current <= getBreakpointIndex(max)
112
+ }, [breakpoint])
113
+
114
+ // Funciones de comparación de ancho
115
+ const isWidthUp = useCallback((minWidth: number): boolean => {
116
+ return width >= minWidth
117
+ }, [width])
118
+
119
+ const isWidthDown = useCallback((maxWidth: number): boolean => {
120
+ return width <= maxWidth
121
+ }, [width])
122
+
123
+ const isWidthBetween = useCallback((minWidth: number, maxWidth: number): boolean => {
124
+ return width >= minWidth && width <= maxWidth
125
+ }, [width])
126
+
127
+ // Funciones de comparación de altura
128
+ const isHeightUp = useCallback((minHeight: number): boolean => {
129
+ return height >= minHeight
130
+ }, [height])
131
+
132
+ const isHeightDown = useCallback((maxHeight: number): boolean => {
133
+ return height <= maxHeight
134
+ }, [height])
135
+
136
+ const isHeightBetween = useCallback((minHeight: number, maxHeight: number): boolean => {
137
+ return height >= minHeight && height <= maxHeight
138
+ }, [height])
139
+
140
+ // Debug mode
141
+ const debug = false
142
+
143
+ return {
144
+ // Estado básico
145
+ breakpoint,
146
+ width,
147
+ height,
148
+ orientation,
149
+
150
+ // Helpers booleanos específicos
151
+ isXs,
152
+ isSm,
153
+ isMd,
154
+ isLg,
155
+ isXl,
156
+ is2Xl,
157
+ is3Xl,
158
+ is4Xl,
159
+ is5Xl,
160
+
161
+ // Helpers booleanos agrupados
162
+ isMobile,
163
+ isTablet,
164
+ isDesktop,
165
+ isSmall,
166
+ isLarge,
167
+ isUltraWide,
168
+ is4K,
169
+ is5K,
170
+
171
+ // Helpers de orientación
172
+ isPortrait,
173
+ isLandscape,
174
+
175
+ // Funciones de comparación
176
+ isBreakpointUp,
177
+ isBreakpointDown,
178
+ isBreakpointBetween,
179
+ isWidthUp,
180
+ isWidthDown,
181
+ isWidthBetween,
182
+ isHeightUp,
183
+ isHeightDown,
184
+ isHeightBetween,
185
+
186
+ // Debug
187
+ debug
188
+ }
189
+ }