From a03fc10b698eda20997252ede9554154542e94e7 Mon Sep 17 00:00:00 2001 From: tylerapfledderer Date: Wed, 21 Aug 2024 01:20:21 -0400 Subject: [PATCH 1/2] feat(hooks): build local `useBreakpointValue` and `useMediaQuery` hooks --- src/hooks/useBreakpointValue.ts | 51 ++++++++++++++++++++++++++ src/hooks/useMediaQuery.ts | 63 +++++++++++++++++++++++++++++++++ tailwind.config.ts | 10 ++---- tailwind/screens.ts | 9 +++++ 4 files changed, 126 insertions(+), 7 deletions(-) create mode 100644 src/hooks/useBreakpointValue.ts create mode 100644 src/hooks/useMediaQuery.ts create mode 100644 tailwind/screens.ts diff --git a/src/hooks/useBreakpointValue.ts b/src/hooks/useBreakpointValue.ts new file mode 100644 index 00000000000..44a7f756d88 --- /dev/null +++ b/src/hooks/useBreakpointValue.ts @@ -0,0 +1,51 @@ +import { screens } from "../../tailwind/screens" + +import { useMediaQuery } from "./useMediaQuery" + +const breakpointMap = { + // Essentially to query from no width if smaller than "sm" + base: "0px", + ...screens, +} + +type BreakpointKeys = keyof typeof breakpointMap + +export const useBreakpointValue = ( + values: Partial>, + fallbackBreakpoint?: BreakpointKeys +): T => { + const breakpointKeys = Object.keys(values) as BreakpointKeys[] + + let fallbackPassed = false + + const setBreakpoints = Object.entries(breakpointMap) + .map(([breakpoint, value]) => { + const item = { + breakpoint, + query: `(min-width: ${value})`, + fallback: !fallbackPassed, + } + + if (breakpoint === fallbackBreakpoint) { + fallbackPassed = true + } + + return item + }) + .filter(({ breakpoint }) => + breakpointKeys.includes(breakpoint as BreakpointKeys) + ) + + const fallbackQueries = setBreakpoints.map(({ fallback }) => fallback) + + const queryValues = useMediaQuery( + setBreakpoints.map((bp) => bp.query), + fallbackQueries + ) + + const index = queryValues.lastIndexOf(true) + + const breakpointPicked = breakpointKeys[index] + + return values[breakpointPicked] as T +} diff --git a/src/hooks/useMediaQuery.ts b/src/hooks/useMediaQuery.ts new file mode 100644 index 00000000000..a3004dd190b --- /dev/null +++ b/src/hooks/useMediaQuery.ts @@ -0,0 +1,63 @@ +import { useState } from "react" + +import { useIsomorphicLayoutEffect } from "./useIsomorphicLayoutEffect" + +const IS_SERVER = typeof window === "undefined" + +/** + * Modified from https://usehooks-ts.com/react-hook/use-media-query + * to account for an array of queries. + * + * Modifications sourced from Chakra-UI. + * https://github.com/chakra-ui/chakra-ui/blob/main/packages/hooks/src/use-media-query.ts + */ +export function useMediaQuery( + queries: string[], + fallbackQueries?: boolean[] +): boolean[] { + const _fallbackQueries = fallbackQueries?.filter( + (val) => val !== null + ) as boolean[] + + const [value, setValue] = useState(() => { + return queries.map((query, idx) => ({ + media: query, + matches: !IS_SERVER + ? window.matchMedia(query).matches + : !!_fallbackQueries[idx], + })) + }) + + useIsomorphicLayoutEffect(() => { + const matchMedias = queries.map((query) => window.matchMedia(query)) + + // Handles the change event of the media query. + function handleChange(evt: MediaQueryListEvent) { + setValue((prev) => { + return prev.slice().map((item) => { + if (item.media === evt.media) return { ...item, matches: evt.matches } + return item + }) + }) + return + } + + const cleanups = matchMedias.map((v) => listen(v, handleChange)) + return () => cleanups.forEach((fn) => fn()) + }, [queries]) + + return value.map((item) => item.matches) +} + +type MediaQueryCallback = (event: MediaQueryListEvent) => void + +function listen(query: MediaQueryList, callback: MediaQueryCallback) { + // Use deprecated `addListener` and `removeListener` to support Safari < 14 (#135) + try { + query.addEventListener("change", callback) + return () => query.removeEventListener("change", callback) + } catch (e) { + query.addListener(callback) + return () => query.removeListener(callback) + } +} diff --git a/tailwind.config.ts b/tailwind.config.ts index 1fabb2c5498..0a6d8b60e8e 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -1,6 +1,8 @@ import type { Config } from "tailwindcss" import plugin from "tailwindcss/plugin" +import { screens } from "./tailwind/screens" + const config = { darkMode: ["selector", "[data-theme='dark']"], content: [ @@ -11,13 +13,7 @@ const config = { prefix: "", theme: { extend: { - screens: { - sm: "480px", - md: "768px", - lg: "992px", - xl: "1280px", - "2xl": "1536px", - }, + screens, fontFamily: { heading: "var(--font-inter)", body: "var(--font-inter)", diff --git a/tailwind/screens.ts b/tailwind/screens.ts new file mode 100644 index 00000000000..beba7395305 --- /dev/null +++ b/tailwind/screens.ts @@ -0,0 +1,9 @@ +import type { ScreensConfig } from "tailwindcss/types/config" + +export const screens = { + sm: "480px", + md: "768px", + lg: "992px", + xl: "1280px", + "2xl": "1536px", +} satisfies ScreensConfig From 8daf17d139823c833f63d012e6391c0b969463a1 Mon Sep 17 00:00:00 2001 From: tylerapfledderer Date: Wed, 21 Aug 2024 22:26:40 -0400 Subject: [PATCH 2/2] refactor: replace Chakra `useBreakpointValue` with local export --- src/components/EnergyConsumptionChart/index.tsx | 2 +- src/components/Morpher.tsx | 3 ++- src/components/Simulator/WalletHome/NFTList.tsx | 10 +++------- 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/src/components/EnergyConsumptionChart/index.tsx b/src/components/EnergyConsumptionChart/index.tsx index a14ba629fa6..43936c43655 100644 --- a/src/components/EnergyConsumptionChart/index.tsx +++ b/src/components/EnergyConsumptionChart/index.tsx @@ -12,7 +12,6 @@ import ChartDataLabels from "chartjs-plugin-datalabels" import { useRouter } from "next/router" import { useTranslation } from "next-i18next" import { Bar } from "react-chartjs-2" -import { useBreakpointValue } from "@chakra-ui/react" import type { Lang } from "@/lib/types" @@ -21,6 +20,7 @@ import { Center } from "@/components/ui/flex" import { wrapLabel } from "@/lib/utils/charts" import { isLangRightToLeft } from "@/lib/utils/translations" +import { useBreakpointValue } from "@/hooks/useBreakpointValue" import useColorModeValue from "@/hooks/useColorModeValue" import { useIsClient } from "@/hooks/useIsClient" diff --git a/src/components/Morpher.tsx b/src/components/Morpher.tsx index 36e157bad76..1a084796890 100644 --- a/src/components/Morpher.tsx +++ b/src/components/Morpher.tsx @@ -1,5 +1,4 @@ import { useEffect, useState } from "react" -import { useBreakpointValue } from "@chakra-ui/react" import { Button } from "@/components/Buttons" @@ -9,6 +8,8 @@ import { MOBILE_LANGUAGE_BUTTON_NAME, } from "@/lib/constants" +import { useBreakpointValue } from "@/hooks/useBreakpointValue" + const Morpher = () => { const [state, setState] = useState({ text: "Ethereum", diff --git a/src/components/Simulator/WalletHome/NFTList.tsx b/src/components/Simulator/WalletHome/NFTList.tsx index add4e623ea3..29c072b4f4d 100644 --- a/src/components/Simulator/WalletHome/NFTList.tsx +++ b/src/components/Simulator/WalletHome/NFTList.tsx @@ -1,16 +1,12 @@ import React from "react" -import { - Box, - Flex, - type FlexProps, - Text, - useBreakpointValue, -} from "@chakra-ui/react" +import { Box, Flex, type FlexProps, Text } from "@chakra-ui/react" import { Image } from "@/components/Image" import type { NFT } from "./interfaces" +import { useBreakpointValue } from "@/hooks/useBreakpointValue" + type NFTListProps = FlexProps & { nfts: Array }