@opensite/hooks 2.0.8 → 2.1.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/README.md +2 -0
- package/dist/browser/opensite-hooks.umd.cjs +1 -1
- package/dist/browser/opensite-hooks.umd.js +1 -1
- package/dist/browser/opensite-hooks.umd.js.map +1 -1
- package/dist/core/index.cjs +2 -0
- package/dist/core/index.d.ts +5 -1
- package/dist/core/index.js +2 -0
- package/dist/core/useIsTouchDevice.cjs +151 -0
- package/dist/core/useIsTouchDevice.d.ts +104 -0
- package/dist/core/useIsTouchDevice.js +151 -0
- package/dist/core/useMediaQuery.cjs +6 -8
- package/dist/core/useMediaQuery.js +6 -8
- package/dist/core/useScreen.cjs +205 -0
- package/dist/core/useScreen.d.ts +155 -0
- package/dist/core/useScreen.js +205 -0
- package/package.json +11 -1
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tailwind CSS breakpoint identifiers.
|
|
3
|
+
*
|
|
4
|
+
* - `"default"` – viewport width below the smallest defined breakpoint (< 640px)
|
|
5
|
+
* - `"sm"` – small devices (>= 640px)
|
|
6
|
+
* - `"md"` – medium devices (>= 768px)
|
|
7
|
+
* - `"lg"` – large devices (>= 1024px)
|
|
8
|
+
* - `"xl"` – extra large devices (>= 1280px)
|
|
9
|
+
* - `"2xl"` – 2x extra large devices (>= 1536px)
|
|
10
|
+
*/
|
|
11
|
+
export type TailwindSize = "default" | "sm" | "md" | "lg" | "xl" | "2xl";
|
|
12
|
+
/**
|
|
13
|
+
* Semantic screen type classification for layout decisions.
|
|
14
|
+
*
|
|
15
|
+
* - `"MOBILE"` – phone-sized viewports (default + sm breakpoints)
|
|
16
|
+
* - `"TABLET"` – tablet-sized viewports (md breakpoint)
|
|
17
|
+
* - `"DESKTOP"` – desktop-sized viewports (lg, xl, 2xl breakpoints)
|
|
18
|
+
* - `"UNKNOWN"` – returned during SSR before client-side detection
|
|
19
|
+
*/
|
|
20
|
+
export type ScreenType = "UNKNOWN" | "MOBILE" | "TABLET" | "DESKTOP";
|
|
21
|
+
/**
|
|
22
|
+
* Breakpoint configuration with pixel thresholds.
|
|
23
|
+
*
|
|
24
|
+
* Values represent the minimum width (in pixels) for each breakpoint.
|
|
25
|
+
* Follows Tailwind CSS v4 defaults.
|
|
26
|
+
*/
|
|
27
|
+
export interface ScreenBreakpoints {
|
|
28
|
+
/** Minimum width for `sm` breakpoint. Default: 640 */
|
|
29
|
+
sm: number;
|
|
30
|
+
/** Minimum width for `md` breakpoint. Default: 768 */
|
|
31
|
+
md: number;
|
|
32
|
+
/** Minimum width for `lg` breakpoint. Default: 1024 */
|
|
33
|
+
lg: number;
|
|
34
|
+
/** Minimum width for `xl` breakpoint. Default: 1280 */
|
|
35
|
+
xl: number;
|
|
36
|
+
/** Minimum width for `2xl` breakpoint. Default: 1536 */
|
|
37
|
+
"2xl": number;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Mapping of Tailwind breakpoints to semantic screen types.
|
|
41
|
+
*/
|
|
42
|
+
export interface ScreenTypeMapping {
|
|
43
|
+
/** Screen type for default (< sm) viewport. Default: "MOBILE" */
|
|
44
|
+
default: ScreenType;
|
|
45
|
+
/** Screen type for sm viewport. Default: "MOBILE" */
|
|
46
|
+
sm: ScreenType;
|
|
47
|
+
/** Screen type for md viewport. Default: "TABLET" */
|
|
48
|
+
md: ScreenType;
|
|
49
|
+
/** Screen type for lg viewport. Default: "DESKTOP" */
|
|
50
|
+
lg: ScreenType;
|
|
51
|
+
/** Screen type for xl viewport. Default: "DESKTOP" */
|
|
52
|
+
xl: ScreenType;
|
|
53
|
+
/** Screen type for 2xl viewport. Default: "DESKTOP" */
|
|
54
|
+
"2xl": ScreenType;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Configuration options for {@link useScreen}.
|
|
58
|
+
*/
|
|
59
|
+
export interface UseScreenOptions {
|
|
60
|
+
/**
|
|
61
|
+
* Custom breakpoint pixel thresholds.
|
|
62
|
+
* Partial object that merges with Tailwind v4 defaults.
|
|
63
|
+
*/
|
|
64
|
+
breakpoints?: Partial<ScreenBreakpoints>;
|
|
65
|
+
/**
|
|
66
|
+
* Custom mapping of breakpoints to screen types.
|
|
67
|
+
* Partial object that merges with defaults.
|
|
68
|
+
*/
|
|
69
|
+
screenTypeMapping?: Partial<ScreenTypeMapping>;
|
|
70
|
+
/**
|
|
71
|
+
* Default screen type returned during SSR and before detection.
|
|
72
|
+
* Defaults to `"UNKNOWN"`.
|
|
73
|
+
*/
|
|
74
|
+
defaultScreenType?: ScreenType;
|
|
75
|
+
/**
|
|
76
|
+
* Default Tailwind size returned during SSR and before detection.
|
|
77
|
+
* Defaults to `"default"`.
|
|
78
|
+
*/
|
|
79
|
+
defaultTailwindSize?: TailwindSize;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Shape returned by {@link useScreen}.
|
|
83
|
+
*
|
|
84
|
+
* The object reference is memoized with `useMemo` so it remains referentially
|
|
85
|
+
* stable across re-renders when values have not changed.
|
|
86
|
+
*/
|
|
87
|
+
export interface UseScreenResult {
|
|
88
|
+
/** Current viewport width in pixels. `0` during SSR. */
|
|
89
|
+
width: number;
|
|
90
|
+
/** Current viewport height in pixels. `0` during SSR. */
|
|
91
|
+
height: number;
|
|
92
|
+
/**
|
|
93
|
+
* Current Tailwind CSS breakpoint identifier.
|
|
94
|
+
* Determined by the largest matching `min-width` breakpoint.
|
|
95
|
+
*/
|
|
96
|
+
tailwindSize: TailwindSize;
|
|
97
|
+
/**
|
|
98
|
+
* Semantic screen type for layout decisions.
|
|
99
|
+
* Maps `tailwindSize` to a simplified classification.
|
|
100
|
+
*/
|
|
101
|
+
screenType: ScreenType;
|
|
102
|
+
/** Re-measure viewport dimensions on demand. */
|
|
103
|
+
refresh: () => void;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Track viewport dimensions and compute Tailwind breakpoint / screen type.
|
|
107
|
+
*
|
|
108
|
+
* Provides real-time viewport width and height, along with derived values for
|
|
109
|
+
* the current Tailwind CSS breakpoint (`tailwindSize`) and a semantic screen
|
|
110
|
+
* type classification (`screenType`) for layout decisions.
|
|
111
|
+
*
|
|
112
|
+
* ### SSR Safety
|
|
113
|
+
*
|
|
114
|
+
* The hook returns safe defaults during SSR (`width: 0`, `height: 0`,
|
|
115
|
+
* `tailwindSize: "default"`, `screenType: "UNKNOWN"`) to prevent hydration
|
|
116
|
+
* mismatches. After mount, values update to reflect the actual viewport.
|
|
117
|
+
*
|
|
118
|
+
* ### Performance
|
|
119
|
+
*
|
|
120
|
+
* - Uses `useMediaQuery` internally for efficient breakpoint detection via
|
|
121
|
+
* CSS media queries (no polling).
|
|
122
|
+
* - Viewport dimensions update on window `resize` events.
|
|
123
|
+
* - The returned object is memoized; values must change for the reference
|
|
124
|
+
* to update.
|
|
125
|
+
*
|
|
126
|
+
* @param options - Optional configuration. See {@link UseScreenOptions}.
|
|
127
|
+
* @returns A memoized {@link UseScreenResult} object.
|
|
128
|
+
*
|
|
129
|
+
* @example
|
|
130
|
+
* ```tsx
|
|
131
|
+
* import { useScreen } from "@opensite/hooks/useScreen";
|
|
132
|
+
*
|
|
133
|
+
* function ResponsiveLayout() {
|
|
134
|
+
* const { screenType, tailwindSize, width } = useScreen();
|
|
135
|
+
*
|
|
136
|
+
* return (
|
|
137
|
+
* <div>
|
|
138
|
+
* <p>Viewport: {width}px ({tailwindSize})</p>
|
|
139
|
+
* {screenType === "MOBILE" && <MobileNav />}
|
|
140
|
+
* {screenType === "TABLET" && <TabletNav />}
|
|
141
|
+
* {screenType === "DESKTOP" && <DesktopNav />}
|
|
142
|
+
* </div>
|
|
143
|
+
* );
|
|
144
|
+
* }
|
|
145
|
+
* ```
|
|
146
|
+
*
|
|
147
|
+
* @example
|
|
148
|
+
* ```tsx
|
|
149
|
+
* // With custom breakpoints
|
|
150
|
+
* const { screenType } = useScreen({
|
|
151
|
+
* breakpoints: { sm: 600, md: 900, lg: 1200, xl: 1400, "2xl": 1800 },
|
|
152
|
+
* });
|
|
153
|
+
* ```
|
|
154
|
+
*/
|
|
155
|
+
export declare function useScreen(options?: UseScreenOptions): UseScreenResult;
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
2
|
+
import { useMediaQuery } from "./useMediaQuery.js";
|
|
3
|
+
// ---------------------------------------------------------------------------
|
|
4
|
+
// Constants
|
|
5
|
+
// ---------------------------------------------------------------------------
|
|
6
|
+
/**
|
|
7
|
+
* Tailwind CSS v4 default breakpoints (min-width values in pixels).
|
|
8
|
+
* @see https://tailwindcss.com/docs/responsive-design
|
|
9
|
+
*/
|
|
10
|
+
const DEFAULT_BREAKPOINTS = {
|
|
11
|
+
sm: 640,
|
|
12
|
+
md: 768,
|
|
13
|
+
lg: 1024,
|
|
14
|
+
xl: 1280,
|
|
15
|
+
"2xl": 1536,
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Default mapping of Tailwind breakpoints to semantic screen types.
|
|
19
|
+
*
|
|
20
|
+
* - default, sm → MOBILE
|
|
21
|
+
* - md → TABLET
|
|
22
|
+
* - lg, xl, 2xl → DESKTOP
|
|
23
|
+
*/
|
|
24
|
+
const DEFAULT_SCREEN_TYPE_MAPPING = {
|
|
25
|
+
default: "MOBILE",
|
|
26
|
+
sm: "MOBILE",
|
|
27
|
+
md: "TABLET",
|
|
28
|
+
lg: "DESKTOP",
|
|
29
|
+
xl: "DESKTOP",
|
|
30
|
+
"2xl": "DESKTOP",
|
|
31
|
+
};
|
|
32
|
+
// ---------------------------------------------------------------------------
|
|
33
|
+
// Helpers
|
|
34
|
+
// ---------------------------------------------------------------------------
|
|
35
|
+
/**
|
|
36
|
+
* Get current viewport dimensions.
|
|
37
|
+
* Returns `{ width: 0, height: 0 }` during SSR.
|
|
38
|
+
*/
|
|
39
|
+
function getViewportDimensions() {
|
|
40
|
+
if (typeof window === "undefined") {
|
|
41
|
+
return { width: 0, height: 0 };
|
|
42
|
+
}
|
|
43
|
+
return {
|
|
44
|
+
width: window.innerWidth,
|
|
45
|
+
height: window.innerHeight,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Determine the current Tailwind breakpoint based on viewport width.
|
|
50
|
+
* Uses mobile-first logic: returns the largest breakpoint that matches.
|
|
51
|
+
*/
|
|
52
|
+
function calculateTailwindSize(width, breakpoints) {
|
|
53
|
+
// Check breakpoints from largest to smallest (mobile-first)
|
|
54
|
+
if (width >= breakpoints["2xl"])
|
|
55
|
+
return "2xl";
|
|
56
|
+
if (width >= breakpoints.xl)
|
|
57
|
+
return "xl";
|
|
58
|
+
if (width >= breakpoints.lg)
|
|
59
|
+
return "lg";
|
|
60
|
+
if (width >= breakpoints.md)
|
|
61
|
+
return "md";
|
|
62
|
+
if (width >= breakpoints.sm)
|
|
63
|
+
return "sm";
|
|
64
|
+
return "default";
|
|
65
|
+
}
|
|
66
|
+
// ---------------------------------------------------------------------------
|
|
67
|
+
// Hook
|
|
68
|
+
// ---------------------------------------------------------------------------
|
|
69
|
+
/**
|
|
70
|
+
* Track viewport dimensions and compute Tailwind breakpoint / screen type.
|
|
71
|
+
*
|
|
72
|
+
* Provides real-time viewport width and height, along with derived values for
|
|
73
|
+
* the current Tailwind CSS breakpoint (`tailwindSize`) and a semantic screen
|
|
74
|
+
* type classification (`screenType`) for layout decisions.
|
|
75
|
+
*
|
|
76
|
+
* ### SSR Safety
|
|
77
|
+
*
|
|
78
|
+
* The hook returns safe defaults during SSR (`width: 0`, `height: 0`,
|
|
79
|
+
* `tailwindSize: "default"`, `screenType: "UNKNOWN"`) to prevent hydration
|
|
80
|
+
* mismatches. After mount, values update to reflect the actual viewport.
|
|
81
|
+
*
|
|
82
|
+
* ### Performance
|
|
83
|
+
*
|
|
84
|
+
* - Uses `useMediaQuery` internally for efficient breakpoint detection via
|
|
85
|
+
* CSS media queries (no polling).
|
|
86
|
+
* - Viewport dimensions update on window `resize` events.
|
|
87
|
+
* - The returned object is memoized; values must change for the reference
|
|
88
|
+
* to update.
|
|
89
|
+
*
|
|
90
|
+
* @param options - Optional configuration. See {@link UseScreenOptions}.
|
|
91
|
+
* @returns A memoized {@link UseScreenResult} object.
|
|
92
|
+
*
|
|
93
|
+
* @example
|
|
94
|
+
* ```tsx
|
|
95
|
+
* import { useScreen } from "@opensite/hooks/useScreen";
|
|
96
|
+
*
|
|
97
|
+
* function ResponsiveLayout() {
|
|
98
|
+
* const { screenType, tailwindSize, width } = useScreen();
|
|
99
|
+
*
|
|
100
|
+
* return (
|
|
101
|
+
* <div>
|
|
102
|
+
* <p>Viewport: {width}px ({tailwindSize})</p>
|
|
103
|
+
* {screenType === "MOBILE" && <MobileNav />}
|
|
104
|
+
* {screenType === "TABLET" && <TabletNav />}
|
|
105
|
+
* {screenType === "DESKTOP" && <DesktopNav />}
|
|
106
|
+
* </div>
|
|
107
|
+
* );
|
|
108
|
+
* }
|
|
109
|
+
* ```
|
|
110
|
+
*
|
|
111
|
+
* @example
|
|
112
|
+
* ```tsx
|
|
113
|
+
* // With custom breakpoints
|
|
114
|
+
* const { screenType } = useScreen({
|
|
115
|
+
* breakpoints: { sm: 600, md: 900, lg: 1200, xl: 1400, "2xl": 1800 },
|
|
116
|
+
* });
|
|
117
|
+
* ```
|
|
118
|
+
*/
|
|
119
|
+
export function useScreen(options = {}) {
|
|
120
|
+
const { breakpoints: customBreakpoints, screenTypeMapping: customMapping, defaultScreenType = "UNKNOWN", defaultTailwindSize = "default", } = options;
|
|
121
|
+
// Merge custom config with defaults
|
|
122
|
+
const breakpoints = useMemo(() => ({ ...DEFAULT_BREAKPOINTS, ...customBreakpoints }), [customBreakpoints]);
|
|
123
|
+
const screenTypeMapping = useMemo(() => ({ ...DEFAULT_SCREEN_TYPE_MAPPING, ...customMapping }), [customMapping]);
|
|
124
|
+
// Track viewport dimensions
|
|
125
|
+
const [dimensions, setDimensions] = useState(() => getViewportDimensions());
|
|
126
|
+
// Use media queries for breakpoint detection (more reliable than width alone
|
|
127
|
+
// for edge cases and provides instant updates via matchMedia)
|
|
128
|
+
const isSm = useMediaQuery(`(min-width: ${breakpoints.sm}px)`);
|
|
129
|
+
const isMd = useMediaQuery(`(min-width: ${breakpoints.md}px)`);
|
|
130
|
+
const isLg = useMediaQuery(`(min-width: ${breakpoints.lg}px)`);
|
|
131
|
+
const isXl = useMediaQuery(`(min-width: ${breakpoints.xl}px)`);
|
|
132
|
+
const is2xl = useMediaQuery(`(min-width: ${breakpoints["2xl"]}px)`);
|
|
133
|
+
// Stable refresh callback
|
|
134
|
+
const refresh = useCallback(() => {
|
|
135
|
+
if (typeof window === "undefined")
|
|
136
|
+
return;
|
|
137
|
+
setDimensions(getViewportDimensions());
|
|
138
|
+
}, []);
|
|
139
|
+
// Subscribe to resize events for dimension tracking
|
|
140
|
+
useEffect(() => {
|
|
141
|
+
if (typeof window === "undefined")
|
|
142
|
+
return;
|
|
143
|
+
// Initial measurement
|
|
144
|
+
setDimensions(getViewportDimensions());
|
|
145
|
+
const handleResize = () => {
|
|
146
|
+
setDimensions(getViewportDimensions());
|
|
147
|
+
};
|
|
148
|
+
window.addEventListener("resize", handleResize);
|
|
149
|
+
return () => window.removeEventListener("resize", handleResize);
|
|
150
|
+
}, []);
|
|
151
|
+
// Compute tailwindSize from media query results (most reliable)
|
|
152
|
+
const tailwindSize = useMemo(() => {
|
|
153
|
+
// During SSR, all media queries return false
|
|
154
|
+
if (!isSm && !isMd && !isLg && !isXl && !is2xl) {
|
|
155
|
+
// Check if we're on client with actual dimensions
|
|
156
|
+
if (dimensions.width > 0) {
|
|
157
|
+
return calculateTailwindSize(dimensions.width, breakpoints);
|
|
158
|
+
}
|
|
159
|
+
return defaultTailwindSize;
|
|
160
|
+
}
|
|
161
|
+
// Determine from media queries (largest matching breakpoint wins)
|
|
162
|
+
if (is2xl)
|
|
163
|
+
return "2xl";
|
|
164
|
+
if (isXl)
|
|
165
|
+
return "xl";
|
|
166
|
+
if (isLg)
|
|
167
|
+
return "lg";
|
|
168
|
+
if (isMd)
|
|
169
|
+
return "md";
|
|
170
|
+
if (isSm)
|
|
171
|
+
return "sm";
|
|
172
|
+
return "default";
|
|
173
|
+
}, [
|
|
174
|
+
isSm,
|
|
175
|
+
isMd,
|
|
176
|
+
isLg,
|
|
177
|
+
isXl,
|
|
178
|
+
is2xl,
|
|
179
|
+
dimensions.width,
|
|
180
|
+
breakpoints,
|
|
181
|
+
defaultTailwindSize,
|
|
182
|
+
]);
|
|
183
|
+
// Derive screen type from tailwind size
|
|
184
|
+
const screenType = useMemo(() => {
|
|
185
|
+
// During SSR before detection
|
|
186
|
+
if (tailwindSize === defaultTailwindSize && dimensions.width === 0) {
|
|
187
|
+
return defaultScreenType;
|
|
188
|
+
}
|
|
189
|
+
return screenTypeMapping[tailwindSize];
|
|
190
|
+
}, [
|
|
191
|
+
tailwindSize,
|
|
192
|
+
screenTypeMapping,
|
|
193
|
+
dimensions.width,
|
|
194
|
+
defaultScreenType,
|
|
195
|
+
defaultTailwindSize,
|
|
196
|
+
]);
|
|
197
|
+
// Memoize the return object for stable references
|
|
198
|
+
return useMemo(() => ({
|
|
199
|
+
width: dimensions.width,
|
|
200
|
+
height: dimensions.height,
|
|
201
|
+
tailwindSize,
|
|
202
|
+
screenType,
|
|
203
|
+
refresh,
|
|
204
|
+
}), [dimensions.width, dimensions.height, tailwindSize, screenType, refresh]);
|
|
205
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opensite/hooks",
|
|
3
|
-
"version": "2.0
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"description": "Performance-first React hooks for UI state, storage, events, and responsive behavior with tree-shakable exports.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"react",
|
|
@@ -73,6 +73,11 @@
|
|
|
73
73
|
"require": "./dist/core/useIsClient.cjs",
|
|
74
74
|
"types": "./dist/core/useIsClient.d.ts"
|
|
75
75
|
},
|
|
76
|
+
"./useIsTouchDevice": {
|
|
77
|
+
"import": "./dist/core/useIsTouchDevice.js",
|
|
78
|
+
"require": "./dist/core/useIsTouchDevice.cjs",
|
|
79
|
+
"types": "./dist/core/useIsTouchDevice.d.ts"
|
|
80
|
+
},
|
|
76
81
|
"./useIsomorphicLayoutEffect": {
|
|
77
82
|
"import": "./dist/core/useIsomorphicLayoutEffect.js",
|
|
78
83
|
"require": "./dist/core/useIsomorphicLayoutEffect.cjs",
|
|
@@ -118,6 +123,11 @@
|
|
|
118
123
|
"require": "./dist/core/useResizeObserver.cjs",
|
|
119
124
|
"types": "./dist/core/useResizeObserver.d.ts"
|
|
120
125
|
},
|
|
126
|
+
"./useScreen": {
|
|
127
|
+
"import": "./dist/core/useScreen.js",
|
|
128
|
+
"require": "./dist/core/useScreen.cjs",
|
|
129
|
+
"types": "./dist/core/useScreen.d.ts"
|
|
130
|
+
},
|
|
121
131
|
"./useSessionStorage": {
|
|
122
132
|
"import": "./dist/core/useSessionStorage.js",
|
|
123
133
|
"require": "./dist/core/useSessionStorage.cjs",
|