React 동적 UI - part3

wltjd1688·2025년 4월 18일

풀사이클

목록 보기
56/74

오늘은 3가지 기본 컴포넌트를 만들 예정

Title 컴포넌트

import styled from 'styled-components';
 import { ColorKey, HeadingSize } from '../../style/theme';
 
 interface Props {
     children: React.ReactNode;
     size: HeadingSize;
     color?: ColorKey;
 }
 
 function Title({children, size, color} : Props) {
     return (
         <TitleStyle size={size} color={color}>
             {children}
         </TitleStyle>
     )
 }
 
 const TitleStyle = styled.h1<Omit<Props, "children">>`
     font-size: ${({theme, size}) => theme.heading[size].fontSize};
     color: ${({theme, color})=> color ? theme.color[color]: theme.color.primary}
 `;
 
 export default Title

테스트 파일

import { render, screen } from '@testing-library/react'
 import Title from './Title'
 import { BookSotreThemeProvider } from '../../context/themeContext';
 
 describe("Title 컴포넌트 테스트",()=>{
     it("렌더를 확인", ()=>{
         // 1. 렌더
         render(
         <BookSotreThemeProvider>
             <Title size="large">제목</Title>
         </BookSotreThemeProvider>);
 
         // 2. 확인
         expect(screen.getByText('제목')).toBeInTheDocument();
     });
 
     it('size props 적용', ()=>{
         const { container} = render(
             <BookSotreThemeProvider>
                 <Title size="large">제목</Title>
             </BookSotreThemeProvider>
         );
 
         expect(container?.firstChild).toHaveStyle({ fontSize: "2rem"});
     })
 
     it("color props 적용", ()=>{
         const { container} = render(
             <BookSotreThemeProvider>
                 <Title size="large">제목</Title>
             </BookSotreThemeProvider>
         );
 
         expect(container?.firstChild).toHaveStyle({ color: "brown"});
     })
 });

Button 컴포넌트

import styled from "styled-components";
 import { ButtonScheme, ButtonSize } from "../../style/theme";
 
 interface Props{
     children: React.ReactNode;
     size: ButtonSize;
     scheme: ButtonScheme;
     disabled?: boolean;
     isLoading?: boolean;
 }
 
 function Button({children, size, scheme, disabled, isLoading}:Props) {
     return (
         <ButtonStyle size={size} scheme={scheme} disabled={disabled} isLoading={isLoading}>
             {children}
         </ButtonStyle>
     )
 }
 
 const ButtonStyle = styled.button<Omit<Props,"children">>`
     font-size: ${({theme, size})=>theme.button[size].fontSize};
     background-color: ${({theme, scheme})=>theme.buttonScheme[scheme].color};
     color: ${({theme, scheme})=> theme.buttonScheme[scheme].color};
     border: 0;
     border-radius: ${({theme})=> theme.borderRadius.default};
     opacity: ${({ disabled })=> (disabled? 0.5: 1)};
     pointer-events: ${({disabled})=> (disabled? "none": "auto")};
     cursor: ${({ disabled })=> (disabled? 'none': 'pointer')};;
 `;
 
 
 export default Button;

테스트 파일

import { render, screen } from '@testing-library/react'
 import Button from './Button';
 import { BookSotreThemeProvider } from '../../context/themeContext';
 
 describe("Button 컴포넌트 테스트",()=>{
     it("렌더를 확인", ()=>{
         // 1. 렌더
         render(
         <BookSotreThemeProvider>
             <Button size="large" scheme="primary">버튼</Button>
         </BookSotreThemeProvider>);
 
         // 2. 확인
         expect(screen.getByText('버튼')).toBeInTheDocument();
     });
 
     it('size props 적용', ()=>{
         const { container } = render(
             <BookSotreThemeProvider>
                 <Button size="large" scheme="primary">
                     버튼
                 </Button>
             </BookSotreThemeProvider>
         );
 
         expect(screen.getByRole("button",{name:"버튼"})).toHaveStyle({ fontSize: "1.5rem"});
     })
 
     it("color props 적용", ()=>{
         const { container} = render(
             <BookSotreThemeProvider>
                 <Button size="large" scheme="primary">버튼</Button>
             </BookSotreThemeProvider>
         );
 
         expect(container?.firstChild).toHaveStyle({ color: "white"});
     })
 
     it("disabled props 적용",()=>{
         render(
             <BookSotreThemeProvider>
                 <Button size="large" scheme="primary" disabled>버튼</Button>
             </BookSotreThemeProvider>
         )
 
         expect(screen.getByRole("button")).toBeDisabled()
     })
 });

Input 컴포넌트

import React, {ForwardedRef} from "react";
 import styled from "styled-components";
 
 interface Props{
     placeholder?: string;
 }
 
 const InputText = React.forwardRef(({ placeholder }: Props, ref: ForwardedRef<HTMLInputElement>)=>{
     return <InputTextStyle placeholder={placeholder} ref={ref}/>
 });
 
 const InputTextStyle = styled.input.attrs({ type:"text"})`
     padding: 0.25rem 0.75rem;
     border: 1px solid ${({ theme })=> theme.color.border};
     border-radius: ${({ theme})=> theme.borderRadius.default};
     font-size: 1rem;
     line-height: 1.5;
     color: ${({theme})=> theme.color.text};
 `
 
 export default InputText;

테스트 파일

import React from 'react';
 import { render, screen } from '@testing-library/react'
 import InputText from './InputText';
 import { BookSotreThemeProvider } from '../../context/themeContext';
 
 describe("InputText 컴포넌트 테스트",()=>{
     it("렌더를 확인", ()=>{
         // 1. 렌더
         render(
         <BookSotreThemeProvider>
             <InputText placeholder="여기에 입력하세요"/>
         </BookSotreThemeProvider>);
 
         // 2. 확인
         expect(screen.getByPlaceholderText("여기에 입력하세요")).toBeInTheDocument();
     });
 
     it("forwardRef 테스트", ()=>{
         const ref = React.createRef<HTMLInputElement>();
         render(
         <BookSotreThemeProvider>
             <InputText placeholder="여기에 입력하세요" ref={ref}/>
         </BookSotreThemeProvider>);
 
         expect(ref.current).toBeInstanceOf(HTMLInputElement);
     });
 });

이제 진짜로 시작한다

헤더와 푸터

해당 사진을 기준으로 헤더와 푸터를 제작하였다.

이걸 볼 사람이 있을지는 모르겠는데, 지금 react-icons@5.5.0버전에서 jsx파일 내에서 사용하면 TS2786 에러가 나온다.
이거 버전만 낮추면 해결되니 참고하길 바란다.
링크

profile
일단 해!!!!

0개의 댓글