- 일시 : 23.07.24 월요일 ~ 07.26 수요일
- 주제 : 통증관리 시스템 - 분류 3.포인티 디자인 시스템 라이브러리 개발
✅: 완료, 🌀: 진행중, 🆕: 대기
파일구조
- Components
- Thumbnails
├─ Image
│ └─ index.tsx
├─ Video
│ └─ index.tsx
├─ index.tsx
├─ styles.ts
└─ type.ts
Image/index.tsx
// 생략 (Video/index.tsx 참고)
import Icon from '@lib/foundation/Icon';
import * as St from '../styles';
import { IVideo } from '../type';
const VideoThumbnails = ({ state = 'normal', type = 'video', src, runningtime = 0 }: IVideo) => {
const runningTimeMin = runningtime && String(Math.floor(runningtime / 60000));
const runningTimeSec = (runningtime && String(Math.floor((runningtime / 1000) % 60)).padStart(2, '0')) || '00';
return (
<St.ThumbnailContainer src={src} state={state}>
<St.VideoRunningTime>
{runningTimeMin}:{runningTimeSec}
</St.VideoRunningTime>
<St.BackgroundColor state={state} />
{state === 'delete' && (
<St.DeleteState>
<Icon name='thumbnails_deletecircle' />
</St.DeleteState>
)}
{state === 'select' && (
<St.SelectState>
<Icon name='thumbnails_checkcircle' />
</St.SelectState>
)}
{state === 'error' && (
<St.ErrorState>
<Icon name='thumbnails_errorcircle' />
<p>원본 파일 삭제됨</p>
</St.ErrorState>
)}
{state === 'video_play' && (
<St.PlayState>
<Icon name='thumbnails_playcircle' />
</St.PlayState>
)}
</St.ThumbnailContainer>
);
};
export default VideoThumbnails;
import styled from 'styled-components';
import { IImage, IVideo } from './type';
export const ThumbnailContainer = styled.div<IVideo | IImage>`
position: relative;
width: 100px;
height: 100px;
margin: ${({ state }) => (state === 'delete' ? '10px 10px 0 0' : null)};
border-radius: 4px;
background-image: url(${(props) => props.src});
background-size: cover;
background-position: center;
color: var(--Bg_300);
font-size: 0.75rem;
`;
export const VideoRunningTime = styled.div`
/* 생략 */
`;
export const BackgroundColor = styled.div<IVideo | IImage>`
position: absolute;
width: 100%;
height: 100%;
border-radius: 4px;
opacity: 0.6;
/* state에 따른 backgorund 색상 변경 */
background-color: ${({ state }) => {
switch (state) {
case 'select':
return 'var(--Pri_500)';
case 'video_play':
return 'var(--Dim)';
case 'error':
return 'var(--Dim)';
default:
return '';
}
}};
cursor: ${({ state }) => (state === 'video_play' ? 'pointer' : null)};
`;
// 이하 생략
export interface IVideo {
type?: 'video';
src?: string;
state?: VideoState;
runningtime?: number;
}
export interface IImage {
type?: 'image';
src?: string;
state?: ImageState;
}
type VideoState = 'normal' | 'delete' | 'select' | 'video_play' | 'error';
type ImageState = 'normal' | 'delete' | 'select' | 'error';
// ex)
<ImageThumbnails src={{/*이미지 url*/}} state='error'/>
<VideoThumbnails src={{/*이미지 url*/}} runningtime={5685263} state='delete'/>
이번 썸네일 컴포넌트에서 이미지와 영상의 차이점이라면 영상은 running time이 있고, 상태 중에 play버튼을 누를 수 있는 상태가 있다는 것이다. 따라서 두 가지 차이점 빼고는 공통점이 너무 많아서 최대한 코드를 줄일 수 있지 않을까 했다. 또한 아이콘만 다르고 (삭제 빼고) 모두 가운데 위치하는 것도 같아서 그것도 상태별로 props를 받아서 조작할 수 있지 않을까 하면서 코드를 짜려니까 굉장히 복잡하고 고민하는 시간이 오래 걸렸다.
처음엔 이미지와 영상을 분리하지 않고 하나의 컴포넌트로 조건(이미지/영상)을 바꾸면서 바뀐 조건에 따라 타입도 바꿔서 적용시키고자 했는데 쉽지 않았다.
(위 그림처럼 video일 땐 VideoState가 적용되고 image일 땐 ImageState가 적용되도록 하고싶었다.)
이와 관련하여 '조건부 타입'이라는 타입스크립트의 활용법을 찾아보았데 적절히 적용시킬 방안을 찾지 못했고 결국 두 타입으로 나누기로 했다. 두 타입으로 나뉨으로써 두 개의 컴포넌트를 만들었다. 두 컴포넌트 (ImageThumbnail / VideoThumbnail)는 참조하는 interface (IImage / IVideo)가 다르지만 각 상황별에 따른 style은(normal/delete/select/error) 같기 때문에 같은 styled component를 참조하도록 했다.
지금은 이런 방식으로 작동하지만 나중에 시간을 들여 더 고민하여 클린코드로 리팩토링 하고자 한다.
참고 레퍼런스
millisecond로 runningtime 값을 전달 받은 후 이를 분:초
형태로 나타낼 때, 초 단위에는 통상적으로 두 자릿수로 나타내기 때문에 초 단위가 1초여도 앞에 0을 붙여준다. (ex) 10:01) 따라서 이 내장 함수를 이용하였다. (* String에만 사용할 수 있음에 유의한다.)
참고 레퍼런스
Error타입의 문구를 작성하기 위해 p태그 사용했는데 내가 설정하지 않은 margin이 나와서 '뭐지' 싶었다. 평소에 reset css를 설정해 놓아서 p태그의 특징을 잊고 있었다.
이 기회에 p태그의 특징을 다시 한번 익히게 되었다.
<p>
<h2>제목</h2> <!-- x -->
테스트를 위한 텍스트
</p>
<p>
p태그 안에 또 p태그를..?
<p>가능할까..??</p> <!-- x -->
</p>
참고 레퍼런스
본 후기는 유데미-스나이퍼팩토리 10주 완성 프로젝트캠프 학습 일지 후기로 작성 되었습니다.