51 lines
1.3 KiB
TypeScript
51 lines
1.3 KiB
TypeScript
import * as React from "react";
|
|
import { cn } from "@/lib/utils";
|
|
|
|
const LINES = {
|
|
1: "line-clamp-1",
|
|
2: "line-clamp-2",
|
|
3: "line-clamp-3",
|
|
} as const;
|
|
|
|
/**
|
|
* Clamp — a dynamic label (a quiz title, a name, an email, a link) cut off
|
|
* with "…" after a set number of lines, with the full text on hover.
|
|
*
|
|
* Use this rather than Tailwind's `truncate` for anything user- or content-
|
|
* supplied. `truncate` forbids wrapping, and inside a table cell that makes
|
|
* the column grow to the text's full width — the table then overflows its
|
|
* card instead of truncating. A line clamp wraps first and cuts after N
|
|
* lines, and `overflow-wrap: anywhere` lets even an unbroken string (a URL,
|
|
* a handle) break, so it can never widen whatever it sits in.
|
|
*/
|
|
export function Clamp({
|
|
children,
|
|
lines = 1,
|
|
title,
|
|
className,
|
|
}: {
|
|
children: React.ReactNode;
|
|
lines?: keyof typeof LINES;
|
|
/** Hover text; defaults to the label itself when it's plain text. */
|
|
title?: string;
|
|
className?: string;
|
|
}) {
|
|
const hover =
|
|
title ??
|
|
(typeof children === "string" || typeof children === "number"
|
|
? String(children)
|
|
: undefined);
|
|
return (
|
|
<span
|
|
title={hover}
|
|
className={cn(
|
|
"block min-w-0 [overflow-wrap:anywhere]",
|
|
LINES[lines],
|
|
className,
|
|
)}
|
|
>
|
|
{children}
|
|
</span>
|
|
);
|
|
}
|