styled-components / xstyled

A utility-first CSS-in-JS framework built for React. 💅👩‍🎤⚡️
https://xstyled.dev
MIT License
2.27k stars 106 forks source link

Ability to force a color mode for a specific component subtree #316

Open quantizor opened 2 years ago

quantizor commented 2 years ago

The ColorModeProvider component currently enables color shifting in general, but as far as I can tell there is no way to explicitly force a particular color mode for a given subtree. The use case I'm working on is there are particular screens where the insides are meant to use a dark style, but the outer UI is normal 🤷🏼

sctgraham commented 2 years ago

This would be great, I could really use this too. +1 Did you happen to find a work around to achieve something similar ?

quantizor commented 2 years ago

@sctgraham this is what I came up with:

const XSTYLED_CLASS_PREFIX = 'xstyled-color-mode-';
const ColorModeContext = React.createContext<DSIColorModes>('light');

/**
 * Composes the typical `<ThemeProvider>` with some goodies. This wrapper should be placed at the root of your application.
 */
export function DSStyleProvider({
  children,
  colorMode = 'light',
  theme,
}: React.PropsWithChildren<{
  /**
   * Forces a particular color mode.
   */
  colorMode?: 'light' | 'dark';
  theme: DSITheme;
}>) {
  const Wrapper = (() => {
    switch (colorMode) {
      case 'dark':
        return DSDarkModeProvider;
      default:
        return DSLightModeProvider;
    }
  })();

  return (
    <ThemeProvider
      // @ts-expect-error need to make ThemeProvider less restrictive
      theme={theme}
    >
      <ColorModeProvider targetSelector={`[class*="${XSTYLED_CLASS_PREFIX}"]`}>
        <Wrapper>
          {children}
        </Wrapper>
      </ColorModeProvider>
    </ThemeProvider>
  );
}

/**
 * This wrapper forces on dark mode for a particular component subtree. Note that color mode shifting is not enabled by default
 * in DSP (controlled by the `useColorSchemeMediaQuery` theme variable) for UX reasons; dark mode is currently reserved for specific
 * interfaces like CAD view.
 */
 export function DSDarkModeProvider({ children }: React.PropsWithChildren<{}>) {
  return (
    <ColorModeContext.Provider value="dark">
      <x.div className={`${XSTYLED_CLASS_PREFIX}dark`} display="contents">
        {children}
      </x.div>
    </ColorModeContext.Provider>
  );
}

/**
 * This wrapper forces on light mode for a particular component subtree. Note that color mode shifting is not enabled by default
 * in DSP (controlled by the `useColorSchemeMediaQuery` theme variable) for UX reasons.
 *
 * You should pretty much never need to use this component, but it is included with the library for completeness.
 */
export function DSLightModeProvider({ children }: React.PropsWithChildren<{}>) {
  return (
    <ColorModeContext.Provider value="light">
      <x.div className={`${XSTYLED_CLASS_PREFIX}light`} display="contents">
        {children}
      </x.div>
    </ColorModeContext.Provider>
  );
}

/**
 * Replacement for xstyled's `useColorMode` that pays attention to component
 * subtrees.
 */
export function useColorMode() {
  return React.useContext(ColorModeContext);
}
quantizor commented 2 years ago

@gregberge you might want to steal this impl, it's been working great in our design system

sctgraham commented 2 years ago

Sweet ! Thanks for taking the time. I'll dig into this and see how it goes. Great use of display:contents btw.

quantizor commented 2 years ago

@sctgraham just a heads up I updated my example with a revised targetSelector