표(Table)을 만들 때 사용하는 코드 (3편)
2021. 3. 10. 17:16ㆍ코반주반
기존 사용 방법에서 볼 수 있었듯, 테이블의 한 열(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 처리를 해야 하는 아래와 같은 경우를 위해 추가했다.
코드는 큰 어려움 없이 쭉 쳤는데, 포스팅을 위해 주석을 한 줄 한 줄 달아보니 개선점이 보이는 부분이 좋았던 것 같다.
아쉬운 점은 충분한 설명을 곁들이기에 주석이라는 형식이 그렇게 썩 적합하지 않은 것 같다.