@djangocfg/ui-core 2.1.550 → 2.1.555
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 -5
- package/src/hooks/media/useMediaQuery.ts +32 -17
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@djangocfg/ui-core",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.555",
|
|
4
4
|
"description": "Pure React UI component library without Next.js dependencies - for Electron, Vite, CRA apps",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ui-components",
|
|
@@ -130,7 +130,7 @@
|
|
|
130
130
|
"check:contrast": "node scripts/check-preset-contrast.mjs"
|
|
131
131
|
},
|
|
132
132
|
"peerDependencies": {
|
|
133
|
-
"@djangocfg/i18n": "^2.1.
|
|
133
|
+
"@djangocfg/i18n": "^2.1.555",
|
|
134
134
|
"consola": "^3.4.2",
|
|
135
135
|
"lucide-react": "^0.545.0",
|
|
136
136
|
"moment": "^2.30.1",
|
|
@@ -206,9 +206,9 @@
|
|
|
206
206
|
"vaul": "1.1.2"
|
|
207
207
|
},
|
|
208
208
|
"devDependencies": {
|
|
209
|
-
"@djangocfg/eslint-config": "^2.1.
|
|
210
|
-
"@djangocfg/i18n": "^2.1.
|
|
211
|
-
"@djangocfg/typescript-config": "^2.1.
|
|
209
|
+
"@djangocfg/eslint-config": "^2.1.555",
|
|
210
|
+
"@djangocfg/i18n": "^2.1.555",
|
|
211
|
+
"@djangocfg/typescript-config": "^2.1.555",
|
|
212
212
|
"@storybook/react-vite": "^10.5.0",
|
|
213
213
|
"@types/node": "^24.13.3",
|
|
214
214
|
"@types/react": "19.2.15",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { useCallback, useSyncExternalStore } from 'react';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Tailwind v4 default breakpoints (rem → px at 16px base).
|
|
@@ -23,29 +23,44 @@ export type Breakpoint = keyof typeof BREAKPOINTS
|
|
|
23
23
|
/**
|
|
24
24
|
* Reactive media query hook.
|
|
25
25
|
*
|
|
26
|
+
* WHY useSyncExternalStore:
|
|
27
|
+
* A media query has no server value, so the hydration render MUST return the
|
|
28
|
+
* same `false` the server rendered — reading `matchMedia` there makes React
|
|
29
|
+
* throw "server rendered HTML didn't match the client" for any component that
|
|
30
|
+
* branches on the result (a narrow-viewport `<Sidebar>` renders its drawer
|
|
31
|
+
* branch against the server's desktop markup). `getServerSnapshot` is the
|
|
32
|
+
* only reliable way to pin that first render: React calls it during hydration
|
|
33
|
+
* as well as on the server, then re-renders with the real match after mount.
|
|
34
|
+
* A lazy `useState` initializer cannot express that distinction.
|
|
35
|
+
*
|
|
36
|
+
* Consequence to design around: the first painted frame is always the
|
|
37
|
+
* `false` branch. Components that must not flash should let CSS decide
|
|
38
|
+
* (Tailwind `md:` variants) rather than branch in JS.
|
|
39
|
+
*
|
|
26
40
|
* @example
|
|
27
41
|
* const isPhone = useMediaQuery('(max-width: 639px)')
|
|
28
42
|
* const isDark = useMediaQuery('(prefers-color-scheme: dark)')
|
|
29
43
|
* const isLandscape = useMediaQuery('(orientation: landscape)')
|
|
30
44
|
*/
|
|
31
45
|
export function useMediaQuery(query: string): boolean {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
46
|
+
const subscribe = useCallback(
|
|
47
|
+
(onChange: () => void) => {
|
|
48
|
+
if (typeof window === 'undefined' || !window.matchMedia) return () => {}
|
|
49
|
+
const mediaQuery = window.matchMedia(query)
|
|
50
|
+
mediaQuery.addEventListener('change', onChange)
|
|
51
|
+
return () => mediaQuery.removeEventListener('change', onChange)
|
|
52
|
+
},
|
|
53
|
+
[query]
|
|
54
|
+
)
|
|
39
55
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
// (e.g. hydration mismatch where SSR gave false).
|
|
44
|
-
setMatches(mediaQuery.matches)
|
|
45
|
-
const handler = (event: MediaQueryListEvent) => setMatches(event.matches)
|
|
46
|
-
mediaQuery.addEventListener('change', handler)
|
|
47
|
-
return () => mediaQuery.removeEventListener('change', handler)
|
|
56
|
+
const getSnapshot = useCallback(() => {
|
|
57
|
+
if (typeof window === 'undefined' || !window.matchMedia) return false
|
|
58
|
+
return window.matchMedia(query).matches
|
|
48
59
|
}, [query])
|
|
49
60
|
|
|
50
|
-
return
|
|
61
|
+
return useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function getServerSnapshot(): boolean {
|
|
65
|
+
return false
|
|
51
66
|
}
|