fix(ui,i18n): E30 review round 1 — audit rows in the reader's language, one HU name per thing
- audit log: a job run named from its result data (nested phases too), a skipped run says so, a secret's reset reads as a reset, choices named as Configuration names them, interval / batch / counts localized; the inbox shares the same jobRunText - HU: remux is "Közvetlen közvetítés" (it shared "Közvetlen lejátszás" with direct play); backfill is behúzás, enrich is dúsítás, collection is kollekció, the allow-list is fehérlista; the last "jobnak" / "transzkódolás"; a test holds the options of one choice to distinct names - contract: every skipped / reason word a job returns is named in both languages - person page: the bio wait is timed from the open and a failing ask stops the polling; ClampedText (ui) shared with the collection page, whose short summary lost its dead More - a playlist names season 0 the Specials; the channel re-slot compares in the reader's alphabet; an invite's date is in the admin's language; the language-ordered lists are asked again once a language switch is saved (saveLanguage) - poster grids' column minimum in rem; lint refuses minmax(Npx,…)
This commit is contained in:
@@ -0,0 +1,55 @@
|
||||
import { useLayoutEffect, useRef, useState } from "react";
|
||||
import { Button } from "./Button";
|
||||
|
||||
// Static class names, so Tailwind sees them.
|
||||
const CLAMP = { 3: "line-clamp-3", 8: "line-clamp-8" } as const;
|
||||
|
||||
/** A long text (a biography, a summary) clamped to a few lines, with a More / Less toggle offered only
|
||||
* when the text is actually cut — measured, so a short text carries no dead button. Re-measured on a
|
||||
* resize: the window and the text-size slider both move where the cut falls. */
|
||||
export function ClampedText({
|
||||
text,
|
||||
lines,
|
||||
moreLabel,
|
||||
lessLabel,
|
||||
className,
|
||||
}: {
|
||||
text: string;
|
||||
lines: keyof typeof CLAMP;
|
||||
moreLabel: string;
|
||||
lessLabel: string;
|
||||
className?: string;
|
||||
}) {
|
||||
const [expanded, setExpanded] = useState(false);
|
||||
const [clipped, setClipped] = useState(false);
|
||||
const ref = useRef<HTMLParagraphElement>(null);
|
||||
useLayoutEffect(() => {
|
||||
const el = ref.current;
|
||||
if (!el || expanded) return;
|
||||
const measure = () => setClipped(el.scrollHeight > el.clientHeight + 1);
|
||||
measure();
|
||||
const ro = new ResizeObserver(measure);
|
||||
ro.observe(el);
|
||||
return () => ro.disconnect();
|
||||
}, [text, expanded]);
|
||||
return (
|
||||
<div className={className}>
|
||||
<p
|
||||
ref={ref}
|
||||
className={`measure whitespace-pre-line text-sm leading-relaxed text-muted ${expanded ? "" : CLAMP[lines]}`}
|
||||
>
|
||||
{text}
|
||||
</p>
|
||||
{(clipped || expanded) && (
|
||||
<Button
|
||||
variant="link"
|
||||
aria-expanded={expanded}
|
||||
onClick={() => setExpanded((v) => !v)}
|
||||
className="mt-1 text-xs"
|
||||
>
|
||||
{expanded ? lessLabel : moreLabel}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user