diff --git a/src/App.tsx b/src/App.tsx
index 2ec9ece..bb91b7f 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -1,59 +1,14 @@
import { createBrowserRouter, RouterProvider } from "react-router-dom";
+import Layout from "./components/Layout";
+import routerInfo from "./shared/routing/routerInfo.tsx";
-import Layout from "./components/Layout/index.tsx";
-import Main from "./pages/Main/index.tsx";
-import Artist from "./pages/Artist/index.tsx";
-import Booth from "./pages/Booth/index.tsx";
-import BoothNFoodList from "./pages/BoothNFoodList/index.tsx";
-import Foodtruck from "./pages/Foodtruck/index.tsx";
-import Makers from "./pages/Makers/index.tsx";
-import Map from "./pages/Map/index.tsx";
-import Notice from "./pages/Notice/index.tsx";
-import QnA from "./pages/QnA/index.tsx";
-import Timetable from "./pages/Timetable/index.tsx";
+const RouterPath = routerInfo.map((info) => {
+ return {
+ path: info.path,
+ element: info.element,
+ };
+});
-const RouterPath = [
- {
- path: "",
- element: ,
- },
- {
- path: "artist/:id",
- element: ,
- },
- {
- path: "booth/:id",
- element: ,
- },
- {
- path: "booth_foodtruck_list",
- element: ,
- },
- {
- path: "foodtruck/:id",
- element: ,
- },
- {
- path: "makers",
- element: ,
- },
- {
- path: "map",
- element: ,
- },
- {
- path: "notice",
- element: ,
- },
- {
- path: "QnA",
- element: ,
- },
- {
- path: "timetable",
- element: ,
- },
-];
const router = createBrowserRouter([
{
path: "/",
diff --git a/src/assets/logo.svg b/src/assets/logo.svg
new file mode 100644
index 0000000..08c652d
--- /dev/null
+++ b/src/assets/logo.svg
@@ -0,0 +1,161 @@
+
+
+
diff --git a/src/assets/menu_buton.json b/src/assets/menu_buton.json
new file mode 100644
index 0000000..cf5bf59
--- /dev/null
+++ b/src/assets/menu_buton.json
@@ -0,0 +1 @@
+{"v":"5.4.4","fr":60,"ip":0,"op":140,"w":100,"h":100,"nm":"Menu","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Buttom","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":40,"s":[0],"e":[-45]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":60,"s":[-45],"e":[-45]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":85,"s":[-45],"e":[0]},{"t":105}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":10,"s":[51,70,0],"e":[51,78,0],"to":[0,1.333,0],"ti":[0,3.333,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":23,"s":[51,78,0],"e":[51,50,0],"to":[0,-3.333,0],"ti":[0,4.667,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":36,"s":[51,50,0],"e":[51,50,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":110,"s":[51,50,0],"e":[51,78,0],"to":[0,4.667,0],"ti":[0,-3.333,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":123,"s":[51,78,0],"e":[51,70,0],"to":[0,3.333,0],"ti":[0,1.333,0]},{"t":136}],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[31,4],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":2,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[200,200],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Buttom","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":331,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Center","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":40,"s":[0],"e":[45]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":60,"s":[45],"e":[45]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":85,"s":[45],"e":[0]},{"t":105}],"ix":10},"p":{"a":0,"k":[51,50,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[31,4],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":2,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[200,200],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Center","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":331,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"Upper","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":40,"s":[0],"e":[-45]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":60,"s":[-45],"e":[-45]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":85,"s":[-45],"e":[0]},{"t":105}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":10,"s":[51,30,0],"e":[51,22,0],"to":[0,-1.333,0],"ti":[0,-3.333,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":23,"s":[51,22,0],"e":[51,50,0],"to":[0,3.333,0],"ti":[0,-4.667,0]},{"i":{"x":0.831,"y":0.831},"o":{"x":0.165,"y":0.165},"t":36,"s":[51,50,0],"e":[51,50,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":110,"s":[51,50,0],"e":[51,22,0],"to":[0,-4.667,0],"ti":[0,3.333,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":123,"s":[51,22,0],"e":[51,30,0],"to":[0,-3.333,0],"ti":[0,-1.333,0]},{"t":136}],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[31,4],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":2,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[200,200],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Upper","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":331,"st":0,"bm":0}],"markers":[]}
\ No newline at end of file
diff --git a/src/components/Header/.css.ts b/src/components/Header/.css.ts
new file mode 100644
index 0000000..e2b1e15
--- /dev/null
+++ b/src/components/Header/.css.ts
@@ -0,0 +1,122 @@
+import { style } from "@vanilla-extract/css";
+import { vars } from "../../shared/styles/vars.css";
+
+const mobileBreakpoint = "1000px";
+
+export const headerStyles = style({
+ display: "flex",
+ justifyContent: "space-between",
+ alignItems: "center",
+ padding: "10px 20px",
+ position: "relative",
+});
+
+export const logoStyles = style({
+ width: "4em",
+ height: "auto",
+ fill: vars.color.green2,
+ ":hover": {
+ fill: vars.color.green1,
+ },
+});
+
+export const currentPageStyles = style({
+ color: "white",
+ fontSize: "16px",
+ fontWeight: "bold",
+});
+
+export const toggleBtnStyles = style({
+ background: "none",
+ border: "none",
+ color: "white",
+ fontSize: "24px",
+ cursor: "pointer",
+ display: "block",
+ height: "1.5em",
+ "@media": {
+ [`(min-width: ${mobileBreakpoint})`]: {
+ display: "none",
+ },
+ },
+});
+
+const menuTransition = "transform 0.3s ease-out, opacity 0.3s ease-out";
+
+export const menuStyles = style({
+ position: "absolute",
+ top: "100%",
+ left: 0,
+ right: 0,
+ padding: "20px",
+ boxShadow: "0 4px 6px rgba(0, 0, 0, 0.1)",
+ transform: "translateX(-100%)",
+ opacity: 0,
+ pointerEvents: "none",
+ transition: menuTransition,
+ selectors: {
+ "&.active": {
+ transform: "translateX(0)",
+ opacity: 1,
+ pointerEvents: "auto",
+ backdropFilter: "blur(5px)",
+ },
+ },
+ "@media": {
+ [`(min-width: ${mobileBreakpoint})`]: {
+ position: "static",
+ display: "flex",
+ flexDirection: "row",
+ boxShadow: "none",
+ padding: 0,
+ transform: "none",
+ opacity: 1,
+ pointerEvents: "auto",
+ transition: "none",
+ },
+ },
+});
+
+export const menuListStyles = style({
+ listStyle: "none",
+ padding: 0,
+ margin: 0,
+ display: "flex",
+ flexDirection: "column",
+ alignItems: "flex-end",
+ "@media": {
+ [`(min-width: ${mobileBreakpoint})`]: {
+ flexDirection: "row",
+ alignItems: "center",
+ },
+ },
+});
+
+export const menuItemStyles = style({
+ margin: "16px",
+ fontSize: "large",
+ "@media": {
+ [`(min-width: ${mobileBreakpoint})`]: {
+ margin: "0 0 0 20px",
+ },
+ },
+});
+
+export const menuItemLinkStyles = style({
+ display: "inline-block",
+ padding: "10px 15px",
+ color: "white",
+ textDecoration: "none",
+ fontSize: "16px",
+ borderRadius: "20px",
+ backgroundColor: "rgba(255, 255, 255, 0.1)",
+ transition: "background-color 0.3s ease",
+ ":hover": {
+ backgroundColor: "rgba(255, 255, 255, 0.2)",
+ },
+});
+
+export const highlightStyles = style({
+ fontWeight: "bold",
+ backgroundColor: "rgba(255, 255, 255, 0.3)",
+});
diff --git a/src/components/Header/index.tsx b/src/components/Header/index.tsx
new file mode 100644
index 0000000..9ca1da3
--- /dev/null
+++ b/src/components/Header/index.tsx
@@ -0,0 +1,116 @@
+import React, { useCallback, useEffect, useRef, useState } from "react";
+import { Link, matchPath, useLocation } from "react-router-dom";
+import routerInfo from "../../shared/routing/routerInfo.tsx";
+import Logo from "../../assets/logo.svg?react";
+import Menu from "../../assets/menu_buton.json";
+import {
+ currentPageStyles,
+ headerStyles,
+ highlightStyles,
+ logoStyles,
+ menuItemLinkStyles,
+ menuItemStyles,
+ menuListStyles,
+ menuStyles,
+ toggleBtnStyles,
+} from "./.css.ts";
+import { routerInfoType } from "../../shared/types/routing.ts";
+import Lottie, { LottieRefCurrentProps } from "lottie-react";
+
+/**
+ * 현재 경로인지 확인하는 함수
+ * @param path 비교할 경로
+ * @param pathname 현재 경로(useLocation().pathname)
+ * @return boolean
+ */
+const isCurrentPath = (path: string, pathname: string): boolean =>
+ !!matchPath({ path, end: true }, pathname);
+
+/**
+ * Header 컴포넌트
+ * @component
+ * @returns {React.ReactElement} Header 컴포넌트 요소
+ */
+export const Header: React.FC = () => {
+ const [isActive, setIsActive] = useState(false);
+ const [currentPage, setCurrentPage] = useState("/");
+ const location = useLocation();
+
+ // lottie 애니메이션을 제어하기 위한 ref | 참고: https://lottiereact.com/#calling-the-methods
+ const lottieRef = useRef(null);
+
+ /**
+ * 토글 버튼 클릭 이벤트 핸들러
+ * @function handleToggle
+ * @return {void}
+ */
+ const handleToggle = useCallback(() => {
+ setIsActive((prevState) => {
+ const newState = !prevState;
+ if (lottieRef.current) {
+ if (newState) {
+ lottieRef.current.playSegments([0, 60], true);
+ } else {
+ lottieRef.current.playSegments([60, 120], true);
+ }
+ }
+ return newState;
+ });
+ }, []);
+
+ /**
+ * 컴포넌트가 마운트될 때 현재 페이지를 설정하고, isActive를 false로 초기화함
+ */
+ useEffect(() => {
+ const currentRoute = routerInfo.find((route) => isCurrentPath(route.path, location.pathname));
+ setCurrentPage(currentRoute?.korean ?? "/");
+ setIsActive(false);
+ lottieRef.current?.setSpeed(1.5);
+ }, [location.pathname]);
+
+ return (
+
+ {/*로고 부분*/}
+
+
+
+
+ {/*현재 페이지 출력 부분*/}
+ {currentPage}
+
+ {/*토글 버튼 (lottie)*/}
+
+
+ {/*페이지 메뉴*/}
+
+
+ );
+};
+
+export default Header;
diff --git a/src/components/Layout/index.tsx b/src/components/Layout/index.tsx
index 3c8f64a..78fb21f 100644
--- a/src/components/Layout/index.tsx
+++ b/src/components/Layout/index.tsx
@@ -1,11 +1,13 @@
import { Outlet } from "react-router-dom";
import "../../shared/styles/globalStyle.css";
import backgroundImg from "../../assets/background.png";
+import Header from "../Header";
// TODO: 모든 페이지에 공통으로 들어가는 레이아웃을 작성합니다. 필요 없으면 삭제해주세요
export default function Layout() {
return (
+
diff --git a/src/shared/routing/routerInfo.tsx b/src/shared/routing/routerInfo.tsx
new file mode 100644
index 0000000..d5c5516
--- /dev/null
+++ b/src/shared/routing/routerInfo.tsx
@@ -0,0 +1,86 @@
+import { routerInfoType } from "../types/routing.ts";
+import Main from "../../pages/Main";
+import Artist from "../../pages/Artist";
+import Booth from "../../pages/Booth";
+import BoothNFoodList from "../../pages/BoothNFoodList";
+import Foodtruck from "../../pages/Foodtruck";
+import Makers from "../../pages/Makers";
+import Map from "../../pages/Map";
+import Notice from "../../pages/Notice";
+import QnA from "../../pages/QnA";
+import Timetable from "../../pages/Timetable";
+
+export const routerInfo: routerInfoType[] = [
+ {
+ path: "/",
+ element: ,
+ english: "Main",
+ korean: "메인",
+ expose: true,
+ },
+ {
+ path: "artist/:id",
+ element: ,
+ english: "Artist",
+ korean: "아티스트",
+ expose: false,
+ },
+ {
+ path: "booth/:id",
+ element: ,
+ english: "Booth",
+ korean: "부스",
+ expose: false,
+ },
+ {
+ path: "booth_foodtruck_list",
+ element: ,
+ english: "Booth & Foodtruck List",
+ korean: "부스 & 푸드트럭 리스트",
+ expose: true,
+ },
+ {
+ path: "foodtruck/:id",
+ element: ,
+ english: "Foodtruck",
+ korean: "푸드트럭",
+ expose: false,
+ },
+ {
+ path: "makers",
+ element: ,
+ english: "Makers",
+ korean: "메이커스",
+ expose: false,
+ },
+ {
+ path: "map",
+ element: ,
+ english: "Map",
+ korean: "지도",
+ expose: true,
+ },
+ {
+ path: "notice",
+ element: ,
+ english: "Notice",
+ korean: "공지사항",
+ expose: true,
+ },
+ {
+ path: "QnA",
+ element: ,
+ english: "QnA",
+ korean: "QnA",
+ expose: true,
+ },
+ {
+ path: "timetable",
+ element: ,
+ english: "Timetable",
+ korean: "타임테이블",
+ expose: true,
+ },
+];
+
+export default routerInfo;
diff --git a/src/shared/types/routing.ts b/src/shared/types/routing.ts
new file mode 100644
index 0000000..7392783
--- /dev/null
+++ b/src/shared/types/routing.ts
@@ -0,0 +1,8 @@
+// 라우팅 정보 객체 타입
+export interface routerInfoType {
+ path: string;
+ element: React.ReactElement;
+ english: string;
+ korean: string;
+ expose: boolean;
+}