rm two packages for hooks (#251)
* rm two packages for hooks * fix navigation
This commit is contained in:
1 parent
f4c5132d7c
commit
dd9058b798
13 files changed
+188
-272
No files matched your search
@@ -1,11 +1,11 @@
|
||||
import { MenuToggle } from "../MenuToggle"
|
||||
import { Menu } from "../Menu"
|
||||
import { Overlay } from "@/ui/Overlay"
|
||||
import { useContext } from "react"
|
||||
import { useCallback, useContext } from "react"
|
||||
import { MenuContext } from "@/context/MenuContext"
|
||||
import { Box } from "@/ui/Box"
|
||||
import { Spacer } from "../Spacer"
|
||||
import { useToggle } from "react-use"
|
||||
import { useToggle } from "@/hooks/useToggle"
|
||||
import HudozkaLogo from "../HudozkaLogo"
|
||||
import { HudozkaTitle } from "../HudozkaTitle"
|
||||
import Link from "next/link"
|
||||
@@ -16,6 +16,7 @@ export type MobileNavigationProps = {
|
||||
|
||||
export const MobileNavigation: React.FC<MobileNavigationProps> = ({ style }) => {
|
||||
const [open, toggleOpen] = useToggle(false)
|
||||
const handleToggle = useCallback(() => toggleOpen(), [toggleOpen])
|
||||
const menu = useContext(MenuContext)
|
||||
|
||||
return (
|
||||
@@ -32,7 +33,7 @@ export const MobileNavigation: React.FC<MobileNavigationProps> = ({ style }) =>
|
||||
<Spacer />
|
||||
<MenuToggle
|
||||
open={open}
|
||||
onClick={toggleOpen}
|
||||
onClick={handleToggle}
|
||||
/>
|
||||
</Box>
|
||||
<Box style={{
|
||||
@@ -52,7 +53,7 @@ export const MobileNavigation: React.FC<MobileNavigationProps> = ({ style }) =>
|
||||
<Spacer />
|
||||
<MenuToggle
|
||||
open={open}
|
||||
onClick={toggleOpen}
|
||||
onClick={handleToggle}
|
||||
/>
|
||||
</Box>
|
||||
</>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import useMedia from "use-media"
|
||||
import { useMediaQuery } from "@/hooks/useMediaQuery"
|
||||
|
||||
export function useDarkTheme(): boolean {
|
||||
return useMedia("(prefers-color-scheme: dark)")
|
||||
return useMediaQuery("(prefers-color-scheme: dark)")
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
import { renderHook } from "@testing-library/react"
|
||||
import { describe, it, expect, beforeEach } from "vitest"
|
||||
import { useLockBodyScroll } from "./useLockBodyScroll"
|
||||
|
||||
describe("useLockBodyScroll", () => {
|
||||
beforeEach(() => {
|
||||
document.body.style.overflow = ""
|
||||
})
|
||||
|
||||
it("sets overflow hidden when locked", () => {
|
||||
renderHook(() => useLockBodyScroll(true))
|
||||
expect(document.body.style.overflow).toBe("hidden")
|
||||
})
|
||||
|
||||
it("does not change overflow when unlocked", () => {
|
||||
document.body.style.overflow = "auto"
|
||||
renderHook(() => useLockBodyScroll(false))
|
||||
expect(document.body.style.overflow).toBe("auto")
|
||||
})
|
||||
|
||||
it("restores original overflow on unmount", () => {
|
||||
document.body.style.overflow = "scroll"
|
||||
const { unmount } = renderHook(() => useLockBodyScroll(true))
|
||||
expect(document.body.style.overflow).toBe("hidden")
|
||||
unmount()
|
||||
expect(document.body.style.overflow).toBe("scroll")
|
||||
})
|
||||
|
||||
it("responds to lock/unlock transitions", () => {
|
||||
const { rerender } = renderHook(
|
||||
({ locked }) => useLockBodyScroll(locked),
|
||||
{ initialProps: { locked: false } },
|
||||
)
|
||||
expect(document.body.style.overflow).toBe("")
|
||||
rerender({ locked: true })
|
||||
expect(document.body.style.overflow).toBe("hidden")
|
||||
rerender({ locked: false })
|
||||
expect(document.body.style.overflow).toBe("")
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,16 @@
|
||||
import { useEffect } from "react"
|
||||
|
||||
export function useLockBodyScroll(locked: boolean): void {
|
||||
useEffect(() => {
|
||||
if (!locked) {
|
||||
return
|
||||
}
|
||||
|
||||
const original = document.body.style.overflow
|
||||
document.body.style.overflow = "hidden"
|
||||
|
||||
return () => {
|
||||
document.body.style.overflow = original
|
||||
}
|
||||
}, [locked])
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
import { renderHook, act } from "@testing-library/react"
|
||||
import { describe, it, expect, beforeEach, vi } from "vitest"
|
||||
import { useMediaQuery } from "./useMediaQuery"
|
||||
|
||||
describe("useMediaQuery", () => {
|
||||
let listeners: Map<string, Set<() => void>>
|
||||
let matches: Map<string, boolean>
|
||||
|
||||
beforeEach(() => {
|
||||
listeners = new Map()
|
||||
matches = new Map()
|
||||
|
||||
vi.stubGlobal("matchMedia", (query: string) => ({
|
||||
matches: matches.get(query) ?? false,
|
||||
addEventListener: (_: string, cb: () => void) => {
|
||||
if (!listeners.has(query)) {
|
||||
listeners.set(query, new Set())
|
||||
}
|
||||
listeners.get(query)!.add(cb)
|
||||
},
|
||||
removeEventListener: (_: string, cb: () => void) => {
|
||||
listeners.get(query)?.delete(cb)
|
||||
},
|
||||
}))
|
||||
})
|
||||
|
||||
it("returns current match state", () => {
|
||||
matches.set("(min-width: 800px)", true)
|
||||
const { result } = renderHook(() => useMediaQuery("(min-width: 800px)"))
|
||||
expect(result.current).toBe(true)
|
||||
})
|
||||
|
||||
it("returns false when query does not match", () => {
|
||||
const { result } = renderHook(() => useMediaQuery("(min-width: 800px)"))
|
||||
expect(result.current).toBe(false)
|
||||
})
|
||||
|
||||
it("updates on change events", () => {
|
||||
const query = "(min-width: 800px)"
|
||||
const { result } = renderHook(() => useMediaQuery(query))
|
||||
expect(result.current).toBe(false)
|
||||
|
||||
act(() => {
|
||||
matches.set(query, true)
|
||||
listeners.get(query)?.forEach((cb) => cb())
|
||||
})
|
||||
expect(result.current).toBe(true)
|
||||
})
|
||||
|
||||
it("cleans up listener on unmount", () => {
|
||||
const query = "(min-width: 800px)"
|
||||
const { unmount } = renderHook(() => useMediaQuery(query))
|
||||
expect(listeners.get(query)?.size).toBe(1)
|
||||
unmount()
|
||||
expect(listeners.get(query)?.size).toBe(0)
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,13 @@
|
||||
import { useSyncExternalStore } from "react"
|
||||
|
||||
export function useMediaQuery(query: string, defaultValue = false): boolean {
|
||||
return useSyncExternalStore(
|
||||
(callback) => {
|
||||
const mql = window.matchMedia(query)
|
||||
mql.addEventListener("change", callback)
|
||||
return () => mql.removeEventListener("change", callback)
|
||||
},
|
||||
() => window.matchMedia(query).matches,
|
||||
() => defaultValue,
|
||||
)
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import useMedia from "use-media"
|
||||
import { useMediaQuery } from "@/hooks/useMediaQuery"
|
||||
|
||||
export function useMobile(): boolean {
|
||||
return useMedia("(max-width: 31.25em)", false)
|
||||
return useMediaQuery("(max-width: 31.25em)", false)
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import useMedia from "use-media"
|
||||
import { useMediaQuery } from "@/hooks/useMediaQuery"
|
||||
|
||||
export function useReducedMotion(): boolean {
|
||||
return useMedia("(prefers-reduced-motion: reduce)")
|
||||
return useMediaQuery("(prefers-reduced-motion: reduce)")
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import { renderHook, act } from "@testing-library/react"
|
||||
import { describe, it, expect } from "vitest"
|
||||
import { useToggle } from "./useToggle"
|
||||
|
||||
describe("useToggle", () => {
|
||||
it("returns initial value", () => {
|
||||
const { result } = renderHook(() => useToggle(false))
|
||||
expect(result.current[0]).toBe(false)
|
||||
})
|
||||
|
||||
it("toggles value", () => {
|
||||
const { result } = renderHook(() => useToggle(false))
|
||||
act(() => result.current[1]())
|
||||
expect(result.current[0]).toBe(true)
|
||||
act(() => result.current[1]())
|
||||
expect(result.current[0]).toBe(false)
|
||||
})
|
||||
|
||||
it("sets explicit true", () => {
|
||||
const { result } = renderHook(() => useToggle(false))
|
||||
act(() => result.current[1](true))
|
||||
expect(result.current[0]).toBe(true)
|
||||
})
|
||||
|
||||
it("sets explicit false", () => {
|
||||
const { result } = renderHook(() => useToggle(true))
|
||||
act(() => result.current[1](false))
|
||||
expect(result.current[0]).toBe(false)
|
||||
})
|
||||
|
||||
it("has a stable dispatch reference", () => {
|
||||
const { result, rerender } = renderHook(() => useToggle(false))
|
||||
const first = result.current[1]
|
||||
rerender()
|
||||
expect(result.current[1]).toBe(first)
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,8 @@
|
||||
import { useReducer } from "react"
|
||||
|
||||
export function useToggle(initialValue: boolean): [boolean, (nextValue?: boolean) => void] {
|
||||
return useReducer(
|
||||
(state: boolean, next?: boolean) => typeof next === "boolean" ? next : !state,
|
||||
initialValue,
|
||||
)
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
import s from "./styles.module.css"
|
||||
|
||||
import { Transition } from "./Transition"
|
||||
import { useReducedMotion } from "../../hooks/useReducedMotion"
|
||||
import { useLockBodyScroll } from "react-use"
|
||||
import { useReducedMotion } from "@/hooks/useReducedMotion"
|
||||
import { useLockBodyScroll } from "@/hooks/useLockBodyScroll"
|
||||
|
||||
export type OverlayProps = {
|
||||
children?: React.ReactNode
|
||||
|
||||
Reference in new issue
Block a user