Skip to content

Commit a03fc10

Browse files
feat(hooks): build local useBreakpointValue and useMediaQuery hooks
1 parent 272db97 commit a03fc10

File tree

4 files changed

+126
-7
lines changed

4 files changed

+126
-7
lines changed

src/hooks/useBreakpointValue.ts

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import { screens } from "../../tailwind/screens"
2+
3+
import { useMediaQuery } from "./useMediaQuery"
4+
5+
const breakpointMap = {
6+
// Essentially to query from no width if smaller than "sm"
7+
base: "0px",
8+
...screens,
9+
}
10+
11+
type BreakpointKeys = keyof typeof breakpointMap
12+
13+
export const useBreakpointValue = <T = unknown>(
14+
values: Partial<Record<BreakpointKeys, T>>,
15+
fallbackBreakpoint?: BreakpointKeys
16+
): T => {
17+
const breakpointKeys = Object.keys(values) as BreakpointKeys[]
18+
19+
let fallbackPassed = false
20+
21+
const setBreakpoints = Object.entries(breakpointMap)
22+
.map(([breakpoint, value]) => {
23+
const item = {
24+
breakpoint,
25+
query: `(min-width: ${value})`,
26+
fallback: !fallbackPassed,
27+
}
28+
29+
if (breakpoint === fallbackBreakpoint) {
30+
fallbackPassed = true
31+
}
32+
33+
return item
34+
})
35+
.filter(({ breakpoint }) =>
36+
breakpointKeys.includes(breakpoint as BreakpointKeys)
37+
)
38+
39+
const fallbackQueries = setBreakpoints.map(({ fallback }) => fallback)
40+
41+
const queryValues = useMediaQuery(
42+
setBreakpoints.map((bp) => bp.query),
43+
fallbackQueries
44+
)
45+
46+
const index = queryValues.lastIndexOf(true)
47+
48+
const breakpointPicked = breakpointKeys[index]
49+
50+
return values[breakpointPicked] as T
51+
}

src/hooks/useMediaQuery.ts

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import { useState } from "react"
2+
3+
import { useIsomorphicLayoutEffect } from "./useIsomorphicLayoutEffect"
4+
5+
const IS_SERVER = typeof window === "undefined"
6+
7+
/**
8+
* Modified from https://usehooks-ts.com/react-hook/use-media-query
9+
* to account for an array of queries.
10+
*
11+
* Modifications sourced from Chakra-UI.
12+
* https://github.com/chakra-ui/chakra-ui/blob/main/packages/hooks/src/use-media-query.ts
13+
*/
14+
export function useMediaQuery(
15+
queries: string[],
16+
fallbackQueries?: boolean[]
17+
): boolean[] {
18+
const _fallbackQueries = fallbackQueries?.filter(
19+
(val) => val !== null
20+
) as boolean[]
21+
22+
const [value, setValue] = useState(() => {
23+
return queries.map((query, idx) => ({
24+
media: query,
25+
matches: !IS_SERVER
26+
? window.matchMedia(query).matches
27+
: !!_fallbackQueries[idx],
28+
}))
29+
})
30+
31+
useIsomorphicLayoutEffect(() => {
32+
const matchMedias = queries.map((query) => window.matchMedia(query))
33+
34+
// Handles the change event of the media query.
35+
function handleChange(evt: MediaQueryListEvent) {
36+
setValue((prev) => {
37+
return prev.slice().map((item) => {
38+
if (item.media === evt.media) return { ...item, matches: evt.matches }
39+
return item
40+
})
41+
})
42+
return
43+
}
44+
45+
const cleanups = matchMedias.map((v) => listen(v, handleChange))
46+
return () => cleanups.forEach((fn) => fn())
47+
}, [queries])
48+
49+
return value.map((item) => item.matches)
50+
}
51+
52+
type MediaQueryCallback = (event: MediaQueryListEvent) => void
53+
54+
function listen(query: MediaQueryList, callback: MediaQueryCallback) {
55+
// Use deprecated `addListener` and `removeListener` to support Safari < 14 (#135)
56+
try {
57+
query.addEventListener("change", callback)
58+
return () => query.removeEventListener("change", callback)
59+
} catch (e) {
60+
query.addListener(callback)
61+
return () => query.removeListener(callback)
62+
}
63+
}

tailwind.config.ts

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import type { Config } from "tailwindcss"
22
import plugin from "tailwindcss/plugin"
33

4+
import { screens } from "./tailwind/screens"
5+
46
const config = {
57
darkMode: ["selector", "[data-theme='dark']"],
68
content: [
@@ -11,13 +13,7 @@ const config = {
1113
prefix: "",
1214
theme: {
1315
extend: {
14-
screens: {
15-
sm: "480px",
16-
md: "768px",
17-
lg: "992px",
18-
xl: "1280px",
19-
"2xl": "1536px",
20-
},
16+
screens,
2117
fontFamily: {
2218
heading: "var(--font-inter)",
2319
body: "var(--font-inter)",

tailwind/screens.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import type { ScreensConfig } from "tailwindcss/types/config"
2+
3+
export const screens = {
4+
sm: "480px",
5+
md: "768px",
6+
lg: "992px",
7+
xl: "1280px",
8+
"2xl": "1536px",
9+
} satisfies ScreensConfig

0 commit comments

Comments
 (0)