diff --git a/app/javascript/wardrobe-2020/AppProvider.js b/app/javascript/wardrobe-2020/AppProvider.js index bca86234..23bf08f5 100644 --- a/app/javascript/wardrobe-2020/AppProvider.js +++ b/app/javascript/wardrobe-2020/AppProvider.js @@ -4,12 +4,15 @@ import { Integrations } from "@sentry/tracing"; import { ChakraProvider, Box, - theme as defaultTheme, - useColorModeValue, + css as resolveCSS, + extendTheme, + useColorMode, + useTheme, } from "@chakra-ui/react"; +import { mode } from "@chakra-ui/theme-tools"; import { ApolloProvider } from "@apollo/client"; import { BrowserRouter } from "react-router-dom"; -import { Global } from "@emotion/react"; +import { css, Global } from "@emotion/react"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import produce from "immer"; @@ -17,13 +20,29 @@ import apolloClient from "./apolloClient"; const reactQueryClient = new QueryClient(); -// Use Chakra's default theme, but with the global styles removed. (We've -// copied them into our `ScopedCSSReset` component, to apply in only the places -// we actually want them!) -const theme = produce(defaultTheme, (theme) => { - theme.styles.global = {}; +let theme = extendTheme({ + styles: { + global: (props) => ({ + body: { + background: mode("gray.50", "gray.800")(props), + color: mode("green.800", "green.50")(props), + transition: "all 0.25s", + }, + }), + }, }); +// Capture the global styles function from our theme, but remove it from the +// theme's `styles.global` key. We don't actually want to apply these styles +// globally, because they conflict with the app's other styles when embedded in +// e.g. the item page! We'll apply them more carefully in `ScopedCSSReset`. +// +// NOTE: We get this from `theme`, instead of just declaring the function +// separately, because `extendTheme` returns a wrapped version of our function +// that also returns some of Chakra's own default global styles. +const globalStyles = theme.styles.global; +theme.styles.global = {}; + export default function AppProvider({ children }) { React.useEffect(() => setupLogging(), []); @@ -94,26 +113,46 @@ function setupLogging() { * the selector `:where(.chakra-css-reset) h1` is lower specificity. */ function ScopedCSSReset({ children }) { - const baseTextColor = useColorModeValue("green.800", "green.50"); + // Get the current theme and color mode. + // + // NOTE: The theme object returned by `useTheme` has some extensions that are + // necessary for the code below, but aren't present in the theme config + // returned by `extendTheme`! That's why we use this here instead of `theme`. + const liveTheme = useTheme(); + const colorMode = useColorMode(); + + // Resolve the theme's global styles into CSS objects for Emotion. + const globalStylesCSS = resolveCSS( + globalStyles({ theme: liveTheme, colorMode }), + )(liveTheme); + + // Prepend our special scope selector to the global styles. + const scopedGlobalStylesCSS = {}; + for (let [selector, rules] of Object.entries(globalStylesCSS)) { + // The `body` selector is where typography etc rules go, but `body` isn't + // actually *inside* our scoped element! Instead, ignore the `body` part, + // and just apply it to the scoping element itself. + if (selector.trim() === "body") { + selector = ""; + } + + const scopedSelector = + ":where(.chakra-css-reset, .chakra-portal) " + selector; + scopedGlobalStylesCSS[scopedSelector] = rules; + } return ( <> - - {children} - + {children}