9/10 TID : 기술스택 보여주는 섹션 만들기

그른손·2023년 9월 10일
0

뭐했나요

언제나와 같이 특별한 건 없습니다. 기존의 Skills페이지를 없애고 About 페이지에서 보여주기로 했고, 스킬을 카드 형태로 보여주는 컴포넌트와 볼 카드를 선택할 수 있는 리스트 컴포넌트를 만들었습니다.

어떻게 했나요

SkillCard

먼저 SkillCard 컴포넌트를 간단하게 만들었습니다. 모션을 활용할 것을 고려해서, 흰색 로고만 들어있는 png 이미지와 그 뒤에 깔릴 백그라운드를 만들어주고, 타이틀 띄워주고, 설명 띄워줍니다.

interface SkillCardProps {
  skill: Skill;
}

export default function SkillCard({ skill }: SkillCardProps) {
  return (
    <li className="flex flex-col">
      <section style={{ background: `${skill.bgColorCode}` }}>
        <img src={skill.imgUrl} alt={skill.title} className="w-full" />
      </section>
      <section>
        <section>{skill.title}</section>
        <ul>
          {skill.description.map((item) => (
            <li>{item}</li>
          ))}
        </ul>
      </section>
    </li>
  );
}

피그마로 각 스킬 로고, bg만들기


기술 스택 로고 svg 파일을 받아와서 로고 벡터만 떼어내고, 흰색으로 바꾸고, 복붙해서 레이어블러 먹여서 그림자 만들고 배치한다음 대표색 따놓고 바꾸고 싶은건 바꿨습니다. 기존의 색에서 밝기를 높이고 채도를 낮추는 게 제 취향에 맞더라구요.
스타일드 컴포넌트는 로고가 별로 안예쁜 것 같아요
Vite는 노란 번개가 포인트인 것 같아서 남겨줬습니다 흰색으로 밀어버리면 별로길래

db에 스킬 정보 입력하기

스킬 정보를 어떻게 관리할지가 판단이 잘 안서서 고생했습니다. 일단 리스트를 만들어두고 거기에서 보고싶은 걸 클릭하면 그걸 카드로 띄워줄 건데, 지금은 할 줄 아는게 쥐뿔도 없어서 아이템들이 적지만 점점 늘어나면 카테고리 별 분류가 필요할 것 같더라구요. 그래서 이렇게 했습니다.

  • skills 콜렉션
    • 01_languages
      • title : 리스트에 띄워줄 스킬셋 제목
      • data : map이 들어있는 배열
        • JavaScript
          • title : 기술이름 ('JavaScript')
          • bgColorCode : 배경색 코드 (그라디언트면 linear-gradient가 들어가고 아닐 경우 그냥 헥사코드)
          • description : 배열 (리스트 형식으로 만들려고 배열형태로 만들었습니다. 각 꼭지가 문자열로 들어갑니다)
          • imgUrl : 로고이미지 주소(파이어베이스 스토리지에 올려둠)
        • TypeScript
    • 02_frameworksAndLibraries
      • title
      • data
        • React (프레임워크는 아닌거 알고있지만)
        • Redux
        • Recoil
    • 03_styling
      • title
      • data
        • Tailwind CSS
        • styled-components
    • 04_buildTools
      • title
      • data
        • Vite
    • 05_DeploymentAndHosting
      • title
      • data
        • Firebase

(개중에는 배운지 얼마 안됐거나 굳이 스택에 써놓을만한 게 아닌 것도 있지만, 일단 최대한 많이 적는 게 UI 테스트하기에 좋을 것 같아서 이것저것 우겨넣었습니다 react-router-dom같은거 빼고)
이제 skills 가져오면 5개의 스킬셋을 가져오고, 그 안의 data 필드에는 각각의 스킬 정보들이 들어있습니다.

SkillList

interface SkillListProps {
  data: SkillSet[];
  onItemClick: (skill: Skill) => void;
  selectedSkill: Skill | null;
}

export default function SkillList({
  data,
  onItemClick,
  selectedSkill,
}: SkillListProps) {
  return (
    <ul>
      {data.map((skillSet: SkillSet) => (
        <li key={skillSet.title}>
          <section>{skillSet.title}</section>
          <ul>
            {skillSet.data.map((skill: Skill) => (
              <li
                key={skill.title}
                onClick={() => onItemClick(skill)}
                className={`cursor-pointer ${
                  selectedSkill && selectedSkill.title === skill.title
                    ? "font-bold cursor-default"
                    : ""
                }`}
              >
                {skill.title}
              </li>
            ))}
          </ul>
        </li>
      ))}
    </ul>
  );
}

리스트 컴포넌트입니다. 각 아이템을 클릭해서 선택할 수 있고, 선택된 아이템은 볼드 표시합니다.

SkillContainer

import { useEffect, useState } from "react";
import SkillList from "./SkillList";
import SkillCard from "./SkillCard";
import { Skill, SkillSet } from "../../types";

interface SkillContainerProps {
  data: SkillSet[];
}

export default function SkillContainer({ data }: SkillContainerProps) {
  const [selectedSkill, setSelectedSkill] = useState<Skill | null>(null);

  useEffect(() => {
    if (data.length > 0 && data[0].data.length > 0) {
      setSelectedSkill(data[0].data[0]);
    }
  }, [data]);

  return (
    <section>
      <section>Skills</section>
      {selectedSkill && (
        <SkillCard key={selectedSkill.title} skill={selectedSkill} />
      )}

      <SkillList
        data={data}
        onItemClick={(skill: Skill) => setSelectedSkill(skill)}
        selectedSkill={selectedSkill}
      />
    </section>
  );
}

About 페이지에 띄워줄 SkillContainer 컴포넌트입니다. 선택된 스킬을 상태로 관리하고, 데이터를 불러올 때 selectedSkill을 첫 번째 스킬셋의 첫 번째 스킬로 설정합니다. 원래는 useState의 초기값을 그렇게 설정했는데, data가 아직 다 불러와지지 않은 상태에서 data[0].data[0]에 접근하려고 하니 타입에러가 뜨더라구요...

About에서 적용하기

좀 깁니다... 코드 접기 기능같은거 없나요

export default function About() {
  const darkMode = useDarkMode();
  const {
    data: profileData,
    loading: profileLoading,
    error: profileError,
  } = useFetchDocument<Profile>("/about", "profile");
  const {
    data: skillsData,
    loading: skillsLoading,
    error: skillsError,
  } = useFetchCollection<SkillSet>("skills");

  return (
    <div
      className={`${
        darkMode ? "text-white bg-slate-500" : "text-gray-700"
      } transition-all duration-300 ease-in-out`}
    >
      <section>
        <section className="mt-16">
          <main className="flex flex-col-reverse md:flex-row md:justify-between w-full">
            <section className="flex flex-col w-full p-10  ml-0 items-center md:ml-32 md:w-2/3">
              <section>
                {profileLoading && <div>Loading profile data...</div>}
                {profileError && (
                  <div>
                    Failed to fetch profile data : {profileError.message}
                  </div>
                )}
                {profileData && (
                  <ProfileBox description={profileData.description} />
                )}
              </section>
              <section>
                {skillsLoading && <div>Loading skill data...</div>}
                {skillsError && (
                  <div>Failed to fetch skill data : {skillsError.message}</div>
                )}
                {skillsData && <SkillContainer data={skillsData} />}
              </section>
            </section>
            {profileData && (
              <img
                src={profileData.imgUrl}
                alt={profileData.imgUrl}
                className="rounded-xl min-w-[250px] m-6 md:w-1/5 md:m-0 md:h-auto md:rounded-none md:rounded-bl-[20%]"
              />
            )}
          </main>
        </section>
      </section>
    </div>
  );
}

useFetchCollection 훅으로 skills 콜렉션을 싸그리 불러오고, SkillContainer에 넣어줍니다... 구조가 좀 개판인 느낌인데 꾸미기 할 때 고생할 것 같네요... 그럼 또 블로깅할 거리가 생기겠죠 뭐...

왜 이리 힘이 없나요

하면서도 이게 맞나 싶은 순간이 너무 많아서 그렇습니다
한번쯤은 '와 나 천잰가?'싶은 경험들이 있어야 블로깅도 하고 이력서에 쓸 거리도 생길 텐데...
고민하고 해결할 만한 수준의 문제가 발생하지 않는 프로젝트를 하고 있어서 그런 걸까요
일단 잡니다... 내일은 월요일이니까요

profile
프론트엔드 개발자

0개의 댓글