@btja/use-tailwind-media-query 0.1.1 → 0.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/README.md +81 -46
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +33 -3
- package/dist/index.d.ts +33 -3
- package/dist/index.js +1 -1
- package/package.json +6 -2
package/README.md
CHANGED
|
@@ -1,38 +1,30 @@
|
|
|
1
1
|
# use-tailwind-media-query
|
|
2
2
|
|
|
3
|
-
React hooks for Tailwind-style breakpoints and arbitrary media queries.
|
|
3
|
+
React 19 hooks for Tailwind-style breakpoints and arbitrary media queries.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
The hooks share a small media-query store so repeated breakpoint checks can reuse the
|
|
18
|
-
same underlying subscription instead of attaching duplicate `matchMedia` listeners in
|
|
19
|
-
every component.
|
|
5
|
+
<table align="center">
|
|
6
|
+
<tr>
|
|
7
|
+
<td align="center">
|
|
8
|
+
<img src="docs/desktop-view.png" alt="Desktop adaptive view" height="320" />
|
|
9
|
+
<div><strong>Desktop view</strong></div>
|
|
10
|
+
</td>
|
|
11
|
+
<td align="center">
|
|
12
|
+
<img src="docs/mobile-view.png" alt="Mobile adaptive view" height="320" />
|
|
13
|
+
<div><strong>Mobile view</strong></div>
|
|
14
|
+
</td>
|
|
15
|
+
</tr>
|
|
16
|
+
</table>
|
|
20
17
|
|
|
21
|
-
|
|
22
|
-
|
|
18
|
+
<div align="center">
|
|
19
|
+
<a href="https://bwt2.github.io/use-tailwind-media-query">Live demo</a>
|
|
20
|
+
</div>
|
|
23
21
|
|
|
24
22
|
## Installation
|
|
25
23
|
|
|
26
24
|
```bash
|
|
27
25
|
npm i @btja/use-tailwind-media-query
|
|
28
|
-
```
|
|
29
|
-
```bash
|
|
30
26
|
yarn add @btja/use-tailwind-media-query
|
|
31
|
-
```
|
|
32
|
-
```bash
|
|
33
27
|
pnpm add @btja/use-tailwind-media-query
|
|
34
|
-
```
|
|
35
|
-
```bash
|
|
36
28
|
bun add @btja/use-tailwind-media-query
|
|
37
29
|
```
|
|
38
30
|
|
|
@@ -64,8 +56,8 @@ Hooks:
|
|
|
64
56
|
|
|
65
57
|
| Hook | Returns | Purpose |
|
|
66
58
|
| --- | --- | --- |
|
|
67
|
-
| `useMediaQuery(query: string)` | `bool` | Subscribe to any CSS media query |
|
|
68
|
-
| `useTailwindBreakpoint(tailwindBreakpoint: TailwindBreakpoint)` | `bool` | Tailwind breakpoint helper |
|
|
59
|
+
| `useMediaQuery(query: string, options?)` | `bool` | Subscribe to any CSS media query |
|
|
60
|
+
| `useTailwindBreakpoint(tailwindBreakpoint: TailwindBreakpoint, options?)` | `bool` | Tailwind breakpoint helper |
|
|
69
61
|
| `useIsMobile()` | `bool` | `true` below `md` |
|
|
70
62
|
| `useIsTablet()` | `bool` | `true` from `md` to below `lg` |
|
|
71
63
|
| `useIsDesktop()` | `bool` | `true` at `lg` and above |
|
|
@@ -85,19 +77,6 @@ export function Example() {
|
|
|
85
77
|
}
|
|
86
78
|
```
|
|
87
79
|
|
|
88
|
-
<table align="center">
|
|
89
|
-
<tr>
|
|
90
|
-
<td align="center">
|
|
91
|
-
<img src="docs/desktop-view.png" alt="Desktop adaptive view" height="320" />
|
|
92
|
-
<div><strong>Desktop view</strong></div>
|
|
93
|
-
</td>
|
|
94
|
-
<td align="center">
|
|
95
|
-
<img src="docs/mobile-view.png" alt="Mobile adaptive view" height="320" />
|
|
96
|
-
<div><strong>Mobile view</strong></div>
|
|
97
|
-
</td>
|
|
98
|
-
</tr>
|
|
99
|
-
</table>
|
|
100
|
-
|
|
101
80
|
`useTailwindBreakpoint`:
|
|
102
81
|
|
|
103
82
|
```tsx
|
|
@@ -110,6 +89,16 @@ export function SidebarLayout() {
|
|
|
110
89
|
}
|
|
111
90
|
```
|
|
112
91
|
|
|
92
|
+
```tsx
|
|
93
|
+
import { useTailwindBreakpoint } from '@btja/use-tailwind-media-query'
|
|
94
|
+
|
|
95
|
+
export function SsrSidebarLayout() {
|
|
96
|
+
const showSidebar = useTailwindBreakpoint('lg', { noWindowDefault: true })
|
|
97
|
+
|
|
98
|
+
return showSidebar ? <DesktopLayout /> : <CompactLayout />
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
113
102
|
`useMediaQuery`:
|
|
114
103
|
|
|
115
104
|
```tsx
|
|
@@ -122,18 +111,31 @@ export function MotionPreference() {
|
|
|
122
111
|
}
|
|
123
112
|
```
|
|
124
113
|
|
|
114
|
+
```tsx
|
|
115
|
+
import { useMediaQuery } from '@btja/use-tailwind-media-query'
|
|
116
|
+
|
|
117
|
+
export function SsrDesktopShell() {
|
|
118
|
+
const isDesktop = useMediaQuery('(min-width: 1024px)', { noWindowDefault: true })
|
|
119
|
+
|
|
120
|
+
return isDesktop ? <DesktopShell /> : <MobileShell />
|
|
121
|
+
}
|
|
122
|
+
```
|
|
123
|
+
|
|
125
124
|
|
|
126
125
|
## SEO and SSR
|
|
127
126
|
|
|
128
127
|
If possible, keep both screen variants semantically equivalent for SEO, accessibility,
|
|
129
128
|
and consistency purposes.
|
|
130
129
|
|
|
131
|
-
With this library, server render does not know the real viewport
|
|
130
|
+
With this library, server render does not know the real viewport unless you provide an
|
|
131
|
+
explicit fallback with `noWindowDefault`:
|
|
132
132
|
|
|
133
133
|
| Hook | Server-render value |
|
|
134
134
|
| --- | --- |
|
|
135
135
|
| `useMediaQuery()` | `false` |
|
|
136
|
+
| `useMediaQuery(query, { noWindowDefault: true })` | `true` |
|
|
136
137
|
| `useTailwindBreakpoint()` | `false` |
|
|
138
|
+
| `useTailwindBreakpoint(breakpoint, { noWindowDefault: true })` | `true` |
|
|
137
139
|
| `useIsMobile()` | `true` |
|
|
138
140
|
| `useIsDesktop()` | `false` |
|
|
139
141
|
|
|
@@ -145,6 +147,19 @@ return useIsMobile() ? <MobileView /> : <DesktopView />
|
|
|
145
147
|
|
|
146
148
|
renders the mobile branch on the server, then hydrates to the real client viewport.
|
|
147
149
|
|
|
150
|
+
Use `noWindowDefault` when you want the server-rendered branch to default to a known
|
|
151
|
+
assumption before the browser can evaluate `matchMedia`. This is useful when your SSR
|
|
152
|
+
environment already has a strong hint about the likely viewport and you want to reduce
|
|
153
|
+
layout flipping during hydration.
|
|
154
|
+
|
|
155
|
+
Ways to choose `noWindowDefault` on the server include:
|
|
156
|
+
- infer a mobile/desktop default from the user-agent string
|
|
157
|
+
- use device hints or request headers provided by your framework, CDN, or edge runtime
|
|
158
|
+
- store a prior device preference in a cookie and reuse it during SSR
|
|
159
|
+
- pick a product-level default such as mobile-first when no reliable hint exists
|
|
160
|
+
|
|
161
|
+
If the server guess is wrong, the client will correct to the real media-query result after hydration (with risk of layout flash).
|
|
162
|
+
|
|
148
163
|
If SEO matters, avoid putting unique primary content in only one branch. Keep headings,
|
|
149
164
|
copy, links, and other canonical content the same when possible, and reserve screen-
|
|
150
165
|
specific differences for layout or secondary UI.
|
|
@@ -154,6 +169,26 @@ Current limitation: `useMediaQuery` depends on `MediaQueryList.addEventListener`
|
|
|
154
169
|
`removeEventListener`. Older Safari/WebView environments that only implement
|
|
155
170
|
`addListener` / `removeListener` are not supported.
|
|
156
171
|
|
|
172
|
+
|
|
173
|
+
## Motivation
|
|
174
|
+
|
|
175
|
+
### Adaptive design
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
This hook is intended to be used in adaptive designs in React.
|
|
179
|
+
|
|
180
|
+
Use adaptive design when layouts should change across breakpoints (for example: dashboards, dense data views, and flows on desktop vs mobile). Prefer plain responsive design when the same hierarchy still works and only spacing,
|
|
181
|
+
columns, or typography need to change.
|
|
182
|
+
|
|
183
|
+
### Store
|
|
184
|
+
|
|
185
|
+
The hooks share a small media-query store so repeated breakpoint checks can reuse the
|
|
186
|
+
same underlying subscription instead of attaching duplicate `matchMedia` listeners in
|
|
187
|
+
every component.
|
|
188
|
+
|
|
189
|
+
Multiple components on the same screen can listen to the
|
|
190
|
+
same media query without duplicating listeners and wasting memory.
|
|
191
|
+
|
|
157
192
|
## Development
|
|
158
193
|
Using pnpm.
|
|
159
194
|
|
|
@@ -174,14 +209,14 @@ pnpm run build
|
|
|
174
209
|
Before releasing (after `npm login`):
|
|
175
210
|
|
|
176
211
|
```bash
|
|
177
|
-
pnpm bump:version
|
|
178
212
|
pnpm check:release
|
|
213
|
+
pnpm bump:version
|
|
214
|
+
|
|
179
215
|
npm publish --access public
|
|
180
216
|
```
|
|
181
217
|
|
|
182
218
|
## AI Disclosure
|
|
183
|
-
Codex 5.4 was used to review and sanity-check:
|
|
184
|
-
-
|
|
185
|
-
-
|
|
186
|
-
|
|
187
|
-
Codex 5.4 was used to generate mockup UI in '`./playground`' demo.
|
|
219
|
+
Codex 5.4 was used to review and sanity-check the code. Codex was also used for:
|
|
220
|
+
- Mockup UI in '`./playground`' demo
|
|
221
|
+
- JSDoc
|
|
222
|
+
- Tests
|
package/dist/index.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});let e=require("react/compiler-runtime"),t=require("react");const n=new Map;function r(e){let
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});let e=require("react/compiler-runtime"),t=require("react");const n=new Map;function r(e,t=!1,r=e){let i=typeof window<`u`?window.matchMedia(e):void 0,a=new Set,o=()=>{a.forEach(e=>e())};return{getSnapshot(){return(i==null?void 0:i.matches)??t},getServerSnapshot(){return t},subscribe(e){return i?(a.add(e),a.size===1&&i.addEventListener(`change`,o),()=>{a.delete(e),a.size===0&&(i.removeEventListener(`change`,o),n.delete(r))}):()=>{}}}}function i(e,t=!1){if(typeof window>`u`)return r(e,t);let i=`${e}::${String(t)}`,a=n.get(i);return a||(a=r(e,t,i),n.set(i,a)),a}function a(n,r){let a=(0,e.c)(5),o;a[0]===r?o=a[1]:(o=r===void 0?{}:r,a[0]=r,a[1]=o);let s=o.noWindowDefault??!1,c;a[2]!==n||a[3]!==s?(c=i(n,s),a[2]=n,a[3]=s,a[4]=c):c=a[4];let l=c;return(0,t.useSyncExternalStore)(l.subscribe,l.getSnapshot,l.getServerSnapshot)}const o={sm:`(min-width: 640px)`,md:`(min-width: 768px)`,lg:`(min-width: 1024px)`,xl:`(min-width: 1280px)`,"2xl":`(min-width: 1536px)`};function s(e,t){return a(o[e],t)}function c(){return!s(`md`)}function l(){let e=s(`md`),t=s(`lg`);return e&&!t}function u(){return s(`lg`)}function d(){return s(`xl`)}exports.useIsDesktop=u,exports.useIsLargeDesktop=d,exports.useIsMobile=c,exports.useIsTablet=l,exports.useMediaQuery=a,exports.useTailwindBreakpoint=s;
|
package/dist/index.d.cts
CHANGED
|
@@ -1,7 +1,19 @@
|
|
|
1
1
|
//#region src/useMediaQuery.d.ts
|
|
2
|
-
|
|
2
|
+
type UseMediaQueryOptions = {
|
|
3
|
+
/**
|
|
4
|
+
* Value returned when `window` is unavailable, such as during server rendering.
|
|
5
|
+
*/
|
|
6
|
+
noWindowDefault?: boolean;
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* Subscribes to an arbitrary CSS media query and returns whether it currently matches.
|
|
10
|
+
*/
|
|
11
|
+
declare function useMediaQuery(query: string, options?: UseMediaQueryOptions): boolean;
|
|
3
12
|
//#endregion
|
|
4
13
|
//#region src/tailwindBreakpoints.d.ts
|
|
14
|
+
/**
|
|
15
|
+
* Built-in Tailwind-style media queries keyed by breakpoint name.
|
|
16
|
+
*/
|
|
5
17
|
declare const tailwindBreakpointQueries: {
|
|
6
18
|
readonly sm: "(min-width: 640px)";
|
|
7
19
|
readonly md: "(min-width: 768px)";
|
|
@@ -9,13 +21,31 @@ declare const tailwindBreakpointQueries: {
|
|
|
9
21
|
readonly xl: "(min-width: 1280px)";
|
|
10
22
|
readonly "2xl": "(min-width: 1536px)";
|
|
11
23
|
};
|
|
24
|
+
/**
|
|
25
|
+
* Supported Tailwind breakpoint names for the built-in breakpoint helpers.
|
|
26
|
+
*/
|
|
12
27
|
type TailwindBreakpoint = keyof typeof tailwindBreakpointQueries;
|
|
13
28
|
//#endregion
|
|
14
29
|
//#region src/useTailwindBreakpoint.d.ts
|
|
15
|
-
|
|
30
|
+
/**
|
|
31
|
+
* Subscribes to one of the built-in Tailwind breakpoint queries.
|
|
32
|
+
*/
|
|
33
|
+
declare function useTailwindBreakpoint(tailwindBreakpoint: TailwindBreakpoint, options?: UseMediaQueryOptions): boolean;
|
|
34
|
+
/**
|
|
35
|
+
* Returns `true` when the viewport is smaller than Tailwind's `md` breakpoint.
|
|
36
|
+
*/
|
|
16
37
|
declare function useIsMobile(): boolean;
|
|
38
|
+
/**
|
|
39
|
+
* Returns `true` when the viewport is between Tailwind's `md` and `lg` breakpoints.
|
|
40
|
+
*/
|
|
17
41
|
declare function useIsTablet(): boolean;
|
|
42
|
+
/**
|
|
43
|
+
* Returns `true` when the viewport is at or above Tailwind's `lg` breakpoint.
|
|
44
|
+
*/
|
|
18
45
|
declare function useIsDesktop(): boolean;
|
|
46
|
+
/**
|
|
47
|
+
* Returns `true` when the viewport is at or above Tailwind's `xl` breakpoint.
|
|
48
|
+
*/
|
|
19
49
|
declare function useIsLargeDesktop(): boolean;
|
|
20
50
|
//#endregion
|
|
21
|
-
export { type TailwindBreakpoint, type tailwindBreakpointQueries, useIsDesktop, useIsLargeDesktop, useIsMobile, useIsTablet, useMediaQuery, useTailwindBreakpoint };
|
|
51
|
+
export { type TailwindBreakpoint, UseMediaQueryOptions, type tailwindBreakpointQueries, useIsDesktop, useIsLargeDesktop, useIsMobile, useIsTablet, useMediaQuery, useTailwindBreakpoint };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,19 @@
|
|
|
1
1
|
//#region src/useMediaQuery.d.ts
|
|
2
|
-
|
|
2
|
+
type UseMediaQueryOptions = {
|
|
3
|
+
/**
|
|
4
|
+
* Value returned when `window` is unavailable, such as during server rendering.
|
|
5
|
+
*/
|
|
6
|
+
noWindowDefault?: boolean;
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* Subscribes to an arbitrary CSS media query and returns whether it currently matches.
|
|
10
|
+
*/
|
|
11
|
+
declare function useMediaQuery(query: string, options?: UseMediaQueryOptions): boolean;
|
|
3
12
|
//#endregion
|
|
4
13
|
//#region src/tailwindBreakpoints.d.ts
|
|
14
|
+
/**
|
|
15
|
+
* Built-in Tailwind-style media queries keyed by breakpoint name.
|
|
16
|
+
*/
|
|
5
17
|
declare const tailwindBreakpointQueries: {
|
|
6
18
|
readonly sm: "(min-width: 640px)";
|
|
7
19
|
readonly md: "(min-width: 768px)";
|
|
@@ -9,13 +21,31 @@ declare const tailwindBreakpointQueries: {
|
|
|
9
21
|
readonly xl: "(min-width: 1280px)";
|
|
10
22
|
readonly "2xl": "(min-width: 1536px)";
|
|
11
23
|
};
|
|
24
|
+
/**
|
|
25
|
+
* Supported Tailwind breakpoint names for the built-in breakpoint helpers.
|
|
26
|
+
*/
|
|
12
27
|
type TailwindBreakpoint = keyof typeof tailwindBreakpointQueries;
|
|
13
28
|
//#endregion
|
|
14
29
|
//#region src/useTailwindBreakpoint.d.ts
|
|
15
|
-
|
|
30
|
+
/**
|
|
31
|
+
* Subscribes to one of the built-in Tailwind breakpoint queries.
|
|
32
|
+
*/
|
|
33
|
+
declare function useTailwindBreakpoint(tailwindBreakpoint: TailwindBreakpoint, options?: UseMediaQueryOptions): boolean;
|
|
34
|
+
/**
|
|
35
|
+
* Returns `true` when the viewport is smaller than Tailwind's `md` breakpoint.
|
|
36
|
+
*/
|
|
16
37
|
declare function useIsMobile(): boolean;
|
|
38
|
+
/**
|
|
39
|
+
* Returns `true` when the viewport is between Tailwind's `md` and `lg` breakpoints.
|
|
40
|
+
*/
|
|
17
41
|
declare function useIsTablet(): boolean;
|
|
42
|
+
/**
|
|
43
|
+
* Returns `true` when the viewport is at or above Tailwind's `lg` breakpoint.
|
|
44
|
+
*/
|
|
18
45
|
declare function useIsDesktop(): boolean;
|
|
46
|
+
/**
|
|
47
|
+
* Returns `true` when the viewport is at or above Tailwind's `xl` breakpoint.
|
|
48
|
+
*/
|
|
19
49
|
declare function useIsLargeDesktop(): boolean;
|
|
20
50
|
//#endregion
|
|
21
|
-
export { type TailwindBreakpoint, type tailwindBreakpointQueries, useIsDesktop, useIsLargeDesktop, useIsMobile, useIsTablet, useMediaQuery, useTailwindBreakpoint };
|
|
51
|
+
export { type TailwindBreakpoint, UseMediaQueryOptions, type tailwindBreakpointQueries, useIsDesktop, useIsLargeDesktop, useIsMobile, useIsTablet, useMediaQuery, useTailwindBreakpoint };
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{c as e}from"react/compiler-runtime";import{useSyncExternalStore as t}from"react";const n=new Map;function r(e){let
|
|
1
|
+
import{c as e}from"react/compiler-runtime";import{useSyncExternalStore as t}from"react";const n=new Map;function r(e,t=!1,r=e){let i=typeof window<`u`?window.matchMedia(e):void 0,a=new Set,o=()=>{a.forEach(e=>e())};return{getSnapshot(){return(i==null?void 0:i.matches)??t},getServerSnapshot(){return t},subscribe(e){return i?(a.add(e),a.size===1&&i.addEventListener(`change`,o),()=>{a.delete(e),a.size===0&&(i.removeEventListener(`change`,o),n.delete(r))}):()=>{}}}}function i(e,t=!1){if(typeof window>`u`)return r(e,t);let i=`${e}::${String(t)}`,a=n.get(i);return a||(a=r(e,t,i),n.set(i,a)),a}function a(n,r){let a=e(5),o;a[0]===r?o=a[1]:(o=r===void 0?{}:r,a[0]=r,a[1]=o);let s=o.noWindowDefault??!1,c;a[2]!==n||a[3]!==s?(c=i(n,s),a[2]=n,a[3]=s,a[4]=c):c=a[4];let l=c;return t(l.subscribe,l.getSnapshot,l.getServerSnapshot)}const o={sm:`(min-width: 640px)`,md:`(min-width: 768px)`,lg:`(min-width: 1024px)`,xl:`(min-width: 1280px)`,"2xl":`(min-width: 1536px)`};function s(e,t){return a(o[e],t)}function c(){return!s(`md`)}function l(){let e=s(`md`),t=s(`lg`);return e&&!t}function u(){return s(`lg`)}function d(){return s(`xl`)}export{u as useIsDesktop,d as useIsLargeDesktop,c as useIsMobile,l as useIsTablet,a as useMediaQuery,s as useTailwindBreakpoint};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@btja/use-tailwind-media-query",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.2.0",
|
|
5
5
|
"description": "Helper react hook to listen for tailwind media breakpoints. Intended for use with adaptive design.",
|
|
6
6
|
"author": "Brian W Tjahjadi <brian.w.tjahjadi@gmail.com>",
|
|
7
7
|
"license": "MIT",
|
|
@@ -13,7 +13,10 @@
|
|
|
13
13
|
"react-hooks",
|
|
14
14
|
"hook",
|
|
15
15
|
"custom-hook",
|
|
16
|
-
"use-tailwind-media-query"
|
|
16
|
+
"use-tailwind-media-query",
|
|
17
|
+
"tailwind-media-query",
|
|
18
|
+
"tailwind-breakpoints",
|
|
19
|
+
"adaptive-design"
|
|
17
20
|
],
|
|
18
21
|
"homepage": "https://github.com/bwt2/use-tailwind-media-query#readme",
|
|
19
22
|
"repository": {
|
|
@@ -37,6 +40,7 @@
|
|
|
37
40
|
"build": "tsdown",
|
|
38
41
|
"dev": "tsdown --watch",
|
|
39
42
|
"play": "vite",
|
|
43
|
+
"play:build": "vite build",
|
|
40
44
|
"test": "vitest",
|
|
41
45
|
"typecheck": "tsc --noEmit",
|
|
42
46
|
"pack:dry-run": "npm pack --dry-run",
|