[React] svg icon을 컴포넌트화 하기 + svg 조건에 따라 색 변환하기

박세화·2023년 7월 24일

React JS

목록 보기
12/22


팀프로젝트 중, 네비게이션 안의 아이콘들(svg)과 밑의 페이지 텍스트를 합쳐서 하나의 icon box로 구현중이었다. 그냥 반복되는 코드로 하나씩 집어넣는 방법도 있으나 되도록이면 컴포넌트화 하고 싶었다. 또한 클릭해서 페이지가 전환됨에 따라 아이콘과 텍스트 색이 변하게 하고 싶었다.

iconFactory.tsx

import { ReactElement } from "react";

import { ReactComponent as Home } from "../icons/Home.svg";
import { ReactComponent as Edit } from "../icons/Edit.svg";
import { ReactComponent as Chat } from "../icons/Chat.svg";
import { ReactComponent as Setting } from "../icons/Setting.svg"

import "./iconFactory.styles.css"

const icons = {
  home: Home,
  edit: Edit,
  chat: Chat,
  setting: Setting,
};

export type IconType = keyof typeof icons;
export const iconTypes: IconType[] = Object.keys(icons) as IconType[];

export interface IconProps {
  icon: IconType;
  isActive: boolean;
}

function Icon({ icon, isActive }: IconProps): ReactElement {
  const SVGIcon = icons[icon];
  return <SVGIcon className={isActive? "active" : "inactive"} />;
}

export default Icon;
  • 먼저 각 svg 파일들을 icon 컴포넌트로 뱉어내주는 함수를 작성했다.
  • 각 svg를 ReactComponent로 가져온 뒤, icons 라는 배열을 만들어 각각에 해당하는 string에 속성값으로 매치해줬다.
  • Icon 컴포넌트는 페이지 텍스트 string을 prop으로 받아서, 그에 해당하는 svg 컴포넌트를 뱉어낸다.

처음에 이를 구현할 때, return <{icon}> 이런 식으로 icon 값을 받으려는 바보같은 짓을 했다. 바로 컴포넌트 이름에 jsx가 들어갈 순 없고, 먼저 변수로 받은 후 그 변수를 컴포넌트로 넣으면 된다.

  • 클래스명은 isActive가 true 이면 active를, false이면 inactive를 가진다. 아이콘 색 변환에 쓰일 클래스명이다.

iconBox.tsx

import { NavLink, useLocation } from "react-router-dom";

import styled from "styled-components";
import Icon, { IconType } from "./iconFactory";

interface IconBoxProps {
  iconName: IconType;
  text: string;
  route: string;
  key: number;
}

export const IconItems:IconBoxProps[] = [
  { iconName: "home", text: "홈", route: "/main", key: 1 },
  { iconName: "edit", text: "일지", route: "/diary", key: 2 },
  { iconName: "chat", text: "커뮤니티", route: "/community", key: 3 },
  { iconName: "setting", text: "설정", route: "/setting", key: 5 },
];

const StyledDiv = styled.div`
  display: flex;
  flex-direction: column;
  gap: 5px;
  align-items: center;
`;

type textProp = {
  active: boolean;
}

const Text = styled.span<textProp>`
  font-size: 12px;
  color: ${(props) => props.active? "#FFD954" : "#777777"};
`;


const IconBox = ({ iconName, text, route }: IconBoxProps) => {
  const location = useLocation();
  const { pathname } = location;

  return (
    <StyledDiv>
      <NavLink to={route}>
        <Icon icon={iconName} isActive={route === pathname} />
      </NavLink>
      <Text active={route === pathname}>{text}</Text>
    </StyledDiv>
  );
};

export default IconBox;
import IconBox, {IconItems} from "./iconbox";

const Navigation = () => {
  return (
    <NavBox>
      {IconItems.map((item) => (
        <IconBox
          key={item.key}
          iconName={item.iconName}
          text={item.text}
          route={item.route}
        />
      ))}
    </NavBox>
  );
};
  • 각 네비게이션 요소가 될 iconBox 컴포넌트이다.
  • NavLinkText로 박스를 구성하고, Icon 컴포넌트는 NavLink 사이에 끼워넣는다.
  • icon, isActive prop들도 각각 전달한다.
  • Text도 마찬가지로 active라는 prop을 전달받는다.

useLocation을 이용하여 현재 위치를 pathname이라는 변수에 넣어주었다. route와 pathname이 일치하면 isActive가 true가 된다. 이 boolean값을 이용하여 색 변환을 해줄 것이다.

  • Navigation 컴포넌트에는 IconItems 라는 배열을 만들어 아이콘 이름, 페이지 텍스트, 페이지 주소, 그리고 key값을 넣었다.
  • 이 배열을 navigation.tsx에서 불러와서(지금 보니 배열을 그냥 아예 별개의 파일로 만들걸 그랬다) 하나씩 매핑한다. prop도 하나씩 전달한다.

iconFactory.styles.css

/* home, edit */
.active > g > path[id="Rectangle 1"] {
    stroke: var(--sleeper-yellow);
}

/* home */
.active > g >  path[id="Vector 3"]  {
    stroke: var(--sleeper-yellow);
}

/* chat icon */
.active > g > path[id="Union"] {
    stroke: var(--sleeper-yellow);
}
.active > g > path[id="Vector 7"] {
    stroke: var(--sleeper-yellow);
}
.active > g > path[id="Vector 8"] {
    stroke: var(--sleeper-yellow);
}

/* edit */
.active > g > path[id="Vector 107"] {
    fill: var(--sleeper-yellow);
}

/* setting */
.active > g > path[id="Subtract"] {
    fill: var(--sleeper-yellow);
}
  • 전체 구현 과정 중 가장 황당한 부분이지만, svg 파일 속의 stroke와 fill 속성들을 아이콘 하나하나 각각 조작해야했다. (어떤 아이콘은 fill은 비워두고 stroke만 칠한다든지, stroke가 여러 개 있어서 모든 것을 바꿔줘야 한다든지...)

  • 그래서 아이콘의 클래스명이 active일 때만 stroke나 fill을 변경해주되, 각각의 아이콘들에 해당하는 스타일 속성을 따로 작성했다.


🌿 솔직히 이것보다 똑똑한 코드가 있겠으나...그래도 일단 아이콘을 사용할 때 하드코딩을 피하는 방법을 알아낸 것에 의의를 둔다.

0개의 댓글