LongValueDisclosure.tsx 2.3 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152
  1. "use client";
  2. import { useMemo, useState } from "react";
  3. import { ChevronRight } from "lucide-react";
  4. import { RichText } from "@/components/ui/RichText";
  5. import { ValueView } from "@/components/ui/ValueView";
  6. interface Props {
  7. value: unknown;
  8. label?: string;
  9. threshold?: number;
  10. previewLength?: number;
  11. className?: string;
  12. }
  13. export function LongValueDisclosure({ value, label = "完整内容", threshold = 800, previewLength = 240, className = "" }: Props) {
  14. const text = useMemo(() => printable(value), [value]);
  15. const characters = useMemo(() => Array.from(text), [text]);
  16. const isLong = characters.length > threshold || (Array.isArray(value) && value.length > 5);
  17. const [open, setOpen] = useState(false);
  18. if (!isLong) return <ReadableValue value={value} />;
  19. return <details className={`comparisonLongValue ${className}`} onToggle={(event) => setOpen(event.currentTarget.open)}>
  20. <summary aria-label={`${open ? "收起" : "展开"}${label}`}>
  21. <span className="comparisonDisclosureTitle"><ChevronRight aria-hidden="true" size={15} /><strong>{label}</strong></span>
  22. <small>{characters.length.toLocaleString()} 字符</small>
  23. {!open ? <span className="comparisonDisclosurePreview">{preview(value, text, previewLength)}</span> : null}
  24. </summary>
  25. {open ? <div className="comparisonDisclosureBody"><ReadableValue value={value} /></div> : null}
  26. </details>;
  27. }
  28. export function ReadableValue({ value }: { value: unknown }) {
  29. if (typeof value === "string") return <RichText value={value || "(空)"} className="comparisonReadableText" />;
  30. if (value === null || value === undefined || value === "") return <p className="comparisonEmptyValue">(空)</p>;
  31. return <ValueView value={value} />;
  32. }
  33. function printable(value: unknown) {
  34. if (typeof value === "string") return value;
  35. if (value === undefined) return "";
  36. try { return JSON.stringify(value, null, 2) || ""; } catch { return String(value); }
  37. }
  38. function preview(value: unknown, text: string, previewLength: number) {
  39. if (Array.isArray(value) && value.length > 5) {
  40. const first = printable(value.slice(0, 3));
  41. return `${first}\n…还有 ${value.length - 3} 项`;
  42. }
  43. const characters = Array.from(text);
  44. return `${characters.slice(0, previewLength).join("")}${characters.length > previewLength ? "…" : ""}`;
  45. }