From 130cf34bf71013394f0c5abd00cf3866a140341b Mon Sep 17 00:00:00 2001
From: hajiiiin <101108440+hajiiiin@users.noreply.github.com>
Date: Fri, 14 Mar 2025 17:59:56 +0900
Subject: [PATCH 1/3] =?UTF-8?q?fix:=20=EC=83=81=EC=84=B8=ED=8E=98=EC=9D=B4?=
=?UTF-8?q?=EC=A7=80=20=EA=B0=94=EB=8B=A4=EC=99=80=EB=8F=84=20=ED=95=84?=
=?UTF-8?q?=ED=84=B0=20=EC=9C=A0=EC=A7=80?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/components/Card.tsx | 2 +-
src/components/ServiceTab.tsx | 30 ++++++++++++++----------------
2 files changed, 15 insertions(+), 17 deletions(-)
diff --git a/src/components/Card.tsx b/src/components/Card.tsx
index 0afc3c1..a531916 100644
--- a/src/components/Card.tsx
+++ b/src/components/Card.tsx
@@ -70,7 +70,7 @@ export default function Card({
variants={cardVariants}
initial="hidden"
animate={inView ? "visible" : "hidden"}
- transition={{ duration: 0.7, ease: "easeInOut" }}
+ transition={{ duration: 0.5, ease: "easeInOut" }}
className="relative my-5"
>
(
- () => (searchParams.get("type") || "DALLAEMFIT") as "DALLAEMFIT" | "WORKATION",
- );
- const [selectedCategory, setSelectedCategory] = useState
("전체");
+export default function ServiceTab({ onCategoryChange, isFilteringLoading }: ServiceTabProps) {
+ const searchParams = useSearchParams();
- // URL이 변경되었을 때 필터링 로딩 상태 해제
- useEffect(() => {
- if (!isFilteringLoading) return;
+ const [selectedTab, setSelectedTab] = useState(
+ () => SERVICE_TABS.find((t) => t.type === searchParams.get("type"))?.name || "DALLAEMFIT",
+ );
- const currentType = searchParams.get("type") || "DALLAEMFIT";
- setSelectedTab(currentType as "DALLAEMFIT" | "WORKATION");
- }, [searchParams, isFilteringLoading]);
+ const [selectedCategory, setSelectedCategory] = useState(
+ () => CATEGORIES.find((c) => c.type === searchParams.get("type"))?.name || "전체",
+ );
- // searchParams 변경 감지해서 반영
+ //searchParams 변경 감지해서 반영
useEffect(() => {
const currentType = searchParams.get("type") || "DALLAEMFIT";
-
+ console.log("선택된 타입: ", currentType);
if (currentType !== selectedTab) {
- setSelectedTab(currentType as "DALLAEMFIT" | "WORKATION");
+ //setSelectedTab(currentType as "DALLAEMFIT" | "WORKATION");
+ setSelectedTab(SERVICE_TABS.find((t) => t.type === currentType)?.name || "달램핏");
}
}, [searchParams]);
@@ -53,8 +52,7 @@ export default function ServiceTab({ searchParams, onCategoryChange, isFiltering
const tabType = SERVICE_TABS.find((t) => t.name === tabName)?.type;
if (!tabType) return;
- setSelectedTab(tabType as "DALLAEMFIT" | "WORKATION");
-
+ setSelectedTab(tabName);
onCategoryChange(tabType);
handleCategoryReset();
};
From 415f54b3b5369dab523ea330ef0b490960ea3ae8 Mon Sep 17 00:00:00 2001
From: hajiiiin <101108440+hajiiiin@users.noreply.github.com>
Date: Fri, 14 Mar 2025 20:46:12 +0900
Subject: [PATCH 2/3] =?UTF-8?q?fix:=20tab=20=ED=95=84=ED=84=B0=EB=A7=81=20?=
=?UTF-8?q?searchParams=20=EA=B8=B0=EB=B0=98=EC=9C=BC=EB=A1=9C=20=EB=B3=80?=
=?UTF-8?q?=EA=B2=BD?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/components/MainTab.tsx | 129 ++++++++++++++++++++++++++++++++++
src/components/ServiceTab.tsx | 34 ++++++---
2 files changed, 154 insertions(+), 9 deletions(-)
create mode 100644 src/components/MainTab.tsx
diff --git a/src/components/MainTab.tsx b/src/components/MainTab.tsx
new file mode 100644
index 0000000..0c1c2d7
--- /dev/null
+++ b/src/components/MainTab.tsx
@@ -0,0 +1,129 @@
+"use client";
+
+import { createContext, useContext, useEffect, useRef, useState } from "react";
+import { useSearchParams, useRouter, usePathname } from "next/navigation";
+
+type MainTabContextType = {
+ activeIndex: number;
+ setActiveIndex: (index: number) => void;
+ addTabRefs: (index: number, ref: HTMLLIElement | null) => void;
+ sliderStyle: { width: number; translateX: number };
+};
+
+const MainTabContext = createContext(null);
+
+function useTabContext() {
+ const context = useContext(MainTabContext);
+ if (!context) {
+ throw new Error("Tab compound components must be used within a Tab.Root");
+ }
+ return context;
+}
+
+// Tab의 props
+type MainTabProps = {
+ children: React.ReactNode;
+ category?: React.ReactNode; // 카태고리 버튼
+ targetIndex?: number; // 클릭 시 카테고리가 나와야 할 index
+ gap?: string; // 탭과 카테고리의 간격
+};
+// Tab 루트 컴포넌트
+export default function MainTab({ children, category, targetIndex, gap = "gap-4" }: MainTabProps) {
+ const searchParams = useSearchParams();
+ const router = useRouter();
+ const pathname = usePathname();
+
+ // 현재 활성화된 탭의 인덱스
+ const [activeIndex, setActiveIndex] = useState(0);
+ // 슬라이더의 길이 및 X축 이동거리
+ const [sliderStyle, setSliderStyle] = useState({ width: 0, translateX: 0 });
+ // 탭들의 ref
+ const tabRefs = useRef<(HTMLLIElement | null)[]>([]);
+
+ // URL에서 `type` 값을 읽어 `activeIndex` 업데이트 (뒤로 가기, 새로고침 대응)
+ useEffect(() => {
+ const currentType = searchParams.get("type") || "DALLAEMFIT";
+ const selectedIndex = SERVICE_TABS.findIndex((t) => t.type === currentType);
+
+ if (selectedIndex !== -1 && selectedIndex !== activeIndex) {
+ setActiveIndex(selectedIndex);
+ }
+ }, [searchParams]);
+
+ useEffect(() => {
+ if (!tabRefs.current.length) return; // 아직 ref 배열이 비어 있다면 패스
+ // 현재 활성화 된 Tab
+ const activeTab = tabRefs.current[activeIndex];
+ if (activeTab) {
+ const width = activeTab.offsetWidth;
+ // 활성 탭 이전 탭들의 누적 offsetWidth를 계산. gap인 12px을 더해준다.
+ const offsetLeft = tabRefs.current
+ .slice(0, activeIndex)
+ .reduce((acc, el) => acc + (el?.offsetWidth || 0) + 12, 0);
+ setSliderStyle({ width, translateX: offsetLeft });
+ }
+ }, [activeIndex]);
+
+ // context에 전달할 값들
+ const contextValue = {
+ activeIndex,
+ setActiveIndex: (index: number) => {
+ setActiveIndex(index);
+
+ // 📌 URL도 함께 업데이트 (뒤로 가기 대응)
+ const tabType = SERVICE_TABS[index].type;
+ router.push(`${pathname}?type=${tabType}`);
+ },
+ addTabRefs: (index: number, ref: HTMLLIElement | null) => {
+ tabRefs.current[index] = ref;
+ },
+ sliderStyle,
+ };
+ return (
+
+
+
+ {/* 탭 */}
+
+ {/* 슬라이더 */}
+
+
+ {targetIndex === activeIndex &&
{category}
}
+
+
+ );
+}
+
+// 탭 아이템
+type ItemProps = {
+ index: number;
+ children: React.ReactNode;
+};
+MainTab.Item = function ({ index, children }: ItemProps) {
+ const { activeIndex, setActiveIndex, addTabRefs } = useTabContext();
+
+ return (
+ setActiveIndex(index)}
+ ref={(el) => {
+ addTabRefs(index, el);
+ }}
+ // 활성화된 탭이면 text의 색을 변경
+ className={`${activeIndex === index && "text-gray-900"} mb-1 flex cursor-pointer items-center gap-1 transition-colors duration-300`}
+ >
+ {children}
+
+ );
+};
+
+// 📌 서비스 탭 리스트 (예제)
+const SERVICE_TABS = [
+ { name: "달램핏", type: "DALLAEMFIT" },
+ { name: "워케이션", type: "WORKATION" },
+];
diff --git a/src/components/ServiceTab.tsx b/src/components/ServiceTab.tsx
index 68cc6bd..e572b59 100644
--- a/src/components/ServiceTab.tsx
+++ b/src/components/ServiceTab.tsx
@@ -3,7 +3,7 @@
import { useState, useEffect, useCallback } from "react";
import { useSearchParams } from "next/navigation";
import CategoryButton from "@/components/CategoryButton";
-import Tab from "@/components/Tab";
+import MainTab from "@/components/MainTab";
import Dalaemfit from "@/images/dalaemfit.svg";
import Workation from "@/images/workation.svg";
@@ -38,10 +38,21 @@ export default function ServiceTab({ onCategoryChange, isFilteringLoading }: Ser
//searchParams 변경 감지해서 반영
useEffect(() => {
const currentType = searchParams.get("type") || "DALLAEMFIT";
+
console.log("선택된 타입: ", currentType);
- if (currentType !== selectedTab) {
- //setSelectedTab(currentType as "DALLAEMFIT" | "WORKATION");
- setSelectedTab(SERVICE_TABS.find((t) => t.type === currentType)?.name || "달램핏");
+
+ if (currentType) {
+ const tabName = SERVICE_TABS.find((t) => t.type === currentType)?.name;
+
+ console.log("찾은 탭 이름1:", tabName);
+ console.log("selectedTab은? ", selectedTab);
+ if (tabName && tabName == selectedTab) {
+ setSelectedTab(tabName);
+ console.log("찾은 탭 이름2:", tabName);
+
+ // 📌 `handleTabChange` 실행 (중복 실행 방지됨)
+ handleTabChange(tabName);
+ }
}
}, [searchParams]);
@@ -52,7 +63,12 @@ export default function ServiceTab({ onCategoryChange, isFilteringLoading }: Ser
const tabType = SERVICE_TABS.find((t) => t.name === tabName)?.type;
if (!tabType) return;
- setSelectedTab(tabName);
+ // URL의 type 값을 가져와서 selectedTab 업데이트
+ const currentType = searchParams.get("type") || tabType; // 없으면 클릭한 탭을 기본값으로
+ console.log("핸들러 실행됨11:", currentType);
+ setSelectedTab(currentType);
+
+ setSelectedTab(tabType);
onCategoryChange(tabType);
handleCategoryReset();
};
@@ -75,7 +91,7 @@ export default function ServiceTab({ onCategoryChange, isFilteringLoading }: Ser
return (
<>
- c.name)}
@@ -91,7 +107,7 @@ export default function ServiceTab({ onCategoryChange, isFilteringLoading }: Ser
targetIndex={0}
>
{SERVICE_TABS.map((tabItem, idx) => (
-
+
-
+
))}
-
+
>
);
}
From abbd658f24f4513c42212e19739fd5d1517dd074 Mon Sep 17 00:00:00 2001
From: hajiiiin <101108440+hajiiiin@users.noreply.github.com>
Date: Sat, 15 Mar 2025 00:30:37 +0900
Subject: [PATCH 3/3] =?UTF-8?q?fix:=20=ED=85=8C=EC=8A=A4=ED=8A=B8=EC=9A=A9?=
=?UTF-8?q?=20=EC=B6=9C=EB=A0=A5=20=EC=82=AD=EC=A0=9C?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/components/MainTab.tsx | 4 ++--
src/components/ServiceTab.tsx | 7 -------
2 files changed, 2 insertions(+), 9 deletions(-)
diff --git a/src/components/MainTab.tsx b/src/components/MainTab.tsx
index 0c1c2d7..90b36bd 100644
--- a/src/components/MainTab.tsx
+++ b/src/components/MainTab.tsx
@@ -70,7 +70,7 @@ export default function MainTab({ children, category, targetIndex, gap = "gap-4"
setActiveIndex: (index: number) => {
setActiveIndex(index);
- // 📌 URL도 함께 업데이트 (뒤로 가기 대응)
+ // URL도 함께 업데이트
const tabType = SERVICE_TABS[index].type;
router.push(`${pathname}?type=${tabType}`);
},
@@ -122,7 +122,7 @@ MainTab.Item = function ({ index, children }: ItemProps) {
);
};
-// 📌 서비스 탭 리스트 (예제)
+// 서비스 탭 리스트
const SERVICE_TABS = [
{ name: "달램핏", type: "DALLAEMFIT" },
{ name: "워케이션", type: "WORKATION" },
diff --git a/src/components/ServiceTab.tsx b/src/components/ServiceTab.tsx
index e572b59..ca47559 100644
--- a/src/components/ServiceTab.tsx
+++ b/src/components/ServiceTab.tsx
@@ -39,18 +39,12 @@ export default function ServiceTab({ onCategoryChange, isFilteringLoading }: Ser
useEffect(() => {
const currentType = searchParams.get("type") || "DALLAEMFIT";
- console.log("선택된 타입: ", currentType);
-
if (currentType) {
const tabName = SERVICE_TABS.find((t) => t.type === currentType)?.name;
- console.log("찾은 탭 이름1:", tabName);
- console.log("selectedTab은? ", selectedTab);
if (tabName && tabName == selectedTab) {
setSelectedTab(tabName);
- console.log("찾은 탭 이름2:", tabName);
- // 📌 `handleTabChange` 실행 (중복 실행 방지됨)
handleTabChange(tabName);
}
}
@@ -65,7 +59,6 @@ export default function ServiceTab({ onCategoryChange, isFilteringLoading }: Ser
// URL의 type 값을 가져와서 selectedTab 업데이트
const currentType = searchParams.get("type") || tabType; // 없으면 클릭한 탭을 기본값으로
- console.log("핸들러 실행됨11:", currentType);
setSelectedTab(currentType);
setSelectedTab(tabType);