ConversationHeader.tsx 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121
  1. "use client";
  2. import { useState, useRef, useEffect } from "react";
  3. import { ChevronDown } from "lucide-react";
  4. import { useI18n } from "@/lib/i18n/provider";
  5. export type ConversationFilter = "all" | "mine" | "others";
  6. export type ConversationListStatus = "open" | "closed";
  7. interface ConversationHeaderProps {
  8. filter: ConversationFilter;
  9. onFilterChange: (filter: ConversationFilter) => void;
  10. /** 与「全部对话」同一行右侧:进行中 / 历史 */
  11. listStatus?: ConversationListStatus;
  12. onListStatusChange?: (status: ConversationListStatus) => void;
  13. }
  14. const FILTER_OPTIONS: { value: ConversationFilter; label: string }[] = [
  15. { value: "all", label: "全部对话" },
  16. { value: "mine", label: "我的对话" },
  17. { value: "others", label: "他人对话" },
  18. ];
  19. export function ConversationHeader({
  20. filter,
  21. onFilterChange,
  22. listStatus,
  23. onListStatusChange,
  24. }: ConversationHeaderProps) {
  25. const { t } = useI18n();
  26. const [open, setOpen] = useState(false);
  27. const ref = useRef<HTMLDivElement>(null);
  28. const options = [
  29. { value: "all" as const, label: t("agent.conversations.filter.all") },
  30. { value: "mine" as const, label: t("agent.conversations.filter.mine") },
  31. { value: "others" as const, label: t("agent.conversations.filter.others") },
  32. ];
  33. const currentLabel =
  34. options.find((o) => o.value === filter)?.label ??
  35. t("agent.conversations.filter.all");
  36. useEffect(() => {
  37. const handleClickOutside = (e: MouseEvent) => {
  38. if (ref.current && !ref.current.contains(e.target as Node)) setOpen(false);
  39. };
  40. if (open) document.addEventListener("mousedown", handleClickOutside);
  41. return () => document.removeEventListener("mousedown", handleClickOutside);
  42. }, [open]);
  43. const showListStatus =
  44. listStatus !== undefined && typeof onListStatusChange === "function";
  45. return (
  46. <div className="h-14 flex items-center gap-2 px-3 border-b border-border bg-background flex-shrink-0 min-w-0">
  47. <div className="relative min-w-0 shrink" ref={ref}>
  48. <button
  49. type="button"
  50. onClick={() => setOpen((v) => !v)}
  51. 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 max-w-[9.5rem] min-w-0"
  52. >
  53. <span className="truncate">{currentLabel}</span>
  54. <ChevronDown
  55. className={`w-4 h-4 text-muted-foreground flex-shrink-0 transition-transform ${open ? "rotate-180" : ""}`}
  56. />
  57. </button>
  58. {open && (
  59. <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)]">
  60. {options.map((opt) => (
  61. <button
  62. key={opt.value}
  63. type="button"
  64. onClick={() => {
  65. onFilterChange(opt.value);
  66. setOpen(false);
  67. }}
  68. className={`w-full px-3 py-2 text-left text-sm transition-colors ${
  69. filter === opt.value
  70. ? "bg-primary/10 text-primary font-medium"
  71. : "text-foreground hover:bg-muted/50"
  72. }`}
  73. >
  74. {opt.label}
  75. </button>
  76. ))}
  77. </div>
  78. )}
  79. </div>
  80. {showListStatus && (
  81. <div className="ml-auto flex-shrink-0">
  82. <div className="inline-flex rounded-md border border-border/70 bg-muted/30 p-0.5">
  83. <button
  84. type="button"
  85. className={`px-2.5 py-1 rounded-[0.25rem] text-[11px] font-medium transition leading-none ${
  86. listStatus === "open"
  87. ? "bg-green-600 text-white shadow-sm"
  88. : "text-muted-foreground hover:text-foreground"
  89. }`}
  90. onClick={() => onListStatusChange!("open")}
  91. >
  92. {t("agent.conversations.status.open")}
  93. </button>
  94. <button
  95. type="button"
  96. className={`px-2.5 py-1 rounded-[0.25rem] text-[11px] font-medium transition leading-none ${
  97. listStatus === "closed"
  98. ? "bg-green-600 text-white shadow-sm"
  99. : "text-muted-foreground hover:text-foreground"
  100. }`}
  101. onClick={() => onListStatusChange!("closed")}
  102. >
  103. {t("agent.conversations.status.closed")}
  104. </button>
  105. </div>
  106. </div>
  107. )}
  108. </div>
  109. );
  110. }