표(Table)을 만들 때 사용하는 코드 (3편)

2021. 3. 10. 17:16코반주반

 

표(Table)을 만들 때 사용하는 코드 (1편)

코드 반 주석 반 첫 번째 포스팅은 여러군데에서 써먹을 수 있을만한 테이블 컴포넌트이다. 시작에 앞서, 포스팅에 사용되는 기술스택은 다음과 같다. react typescript styled-component 만들어야 하는

nookpi.tistory.com

 

표(Table)을 만들 때 사용하는 코드 (2편)

표(Table)을 만들 때 사용하는 코드 (1편) 코드 반 주석 반 첫 번째 포스팅은 여러군데에서 써먹을 수 있을만한 테이블 컴포넌트이다. 시작에 앞서, 포스팅에 사용되는 기술스택은 다음과 같다. reac

nookpi.tistory.com

기존 사용 방법에서 볼 수 있었듯, 테이블의 한 열(row)에는 한 개 혹은 두 개의 열(cell)이 들어올 수 있다.

...

<TableRow>
	<TableCell title="이메일" value="unqocn@gmail.com" />
</TableRow>
<TableRow>
	<TableCell title="주소" value="서울시 중구 어딘가" />
	<TableCell title="집전화" value="-" />
</TableRow>

...

절반 칸에 해당하는 열의 디자인과, 전체가 꽉 차는 열의 디자인은 다르기 때문에, isHalf라는 props를 Cell에 넘겨 디자인에 변화를 준다.

 

TableCell.tsx

import React from "react";
import {
  StyledTableCell,
  StyledTableCellTitle,
  StyledTableCellValue,
} from "./style";
import Typo from "../../utils/Typo";

// 컴포넌트가 받을 Props의 구조 정의
export interface Props {
  // 제목과 내용에 해당하는 부분은 ReactNode(자식요소)로 받는다
  title: React.ReactNode;
  value: React.ReactNode;
  // 절반만 차지하는 칸이라면 디자인이 달라져야 한다
  isHalf?: boolean;
}

const TableCell: React.FC<Props> = (props: Props) => {
  // 제목, 내용, isHalf 값을 꺼내서,
  const { title, value, isHalf } = props;

  return (
    //  절반인지의 여부는 Cell 스타일 컴포넌트에 props로 넘긴다
    <StyledTableCell isHalf={isHalf}>
      {/* 테이블의 제목영역 */}
      <StyledTableCellTitle>
        {/*  제목이 그냥 string 이라면 Typo 컴포넌트로 감싸준다. */}
        {typeof title === "string" ? (
          <Typo color="mainBlack">{title}</Typo>
        ) : (
          title
        )}
      </StyledTableCellTitle>
      {/* 테이블의 내용영역 */}
      <StyledTableCellValue>
        {/*  내용도 마찬가지로 string 이라면 Typo 컴포넌트로 감싸준다. */}
        {typeof value === "string" ? (
          <Typo color="mainBlack">{value}</Typo>
        ) : (
          value
        )}
      </StyledTableCellValue>
    </StyledTableCell>
  );
};

TableCell.defaultProps = {
  isHalf: undefined,
};

export default TableCell;

TableCell/Style.ts

import styled from "styled-components";
import {
  FROM_TABLET_TO_PHONE,
  mediaQueries,
} from "../../../styles/MediaQueries";

export const StyledTableCell = styled.td<any>`
  display: flex;
  border-top: 1px solid #e9eaef;
  border-left: 1px solid #e9eaef;
  ${(props: any) => (props.isHalf || `border-right: 1px solid #e9eaef;`)};
  ${(props: any) => (props.isHalf ? `width: 50%;` : "width:100%;")};

  &:last-child {
    border-right: 1px solid #e9eaef;
  }
  ${mediaQueries(FROM_TABLET_TO_PHONE)} {
    border-right: 1px solid #e9eaef;
    display: flex;
    width: 100%;
  }
`;

export const StyledTableCellTitle = styled.div<any>`
  display: flex;
  align-items: center;
  flex: 0 0 12rem;
  text-align: start;
  background-color: #F5F5F7;
  padding: 1.2rem 1.6rem;
  width: 12rem;
  ${mediaQueries(FROM_TABLET_TO_PHONE)} {
    flex: 0 0 10rem;
  }
`;

export const StyledTableCellValue = styled.div<any>`
  display: flex;
  align-items: center;
  flex: 1;
  padding: 1.2rem 1.6rem;
`;

 

단순 디자인 위주라 설명은 생략

 

 

이제 TableRow를 작성할 시간인데, 만약 한 행에 열이 1개가 오는지, 2개가 오는지 행의 입장에서 알 수 있다면, 자식 요소에게 isHalf props를 자동으로 넘겨줄 수 있고, 사용자 입장에서는 매번 2개의 열에 isHalf를 넘기지 않아도 될 것이다.

 

TableRow.tsx

import React, { useMemo } from "react";
import styled from "styled-components"; // ../../../styles/Themes";
import { WithChildren } from "../../../interfaces";
import {
  FROM_TABLET_TO_PHONE,
  mediaQueries,
} from "../../../styles/MediaQueries";

const StyledTableRow = styled.tr<any>`
  display: flex;
  flex-wrap: wrap;
  width: 100%;
  // 강제로 들어간 half 의 경우, 위에 있는 행에 bottom border 가 없음. 따라서 top border 를 넣어준다.
  // 아래에 있는 td의 너비는 1px 늘려줘야 border px 로 인한 오차를 보정 가능
  ${(props: any) =>
    props.isForcedHalf &&
    "border-top : 1px solid #e9eaef; td { border-top:none; width: calc(50% + 1px); }"};

  // 마지막 행이라면 bottom border 추가
  &:last-child {
    td {
      border-bottom: 1px solid #e9eaef;
    }
  }

  // 모바일에선 행이 아니라 셀에 bottom border 를 준다. 각 열에 있는 셀들이 행으로 변하기 때문
  ${mediaQueries(FROM_TABLET_TO_PHONE)} {
    &:last-child {
      td {
        border-bottom: none;
        width: 100%;
        &:last-child {
          border-bottom: 1px solid #e9eaef;
        }
      }
    }
  }
`;

export default const TableRow = ({
  children,
}: {
  children: React.ReactElement | React.ReactElement[];
}) => {
  const isForcedHalf = useMemo(() => {
    // ? 열이 하나인데, isHalf 가 true 라면?? isHalf 기본값은 undefined 이기 때문에
    // ? 강제로 넣어준 isHalf 로 간주하고 처리
    if (!Array.isArray(children)) {
      const { isHalf } = children.props;
      if (isHalf === true) {
        return true;
      }
    }
    return false;
  }, [children]);

 return (
    <StyledTableRow isForcedHalf={isForcedHalf}>
      {/* ! 열이 하나인 행 */}
      {/* ? 열이 하나인 행은 isHalf props 를 forcedHalf 로 넘겨서 기본값은 꽉 차게 만들어줌 */}
      {!Array.isArray(children) &&
        React.cloneElement(children as React.ReactElement, {
          isHalf: isForcedHalf,
        })}

      {/* ! 2개의 열로 이루어진 행 */}
      {Array.isArray(children) &&
        children.map((child, index) => {
          const key = `${child.props.title}${
            child.props.value
          }${index.toString()}`;
          return (
            <React.Fragment key={key}>
              {/* ? 열이 두 개인 행은 isHalf props 를 true 로 넘겨서 반토막 */}
              {React.cloneElement(child as React.ReactElement, {
                isHalf: true,
              })}
            </React.Fragment>
          );
        })}
    </StyledTableRow>
  );
};

 

forcedHalf는, 자식 요소가 하나인 경우에도 half 처리를 해야 하는 아래와 같은 경우를 위해 추가했다.

 

 


코드는 큰 어려움 없이 쭉 쳤는데, 포스팅을 위해 주석을 한 줄 한 줄 달아보니 개선점이 보이는 부분이 좋았던 것 같다.

 

아쉬운 점은 충분한 설명을 곁들이기에 주석이라는 형식이 그렇게 썩 적합하지 않은 것 같다.