Untitled

제작 기간: 2023.07 ~ 2023.07 기여도: 퍼블리싱 100% 사용 기술: React, TypeScript, SCSS URL: https://craftcharter.vercel.app/

craftcharter 사이트의 메인 페이지, Contact 페이지를 React.js로 클론 코딩하였습니다. 윈도우 스크롤 api와 CSS Animation을 이용하여 모션 및 인터랙션을 구현하였습니다.

🔍Overview


페이지 구조

// Main.tsx
import Home from 'components/home/Home';
import Fleet from 'components/fleet/Fleet';
import Service from 'components/service/Service';
import About from 'components/about/About';
import Contact from 'components/contact/Contact';

const Main = () => {
  return (
    <>
      <Home />
      <Fleet />
      <Service />
      <About />
      <Contact />
    </>
  );
};
export default Main;

// footer.tsx
const Footer = () => {
  return (
    <footer className='Footer'>
      <div className='Footer__header'>
        <Logo fill='#000' /> // 다른 섹션, 컴포넌트에서도 재사용할 수 있는 UI는 컴포넌트화하여 사용
        <nav className='Footer__nav'>
          <ul>
            {footerMenu.map((el, idx) => {
              return (
                <li className='Footer__nav-item' key={`ftmenu${idx}`}>
                  <a href={el.link}>{el.title}</a>
                </li>
              );
            })}
          </ul>
          <Link to='/contact' className='Footer__btn--round'>
            contact
          </Link>
        </nav>
      </div>
      <hr />
      <div className='Footer__bottom'>
        <div className='Footer__link-container'>
          <Social /> // 다른 곳에서도 사용되는 컴포넌트
          <Credit />
        </div>
        <ul className='Footer__info'>
          {infoMenu.map((el, idx) => {
            return (
              <li className='Footer__info-item' key={`ftinfo${idx}`}>
                <a href={el.link}>{el.title}</a>
              </li>
            );
          })}
          <li className='Footer__info-copy'>© 2023 Craft</li>
        </ul>
      </div>
    </footer>
  );
};

export default Footer;

기본적으로 섹션 컴포넌트를 만들어 섹션 별 마크업 확인이 용이하도록 작성하고, 재사용이 가능한 UI는 컴포넌트화하여 중복 코드 작성을 줄이고자 하였습니다.

// App.tsx
function App() {
  const [isOpenCtaPop, setIsOpenCtaPop] = useState(false); 
  // Cta 팝업은 전역 상태로 분리하여 여러 하위 컴포넌트에서도 접근 및 상태 변경이 가능하도록 하였습니다. 

  return (
    <CtaPopContext.Provider value={{ isOpenCtaPop, setIsOpenCtaPop }}>
      <div className='Wrapper'>
        <BrowserRouter>
          <Header />
          <Routes>
            <Route path='/' element={<Main />} />
            <Route path='/contact' element={<ContactPage />} />
          </Routes>
          <Footer />
          <SideCta />
          <SideCtaPop />
        </BrowserRouter>
      </div>
    </CtaPopContext.Provider>
  );
}

메인 페이지/Contact 페이지 간의 전환을 위해 react-router 라이브러리를 사용하였습니다.

Untitled

사이드의 ‘TAKE FLIGHT’ 버튼을 클릭하면 cta 팝업이 출력되는데, 모바일 메뉴 컴포넌트에도 같은 기능을 하는 버튼이 있기 때문에 react의 Context API 를 이용하여 전역 상태로 따로 빼주었습니다.

공통 스타일 mixin

Untitled

//  NOTE font
@mixin exo($weight: 400) {
  font-family: 'Exo 2', sans-serif;
  font-weight: $weight;
  letter-spacing: 0.02em;
  word-spacing: 0.04em;
}

@mixin flex($jc: flex-start, $ai: stretch, $gap: 0, $dir: row) {
  display: flex;
  justify-content: $jc;
  align-items: $ai;
  gap: $gap;
  flex-direction: $dir;
}

@mixin btn-with-arrow($color: $black) {
  @include flex(flex-start, center);
  @include exo(600);
  display: inline-flex;
  cursor: pointer;
  font-size: 1.6rem;
  color: $color;
  white-space: nowrap;

  &::after {
    content: url('../assets/image/common/btn-arrow.svg');
    width: 2rem;
    display: inline-block;
    margin-left: 2rem;
    transition: transform 0.3s;
  }

  &:hover::after {
    transform: translateX(0.6rem);
  }
}

@mixin btn-rounded($color: $white, $hover: $black) {
  @include exo(400);
  @include btn-with-arrow($color);
  padding: 0 3rem;
  height: 4rem;
  border: 0.1rem solid $color;
  background: transparent;
  transition:
    background 0.3s,
    color 0.3s;
  border-radius: 10rem;

  &:hover {
    background: $color;
    color: $hover;
  }

  &::after {
    display: none;
  }
}

공통적으로 여러 곳에 쓰이는 폰트, 버튼 스타일의 경우 SCSS의 mixin 기능을 이용하여 중복을 줄이고 확장성을 높이고자 하였습니다.