ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 스타일 관련 작업 가이드
    기록이라도 하자 2024. 6. 30. 22:54

    * 회사에서 사내 서비스 개발을 위한 디자인 시스템을 만들어 가고 있다. 기존에는 마크업 개발자분들이 UI작업을 해주셨고 프론트엔드 개발자분들이 기능 개발을 맡아서 하다가, 마크업 개발자 분들이 전부 퇴사하시고 기능 개발자분들이 UI까지 맡게 되었다. 나는 입사해서 디자인 시스템과 공통 컴포넌트를 구축해나가는 작업을 진행하고 있으며, MUI를 래핑하여 커스텀하는 작업을 진행했다. 공통 컴포넌트를 만들었는데도 사용법을 몰라서 제대로 쓰지 못하거나(피그마 사용법이나 Mui에 대한 이해도가 상대적으로 낮았다), 개발 규칙이 없어서 개발자별로 각각 너무 다르게 개발하는 것을 막고자 스타일 가이드를 작성했다. 코드와 내용은 사내에서 쓰이는 것과 비슷은 하지만 아예 같지는 않은 상태로 공개한다.


    xx 프로젝트에서는 Material-UI(이하 MUI)를 사용합니다. CSS-in-JS를 별도의 라이브러리를 사용하지 않고 MUI에서 제공하는 styled를 사용합니다. 이 문서는 MUI와 MUI Components를 래핑해 만든 Components와 styled의 간단한 사용법 및 권장사항을 담고 있습니다.

    Figma를 보고 컴포넌트를 구성하는 법

    1. 컴포넌트 클릭시 해당하는 컴포넌트에 대한 설명이 우측에 표시(Dev mode기준)
    2. Props에 지정된 대로 코드화 하면 됨(색상이 토큰화 되어 있어 알아보기 어려울 수 있음, 특정 화면에서 커스터마이징 되지 않은 컴포넌트의 경우 일반적으로 Props만 지정하면 됨

     

    더보기

    예시) 컴포넌트를 구성하는 방법(Button)

     

    <OutlinedButton size="medium" color="primary">

     

     

    권장사항

    1. ui 패키지에 생성된 MUI를 래핑해서 만든 컴포넌트가 있을 경우, UI 패키지에서 가져와서 사용

    ❌ import { Typography } from "@mui/material";
    ✅ import { Typography } from "@package/ui";

     

     

    2. styled는 @mui/material/styles 에서 Import 하여 사용 - 디버깅 편의

    ❌ import { styled } from "@mui/material";
    ✅ import { styled } from "@mui/material/styles";
    • 휴먼 오류 방지 차원에서 es-lint 규칙으로 @mui/material 에서 import 할 경우 에러 발생하도록 설정
    • 더보기
      디버깅 편의에 대한 상세 설명
      @mui/material/styles로 Import한 경우에만 아래처럼 styled로 만든 컴포넌트의 이름이 class명에 표시됨

      next.config.js에서 해당 Import로 compile을 하도록 설정함. - 관련 링크

      @mui/material/styles를 @mui/material 로 바꾸어도 동작을 하지 않아서 styles에서 import할 수 밖에 없었음

       

    3. sx 사용보다는 styled로 감싸서 객체를 만들어서 사용하기를 권장

    🚫 sx 를 사용하여 스타일을 지정한 경우

    sx나, 내부 속성을 사용할 경우 전부 같은 css 클래스로 지정되어 어디서 어떤 값을 설정 했는지 찾아보기가 어려움
    기능 코드(이벤트, 함수 등..)와 스타일 코드가 섞여있어 코드를 알아보기 어려움
    <Box
      sx={{
        display: "flex",
        flexDirection: "column",
        gap: "10px",
        marginTop: "12px",
      }}
    >

     

    ✅ styled로 객체를 따로 만들어 스타일 코드를 분리한 경우

    기능 코드와 스타일 코드의 분리하여 코드 시인성이 좋아짐
    element에 style에 정의된 객체 명이 클래스 내부에 표시되어 스타일 코드와 HTML도 확인하기 편리. 디버깅 용이.
    const Wrap = styled("section")(({ theme }) => ({
      display: "flex",
      flexDirection: "column",
      gap: "10px",
      marginTop: "12px",
    });

     

    4. PC 기준으로 작업하고, 점차 작은 사이즈로 대응하는 것을 권장(Desktop First)

    PC 기준으로 디자인이 나오고 추가적으로 모바일 사이즈 대응하는 식으로 작업하기에 작업 순서에 맞게 지정한 방식임. 추후에 논의하여 바꿔도 상관 없지만, 일단 한가지로 통일해서 작업하기 위해 PC기준으로 정함. (모바일 사이즈는 앱 사용자가 훨씬 더 많음)

    1. theme.breakpoints.down을 사용하여 해상도가 작은 화면을 대응할 것
    styled 컴포넌트 내부에서 theme.breakpoints.down을 사용
    const Wrap = styled("div")(({ theme }) => ({
      maxWidth: "1440px",
      margin: "0 auto",
      padding: "48px 80px",
      [theme.breakpoints.down("xl")]: {
        padding: "48px 30px 120px 30px",
      },
      [theme.breakpoints.down("md")]: {
        padding: "32px 32px 120px 32px",
      },
    }));
    🚫 theme.breakpoints.up을 사용하지 않음
    const MyDataDl = styled("dl")(({ theme }) => ({
      display: "flex",
      [theme.breakpoints.up("md")]: {
        "&:not(:first-child):after": {
          position: "absolute",
          top: "9px",
          left: "0",
          content: "''",
          width: "1px",
          height: "40px",
          backgroundColor: theme.palette.grey[200],
        },
      }
    });
    ⚠️ between을 사용하는 것을 권장하지 않지만 예외적인 상황에서 사용 가능.
    const RankingListSection = styled("section")(
      ({ theme }) => ({
        width: "180px",
        position: "sticky",
        top: "100px",
        zIndex: theme.zIndex.appBar,
        [theme.breakpoints.between("md", "lg")]: {
          backgroundColor: theme.palette.text.contrast,
          display: "flex",
          padding: "0",
          gap: "16px",
        },
      })
    );

     

    5. styled 관련 코드는 컴포넌트 하위에 위치. 공통적으로 쓰인다면 파일 분리.

    - 파일 이름은 [이름].style.ts(x)로 만들기를 권장

    - 컴포넌트 코드 하위에 styled 코드 위치. 한눈에 알아보기 쉬움.

    - ⚠️ 파일 내부에 코드가 너무 많다면 컴포넌트 별로 파일을 분리할 것.
    컴포넌트와 스타일 코드를 한번에 확인할 수 있게 같은 파일에 위치할 것을 권장함.

    같은 파일 내부에 있어서 서버 컴포넌트로 만들 수 없거나, 공통적으로 쓰이는 스타일 객체의 경우에는 파일 분리

    푸터에서 공통으로 사용할 스타일을 모아놓은 아래 파일의 이름은 footer.style.tsx

    export const FooterTitle = styled(Typography)(({ theme, variant }) =>
      theme.unstable_sx({
        typography: variant ?? "title14",
        color: "text.primary",
        [theme.breakpoints.down("md")]: {
          typography: variant ?? "title12",
        },
      }),
    );
    export const FooterSubTitle = styled(Typography)(({ theme }) =>
      theme.unstable_sx({
        typography: "subtitle2",
        color: "text.secondary",
      }),
    );

     


    사용법

    - MUI 컴포넌트의 사용방법은 Material UI components - Material UI 에서 확인

     

    1. 일반적으로 MUI 컴포넌트에서 theme의 색상을 사용하는 방식

    <Typography varaint="title14" color="text.secondary">Title</Typography>
    <Flex bgColor="background.paper"></Flex>

     

    1-a. 컴포넌트 별로 지정된 색상이 있을 경우에는 해당 색을 사용. 예) Button

    <OutlinedButton color="secondary">Button</OutlinedButton>
    <TextButton color="tertiary">Button</TextButton>

     

    1-b. 내부 속성에서 theme을 불러와 지정할 수 있음

    <Box color={(theme) => theme.palette.error.main}></Box>

     

    2. styled를 사용하는 방법

    2-a. 컴포넌트에 스타일 적용하는 방법

    const StartAdornment = styled(InputAdornment)(({ theme }) => ({
      color: theme.palette.text.secondary,
      marginLeft: "16px",
    }));

     

    2-b. 일반 html element에 스타일 적용하는 방법

    const Wrapper = styled("div")({
      paddingTop: "14px",
      width: "100%",
    });
    
    const Contact = styled("address")(({ theme }) => ({
      display: "flex",
      flexDirection: "column",
      gap: "8px",
      
      [theme.breakpoints.down("tablet")]: {
        flexDirection: "row",
        width: "100%",
        marginTop: "16px",
      },
    }));

     

    2-c. 함수에 스타일 적용하는 방법
    같은 props가 지정된 컴포넌트를 한 컴포넌트 내에서 여러번 사용해야할 때 좋음

    const StyledTooltip = styled(({ className, ...props }: TooltipProps) => (
      <Tooltip {...props} classes={{ popper: className }} />
    ))({
      padding: "10px 16px",
      borderRadius: "30px",
      whiteSpace: "nowrap",
      maxWidth: "none",
    });
    
    const DialogButton = styled(
      ({ children, color, ...rest }: ButtonProps) => (
        <Button
          fullWidth
          size="large"
          color="primary"
          {...rest}
        >
          {children}
        </Button>
      ),
    )({});

     

    2-d. 특정 조건에 의해 스타일을 구성한다면
    2-d-가. 컴포넌트에서 사용되는 props에 의해서 스타일이 변경되는 경우

    const StyledFormControl = styled(FormControl)(({ theme, focused, error }) => {
      const token = getInputFieldToken(theme);
      return {
        borderBottom: `2px solid ${
          (error && token.underline.error) ||
          (focused && token.underline.focused) ||
          token.underline.default
        }`,
      };
    });

     

    2-d-나. styled에서 특정 속성(props)에 의해 스타일을 변경할 때,
    shouldFowardProp을 사용해 해당 값이 html 속성으로 전파되지 않도록 막아준다.

    const Text = styled("span", {
      shouldForwardProp: (propName) =>
        propName !== "isUnderline" && propName !== "isBold",
    })<{ underline: boolean; bold: boolean }>(({ isUnderline, isBold }) => ({
      textDecoration: isUnderline ? "underline" : "none",
      fontWeight: isBold ? 700 : 400,
    }));

     

    2-d.다. CSS 값 하나에 대해서 특정 속성을 비교해서 분기할 수 있지만(2-d-나), 특정 속성에 의해 여러 스타일을 지정할 수도 있다.

    type OwnState = Pick<SvgIconProps, "preserveOriginColor" | "size">;
    const StyledSvgIcon = styled(MuiSvgIcon, {
      shouldForwardProp: (prop) => prop !== "ownState",
    })<SvgIconProps & { ownState: OwnState }>`
      ${({ ownState }) =>
        !ownState?.preserveOriginColor && {
          "& > path": {
            fill: "currentColor !important",
          },
        }}
      ${({ ownState }) => ownState?.size && { fontSize: ownState?.size }};
    `;

     

    3. 알아두면 좋을 TIP

    3-a. 일단 이 문서를 읽기 - styled() - MUI System

    3-b. theme.unstable_sx 를 사용하면 styled내에서 sx를 쓰는 것처럼 사용할 수 있다

    const FooterTitle = styled(Typography)(({ theme, variant }) =>
      theme.unstable_sx({
        typography: variant ?? "title14",
        color:"text.secondary",
        [theme.breakpoints.down("tablet")]: {
          typography: variant ?? "title12",
        },
      }),
    );

     

    3-c. styled내에서 typography를 지정하기 위해 하나하나 지정할 필요 없이 스프레드 문법을 사용하면 된다

    const Select = styled("select")(({ theme }) => ({
      marginLeft: "18px",
      ...theme.typography.body14Regular,
    }));
    const Select = styled("select")(({ theme }) => theme.unstable_sx({
      marginLeft: "18px",
      typography: "body14Regular"
    }));

     

Designed by Tistory.