diff --git a/src/components/EnergyConsumptionChart/index.tsx b/src/components/EnergyConsumptionChart/index.tsx index 43f4d9f34c4..6f8d0008657 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/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 } 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 9e0bb7b8948..9ad06a6a1ca 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 = { // TODO: Move to "class" strategy after removing Chakra darkMode: ["selector", '[data-theme="dark"]'], @@ -12,13 +14,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