Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions app/components/common/BottomSheet.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<div className="fixed inset-0 z-50">
<button
type="button"
aria-label="close"
className={cn("absolute inset-0 bg-[#17171833]", overlayClassName)}
onClick={onClose}
/>

<div
className={cn(
"fixed left-1/2 bottom-0 w-full max-w-[480px] -translate-x-1/2",
"rounded-t-[12px] bg-white flex flex-col animate-slide-up",
className,
)}
style={heightStyle}
>
<div className={cn("flex-1 flex flex-col", contentClassName)}>
{children}
</div>
</div>
</div>
);
}
66 changes: 35 additions & 31 deletions app/components/common/FilterButton.tsx
Original file line number Diff line number Diff line change
@@ -1,39 +1,43 @@
interface FilterButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
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 (
<button
className={`flex items-center gap-1 px-2.5 py-1 h-7 rounded-[60px] border transition-colors cursor-pointer ${isActive
? "border-core-1 bg-core-1/10 text-core-1"
: "border-[#E6E6F3] bg-white text-text-gray2"
} ${className}`}
{...props}
>
{truncateLabel(label)}
<svg
width="10"
height="6"
viewBox="0 0 10 6"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className="text-current"
>
<path
d="M1 1L5 5L9 1"
stroke="currentColor"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
</button>
);
export default function FilterButton({
label,
isActive,
className = "",
...props
}: FilterButtonProps) {
return (
<button
className={`flex items-center w-fit h-7 pl-3 pr-1.5 rounded-full border transition-colors cursor-pointer text-[14px] font-Pretendard ${
isActive
? "border-core-3 text-core-1 bg-core-2 font-medium"
: "border-core-2 text-text-gray2 bg-white text-title3"
} ${className}`}
{...props}
>
{truncateLabel(label)}
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="none"
className="w-6 h-6 text-current"
>
<path
d="M6 8L10 12L14 8"
stroke="currentColor"
strokeWidth="1.0"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
</button>
);
}
99 changes: 44 additions & 55 deletions app/routes/chat/components/ChatSortFilterSheet.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import BottomSheet from "../../../components/common/BottomSheet";
import { cn } from "../../../lib/utils";
import { SORT_LABEL, type SortOption } from "./ChatSortFilterConstant";

// 정렬, 필터 옵션 (최신순/협업 중)
Expand Down Expand Up @@ -44,64 +46,51 @@ export function SortFilterSheet<T extends string = SortOption>({
const sheetOptions = (options ?? DEFAULT_OPTIONS) as SortOptionItem<T>[];

return (
<div className="fixed inset-0 z-50">
{/* 딤(배경) */}
<button
type="button"
aria-label="close"
className="absolute inset-0 bg-[#17171833]"
onClick={onClose}
/>

{/* 시트 */}
<div
className={[
"fixed left-1/2 -translate-x-1/2 bottom-[0px] w-full max-w-[480px] h-[500px] bg-white rounded-t-[12px] pt-[20px] px-4 flex flex-col",
containerClassName ?? "",
]
.join(" ")
.trim()}
>
<div className="w-full max-w-[480px] h-[70px] fixed left-1/2 -translate-x-1/2">
<div
className={["px-4 text-medium text-text-black mb-3", titleClassName ?? ""]
.join(" ")
.trim()}
>
{title}
</div>

<div className="bg-[#F3F4F8] px-4 py-3">
<div className="flex gap-6">
{sheetOptions.map((option) => (
<SortOptionButton
key={option.value}
label={option.label}
active={value === option.value}
onClick={() => onChange(option.value)}
className={optionClassName}
/>
))}
</div>
</div>
<BottomSheet
open={open}
onClose={onClose}
height={500}
className={containerClassName}
>
<div className="px-4 pt-5">
<div
className={cn(
"text-title3 font-semibold text-text-black",
titleClassName,
)}
>
{title}
</div>
<div className="flex-1" />
</div>

<div className="flex justify-center py-6">
<button
onClick={onApply}
className={[
"px-6 w-full h-11 rounded-[12px] bg-[#6666E5] text-white text-SemiBold",
applyButtonClassName ?? "",
]
.join(" ")
.trim()}
>
{applyLabel}
</button>
<div className="mt-3 bg-[#F3F4F8] px-4 py-3">
<div className="flex gap-6">
{sheetOptions.map((option) => (
<SortOptionButton
key={option.value}
label={option.label}
active={value === option.value}
onClick={() => onChange(option.value)}
className={optionClassName}
/>
))}
</div>
</div>
</div>

<div className="flex-1" />

<div className="px-4 pb-6">
<button
onClick={onApply}
className={cn(
"w-full h-11 rounded-[12px] bg-[#6666E5] text-white text-[13px] font-semibold",
applyButtonClassName,
)}
>
{applyLabel}
</button>
</div>
</BottomSheet>
);
}

Expand All @@ -120,7 +109,7 @@ function SortOptionButton({
<button
onClick={onClick}
className={[
"text-medium",
"text-title3",
active ? "text-text-black" : "text-text-gray3",
className ?? "",
]
Expand Down
3 changes: 0 additions & 3 deletions app/routes/chat/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -87,9 +87,6 @@ export default function ChatPage() {
onChange={setPendingSort}
onClose={() => setIsSortOpen(false)}
onApply={applySort}
titleClassName="text-[14px] font-semibold"
optionClassName="text-[12px]"
applyButtonClassName="text-[13px] font-semibold"
/>
</div>
);
Expand Down
14 changes: 10 additions & 4 deletions app/routes/matching/brand/brand-content.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -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<string[]>([]);

// 검색 상태
Expand Down Expand Up @@ -114,6 +115,7 @@ export default function BrandContent() {
const handleFilterApply = (sort: string, tags: string[]) => {
setSortOption(sort);
setSelectedTags(tags);
setSortApplied(true);
};


Expand Down Expand Up @@ -172,7 +174,7 @@ export default function BrandContent() {
<div className="flex gap-2">
<FilterButton
label={sortOption}
isActive={true}
isActive={sortApplied}
onClick={() => { setFilterOpenTab("sort"); setIsFilterOpen(true); }}
/>
<FilterButton
Expand Down Expand Up @@ -202,7 +204,11 @@ export default function BrandContent() {
</div>

{/* 필터 바텀시트 */}
<FilterBottomSheet isOpen={isFilterOpen} onClose={() => setIsFilterOpen(false)}>
<BottomSheet
open={isFilterOpen}
onClose={() => setIsFilterOpen(false)}
height={500}
>
<MatchingFilter
filterType={category}
selectedSort={sortOption}
Expand All @@ -211,7 +217,7 @@ export default function BrandContent() {
onClose={() => setIsFilterOpen(false)}
initialTab={filterOpenTab}
/>
</FilterBottomSheet>
</BottomSheet>
</div>
);
}
14 changes: 10 additions & 4 deletions app/routes/matching/campaign/campaign-content.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -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<string[]>([]);

// 검색 상태
Expand Down Expand Up @@ -129,6 +130,7 @@ export default function CampaignContent() {
const handleFilterApply = (sort: string, tags: string[]) => {
setSortOption(sort);
setSelectedTags(tags);
setSortApplied(true);
};


Expand Down Expand Up @@ -180,7 +182,7 @@ export default function CampaignContent() {
<div className="flex gap-2">
<FilterButton
label={sortOption}
isActive={true}
isActive={sortApplied}
onClick={() => setIsFilterOpen(true)}
/>
<FilterButton
Expand Down Expand Up @@ -218,15 +220,19 @@ export default function CampaignContent() {
</div>

{/* 필터 바텀시트 */}
<FilterBottomSheet isOpen={isFilterOpen} onClose={() => setIsFilterOpen(false)}>
<BottomSheet
open={isFilterOpen}
onClose={() => setIsFilterOpen(false)}
height={500}
>
<MatchingFilter
filterType="CONTENT"
selectedSort={sortOption}
selectedTags={selectedTags}
onApply={handleFilterApply}
onClose={() => setIsFilterOpen(false)}
/>
</FilterBottomSheet>
</BottomSheet>
</div>
);
}
Loading
Loading