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
2 changes: 1 addition & 1 deletion src/app/(pages)/albaList/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ export default function AlbaList() {
<div className="w-full border-b border-line-100">
<div className="mx-auto flex max-w-screen-xl flex-col gap-4 px-4 py-4 md:px-6 lg:px-8">
<div className="flex items-center justify-between">
<SearchSection />
<SearchSection pathname={pathname} />
</div>
</div>
</div>
Expand Down
2 changes: 1 addition & 1 deletion src/app/(pages)/albaTalk/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ export default function AlbaTalk() {
<div className="w-full border-b border-line-100">
<div className="mx-auto flex max-w-screen-xl flex-col gap-4 px-4 py-4 md:px-6 lg:px-8">
<div className="flex items-center justify-between">
<SearchSection />
<SearchSection pathname={pathname} />
</div>
</div>
</div>
Expand Down
2 changes: 1 addition & 1 deletion src/app/(pages)/myAlbaform/(role)/applicant/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ export default function ApplicantPage() {
{/* 검색 섹션 */}
<div className="w-full border-b border-line-100">
<div className="mx-auto flex max-w-screen-xl flex-col gap-4 px-4 py-4 md:px-6 lg:px-8">
<SearchSection />
<SearchSection pathname={pathname} />
</div>
</div>

Expand Down
2 changes: 1 addition & 1 deletion src/app/(pages)/myAlbaform/(role)/owner/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ export default function AlbaList() {
<div className="w-full border-b border-line-100">
<div className="mx-auto flex max-w-screen-xl flex-col gap-4 px-4 py-4 md:px-6 lg:px-8">
<div className="flex items-center justify-between">
<SearchSection />
<SearchSection pathname={pathname} />
</div>
</div>
</div>
Expand Down
90 changes: 90 additions & 0 deletions src/app/components/button/dropdown/DropdownList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
"use client";
import { cn } from "@/lib/tailwindUtil";
import { useEffect, useRef } from "react";

// 드롭다운 메뉴의 각 항목 컴포넌트
const DropdownItem = ({
item,
onSelect,
itemStyle,
}: {
item: string;
onSelect: (item: string | null) => void;
itemStyle?: string;
}) => {
// 항목 클릭 핸들러
const handleClick = (e: React.MouseEvent) => {
e.preventDefault();
e.stopPropagation(); // 이벤트 버블링 방지
onSelect(item);
};

return (
<li
onClick={handleClick}
className={cn(
"flex w-full cursor-pointer bg-grayscale-50 px-[10px] py-2 text-sm font-normal leading-[18px] text-black-100 hover:bg-primary-orange-50 lg:text-lg lg:leading-[26px]",
itemStyle
)}
>
{item}
</li>
);
};

// 드롭다운 메뉴 리스트 컴포넌트
const DropdownList = ({
list,
onSelect,
wrapperStyle,
itemStyle,
}: {
list: string[];
onSelect: (item: string | null) => void;
wrapperStyle?: string;
itemStyle?: string;
}) => {
const dropdownRef = useRef<HTMLDivElement>(null);

// 외부 클릭 감지 및 드롭다운 닫기
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) {
event.preventDefault();
onSelect(null);
}
};

document.addEventListener("mousedown", handleClickOutside, { capture: true });
return () => {
document.removeEventListener("mousedown", handleClickOutside, { capture: true });
};
}, [onSelect]);

// 컨테이너 클릭 이벤트 처리
const handleContainerClick = (e: React.MouseEvent) => {
e.stopPropagation();
};

return (
<div
role="menu"
aria-orientation="vertical"
aria-labelledby="options-menu"
ref={dropdownRef}
onClick={handleContainerClick}
className={cn(
"absolute left-0 right-0 z-10 mt-[6px] rounded border border-grayscale-100 bg-grayscale-50 pr-[2px] pt-1",
wrapperStyle
)}
>
<ul className="scrollbar-custom flex max-h-[150px] flex-col">
{list.map((item) => (
<DropdownItem key={item} item={item} onSelect={onSelect} itemStyle={itemStyle} />
))}
</ul>
</div>
);
};

export default DropdownList;
23 changes: 18 additions & 5 deletions src/app/components/button/dropdown/FilterDropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,14 @@ interface FilterDropdownProps {
readOnly?: boolean;
}

// 필터링 옵션을 선택할 수 있는 드롭다운 컴포넌트
const FilterDropdown = ({ options, className = "", onChange, initialValue, readOnly = false }: FilterDropdownProps) => {
// 드롭다운 상태 관리
const [isOpen, setIsOpen] = useState(false);
const [selectedLabel, setSelectedLabel] = useState(initialValue || options[0]);
const dropdownRef = useRef<HTMLDivElement>(null);

// 외부 클릭 감지하여 드롭다운 닫기
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) {
Expand All @@ -30,21 +33,28 @@ const FilterDropdown = ({ options, className = "", onChange, initialValue, readO
};
}, []);

// initialValue가 변경되면 선택된 라벨 업데이트
useEffect(() => {
if (initialValue) {
setSelectedLabel(initialValue);
}
}, [initialValue]);

// 드롭다운 토글 핸들러
const toggleDropdown = () => {
if (readOnly) return;
setIsOpen((prev) => !prev);
};

const handleSelect = (option: string) => {
setSelectedLabel(option);
setIsOpen(false);
onChange?.(option);
// 옵션 선택 핸들러
const handleSelect = (option: string | null) => {
if (option !== null) {
setSelectedLabel(option);
setIsOpen(false);
onChange?.(option);
} else {
setIsOpen(false);
}
};

return (
Expand All @@ -66,7 +76,10 @@ const FilterDropdown = ({ options, className = "", onChange, initialValue, readO
? "border border-grayscale-100 bg-white"
: "border-primary-orange-300 bg-primary-orange-50"
)}
onClick={toggleDropdown}
onClick={(e) => {
e.preventDefault();
toggleDropdown();
}}
disabled={readOnly}
>
<span className={selectedLabel === options[0] ? "text-black-100" : "text-primary-orange-300"}>
Expand Down
122 changes: 95 additions & 27 deletions src/app/components/button/dropdown/InputDropdown.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
"use client";
import React, { forwardRef, useEffect, useState } from "react";
import React, { forwardRef, useEffect, useState, useRef, useCallback } from "react";
import { IoMdArrowDropdown } from "react-icons/io";
import { cn } from "@/lib/tailwindUtil";
import DropdownList from "./dropdownComponent/DropdownList";
Expand All @@ -13,32 +13,88 @@ interface InputDropdownProps {
value?: string;
}

// 직접 입력이 가능한 드롭다운 컴포넌트
const InputDropdown = forwardRef<HTMLInputElement, InputDropdownProps>(
({ options, className = "", errormessage, name }, ref) => {
// 드롭다운 상태 관리
const [isOpen, setIsOpen] = useState<boolean>(false);
const [selectedValue, setSelectedValue] = useState<string>("");
const [isCustomInput, setIsCustomInput] = useState<boolean>(false);
const { setValue, watch } = useFormContext();
const dropdownRef = useRef<HTMLDivElement>(null);

// 외부 클릭 감지하여 드롭다운 닫기
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) {
setIsOpen(false);
}
};

document.addEventListener("mousedown", handleClickOutside);
return () => {
document.removeEventListener("mousedown", handleClickOutside);
};
}, []);

// 드롭다운 토글 핸들러
const toggleDropdown = (e: React.MouseEvent) => {
e.preventDefault();
setIsOpen((prev) => !prev);
};

// 옵션 선택 핸들러
const handleSelect = useCallback(
(option: string | null) => {
if (!option) {
setIsOpen(false);
return;
}

if (option === "직접 입력") {
setIsCustomInput(true);
setSelectedValue("");
setValue(name, "", { shouldDirty: true });
setIsOpen(false);
return;
}

const handleOptionClick = (option: string) => {
if (option === "직접 입력") {
setIsCustomInput(true);
setSelectedValue("");
// 동적으로 받아온 name에 값 할당 -> 훅폼에 저장
setValue(name, selectedValue, { shouldDirty: true });
} else {
setSelectedValue(option);
setIsCustomInput(false);
setValue(name, option, { shouldDirty: true });
setIsOpen(false);
}
};
},
[name, setValue]
);

// 작성중인 탭으로 다시 이동했을때 이전에 저장된 훅폼 데이터 연동
// 입력값 변경 핸들러
const handleInputChange = useCallback(
(e: React.ChangeEvent<HTMLInputElement>) => {
if (isCustomInput) {
const value = e.target.value;
setSelectedValue(value);
setValue(name, value, { shouldDirty: true });
}
},
[isCustomInput, name, setValue]
);

// 입력 필드 클릭 핸들러
const handleInputClick = useCallback(
(e: React.MouseEvent) => {
e.stopPropagation();
if (!isCustomInput) {
setIsOpen(true);
}
},
[isCustomInput]
);

// 폼 값 변경 감지하여 입력값 동기화
useEffect(() => {
const value = watch(name); // 동적으로 필드 값 가져오기
const value = watch(name);
if (value !== undefined) {
setSelectedValue(value); // 초기값 동기화
setSelectedValue(value);
}
}, [name, watch]);

Expand All @@ -49,36 +105,48 @@ const InputDropdown = forwardRef<HTMLInputElement, InputDropdownProps>(
"absolute -bottom-[26px] text-[13px] text-sm font-medium leading-[22px] text-state-error lg:text-base lg:leading-[26px]";

return (
<div className={cn("relative inline-block text-left", "w-80 lg:w-[640px]", textStyle, className, errorStyle)}>
<div
ref={dropdownRef}
className={cn("relative inline-block text-left", "w-80 lg:w-[640px]", textStyle, className, errorStyle)}
>
<div
onMouseDown={() => setIsOpen(!isOpen)}
onClick={isCustomInput ? undefined : toggleDropdown}
className={cn(
"cursor-pointer rounded-md border border-transparent bg-background-200 p-2",
"hover:border-grayscale-200 hover:bg-background-300",
"rounded-md border border-transparent bg-background-200 p-2",
!isCustomInput && "cursor-pointer hover:border-grayscale-200 hover:bg-background-300",
isOpen && "ring-1 ring-grayscale-300"
)}
>
<input
type="text"
ref={ref}
value={selectedValue}
onChange={(e) => {
if (isCustomInput) {
setSelectedValue(e.target.value);
setValue(name, e.target.value);
}
}}
onChange={handleInputChange}
onClick={handleInputClick}
readOnly={!isCustomInput}
className={cn(
"text-grayscale-700 flex w-full items-center justify-between px-4 py-2 font-medium focus:outline-none",
"cursor-pointer bg-transparent"
"flex w-full items-center justify-between px-4 py-2 font-medium focus:outline-none",
isCustomInput ? "cursor-text bg-transparent" : "cursor-pointer bg-transparent",
"text-grayscale-700"
)}
placeholder={isCustomInput ? "직접 입력하세요" : "선택"}
/>
<button type="button" className="absolute right-3 top-3.5 text-3xl">
<button
type="button"
className="absolute right-3 top-3.5 text-3xl"
onClick={(e) => {
e.stopPropagation();
if (isCustomInput) {
setIsCustomInput(false);
setSelectedValue("");
setValue(name, "", { shouldDirty: true });
}
}}
>
<IoMdArrowDropdown className={cn("transition-transform duration-200", isOpen && "rotate-180")} />
</button>
</div>
{isOpen && <DropdownList list={options} onSelect={handleOptionClick} itemStyle={textStyle} />}
{isOpen && <DropdownList list={options} onSelect={handleSelect} itemStyle={textStyle} />}
{errormessage && <p className={cn(errorTextStyle, "right-0 pr-2")}>{errormessage}</p>}
</div>
);
Expand Down
Loading
Loading