diff --git a/app/components/common/BottomSheet.tsx b/app/components/common/BottomSheet.tsx new file mode 100644 index 0000000..76b00a8 --- /dev/null +++ b/app/components/common/BottomSheet.tsx @@ -0,0 +1,55 @@ +import type { ReactNode } from "react"; +import { cn } from "../../lib/utils"; + +type BottomSheetProps = { + open: boolean; + onClose: () => void; + children: ReactNode; + height?: number | string; + className?: string; + contentClassName?: string; + overlayClassName?: string; +}; + +export default function BottomSheet({ + open, + onClose, + children, + height, + className, + contentClassName, + overlayClassName, +}: BottomSheetProps) { + if (!open) return null; + + const heightStyle = + height === undefined + ? undefined + : typeof height === "number" + ? { height: `${height}px` } + : { height }; + + return ( +
+
+ ); +} diff --git a/app/components/common/FilterButton.tsx b/app/components/common/FilterButton.tsx index 3bb5f34..8e8911e 100644 --- a/app/components/common/FilterButton.tsx +++ b/app/components/common/FilterButton.tsx @@ -1,39 +1,43 @@ interface FilterButtonProps extends React.ButtonHTMLAttributes { - label: string; - isActive?: boolean; + label: string; + isActive?: boolean; } // 글자수 초과시 ""..." 처리 const truncateLabel = (text: string, maxLength = 10) => { - return text.length > maxLength ? text.slice(0, maxLength) + "..." : text; + return text.length > maxLength ? text.slice(0, maxLength) + "..." : text; }; -export default function FilterButton({ label, isActive, className = "", ...props }: FilterButtonProps) { - return ( - - ); +export default function FilterButton({ + label, + isActive, + className = "", + ...props +}: FilterButtonProps) { + return ( + + ); } diff --git a/app/routes/chat/components/ChatSortFilterSheet.tsx b/app/routes/chat/components/ChatSortFilterSheet.tsx index 8a8daee..327a80b 100644 --- a/app/routes/chat/components/ChatSortFilterSheet.tsx +++ b/app/routes/chat/components/ChatSortFilterSheet.tsx @@ -1,3 +1,5 @@ +import BottomSheet from "../../../components/common/BottomSheet"; +import { cn } from "../../../lib/utils"; import { SORT_LABEL, type SortOption } from "./ChatSortFilterConstant"; // 정렬, 필터 옵션 (최신순/협업 중) @@ -44,64 +46,51 @@ export function SortFilterSheet({ const sheetOptions = (options ?? DEFAULT_OPTIONS) as SortOptionItem[]; return ( -
- {/* 딤(배경) */} - +
+
+ {sheetOptions.map((option) => ( + onChange(option.value)} + className={optionClassName} + /> + ))}
-
+ +
+ +
+ +
+ ); } @@ -120,7 +109,7 @@ function SortOptionButton({
); diff --git a/app/routes/matching/brand/brand-content.tsx b/app/routes/matching/brand/brand-content.tsx index 569b7da..2753a44 100644 --- a/app/routes/matching/brand/brand-content.tsx +++ b/app/routes/matching/brand/brand-content.tsx @@ -5,7 +5,7 @@ import FilterButton from "../../../components/common/FilterButton"; import BrandCard from "./components/BrandCard"; import { type BrandCategory } from "../../../data/brand"; import BrandFilterBar from "./components/BrandFilterBar"; -import FilterBottomSheet from "../../../components/common/FilterBottomSheet"; +import BottomSheet from "../../../components/common/BottomSheet"; import MatchingFilter from "../components/MatchingFilter"; import { useHideBottomTab } from "../../../hooks/useHideBottomTab"; import EmptyMatchState from "../../../components/common/EmptyMatchState"; @@ -22,6 +22,7 @@ export default function BrandContent() { const [isFilterOpen, setIsFilterOpen] = useState(false); const [filterOpenTab, setFilterOpenTab] = useState<"sort" | "filter">("sort"); const [sortOption, setSortOption] = useState("매칭률 순"); + const [sortApplied, setSortApplied] = useState(false); const [selectedTags, setSelectedTags] = useState([]); // 검색 상태 @@ -114,6 +115,7 @@ export default function BrandContent() { const handleFilterApply = (sort: string, tags: string[]) => { setSortOption(sort); setSelectedTags(tags); + setSortApplied(true); }; @@ -172,7 +174,7 @@ export default function BrandContent() {
{ setFilterOpenTab("sort"); setIsFilterOpen(true); }} /> {/* 필터 바텀시트 */} - setIsFilterOpen(false)}> + setIsFilterOpen(false)} + height={500} + > setIsFilterOpen(false)} initialTab={filterOpenTab} /> - +
); } diff --git a/app/routes/matching/campaign/campaign-content.tsx b/app/routes/matching/campaign/campaign-content.tsx index 83c4d38..7542c28 100644 --- a/app/routes/matching/campaign/campaign-content.tsx +++ b/app/routes/matching/campaign/campaign-content.tsx @@ -6,7 +6,7 @@ import FilterButton from "../../../components/common/FilterButton"; import CampaignCard from "./components/CampaignCard"; import { type CampaignCategory } from "../../../data/campaign"; import CampaignFilterBar from "./components/CampaignFilterBar"; -import FilterBottomSheet from "../../../components/common/FilterBottomSheet"; +import BottomSheet from "../../../components/common/BottomSheet"; import MatchingFilter from "../components/MatchingFilter"; import { useHideBottomTab } from "../../../hooks/useHideBottomTab"; import EmptyMatchState from "../../../components/common/EmptyMatchState"; @@ -23,6 +23,7 @@ export default function CampaignContent() { // 필터 상태 const [isFilterOpen, setIsFilterOpen] = useState(false); const [sortOption, setSortOption] = useState("매칭률 순"); + const [sortApplied, setSortApplied] = useState(false); const [selectedTags, setSelectedTags] = useState([]); // 검색 상태 @@ -129,6 +130,7 @@ export default function CampaignContent() { const handleFilterApply = (sort: string, tags: string[]) => { setSortOption(sort); setSelectedTags(tags); + setSortApplied(true); }; @@ -180,7 +182,7 @@ export default function CampaignContent() {
setIsFilterOpen(true)} /> {/* 필터 바텀시트 */} - setIsFilterOpen(false)}> + setIsFilterOpen(false)} + height={500} + > setIsFilterOpen(false)} /> - +
); } diff --git a/app/routes/matching/components/MatchingFilter.tsx b/app/routes/matching/components/MatchingFilter.tsx index 4cbf91b..4777116 100644 --- a/app/routes/matching/components/MatchingFilter.tsx +++ b/app/routes/matching/components/MatchingFilter.tsx @@ -1,6 +1,7 @@ import { useState } from "react"; import { cn } from "../../../lib/utils"; import FilterChip from "../../../components/common/FilterChip"; +import SelectChip from "../test/components/SelectChip"; import { SORT_OPTIONS, CAMPAIGN_SORT_OPTIONS, @@ -74,34 +75,42 @@ export default function MatchingFilter({ }; return ( -
+
{/* 메인 탭 */} -
- - +
+
+ + +
{/* 컨텐츠 영역 */}
{mainTab === "정렬 필터" ? ( /* 정렬 필터 */ -
+
{sortOptions.map((option) => ( - {(filterData[subTab as keyof typeof filterData] as readonly string[]).map((tag) => ( - toggleTag(tag)} - /> - ))} + {(filterData[subTab as keyof typeof filterData] as readonly string[]).map((tag) => { + const selected = currentTags.includes(tag); + if (filterType === "BEAUTY") { + return ( + toggleTag(tag)} + /> + ); + } + return ( + toggleTag(tag)} + /> + ); + })}
)}
{/* 적용하기 버튼 */} -
+
+ />
{loading ? ( @@ -530,9 +511,6 @@ export default function MyPageLikes() { } title="정렬 필터" applyLabel="적용하기" - titleClassName="text-[14px] font-semibold" - optionClassName="text-[12px]" - applyButtonClassName="text-[13px] font-semibold" />