|
|
@@ -0,0 +1,112 @@
|
|
|
+"use client";
|
|
|
+import { type FC, useRef, useEffect, useCallback } from "react";
|
|
|
+import { useDisclosure } from "@heroui/use-disclosure";
|
|
|
+import { SORT, type SortOrderTypes } from "@/utils/constants";
|
|
|
+import { createUrl } from "@/utils/helper";
|
|
|
+import { usePathname, useRouter, useSearchParams } from "next/navigation";
|
|
|
+
|
|
|
+const ChevronDownIcon = ({ className = "" }: { className?: string }) => (
|
|
|
+ <svg
|
|
|
+ xmlns="http://www.w3.org/2000/svg"
|
|
|
+ width="20"
|
|
|
+ height="20"
|
|
|
+ viewBox="0 0 24 24"
|
|
|
+ fill="none"
|
|
|
+ stroke="currentColor"
|
|
|
+ strokeWidth="2"
|
|
|
+ strokeLinecap="round"
|
|
|
+ strokeLinejoin="round"
|
|
|
+ className={className}
|
|
|
+ >
|
|
|
+ <path d="m6 9 6 6 6-6" />
|
|
|
+ </svg>
|
|
|
+);
|
|
|
+
|
|
|
+interface SearchSortOrderProps {
|
|
|
+ sortOrders: SortOrderTypes[];
|
|
|
+ title: string;
|
|
|
+}
|
|
|
+const SearchSortOrder: FC<SearchSortOrderProps> = ({ sortOrders, title }) => {
|
|
|
+ const searchParams = useSearchParams();
|
|
|
+ const pathname = usePathname();
|
|
|
+ const router = useRouter();
|
|
|
+
|
|
|
+ // 从url拿sort参数
|
|
|
+ const sortValue = searchParams.get(SORT) || sortOrders[0]?.value;
|
|
|
+
|
|
|
+ const { isOpen, onOpen, onClose } = useDisclosure();
|
|
|
+ const toggleDropdown = useCallback(() => {
|
|
|
+ if (isOpen) {
|
|
|
+ onClose();
|
|
|
+ } else {
|
|
|
+ onOpen();
|
|
|
+ }
|
|
|
+ }, [isOpen, onOpen, onClose]);
|
|
|
+
|
|
|
+ const containerRef = useRef<HTMLDivElement>(null);
|
|
|
+ // 找到当前激活项
|
|
|
+ const activeOrder = sortOrders.find((item) => item.value === sortValue);
|
|
|
+ const displayLabel = activeOrder?.title || sortOrders[0]?.title;
|
|
|
+
|
|
|
+ // ✅ 新逻辑:修改URL query,替换原来onChange回调
|
|
|
+ const handleSortChange = (value: string) => {
|
|
|
+ const newParams = new URLSearchParams(searchParams.toString());
|
|
|
+ if (value) newParams.set(SORT, value);
|
|
|
+ const newUrl = createUrl(pathname, newParams);
|
|
|
+ router.replace(newUrl);
|
|
|
+ onClose(); // 选中关闭下拉
|
|
|
+ };
|
|
|
+
|
|
|
+ // 点击空白关闭
|
|
|
+ useEffect(() => {
|
|
|
+ const handleClickOutside = (event: MouseEvent) => {
|
|
|
+ if (containerRef.current && !containerRef.current.contains(event.target as Node)) {
|
|
|
+ if (isOpen) onClose();
|
|
|
+ }
|
|
|
+ };
|
|
|
+ document.addEventListener("mousedown", handleClickOutside);
|
|
|
+ return () => document.removeEventListener("mousedown", handleClickOutside);
|
|
|
+ }, [isOpen, onClose]);
|
|
|
+
|
|
|
+ return (
|
|
|
+ <div className="flex flex-wrap mb-6 justify-end">
|
|
|
+ <div ref={containerRef} className="relative inline-block">
|
|
|
+ <button
|
|
|
+ type="button"
|
|
|
+ onClick={toggleDropdown}
|
|
|
+ className="inline-flex items-center justify-between w-full gap-1.5 text-base font-outfit tracking-wide bg-neutral-100 dark:bg-neutral-800 px-4 py-2 rounded-lg"
|
|
|
+ >
|
|
|
+ <span>{displayLabel}</span>
|
|
|
+ <ChevronDownIcon
|
|
|
+ className={`transition-transform duration-300 ease ${
|
|
|
+ isOpen ? "rotate-180" : "rotate-0"
|
|
|
+ }`}
|
|
|
+ />
|
|
|
+ </button>
|
|
|
+ {isOpen && (
|
|
|
+ <div className="absolute top-full left-0 mt-1 w-full bg-white dark:bg-neutral-800 rounded-lg shadow-lg z-30 ">
|
|
|
+ <div className="flex flex-col">
|
|
|
+ {sortOrders.map((order) => {
|
|
|
+ const isActive = sortValue === order.value;
|
|
|
+ return (
|
|
|
+ <button
|
|
|
+ key={order.value}
|
|
|
+ onClick={() => handleSortChange(order.value)}
|
|
|
+ className={`w-full px-4 py-3 text-base transition-colors text-center ${
|
|
|
+ isActive
|
|
|
+ ? "bg-neutral-100 dark:bg-neutral-700 font-semibold"
|
|
|
+ : "hover:bg-neutral-50 dark:hover:bg-neutral-700/50"
|
|
|
+ }`}
|
|
|
+ >
|
|
|
+ {order.title}
|
|
|
+ </button>
|
|
|
+ );
|
|
|
+ })}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ )}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ );
|
|
|
+};
|
|
|
+export default SearchSortOrder;
|