기본 테마 탐색기

기본 테마 탐색기 (Default theme viewer)

이 트리 뷰(tree view)를 통해 테마 객체가 기본값(default values)으로 어떤 모습인지 한눈에 탐색해 볼 수 있어요. Material UI의 테마가 어떻게 만들어지는지 궁금하다면, createTheme()가 실제로 사용하는 코드들을 따라가 보면서 구조를 익혀보도록 해요.

출처: 문서

본문

이 트리 뷰를 이용하면 테마 객체가 기본값들로 어떤 모습인지 탐색해 볼 수 있어요.

테마가 어떻게 조립되는지 더 자세히 알고 싶다면, material-ui/style/createTheme.ts와 createTheme()이 사용하는 관련 import들을 살펴보세요.

브라우저 콘솔에서도 문서용 테마 객체를 직접 만져볼 수 있어요. 모든 문서 페이지에는 theme 변수가 노출되어 있거든요.

:::warning 문서 사이트는 커스텀 테마(MUI 조직의 브랜딩)를 사용하고 있다는 점을 알아두세요. :::


import * as React from 'react';
import Box from '@mui/material/Box';
import Divider from '@mui/material/Divider';
import { createTheme, styled } from '@mui/material/styles';
import FormControlLabel from '@mui/material/FormControlLabel';
import Switch from '@mui/material/Switch';
import { useTranslate } from '@mui/internal-core-docs/i18n';
import ThemeViewer, {
  useItemIdsLazy,
} from 'docs/src/modules/components/ThemeViewer';
import { blue, grey } from '@mui/internal-core-docs/branding';

const StyledSwitch = styled(Switch)(({ theme }) => [
  {
    display: 'flex',
    padding: 0,
    width: 32,
    height: 20,
    borderRadius: 99,
    '&:active': {
      '& .MuiSwitch-thumb': {
        width: 16,
      },
      '& .MuiSwitch-switchBase.Mui-checked': {
        transform: 'translateX(9px)',
      },
    },
    '& .MuiSwitch-switchBase': {
      padding: 2,
      '&.Mui-checked': {
        transform: 'translateX(12px)',
        color: '#FFF',
        '& + .MuiSwitch-track': {
          opacity: 1,
          backgroundColor: blue[500],
        },
      },
    },
    '& .MuiSwitch-thumb': {
      width: 16,
      height: 16,
      borderRadius: 99,
      transition: theme.transitions.create(['width'], {
        duration: 200,
      }),
    },
    '& .MuiSwitch-track': {
      borderRadius: 16 / 2,
      opacity: 1,
      backgroundColor: grey[400],
      boxSizing: 'border-box',
    },
    [`*:where(${theme.vars ? '[data-mui-color-scheme="dark"]' : '.mode-dark'}) &`]: {
      '& .MuiSwitch-switchBase': {
        '&.Mui-checked': {
          '& + .MuiSwitch-track': {
            backgroundColor: blue[500],
          },
        },
      },
      '& .MuiSwitch-track': {
        backgroundColor: grey[700],
      },
    },
  },
]);

function DefaultTheme() {
  const [checked, setChecked] = React.useState(false);
  const [expandPaths, setExpandPaths] = React.useState(null);
  const t = useTranslate();
  const [darkTheme, setDarkTheme] = React.useState(false);

  React.useEffect(() => {
    let expandPath;
    decodeURI(document.location.search.slice(1))
      .split('&')
      .forEach((param) => {
        const [name, value] = param.split('=');
        if (name === 'expand-path') {
          expandPath = value;
        }
      });

    if (!expandPath) {
      return;
    }

    setExpandPaths(
      expandPath
        .replace('$.', '')
        .split('.')
        .reduce((acc, path) => {
          const last = acc.length > 0 ? `${acc[acc.length - 1]}.` : '';
          acc.push(last + path);
          return acc;
        }, []),
    );
  }, []);

  const data = React.useMemo(() => {
    const themeData = createTheme({
      palette: { mode: darkTheme ? 'dark' : 'light' },
    });

    const {
      unstable_sxConfig: unstableSxConfig,
      unstable_sx: unstableSx,
      ...rest
    } = themeData;

    return rest;
  }, [darkTheme]);

  const allNodeIds = useItemIdsLazy(data);
  React.useDebugValue(allNodeIds);

  const currentExpandPaths = React.useMemo(() => {
    if (expandPaths !== null) {
      return expandPaths;
    }
    return checked ? allNodeIds : [];
  }, [checked, allNodeIds, expandPaths]);

  const collapsedThemeViewer = React.useMemo(
    () => <ThemeViewer data={data} expandPaths={[]} />,
    [data],
  );

  const expandedThemeViewer = React.useMemo(
    () => <ThemeViewer data={data} expandPaths={allNodeIds} />,
    [data, allNodeIds],
  );

  return (
    <Box sx={{ width: '100%' }}>
      <Box sx={{ display: 'flex', gap: 2, mb: 3 }}>
        <FormControlLabel
          label={t('expandAll')}
          sx={{
            m: 0,
            flexDirection: 'row-reverse',
            gap: 1,
            '& .MuiFormControlLabel-label': {
              fontFamily: 'IBM Plex Sans',
              color: 'text.secondary',
            },
          }}
          control={
            <StyledSwitch
              size="small"
              checked={checked}
              onChange={(event) => {
                setChecked(event.target.checked);
              }}
            />
          }
        />
        <Divider orientation="vertical" flexItem />
        <FormControlLabel
          label={t('useDarkTheme')}
          sx={{
            m: 0,
            flexDirection: 'row-reverse',
            gap: 1,
            '& .MuiFormControlLabel-label': {
              fontFamily: 'IBM Plex Sans',
              color: 'text.secondary',
            },
          }}
          control={
            <StyledSwitch
              size="small"
              checked={darkTheme}
              onChange={(event) => {
                setDarkTheme(event.target.checked);
              }}
            />
          }
        />
      </Box>
      {expandPaths !== null ? (
        <ThemeViewer data={data} expandPaths={currentExpandPaths} />
      ) : (
        <React.Fragment>
          <Box sx={{ display: checked ? 'none' : 'block' }}>
            {collapsedThemeViewer}
          </Box>
          <Box sx={{ display: checked ? 'block' : 'none' }}>
            {expandedThemeViewer}
          </Box>
        </React.Fragment>
      )}
    </Box>
  );
}

export default DefaultTheme;

더 알아보기 (Learn more)