Text overflow logic (#4051)

* proper components

* k

* k

* k
This commit is contained in:
pablonyx 2025-02-24 17:05:22 -08:00 committed by GitHub
parent c9a3b45ad4
commit ffd14435a4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 101 additions and 48 deletions

View File

@ -1,4 +1,9 @@
import React, { useState, useEffect } from "react";
import React, {
useState,
useEffect,
useCallback,
useLayoutEffect,
} from "react";
import {
Popover,
PopoverContent,
@ -28,6 +33,7 @@ import { FiAlertTriangle } from "react-icons/fi";
import { Slider } from "@/components/ui/slider";
import { useUser } from "@/components/user/UserProvider";
import { TruncatedText } from "@/components/ui/truncatedText";
interface LLMPopoverProps {
llmProviders: LLMProviderDescriptor[];
@ -158,9 +164,7 @@ export default function LLMPopover({
size: 16,
className: "flex-none my-auto text-black",
})}
<span className="line-clamp-1 ">
{getDisplayNameForModel(name)}
</span>
<TruncatedText text={getDisplayNameForModel(name)} />
{(() => {
if (currentAssistant?.llm_model_version_override === name) {
return (

View File

@ -4,10 +4,7 @@ import React, {
ForwardedRef,
forwardRef,
useContext,
useState,
useCallback,
useLayoutEffect,
useRef,
} from "react";
import Link from "next/link";
import {
@ -50,9 +47,9 @@ import {
} from "@dnd-kit/sortable";
import { useSortable } from "@dnd-kit/sortable";
import { CSS } from "@dnd-kit/utilities";
import { CirclePlus, CircleX, PinIcon } from "lucide-react";
import { CircleX, PinIcon } from "lucide-react";
import { restrictToVerticalAxis } from "@dnd-kit/modifiers";
import { turborepoTraceAccess } from "next/dist/build/turborepo-access-trace";
import { TruncatedText } from "@/components/ui/truncatedText";
interface HistorySidebarProps {
liveAssistant?: Persona | null;
@ -101,24 +98,6 @@ const SortableAssistant: React.FC<SortableAssistantProps> = ({
...(isDragging ? { zIndex: 1000, position: "relative" as const } : {}),
};
const nameRef = useRef<HTMLParagraphElement>(null);
const hiddenNameRef = useRef<HTMLSpanElement>(null);
const [isNameTruncated, setIsNameTruncated] = useState(false);
useLayoutEffect(() => {
const checkTruncation = () => {
if (nameRef.current && hiddenNameRef.current) {
const visibleWidth = nameRef.current.offsetWidth;
const fullTextWidth = hiddenNameRef.current.offsetWidth;
setIsNameTruncated(fullTextWidth > visibleWidth);
}
};
checkTruncation();
window.addEventListener("resize", checkTruncation);
return () => window.removeEventListener("resize", checkTruncation);
}, [assistant.name]);
return (
<div
ref={setNodeRef}
@ -146,27 +125,11 @@ const SortableAssistant: React.FC<SortableAssistantProps> = ({
} relative flex items-center gap-x-2 py-1 px-2 rounded-md`}
>
<AssistantIcon assistant={assistant} size={16} className="flex-none" />
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<p
ref={nameRef}
className="text-base text-left w-fit line-clamp-1 text-ellipsis text-black dark:text-[#D4D4D4]"
>
{assistant.name}
</p>
</TooltipTrigger>
{isNameTruncated && (
<TooltipContent>{assistant.name}</TooltipContent>
)}
</Tooltip>
</TooltipProvider>
<span
ref={hiddenNameRef}
className="absolute left-[-9999px] whitespace-nowrap"
>
{assistant.name}
</span>
<TruncatedText
className="text-base mr-4 text-left w-fit line-clamp-1 text-ellipsis text-black dark:text-[#D4D4D4]"
text={assistant.name}
/>
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>

View File

@ -0,0 +1,86 @@
import React, {
useState,
useRef,
useLayoutEffect,
HTMLAttributes,
} from "react";
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip";
interface TruncatedTextProps extends HTMLAttributes<HTMLSpanElement> {
text: string;
tooltipClassName?: string;
tooltipSide?: "top" | "right" | "bottom" | "left";
tooltipSideOffset?: number;
}
/**
* Renders passed in text on a single line. If text is truncated,
* shows a tooltip on hover with the full text.
*/
export function TruncatedText({
text,
tooltipClassName,
tooltipSide = "right",
tooltipSideOffset = 5,
className = "",
...rest
}: TruncatedTextProps) {
const [isTruncated, setIsTruncated] = useState(false);
const visibleRef = useRef<HTMLSpanElement>(null);
const hiddenRef = useRef<HTMLSpanElement>(null);
useLayoutEffect(() => {
function checkTruncation() {
if (visibleRef.current && hiddenRef.current) {
const visibleWidth = visibleRef.current.offsetWidth;
const fullTextWidth = hiddenRef.current.offsetWidth;
setIsTruncated(fullTextWidth > visibleWidth);
}
}
checkTruncation();
window.addEventListener("resize", checkTruncation);
return () => window.removeEventListener("resize", checkTruncation);
}, [text]);
return (
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<span
ref={visibleRef}
// Ensure the text can actually truncate via line-clamp or overflow
className={`line-clamp-1 break-all flex-grow ${className}`}
{...rest}
>
{text}
</span>
</TooltipTrigger>
{/* Hide offscreen to measure full text width */}
<span
ref={hiddenRef}
className="absolute left-[-9999px] whitespace-nowrap pointer-events-none"
aria-hidden="true"
>
{text}
</span>
{isTruncated && (
<TooltipContent
side={tooltipSide}
sideOffset={tooltipSideOffset}
className={tooltipClassName}
>
<p className="text-xs max-w-[200px] whitespace-normal break-words">
{text}
</p>
</TooltipContent>
)}
</Tooltip>
</TooltipProvider>
);
}