mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-03 04:38:33 +00:00
feat(studio): show the full name on hover for truncated rack labels
The rack's label column is a fixed 86px, so "Gap between repeats" reads as "Gap between re…" and "How many repeats" as "How many repe…" — the part that tells two knobs apart is the part that gets cut. The row already carried a `title`, but that is the param's HINT: what the knob does. Useful, and no substitute for the name. So the name is titled on the label itself and the hint stays on the row — two different questions, neither standing in for the other. Same fix for the three other truncating labels in the rack, since a truncated effect name is the same defect one line up: the node name, its one-line summary, and the carve module's own name and "Listen to" row. Titled unconditionally rather than only when the text overflows: whether it does depends on the rendered font and the panel's width, and a title that matches the visible text costs nothing. Two tests, mutation-checked — the label carries the full name, the row still carries the hint, and the label is titled even when there is no hint. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
c5c6c6313e
commit
b6bac054b2
@@ -230,6 +230,8 @@ export function FxCarveModule({
|
|||||||
// sets `tracking-normal`, and two Tailwind tracking utilities on one
|
// sets `tracking-normal`, and two Tailwind tracking utilities on one
|
||||||
// element resolve by stylesheet order, not by the order written.
|
// element resolve by stylesheet order, not by the order written.
|
||||||
style={{ color: tint, letterSpacing: "0.16em" }}
|
style={{ color: tint, letterSpacing: "0.16em" }}
|
||||||
|
// Truncates in a narrow panel like every other name in the rack.
|
||||||
|
title="Voiceover carve"
|
||||||
aria-expanded={open}
|
aria-expanded={open}
|
||||||
onClick={onToggleOpen}
|
onClick={onToggleOpen}
|
||||||
>
|
>
|
||||||
@@ -256,7 +258,10 @@ export function FxCarveModule({
|
|||||||
<div className="hf-fx-carve-body border-t border-panel-border-input">
|
<div className="hf-fx-carve-body border-t border-panel-border-input">
|
||||||
<div className="hf-fx-carve-controls space-y-0.5 px-1.5 py-1.5">
|
<div className="hf-fx-carve-controls space-y-0.5 px-1.5 py-1.5">
|
||||||
<div className="hf-fx-row flex min-h-6 items-center gap-2">
|
<div className="hf-fx-row flex min-h-6 items-center gap-2">
|
||||||
<span className="hf-fx-label w-[86px] flex-shrink-0 truncate text-[10px] text-panel-text-2">
|
<span
|
||||||
|
className="hf-fx-label w-[86px] flex-shrink-0 truncate text-[10px] text-panel-text-2"
|
||||||
|
title="Listen to"
|
||||||
|
>
|
||||||
Listen to
|
Listen to
|
||||||
</span>
|
</span>
|
||||||
{soleVoice ? (
|
{soleVoice ? (
|
||||||
|
|||||||
@@ -113,3 +113,48 @@ describe("FxParamRow commit", () => {
|
|||||||
expect(onCommit).not.toHaveBeenCalled();
|
expect(onCommit).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("FxParamRow label tooltip", () => {
|
||||||
|
// The label column is a fixed 86px and truncates: "Gap between repeats" reads
|
||||||
|
// as "Gap between re…", "How many repeats" as "How many repe…". The row
|
||||||
|
// already carried a `title`, but that is the HINT — what the knob does — so a
|
||||||
|
// truncated name had no way to be read in full.
|
||||||
|
const LONG: HfAudioFxNumberParam = {
|
||||||
|
kind: "number",
|
||||||
|
key: "time",
|
||||||
|
label: "Gap between repeats",
|
||||||
|
min: 0,
|
||||||
|
max: 1000,
|
||||||
|
step: 1,
|
||||||
|
default: 250,
|
||||||
|
unit: "ms",
|
||||||
|
hint: "How long before the echo comes back.",
|
||||||
|
};
|
||||||
|
|
||||||
|
it("titles the label with the full name, and leaves the hint on the row", () => {
|
||||||
|
const host = document.createElement("div");
|
||||||
|
document.body.append(host);
|
||||||
|
act(() => {
|
||||||
|
createRoot(host).render(<FxParamRow param={LONG} value={250} onChange={vi.fn()} />);
|
||||||
|
});
|
||||||
|
const label = host.querySelector<HTMLElement>(".hf-fx-label");
|
||||||
|
expect(label?.getAttribute("title")).toBe("Gap between repeats");
|
||||||
|
// The hint stays where it was: the two are different questions, and the
|
||||||
|
// name is not a substitute for the explanation either.
|
||||||
|
expect(host.querySelector<HTMLElement>(".hf-fx-row")?.getAttribute("title")).toBe(
|
||||||
|
"How long before the echo comes back.",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("titles it even with no hint to fall back on", () => {
|
||||||
|
const host = document.createElement("div");
|
||||||
|
document.body.append(host);
|
||||||
|
const { hint: _hint, ...noHint } = LONG;
|
||||||
|
act(() => {
|
||||||
|
createRoot(host).render(<FxParamRow param={noHint} value={250} onChange={vi.fn()} />);
|
||||||
|
});
|
||||||
|
expect(host.querySelector<HTMLElement>(".hf-fx-label")?.getAttribute("title")).toBe(
|
||||||
|
"Gap between repeats",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@@ -188,7 +188,15 @@ export function FxParamRow({
|
|||||||
if (param.kind === "enum") {
|
if (param.kind === "enum") {
|
||||||
return (
|
return (
|
||||||
<label className="hf-fx-row flex min-h-6 items-center gap-2" title={param.hint}>
|
<label className="hf-fx-row flex min-h-6 items-center gap-2" title={param.hint}>
|
||||||
<span className="hf-fx-label w-[86px] flex-shrink-0 truncate text-[10px] text-panel-text-2">
|
{/* `title` on the LABEL, not just the row: the row's is the hint, which
|
||||||
|
explains what the knob does — useful, and no substitute for the name
|
||||||
|
when 86px truncates it to "Gap between re…". Titled unconditionally
|
||||||
|
rather than only when it overflows, since whether it does depends on
|
||||||
|
the rendered font and the panel's width. */}
|
||||||
|
<span
|
||||||
|
className="hf-fx-label w-[86px] flex-shrink-0 truncate text-[10px] text-panel-text-2"
|
||||||
|
title={param.label}
|
||||||
|
>
|
||||||
{param.label}
|
{param.label}
|
||||||
</span>
|
</span>
|
||||||
<select
|
<select
|
||||||
@@ -227,10 +235,13 @@ export function FxParamRow({
|
|||||||
title={param.hint}
|
title={param.hint}
|
||||||
data-automated={automated ? "" : undefined}
|
data-automated={automated ? "" : undefined}
|
||||||
>
|
>
|
||||||
|
{/* See the enum row above for why the name is titled here as well as the
|
||||||
|
hint being titled on the row. */}
|
||||||
<span
|
<span
|
||||||
className={`hf-fx-label w-[86px] flex-shrink-0 truncate text-[10px] ${
|
className={`hf-fx-label w-[86px] flex-shrink-0 truncate text-[10px] ${
|
||||||
automated ? "text-panel-accent" : "text-panel-text-2"
|
automated ? "text-panel-accent" : "text-panel-text-2"
|
||||||
}`}
|
}`}
|
||||||
|
title={param.label}
|
||||||
>
|
>
|
||||||
{param.label}
|
{param.label}
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -184,6 +184,9 @@ function FxNodeHeader({
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className={`hf-fx-node-name flex-1 truncate text-left text-[11px] text-panel-text-1 hover:text-panel-text-0 ${family}`}
|
className={`hf-fx-node-name flex-1 truncate text-left text-[11px] text-panel-text-1 hover:text-panel-text-0 ${family}`}
|
||||||
|
// Truncated in the same narrow column as the param labels below, so it
|
||||||
|
// needs the same fallback to the full text on hover.
|
||||||
|
title={label}
|
||||||
aria-expanded={open}
|
aria-expanded={open}
|
||||||
onClick={onToggleOpen}
|
onClick={onToggleOpen}
|
||||||
>
|
>
|
||||||
@@ -304,7 +307,10 @@ export function FxNodeRow({
|
|||||||
onRemove={() => onRemove(index)}
|
onRemove={() => onRemove(index)}
|
||||||
/>
|
/>
|
||||||
{summary ? (
|
{summary ? (
|
||||||
<p className="hf-fx-node-summary truncate px-1.5 pb-1 text-[10px] text-panel-text-2">
|
<p
|
||||||
|
className="hf-fx-node-summary truncate px-1.5 pb-1 text-[10px] text-panel-text-2"
|
||||||
|
title={summary}
|
||||||
|
>
|
||||||
{summary}
|
{summary}
|
||||||
</p>
|
</p>
|
||||||
) : null}
|
) : null}
|
||||||
|
|||||||
Reference in New Issue
Block a user