Skip to content

Commit f88930a

Browse files
committed
feat(hooks): add useMediaQuery hook
1 parent 350627c commit f88930a

File tree

24 files changed

+1109
-0
lines changed

24 files changed

+1109
-0
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ const { useBoolean, useEffectCompare, useWindowEvent } = require('@webeach/react
105105
+ [useLocalStorage](./docs/en/useLocalStorage.md)
106106
+ [useLoop](./docs/en/useLoop.md)
107107
+ [useMap](./docs/en/useMap.md)
108+
+ [useMediaQuery](./docs/en/useMediaQuery.md)
108109
+ [useMemoCompare](./docs/en/useMemoCompare.md)
109110
+ [useNumber](./docs/en/useNumber.md)
110111
+ [useOutsideEvent](./docs/en/useOutsideEvent.md)
@@ -193,6 +194,7 @@ const { useBoolean, useEffectCompare, useWindowEvent } = require('@webeach/react
193194
- [useResizeObserver](./docs/en/useResizeObserver.md)
194195

195196
#### Page & document
197+
- [useMediaQuery](./docs/en/useMediaQuery.md)
196198
- [usePageTitle](./docs/en/usePageTitle.md)
197199
- [usePageVisibility](./docs/en/usePageVisibility.md)
198200

README.ru.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ const { useBoolean, useEffectCompare, useWindowEvent } = require('@webeach/react
105105
+ [useLocalStorage](./docs/ru/useLocalStorage.md)
106106
+ [useLoop](./docs/ru/useLoop.md)
107107
+ [useMap](./docs/ru/useMap.md)
108+
+ [useMediaQuery](./docs/ru/useMediaQuery.md)
108109
+ [useMemoCompare](./docs/ru/useMemoCompare.md)
109110
+ [useNumber](./docs/ru/useNumber.md)
110111
+ [useOutsideEvent](./docs/ru/useOutsideEvent.md)
@@ -193,6 +194,7 @@ const { useBoolean, useEffectCompare, useWindowEvent } = require('@webeach/react
193194
- [useResizeObserver](./docs/ru/useResizeObserver.md)
194195

195196
#### Страница/документ
197+
- [useMediaQuery](./docs/ru/useMediaQuery.md)
196198
- [usePageTitle](./docs/ru/usePageTitle.md)
197199
- [usePageVisibility](./docs/ru/usePageVisibility.md)
198200

docs/en/useMediaQuery.md

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
# `useMediaQuery`
2+
3+
## Description
4+
5+
`useMediaQuery` — a React hook for subscribing to **CSS media queries**. It returns a boolean value indicating whether the current window matches the given conditions.
6+
7+
Supports multiple call forms: single rule, multiple rules, with or without an explicit media type (`all`, `screen`, `print`).
8+
9+
---
10+
11+
## Signature
12+
13+
```ts
14+
// Overload 1: single rule with default type "all"
15+
function useMediaQuery(rule: UseMediaQueryRule): UseMediaQueryReturn;
16+
17+
// Overload 2: multiple rules (OR) with default type "all"
18+
function useMediaQuery(rules: ReadonlyArray<UseMediaQueryRule>): UseMediaQueryReturn;
19+
20+
// Overload 3: explicit type + single rule
21+
function useMediaQuery(type: UseMediaQueryType, rule: UseMediaQueryRule): UseMediaQueryReturn;
22+
23+
// Overload 4: explicit type + multiple rules (OR)
24+
function useMediaQuery(type: UseMediaQueryType, rules: ReadonlyArray<UseMediaQueryRule>): UseMediaQueryReturn;
25+
```
26+
27+
- **Parameters**
28+
- `type?: UseMediaQueryType` — media query type (`all`, `screen`, `print`). Default is `all`.
29+
- `rule | rules` — condition object(s) of type `UseMediaQueryRule` (e.g., `minWidth`, `orientation`, `prefersColorScheme`).
30+
31+
- **Returns**: `UseMediaQueryReturn` — a tuple `[isMatch: boolean]` where `isMatch` reflects the current state.
32+
33+
---
34+
35+
## Examples
36+
37+
### 1) Single media query
38+
39+
```tsx
40+
import { useMediaQuery } from '@webeach/react-hooks/useMediaQuery';
41+
42+
export function Layout() {
43+
const [isLargeScreen] = useMediaQuery({ minWidth: 1024 });
44+
45+
return <div>{isLargeScreen ? 'Desktop' : 'Mobile'}</div>;
46+
}
47+
```
48+
49+
### 2) Multiple rules (OR)
50+
51+
```tsx
52+
import { useMediaQuery } from '@webeach/react-hooks/useMediaQuery';
53+
54+
export function Responsive() {
55+
const [isMobileOrLandscape] = useMediaQuery([
56+
{ maxWidth: 600 },
57+
{ orientation: 'landscape' },
58+
]);
59+
60+
return <div>{isMobileOrLandscape ? 'Compact view' : 'Full view'}</div>;
61+
}
62+
```
63+
64+
### 3) Explicit type
65+
66+
```tsx
67+
import { useMediaQuery } from '@webeach/react-hooks/useMediaQuery';
68+
69+
export function PrintLayout() {
70+
const [isPrintReady] = useMediaQuery('print', { orientation: 'portrait' });
71+
72+
return <div>{isPrintReady ? 'Portrait print layout' : 'Other'}</div>;
73+
}
74+
```
75+
76+
### 4) Multiple conditions in a single rule (AND)
77+
78+
```tsx
79+
import { useMediaQuery } from '@webeach/react-hooks/useMediaQuery';
80+
81+
export function DesktopLandscape() {
82+
const [isDesktopLandscape] = useMediaQuery({
83+
minWidth: 1024,
84+
orientation: 'landscape',
85+
});
86+
87+
return <div>{isDesktopLandscape ? 'Wide desktop' : 'Other'}</div>;
88+
}
89+
```
90+
91+
---
92+
93+
## Behavior
94+
95+
1. **Combining rules**
96+
- If you pass an array of rules, they are joined with commas (`query1, query2`), which corresponds to OR logic.
97+
- If you pass a single rule with multiple properties, they are joined with `and` (AND logic).
98+
99+
2. **SSR compatibility**
100+
- On the server it always returns `[false]`, without attempting to access `window`.
101+
102+
---
103+
104+
## When to use
105+
106+
- For adaptive components (mobile/desktop layout).
107+
- For enabling/disabling UI features based on user preferences (`prefers-reduced-motion`, `prefers-color-scheme`).
108+
- For conditional rendering in print context (`print`).
109+
110+
---
111+
112+
## When **not** to use
113+
114+
- If you need complex resize logic or element size calculations — better use `useResizeObserver`.
115+
- If you only need to check the width once — simpler to use `window.innerWidth`.
116+
117+
---
118+
119+
## Common mistakes
120+
121+
1. **Expecting AND logic with arrays**
122+
- Passing an array works as OR, not AND. For AND combinations, put conditions into a single object.
123+
124+
2. **Incorrect usage in SSR**
125+
- On the server the result is always `false`. Do not rely on it without a fallback.
126+
127+
3. **Ignoring units**
128+
- `minWidth` and `maxWidth` are always in `px`, `minResolution`/`maxResolution` — in `dppx`.
129+
130+
---
131+
132+
## Typing
133+
134+
**Exported types**
135+
136+
- `UseMediaQueryReturn`
137+
- Tuple `[isMatch: boolean]`.
138+
139+
- `UseMediaQueryRule`
140+
- Object of conditions: `minWidth`, `maxWidth`, `orientation`, `prefersColorScheme`, etc.
141+
142+
- `UseMediaQueryType`
143+
- Media query type: `'all' | 'screen' | 'print'`.
144+
145+
---
146+
147+
## See also
148+
149+
- [useResizeObserver](useResizeObserver.md)

docs/en/useResizeObserver.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,3 +165,4 @@ export function ResponsiveGrid() {
165165
## See also
166166

167167
- [useIntersectionObserver](useIntersectionObserver.md)
168+
- [useMediaQuery](useMediaQuery.md)

docs/ru/useMediaQuery.md

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
# `useMediaQuery`
2+
3+
## Описание
4+
5+
`useMediaQuery` — хук для подписки на **CSS media queries** из React. Возвращает булево значение, указывающее, соответствует ли текущее окно указанным условиям.
6+
7+
Поддерживает разные формы вызова: один или несколько правил, с явным или неявным указанием типа (`all`, `screen`, `print`).
8+
9+
---
10+
11+
## Сигнатура
12+
13+
```ts
14+
// Перегрузка 1: одно правило с типом по умолчанию "all"
15+
function useMediaQuery(rule: UseMediaQueryRule): UseMediaQueryReturn;
16+
17+
// Перегрузка 2: несколько правил (OR) с типом по умолчанию "all"
18+
function useMediaQuery(rules: ReadonlyArray<UseMediaQueryRule>): UseMediaQueryReturn;
19+
20+
// Перегрузка 3: явный тип + одно правило
21+
function useMediaQuery(type: UseMediaQueryType, rule: UseMediaQueryRule): UseMediaQueryReturn;
22+
23+
// Перегрузка 4: явный тип + несколько правил (OR)
24+
function useMediaQuery(type: UseMediaQueryType, rules: ReadonlyArray<UseMediaQueryRule>): UseMediaQueryReturn;
25+
```
26+
27+
- **Параметры**
28+
- `type?: UseMediaQueryType` — тип медиа-запроса (`all`, `screen`, `print`). По умолчанию — `all`.
29+
- `rule | rules` — объект(ы) условий `UseMediaQueryRule` (например, `minWidth`, `orientation`, `prefersColorScheme`).
30+
31+
- **Возвращает**: `UseMediaQueryReturn` — кортеж `[isMatch: boolean]`, где `isMatch` отражает текущее состояние.
32+
33+
---
34+
35+
## Примеры
36+
37+
### 1) Один медиа-запрос
38+
39+
```tsx
40+
import { useMediaQuery } from '@webeach/react-hooks/useMediaQuery';
41+
42+
export function Layout() {
43+
const [isLargeScreen] = useMediaQuery({ minWidth: 1024 });
44+
45+
return <div>{isLargeScreen ? 'Desktop' : 'Mobile'}</div>;
46+
}
47+
```
48+
49+
### 2) Несколько правил (OR)
50+
51+
```tsx
52+
import { useMediaQuery } from '@webeach/react-hooks/useMediaQuery';
53+
54+
export function Responsive() {
55+
const [isMobileOrLandscape] = useMediaQuery([
56+
{ maxWidth: 600 },
57+
{ orientation: 'landscape' },
58+
]);
59+
60+
return <div>{isMobileOrLandscape ? 'Compact view' : 'Full view'}</div>;
61+
}
62+
```
63+
64+
### 3) Явный тип
65+
66+
```tsx
67+
import { useMediaQuery } from '@webeach/react-hooks/useMediaQuery';
68+
69+
export function PrintLayout() {
70+
const [isPrintReady] = useMediaQuery('print', { orientation: 'portrait' });
71+
72+
return <div>{isPrintReady ? 'Portrait print layout' : 'Other'}</div>;
73+
}
74+
```
75+
76+
### 4) Несколько условий в одном правиле (AND)
77+
78+
```tsx
79+
import { useMediaQuery } from '@webeach/react-hooks/useMediaQuery';
80+
81+
export function DesktopLandscape() {
82+
const [isDesktopLandscape] = useMediaQuery({
83+
minWidth: 1024,
84+
orientation: 'landscape',
85+
});
86+
87+
return <div>{isDesktopLandscape ? 'Wide desktop' : 'Other'}</div>;
88+
}
89+
```
90+
91+
---
92+
93+
## Поведение
94+
95+
1. **Комбинация правил**
96+
- Если передан массив правил, они объединяются через запятую (`query1, query2`), что соответствует OR-логике.
97+
- Если передано одно правило с несколькими свойствами, они объединяются через `and` (AND-логика).
98+
99+
2. **SSR-совместимость**
100+
- На сервере всегда возвращается `[false]`, без попытки доступа к `window`.
101+
102+
---
103+
104+
## Когда использовать
105+
106+
- Для адаптивных компонентов (мобильная/десктопная верстка).
107+
- Для включения/выключения UI-функций по пользовательским настройкам (`prefers-reduced-motion`, `prefers-color-scheme`).
108+
- Для условного рендера при печати (`print`).
109+
110+
---
111+
112+
## Когда **не** использовать
113+
114+
- Если нужна сложная логика резайза или вычисления размеров элемента — лучше `useResizeObserver.
115+
- Если нужно лишь единоразово проверить ширину — проще использовать `window.innerWidth`.
116+
117+
---
118+
119+
## Частые ошибки
120+
121+
1. **Ожидание AND-логики в массиве**
122+
- Передача массива работает как OR, а не AND. Для AND-комбинаций объединяйте правила в один объект.
123+
124+
2. **Неверное использование в SSR**
125+
- На сервере результат всегда `false`. Не завязывайтесь на него без fallback.
126+
127+
3. **Игнорирование единиц измерения**
128+
- `minWidth` и `maxWidth` всегда интерпретируются в `px`, `minResolution`/`maxResolution` — в `dppx`.
129+
130+
---
131+
132+
## Типизация
133+
134+
**Экспортируемые типы**
135+
136+
- `UseMediaQueryReturn`
137+
- Кортеж `[isMatch: boolean]`.
138+
139+
- `UseMediaQueryRule`
140+
- Объект условий: `minWidth`, `maxWidth`, `orientation`, `prefersColorScheme` и др.
141+
142+
- `UseMediaQueryType`
143+
- Тип медиа-запроса: `'all' | 'screen' | 'print'`.
144+
145+
---
146+
147+
## Смотрите также
148+
149+
- [useResizeObserver](useResizeObserver.md)

docs/ru/useResizeObserver.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,3 +166,4 @@ export function ResponsiveGrid() {
166166
## Смотрите также
167167

168168
- [useIntersectionObserver](useIntersectionObserver.md)
169+
- [useMediaQuery](useMediaQuery.md)

package.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,11 @@
228228
"import": "./lib/esm/hooks/useMap/index.js",
229229
"require": "./lib/cjs/hooks/useMap/index.js"
230230
},
231+
"./useMediaQuery": {
232+
"types": "./lib/types/hooks/useMediaQuery/index.d.ts",
233+
"import": "./lib/esm/hooks/useMediaQuery/index.js",
234+
"require": "./lib/cjs/hooks/useMediaQuery/index.js"
235+
},
231236
"./useMemoCompare": {
232237
"types": "./lib/types/hooks/useMemoCompare/index.d.ts",
233238
"import": "./lib/esm/hooks/useMemoCompare/index.js",

0 commit comments

Comments
 (0)