ConversationHeader.tsx 2.6 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374
  1. "use client";
  2. import { useState, useRef, useEffect } from "react";
  3. import { ChevronDown } from "lucide-react";
  4. export type ConversationFilter = "all" | "mine" | "others";
  5. interface ConversationHeaderProps {
  6. filter: ConversationFilter;
  7. onFilterChange: (filter: ConversationFilter) => void;
  8. }
  9. const FILTER_OPTIONS: { value: ConversationFilter; label: string }[] = [
  10. { value: "all", label: "全部对话" },
  11. { value: "mine", label: "我的对话" },
  12. { value: "others", label: "他人对话" },
  13. ];
  14. export function ConversationHeader({
  15. filter,
  16. onFilterChange,
  17. }: ConversationHeaderProps) {
  18. const [open, setOpen] = useState(false);
  19. const ref = useRef<HTMLDivElement>(null);
  20. const currentLabel = FILTER_OPTIONS.find((o) => o.value === filter)?.label ?? "全部对话";
  21. useEffect(() => {
  22. const handleClickOutside = (e: MouseEvent) => {
  23. if (ref.current && !ref.current.contains(e.target as Node)) setOpen(false);
  24. };
  25. if (open) document.addEventListener("mousedown", handleClickOutside);
  26. return () => document.removeEventListener("mousedown", handleClickOutside);
  27. }, [open]);
  28. return (
  29. <div className="h-14 flex items-center px-3 border-b border-border bg-background flex-shrink-0">
  30. <div className="relative" ref={ref}>
  31. <button
  32. type="button"
  33. onClick={() => setOpen((v) => !v)}
  34. className="inline-flex items-center gap-1.5 px-3 py-2 rounded-lg border border-border bg-background hover:bg-muted/50 text-sm font-medium text-foreground transition-colors min-w-0"
  35. >
  36. <span className="truncate">{currentLabel}</span>
  37. <ChevronDown
  38. className={`w-4 h-4 text-muted-foreground flex-shrink-0 transition-transform ${open ? "rotate-180" : ""}`}
  39. />
  40. </button>
  41. {open && (
  42. <div className="absolute top-full left-0 mt-1 py-1 rounded-lg border border-border bg-popover shadow-md z-50 min-w-[theme(spacing.32)]">
  43. {FILTER_OPTIONS.map((opt) => (
  44. <button
  45. key={opt.value}
  46. type="button"
  47. onClick={() => {
  48. onFilterChange(opt.value);
  49. setOpen(false);
  50. }}
  51. className={`w-full px-3 py-2 text-left text-sm transition-colors ${
  52. filter === opt.value
  53. ? "bg-primary/10 text-primary font-medium"
  54. : "text-foreground hover:bg-muted/50"
  55. }`}
  56. >
  57. {opt.label}
  58. </button>
  59. ))}
  60. </div>
  61. )}
  62. </div>
  63. </div>
  64. );
  65. }