github.com/grafana/pyroscope@v1.18.0/public/app/hooks/colorMode.hook.ts (about)

     1  import { useCallback, useEffect } from 'react';
     2  import { useAppSelector, useAppDispatch } from '@pyroscope/redux/hooks';
     3  import { setColorMode, selectAppColorMode } from '@pyroscope/redux/reducers/ui';
     4  
     5  /** Obtain the current color mode (light|dark) and ensure all representations of it are in sync
     6   *
     7   * Some elements of style rely on the `<body>` tag's `data-theme` attribute
     8   * (accessibile via document.body.dataset.theme)
     9   * while others must consult with the color mode set in the redux store.
    10   *
    11   * This hook ensures that any change in the redux store is reflected in the body theme,
    12   * and vice versa.
    13   */
    14  const useColorMode = () => {
    15    const dispatch = useAppDispatch();
    16    const colorMode = useAppSelector(selectAppColorMode);
    17    const { body } = document;
    18  
    19    /** This callback updates the color mode in the redux store. */
    20    const changeColorMode = useCallback(
    21      (newColorMode: 'light' | 'dark') => dispatch(setColorMode(newColorMode)),
    22      [dispatch]
    23    );
    24  
    25    // This effect sets up an observer for changes in the `<body>` `data-theme` attr
    26    // which updates the redux value to keep them in sync.
    27    useEffect(() => {
    28      const observer = new MutationObserver((mutationsList) => {
    29        const mutation = mutationsList[0];
    30  
    31        if (mutation.type === 'attributes' && body.dataset.theme !== colorMode) {
    32          changeColorMode(
    33            body.dataset.theme === 'light' || body.dataset.theme === 'dark'
    34              ? body.dataset.theme
    35              : 'dark'
    36          );
    37        }
    38      });
    39  
    40      if (body) {
    41        observer.observe(body, {
    42          attributes: true,
    43          attributeFilter: ['data-theme'],
    44          childList: false,
    45          subtree: false,
    46        });
    47      }
    48  
    49      return () => {
    50        observer.disconnect();
    51      };
    52    }, [body, changeColorMode, colorMode]);
    53  
    54    // This effect will update the `<body>` tag's `data-theme` attribute
    55    // to ensure that it matches the color mode from the redux store.
    56    useEffect(() => {
    57      // sync redux value with DOM body attr
    58      if (body.dataset.theme !== colorMode) {
    59        body.dataset.theme = colorMode;
    60      }
    61    }, [colorMode, body]);
    62  
    63    return {
    64      colorMode,
    65      changeColorMode,
    66    };
    67  };
    68  
    69  export default useColorMode;